Browse Source

1. Fix name issue in group chat.
2. Unescape user name.
3. Extract @ infos of text messages in group chat.

liuwons 9 years ago
parent
commit
773373967e
5 changed files with 239 additions and 95 deletions
  1. 1 0
      .gitignore
  2. 14 0
      CHANGES.md
  3. 26 9
      README.md
  4. 25 38
      bot.py
  5. 173 48
      wxbot.py

+ 1 - 0
.gitignore

@@ -68,3 +68,4 @@ target/
 qr.png
 /*.jpg
 *.ini
+*.un~

+ 14 - 0
CHANGES.md

@@ -0,0 +1,14 @@
+# Change Log
+
+
+# V0.1
+
+时间: 2016-06-05
+
+初始版本。
+
+***wxbot.py*** 已经实现了基本的群聊、单聊接口。
+
+***test.py*** 实现了一个功能最基础的机器人,对所有好友的文本消息回复 *hi* ,并不断向好友 *tb* 发送 *schedule* 。
+
+***bot.py*** 利用 ***wxBot*** 和 ***图灵机器人*** 实现了一个群聊机器人。对好友以及群聊中@自己的消息进行回复。

+ 26 - 9
README.md

@@ -39,7 +39,7 @@ Web微信协议参考资料:
 
 ## 1 环境与依赖
 
-目前只能运行于Python 2环境 。
+此版本只能运行于Python 2环境 。
 
 **wxBot** 用到了Python **requests** , **pypng** , 以及 **pyqrcode** 库。
 
@@ -59,7 +59,7 @@ pip install pypng
 
 以下的代码对所有来自好友的文本消息回复 *hi* , 并不断向好友 *tb* 发送 *schedule* 。
 
-`handle_msg_all` 函数用于处理收到的每条消息,而 `schedule` 函数可以做一些任务性的事情(例如不断向好友推送信息或者一些定时任务)。
+`handle_msg_all` 函数用于处理收到的每条消息,而 `schedule` 函数可以做一些任务性的工作(例如不断向好友推送信息或者一些定时任务)。
 
 ```python
 #!/usr/bin/env python
@@ -97,7 +97,7 @@ python test.py
 
 ### 2.3 登录微信
 
-程序运行之后,会在当前目录下生成二维码图片文件 ***qr.png*** ,用微信扫描此二维码并按操作指示确认登录网页微信。
+程序运行之后,会在当前目录下生成二维码图片文件 ***qr.png*** 并自动打开,用微信扫描此二维码并按操作指示确认登录网页微信。
 
 如果运行在Linux下,还可以通过设置 **WXBot** 对象的 `conf['qr']` 为 `tty` 的方式直接在终端打印二维码(此方法只能在Linux终端下使用),效果如下:
 
@@ -131,7 +131,7 @@ python test.py
 | 0 | 初始化消息,内部数据 | 无意义,可以忽略 |
 | 1 | 自己发送的消息 | 无意义,可以忽略 |
 | 2 | 文件消息 | 字典,包含 `type` 与 `data` 字段 |
-| 3 | 群消息 | 字典, 包含 `user` (字典,包含 `id` 与 `name`字段,都是字符串,表示发送此消息的群用户)与 `type` 、 `data` 字段,红包消息除外(只有 `type` 字段) |
+| 3 | 群消息 | 字典, 包含 `user` (字典,包含 `id` 与 `name`字段,都是字符串,表示发送此消息的群用户)与 `type` 、 `data` 字段,红包消息只有 `type` 字段, 文本消息还有detail、desc字段, 参考 **群文本消息** |
 | 4 | 联系人消息 | 字典,包含 `type` 与 `data` 字段 |
 | 5 | 公众号消息 | 字典,包含 `type` 与 `data` 字段 |
 | 6 | 特殊账号消息 | 字典,包含 `type` 与 `data` 字段 |
@@ -156,8 +156,21 @@ python test.py
 | 12 | 红包 | 不可用 |
 | 99 | 未知类型 | 不可用 |
 
+### 4.4 群文本消息
 
-### 4.4 WXBot对象属性
+由于群文本消息中可能含有@信息,因此群文本消息的 `content` 字典除了含有 `type` 与 `data` 字段外,还含有 `detail` 与 `desc` 字段。
+
+各字段内容为:
+
+| 字段 | 内容 |
+| --- | ---- |
+| `type` | 数据类型, 为0(文本) |
+| `data` | 字符串,消息内容,含有@信息 |
+| `desc` | 字符串,删除了所有@信息 |
+| `detail` | 数组,元素类型为含有 `type` 与 `value` 字段的字典, `type` 为字符串 ***str*** (表示元素为普通字符串,此时value为消息内容) 或 ***at*** (表示元素为@信息, 此时value为所@的用户名) |
+
+
+### 4.5 WXBot对象属性
 
 **WXBot** 对象在登录并初始化之后,含有以下的可用数据:
 
@@ -169,7 +182,7 @@ python test.py
 | `special_list` | 特殊账号列表 |
 | `session` | **WXBot** 与WEB微信服务器端交互所用的 **Requests** `Session` 对象 |
 
-### 4.5 WXBot对象方法
+### 4.6 WXBot对象方法
 
 **WXBot** 对象还含有一些可以利用的方法
 
@@ -179,14 +192,14 @@ python test.py
 | `get_head_img(id)` | 获取用户头像并保存到本地文件 ***img_[id].jpg*** ,`id` 为用户id(Web微信数据) |
 | `get_msg_img(msgid)` | 获取图像消息并保存到本地文件 ***img_[msgid].jpg*** , `msgid` 为消息id(Web微信数据) |
 | `get_voice(msgid)` | 获取语音消息并保存到本地文件 ***voice_[msgid].mp3*** , `msgid` 为消息id(Web微信数据) |
-| `get_account_name(uid)` | 获取微信id对应的名称,返回一个可能包含 `remark_name` (备注名), `nickname` (昵称), `display_name` (群名称)的字典|
+| `get_contact_name(uid)` | 获取微信id对应的名称,返回一个可能包含 `remark_name` (备注名), `nickname` (昵称), `display_name` (群名称)的字典|
 | `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` 为文件路径(此时向好友发送文件里的每一行),此方法在有重名好友时会有问题,因此更推荐使用 `send_msg_by_uid(word, dst)` |
 | `is_contact(uid)` | 判断id为 `uid` 的账号是否是本帐号的好友,返回 `True` (是)或 `False` (不是) |
 | `is_public(uid)` | 判断id为 `uid` 的账号是否是本帐号所关注的公众号,返回 `True` (是)或 `False` (不是) |
 
 
-## 5 Example
+## 5 群聊机器人示例
 
 ***bot.py*** 用 **[图灵机器人](http://www.tuling123.com/)** API 以及 **wxBot** 实现了一个自动回复机器人.
 
@@ -230,3 +243,7 @@ python test.py
     ```python
     python bot.py
     ```
+
+## 6 帮助项目
+
+欢迎对本项目提意见、贡献代码,参考: [如何帮助项目](https://github.com/liuwons/wxBot/wiki/How-to-contribute)

+ 25 - 38
bot.py

@@ -43,15 +43,15 @@ class TulingWXBot(WXBot):
 
     def auto_switch(self, msg):
         msg_data = msg['content']['data']
-        STOP = [u'退下', u'走开', u'关闭', u'关掉', u'休息', u'滚开']
-        START = [u'出来', u'启动', u'工作']
+        stop_cmd = [u'退下', u'走开', u'关闭', u'关掉', u'休息', u'滚开']
+        start_cmd = [u'出来', u'启动', u'工作']
         if self.robot_switch:
-            for i in STOP:
+            for i in stop_cmd:
                 if i == msg_data:
                     self.robot_switch = False
                     self.send_msg_by_uid(u'[Robot]' + u'机器人已关闭!', msg['to_user_id'])
         else:
-            for i in START:
+            for i in start_cmd:
                 if i == msg_data:
                     self.robot_switch = True
                     self.send_msg_by_uid(u'[Robot]' + u'机器人已开启!', msg['to_user_id'])
@@ -63,44 +63,31 @@ class TulingWXBot(WXBot):
             self.auto_switch(msg)
         elif msg['msg_type_id'] == 4 and msg['content']['type'] == 0:  # text message from contact
             self.send_msg_by_uid(self.tuling_auto_reply(msg['user']['id'], msg['content']['data']), msg['user']['id'])
-        elif msg['msg_type_id'] == 3:  # group message
-            if msg['content']['data'].find('@') >= 0:  # someone @ another
-                my_names = self.get_group_member_name(msg['user']['id'], self.user['UserName'])
+        elif msg['msg_type_id'] == 3 and msg['content']['type'] == 0:  # group text message
+            if 'detail' in msg['content']:
+                my_names = self.get_group_member_name(self.my_account['UserName'], msg['user']['id'])
                 if my_names is None:
                     my_names = {}
-                if 'NickName' in self.user and len(self.user['NickName']) > 0:
-                    my_names['nickname2'] = self.user['NickName']
-                if 'RemarkName' in self.user and len(self.user['RemarkName']) > 0:
-                    my_names['remark_name2'] = self.user['RemarkName']
+                if 'NickName' in self.my_account and self.my_account['NickName']:
+                    my_names['nickname2'] = self.my_account['NickName']
+                if 'RemarkName' in self.my_account and self.my_account['RemarkName']:
+                    my_names['remark_name2'] = self.my_account['RemarkName']
+
                 is_at_me = False
-                text_msg = ''
-                for _ in my_names:
-                    if msg['content']['data'].find('@'+my_names[_]) >= 0:
-                        is_at_me = True
-                        text_msg = msg['content']['data'].replace('@'+my_names[_], '').strip()
-                        break
-                if is_at_me:  # someone @ me
-                    snames = self.get_group_member_name(msg['user']['id'], msg['content']['user']['id'])
-                    if snames is None:
-                        snames = self.get_account_name(msg['content']['user']['id'])
-                    src_name = ''
-                    if snames is not None:
-                        if 'display_name' in snames and len(snames['display_name']) > 0:
-                            src_name = snames['display_name']
-                        elif 'nickname' in snames and len(snames['nickname']) > 0:
-                            src_name = snames['nickname']
-                        elif 'remark_name' in snames and len(snames['remark_name']) > 0:
-                            src_name = snames['remark_name']
+                for detail in msg['content']['detail']:
+                    if detail['type'] == 'at':
+                        for k in my_names:
+                            if my_names[k] and my_names[k] == detail['value']:
+                                is_at_me = True
+                                break
+                if is_at_me:
+                    src_name = msg['content']['user']['name']
+                    reply = 'to ' + src_name + ': '
+                    if msg['content']['type'] == 0:  # text message
+                        reply += self.tuling_auto_reply(msg['content']['user']['id'], msg['content']['desc'])
                     else:
-                        return
-
-                    if src_name != '':
-                        reply = '@' + src_name + ' '
-                        if msg['content']['type'] == 0:  # text message
-                            reply += self.tuling_auto_reply(msg['content']['user']['id'], text_msg)
-                        else:
-                            reply += u"对不起,只认字,其他杂七杂八的我都不认识,,,Ծ‸Ծ,,"
-                        self.send_msg_by_uid(reply, msg['user']['id'])
+                        reply += u"对不起,只认字,其他杂七杂八的我都不认识,,,Ծ‸Ծ,,"
+                    self.send_msg_by_uid(reply, msg['user']['id'])
 
 
 def main():

+ 173 - 48
wxbot.py

@@ -10,7 +10,8 @@ import time
 import re
 import random
 from requests.exceptions import *
-import os,subprocess,sys
+import os
+import HTMLParser
 
 
 class WXBot:
@@ -29,19 +30,28 @@ class WXBot:
         self.base_request = {}
         self.sync_key_str = ''
         self.sync_key = []
-        self.user = {}
-        self.account_info = {}
-        self.member_list = []  # all kind of accounts: contacts, public accounts, groups, special accounts
-        self.contact_list = []  # contact list
-        self.public_list = []  # public account list
-        self.group_list = []  # group chat list
-        self.special_list = []  # special list account
-        self.group_members = {}  # members of all groups
         self.sync_host = ''
+
         self.session = requests.Session()
         self.session.headers.update({'User-Agent': 'Mozilla/5.0 (X11; Linux i686; U;) Gecko/20070322 Kazehakase/0.4.5'})
         self.conf = {'qr': 'png'}
 
+        self.my_account = {}  # this account
+
+        # all kind of accounts: contacts, public accounts, groups, special accounts
+        self.member_list = []
+
+        # members of all groups, {'group_id1': [member1, member2, ...], ...}
+        self.group_members = {}
+
+        # all accounts, {'group_member':{'id':{'type':'group_member', 'info':{}}, ...}, 'normal_member':{'id':{}, ...}}
+        self.account_info = {'group_member': {}, 'normal_member': {}}
+
+        self.contact_list = []  # contact list
+        self.public_list = []  # public account list
+        self.group_list = []  # group chat list
+        self.special_list = []  # special list account
+
     def get_contact(self):
         """Get information of all contacts of current account."""
         url = self.base_uri + '/webwxgetcontact?pass_ticket=%s&skey=%s&r=%s' \
@@ -67,28 +77,32 @@ class WXBot:
         self.public_list = []
         self.special_list = []
         self.group_list = []
+
         for contact in self.member_list:
             if contact['VerifyFlag'] & 8 != 0:  # public account
                 self.public_list.append(contact)
-                self.account_info[contact['UserName']] = {'type': 'public', 'info': contact}
+                self.account_info['normal_member'][contact['UserName']] = {'type': 'public', 'info': contact}
             elif contact['UserName'] in special_users:  # special account
                 self.special_list.append(contact)
-                self.account_info[contact['UserName']] = {'type': 'special', 'info': contact}
+                self.account_info['normal_member'][contact['UserName']] = {'type': 'special', 'info': contact}
             elif contact['UserName'].find('@@') != -1:  # group
                 self.group_list.append(contact)
-                self.account_info[contact['UserName']] = {'type': 'group', 'info': contact}
-            elif contact['UserName'] == self.user['UserName']:  # self
-                self.account_info[contact['UserName']] = {'type': 'self', 'info': contact}
+                self.account_info['normal_member'][contact['UserName']] = {'type': 'group', 'info': contact}
+            elif contact['UserName'] == self.my_account['UserName']:  # self
+                self.account_info['normal_member'][contact['UserName']] = {'type': 'self', 'info': contact}
                 pass
             else:
                 self.contact_list.append(contact)
+                self.account_info['normal_member'][contact['UserName']] = {'type': 'contact', 'info': 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}
+                    self.account_info['group_member'][member['UserName']] = {'type': 'group_member',
+                                                                             'info': member,
+                                                                             'group': group}
 
         if self.DEBUG:
             with open('contact_list.json', 'w') as f:
@@ -138,25 +152,39 @@ class WXBot:
         for member in group:
             if member['UserName'] == uid:
                 names = {}
-                if 'RemarkName' in member:
+                if 'RemarkName' in member and member['RemarkName']:
                     names['remark_name'] = member['RemarkName']
-                if 'NickName' in member:
+                if 'NickName' in member and member['NickName']:
                     names['nickname'] = member['NickName']
-                if 'DisplayName' in member:
+                if 'DisplayName' in member and member['DisplayName']:
                     names['display_name'] = member['DisplayName']
                 return names
         return None
 
-    def get_account_info(self, uid):
-        if uid in self.account_info:
-            return self.account_info[uid]
+    def get_contact_info(self, uid):
+        if uid in self.account_info['normal_member']:
+            return self.account_info['normal_member'][uid]
         else:
             return None
 
-    def get_account_name(self, uid):
-        info = self.get_account_info(uid)
+    def get_group_member_info(self, uid):
+        if uid in self.account_info['group_member']:
+            return self.account_info['group_member'][uid]
+        else:
+            return None
+
+    def get_group_member_info(self, uid, gid):
+        if gid not in self.group_members:
+            return None
+        for member in self.group_members[gid]:
+            if member['UserName'] == uid:
+                return {'type': 'group_member', 'info': member}
+        return None
+
+    def get_contact_name(self, uid):
+        info = self.get_contact_info(uid)
         if info is None:
-            return 'unknown'
+            return None
         info = info['info']
         name = {}
         if 'RemarkName' in info and info['RemarkName']:
@@ -165,17 +193,68 @@ class WXBot:
             name['nickname'] = info['NickName']
         if 'DisplayName' in info and info['DisplayName']:
             name['display_name'] = info['DisplayName']
-        return name
+        if len(name) == 0:
+            return None
+        else:
+            return name
+
+    def get_group_member_name(self, uid):
+        info = self.get_group_member_info(uid)
+        if info is None:
+            return None
+        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']
+        if len(name) == 0:
+            return None
+        else:
+            return name
+
+    def get_group_member_name(self, uid, gid):
+        info = self.get_group_member_info(uid, gid)
+        if info is None:
+            return None
+        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']
+        if len(name) == 0:
+            return None
+        else:
+            return name
+
+    @staticmethod
+    def get_contact_prefer_name(name):
+        if name is None:
+            return None
+        if 'remark_name' in name:
+            return name['remark_name']
+        if 'nickname' in name:
+            return name['nickname']
+        if 'display_name' in name:
+            return name['display_name']
+        return None
 
     @staticmethod
-    def get_prefer_name(name):
+    def get_group_member_prefer_name(name):
+        if name is None:
+            return None
         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'
+        return None
 
     def get_user_type(self, wx_user_id):
         """
@@ -232,6 +311,39 @@ class WXBot:
         """
         pass
 
+    @staticmethod
+    def proc_at_info(msg):
+        if not msg:
+            return '', []
+        segs = msg.split(u'\u2005')
+        str_msg_all = ''
+        str_msg = ''
+        infos = []
+        if len(segs) > 1:
+            for i in range(0, len(segs)-1):
+                segs[i] += u'\u2005'
+                pm = re.search(u'@.*\u2005', segs[i]).group()
+                if pm:
+                    name = pm[1:-1]
+                    string = segs[i].replace(pm, '')
+                    str_msg_all += string + '@' + name + ' '
+                    str_msg += string
+                    if string:
+                        infos.append({'type': 'str', 'value': string})
+                    infos.append({'type': 'at', 'value': name})
+                else:
+                    infos.append({'type': 'str', 'value': segs[i]})
+                    str_msg_all += segs[i]
+                    str_msg += segs[i]
+            str_msg_all += segs[-1]
+            str_msg += segs[-1]
+            infos.append({'type': 'str', 'value': segs[-1]})
+        else:
+            infos.append({'type': 'str', 'value': segs[-1]})
+            str_msg_all = msg
+            str_msg = msg
+        return str_msg_all.replace(u'\u2005', ''), str_msg.replace(u'\u2005', ''), infos
+
     def extract_msg_content(self, msg_type_id, msg):
         """
         content_type_id:
@@ -252,7 +364,7 @@ class WXBot:
         :return: The extracted content of the message.
         """
         mtype = msg['MsgType']
-        content = msg['Content'].replace('&lt;', '<').replace('&gt;', '>')
+        content = HTMLParser.HTMLParser().unescape(msg['Content'])
         msg_id = msg['MsgId']
 
         msg_content = {}
@@ -266,7 +378,12 @@ class WXBot:
             content = content[sp:]
             content = content.replace('<br/>', '')
             uid = uid[:-1]
-            msg_content['user'] = {'id': uid, 'name': self.get_prefer_name(self.get_account_name(uid))}
+            name = self.get_contact_prefer_name(self.get_contact_name(uid))
+            if not name:
+                name = self.get_group_member_prefer_name(self.get_group_member_name(uid, msg['FromUserName']))
+            if not name:
+                name = 'unknown'
+            msg_content['user'] = {'id': uid, 'name': name}
         else:  # Self, Contact, Special, Public, Unknown
             pass
 
@@ -285,7 +402,16 @@ class WXBot:
                     print '    %s[Location] %s ' % (msg_prefix, pos)
             else:
                 msg_content['type'] = 0
-                msg_content['data'] = content.replace(u'\u2005', '')
+                if msg_type_id == 3 or (msg_type_id == 1 and msg['ToUserName'][:2] == '@@'):  # Group text message
+                    msg_infos = self.proc_at_info(content)
+                    str_msg_all = msg_infos[0]
+                    str_msg = msg_infos[1]
+                    detail = msg_infos[2]
+                    msg_content['data'] = str_msg_all
+                    msg_content['detail'] = detail
+                    msg_content['desc'] = str_msg
+                else:
+                    msg_content['data'] = content
                 if self.DEBUG:
                     print '    %s[Text] %s' % (msg_prefix, msg_content['data'])
         elif mtype == 3:
@@ -361,11 +487,11 @@ class WXBot:
             msg_content['data'] = content
             if self.DEBUG:
                 print '    %s[Redraw]' % msg_prefix
-        elif mtype == 10000:
+        elif mtype == 10000:  # unknown, maybe red packet, or group invite
             msg_content['type'] = 12
             msg_content['data'] = msg['Content']
             if self.DEBUG:
-                print '    [Red Packet]'
+                print '    [Unknown]'
         else:
             msg_content['type'] = 99
             msg_content['data'] = content
@@ -394,7 +520,7 @@ class WXBot:
             if msg['MsgType'] == 51:  # init message
                 msg_type_id = 0
                 user['name'] = 'system'
-            elif msg['FromUserName'] == self.user['UserName']:  # Self
+            elif msg['FromUserName'] == self.my_account['UserName']:  # Self
                 msg_type_id = 1
                 user['name'] = 'self'
             elif msg['ToUserName'] == 'filehelper':  # File Helper
@@ -402,19 +528,22 @@ class WXBot:
                 user['name'] = 'file_helper'
             elif msg['FromUserName'][:2] == '@@':  # Group
                 msg_type_id = 3
-                user['name'] = self.get_prefer_name(self.get_account_name(user['id']))
+                user['name'] = self.get_contact_prefer_name(self.get_contact_name(user['id']))
             elif self.is_contact(msg['FromUserName']):  # Contact
                 msg_type_id = 4
-                user['name'] = self.get_prefer_name(self.get_account_name(user['id']))
+                user['name'] = self.get_contact_prefer_name(self.get_contact_name(user['id']))
             elif self.is_public(msg['FromUserName']):  # Public
                 msg_type_id = 5
-                user['name'] = self.get_prefer_name(self.get_account_name(user['id']))
+                user['name'] = self.get_contact_prefer_name(self.get_contact_name(user['id']))
             elif self.is_special(msg['FromUserName']):  # Special
                 msg_type_id = 6
-                user['name'] = self.get_prefer_name(self.get_account_name(user['id']))
+                user['name'] = self.get_contact_prefer_name(self.get_contact_name(user['id']))
             else:
                 msg_type_id = 99
                 user['name'] = 'unknown'
+            if not user['name']:
+                user['name'] = 'unknown'
+            user['name'] = HTMLParser.HTMLParser().unescape(user['name'])
 
             if self.DEBUG and msg_type_id != 0:
                 print '[MSG] %s:' % user['name']
@@ -472,7 +601,7 @@ class WXBot:
             'Msg': {
                 "Type": 1,
                 "Content": word,
-                "FromUserName": self.user['UserName'],
+                "FromUserName": self.my_account['UserName'],
                 "ToUserName": dst,
                 "LocalID": msg_id,
                 "ClientMsgId": msg_id
@@ -488,6 +617,8 @@ class WXBot:
         return dic['BaseResponse']['Ret'] == 0
 
     def get_user_id(self, name):
+        if name == '':
+            return ''
         for contact in self.contact_list:
             if 'RemarkName' in contact and contact['RemarkName'] == name:
                 return contact['UserName']
@@ -581,13 +712,7 @@ class WXBot:
         qr = pyqrcode.create(string)
         if self.conf['qr'] == 'png':
             qr.png(qr_file_path, scale=8)
-            if sys.platform.find('darwin') >= 0:
-                subprocess.call(['open', qr_file_path])
-            elif sys.platform.find('linux') >= 0:
-                subprocess.call(['xdg-open', qr_file_path])
-            else:
-                os.startfile(qr_file_path)
-
+            os.startfile(qr_file_path)
         elif self.conf['qr'] == 'tty':
             print(qr.terminal(quiet_zone=1))
 
@@ -655,7 +780,7 @@ class WXBot:
         r.encoding = 'utf-8'
         dic = json.loads(r.text)
         self.sync_key = dic['SyncKey']
-        self.user = dic['User']
+        self.my_account = 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
@@ -666,8 +791,8 @@ class WXBot:
         params = {
             'BaseRequest': self.base_request,
             "Code": 3,
-            "FromUserName": self.user['UserName'],
-            "ToUserName": self.user['UserName'],
+            "FromUserName": self.my_account['UserName'],
+            "ToUserName": self.my_account['UserName'],
             "ClientMsgId": int(time.time())
         }
         r = self.session.post(url, data=json.dumps(params))