Browse Source

将handle_msg_all以及schedule放到独立线程中执行

liuwons 8 years ago
parent
commit
b4e5e7f0d4
6 changed files with 426 additions and 218 deletions
  1. 8 0
      CHANGES.md
  2. 32 7
      README.md
  3. 4 0
      bot.py
  4. BIN
      img/wx_group.png
  5. 4 2
      test.py
  6. 378 209
      wxbot.py

+ 8 - 0
CHANGES.md

@@ -1,6 +1,14 @@
 # Change Log
 # Change Log
 
 
 
 
+# V0.2
+
+时间: 2016-09-28
+
+- 将 ```handle_msg_all``` 函数的调用放到独立线程中
+- 将 ```schedule``` 函数调用放到另一个独立线程,并且可以通过 ```WXBot.SCHEDULE_INTV``` 设置 ```schedule``` 函数的调用间隔(单位:秒)
+
+
 # V0.1
 # V0.1
 
 
 时间: 2016-06-05
 时间: 2016-06-05

+ 32 - 7
README.md

@@ -26,6 +26,11 @@
   - [ ] 红包
   - [ ] 红包
   - [ ] 转账
   - [ ] 转账
 
 
+- [x] 消息发送
+  - [x] 文本
+  - [x] 图片
+  - [x] 文件
+
 
 
 
 
 Web微信协议参考资料:
 Web微信协议参考资料:
@@ -58,9 +63,9 @@ pip install Pillow
 
 
 ### 2.1 代码
 ### 2.1 代码
 
 
-以下的代码对所有来自好友的文本消息回复 *hi* , 并不断向好友 *tb* 发送 *schedule* 。
+以下的代码对所有来自好友的文本消息回复文本消息 *hi* 、图片消息 *1.png* 以及文件消息 *1.png* , 并不断向好友 *tb* 发送文本 *schedule* 。
 
 
-`handle_msg_all` 函数用于处理收到的每条消息,而 `schedule` 函数可以做一些任务性的工作(例如不断向好友推送信息或者一些定时任务)。
+`handle_msg_all` 函数用于处理收到的每条消息(为了防止消息发送过于频繁,可以在此函数中发送消息后 ```sleep``` 一段时间),而 `schedule` 函数可以做一些任务性的工作(例如不断向好友推送信息或者一些定时任务,默认5秒执行一次,可以通过 ```WXBot.SCHEDULE_INTV``` 来设定此时间间隔)。
 
 
 ```python
 ```python
 #!/usr/bin/env python
 #!/usr/bin/env python
@@ -73,6 +78,8 @@ class MyWXBot(WXBot):
     def handle_msg_all(self, msg):
     def handle_msg_all(self, msg):
         if msg['msg_type_id'] == 4 and msg['content']['type'] == 0:
         if msg['msg_type_id'] == 4 and msg['content']['type'] == 0:
             self.send_msg_by_uid(u'hi', msg['user']['id'])
             self.send_msg_by_uid(u'hi', msg['user']['id'])
+            self.send_img_msg_by_uid("img/1.png", msg['user']['id'])
+            self.send_file_msg_by_uid("img/1.png", msg['user']['id'])
 
 
     def schedule(self):
     def schedule(self):
         self.send_msg(u'tb', u'schedule')
         self.send_msg(u'tb', u'schedule')
@@ -81,6 +88,7 @@ class MyWXBot(WXBot):
 def main():
 def main():
     bot = MyWXBot()
     bot = MyWXBot()
     bot.DEBUG = True
     bot.DEBUG = True
+    bot.SCHEDULE_INTV = 20
     bot.run()
     bot.run()
 
 
 if __name__ == '__main__':
 if __name__ == '__main__':
@@ -195,8 +203,9 @@ python test.py
 | `get_voice(msgid)` | 获取语音消息并保存到本地文件 ***voice_[msgid].mp3*** , `msgid` 为消息id(Web微信数据) |
 | `get_voice(msgid)` | 获取语音消息并保存到本地文件 ***voice_[msgid].mp3*** , `msgid` 为消息id(Web微信数据) |
 | `get_contact_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_by_uid(word, dst)` | 向好友发送消息,`word` 为消息字符串,`dst` 为好友用户id(Web微信数据) |
-| `send_file_msg_by_uid(fpath, dst)` | 向好友发送文件消息,`fpath` 为文件路径,`dst` 为好友用户id(Web微信数据) |
-| `send_img_msg_by_uid(word, dst)` | 向好友发送图片消息,`fpath` 图片文件路径,`dst` 为好友用户id(Web微信数据) |
+| `send_img_msg_by_uid(fpath, dst)` | 向好友发送图片消息,`fpath` 为本地图片文件路径,`dst` 为好友用户id(Web微信数据) |
+| `send_file_msg_by_uid(fpath, dst)` | 向好友发送文件消息,`fpath` 为本地文件路径,`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_by_uid(word, dst)` |
 | `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_contact(uid)` | 判断id为 `uid` 的账号是否是本帐号的好友,返回 `True` (是)或 `False` (不是) |
 | `is_public(uid)` | 判断id为 `uid` 的账号是否是本帐号所关注的公众号,返回 `True` (是)或 `False` (不是) |
 | `is_public(uid)` | 判断id为 `uid` 的账号是否是本帐号所关注的公众号,返回 `True` (是)或 `False` (不是) |
@@ -212,7 +221,7 @@ python test.py
 
 
 也可以通过发送 *出来* 、 *启动* 、 *工作* 来再次开启机器人的自动回复。
 也可以通过发送 *出来* 、 *启动* 、 *工作* 来再次开启机器人的自动回复。
 
 
-群聊时需要将对应的群保存到联系人列表。
+**群聊时需要将对应的群保存到联系人列表**
 
 
 群聊实现效果:
 群聊实现效果:
 
 
@@ -247,6 +256,22 @@ python test.py
     python bot.py
     python bot.py
     ```
     ```
 
 
-## 6 帮助项目
+## 6 类似项目
+
+[feit/Weixinbot](https://github.com/feit/Weixinbot) Nodejs 封装网页版微信的接口,可编程控制微信消息
+
+[littlecodersh/ItChat](https://github.com/littlecodersh/ItChat) 微信个人号接口、微信机器人及命令行微信,Command line talks through Wechat
+
+[Urinx/WeixinBot](https://github.com/Urinx/WeixinBot) 网页版微信API,包含终端版微信及微信机器人
+
+[zixia/wechaty](https://github.com/zixia/wechaty) Wechaty is wechat for bot in Javascript(ES6). It's a Personal Account Robot Framework/Library.
+
+## 7 交流讨论
+
+问题可以直接开 **issue**
+
+**QQ** 交流群: **429134510**
+
+微信机器人测试群(做好屏蔽群消息的准备):
 
 
-欢迎对本项目提意见、贡献代码,参考: [如何帮助项目](https://github.com/liuwons/wxBot/wiki/How-to-contribute)
+![微信机器人测试群](img/wx_group.png)

+ 4 - 0
bot.py

@@ -33,6 +33,10 @@ class TulingWXBot(WXBot):
                 result = respond['text'].replace('<br>', '  ')
                 result = respond['text'].replace('<br>', '  ')
             elif respond['code'] == 200000:
             elif respond['code'] == 200000:
                 result = respond['url']
                 result = respond['url']
+            elif respond['code'] == 302000:
+                for k in respond['list']:
+                    result = result + u"【" + k['source'] + u"】 " +\
+                        k['article'] + "\t" + k['detailurl'] + "\n"
             else:
             else:
                 result = respond['text'].replace('<br>', '  ')
                 result = respond['text'].replace('<br>', '  ')
 
 

BIN
img/wx_group.png


+ 4 - 2
test.py

@@ -8,16 +8,18 @@ class MyWXBot(WXBot):
     def handle_msg_all(self, msg):
     def handle_msg_all(self, msg):
         if msg['msg_type_id'] == 4 and msg['content']['type'] == 0:
         if msg['msg_type_id'] == 4 and msg['content']['type'] == 0:
             self.send_msg_by_uid(u'hi', msg['user']['id'])
             self.send_msg_by_uid(u'hi', msg['user']['id'])
+            self.send_img_msg_by_uid("img/1.png", msg['user']['id'])
+            self.send_file_msg_by_uid("img/1.png", msg['user']['id'])
 '''
 '''
     def schedule(self):
     def schedule(self):
-        self.send_msg(u'张三', u'测试')
-        time.sleep(1)
+        self.send_msg(u'测试群', u'测试')
 '''
 '''
 
 
 
 
 def main():
 def main():
     bot = MyWXBot()
     bot = MyWXBot()
     bot.DEBUG = True
     bot.DEBUG = True
+    bot.SCHEDULE_INTV = 20
     bot.conf['qr'] = 'png'
     bot.conf['qr'] = 'png'
     bot.run()
     bot.run()
 
 

+ 378 - 209
wxbot.py

@@ -3,29 +3,33 @@
 
 
 import os
 import os
 import sys
 import sys
+import traceback
 import webbrowser
 import webbrowser
 import pyqrcode
 import pyqrcode
 import requests
 import requests
+import mimetypes
 import json
 import json
 import xml.dom.minidom
 import xml.dom.minidom
 import urllib
 import urllib
 import time
 import time
 import re
 import re
 import random
 import random
-import mimetypes
+from traceback import format_exc
 from requests.exceptions import ConnectionError, ReadTimeout
 from requests.exceptions import ConnectionError, ReadTimeout
+from Queue import Queue
 import HTMLParser
 import HTMLParser
+import threading
 
 
 UNKONWN = 'unkonwn'
 UNKONWN = 'unkonwn'
 SUCCESS = '200'
 SUCCESS = '200'
-SCANED  = '201'
+SCANED = '201'
 TIMEOUT = '408'
 TIMEOUT = '408'
 
 
 
 
-def show_image(file):
+def show_image(file_path):
     """
     """
     跨平台显示图片文件
     跨平台显示图片文件
-    :param file: 图片文件路径
+    :param file_path: 图片文件路径
     """
     """
     if sys.version_info >= (3, 3):
     if sys.version_info >= (3, 3):
         from shlex import quote
         from shlex import quote
@@ -33,10 +37,24 @@ def show_image(file):
         from pipes import quote
         from pipes import quote
 
 
     if sys.platform == "darwin":
     if sys.platform == "darwin":
-        command = "open -a /Applications/Preview.app %s&" % quote(file)
+        command = "open -a /Applications/Preview.app %s&" % quote(file_path)
         os.system(command)
         os.system(command)
     else:
     else:
-        webbrowser.open(file)
+        webbrowser.open(os.path.join(os.getcwd(),'temp',file_path))
+
+
+class SafeSession(requests.Session):
+    def request(self, method, url, params=None, data=None, headers=None, cookies=None, files=None, auth=None,
+                timeout=None, allow_redirects=True, proxies=None, hooks=None, stream=None, verify=None, cert=None,
+                json=None):
+        for i in range(3):
+            try:
+                return super(SafeSession, self).request(method, url, params, data, headers, cookies, files, auth,
+                                                        timeout,
+                                                        allow_redirects, proxies, hooks, stream, verify, cert, json)
+            except Exception as e:
+                print e.message, traceback.format_exc()
+                continue
 
 
 
 
 class WXBot:
 class WXBot:
@@ -44,6 +62,7 @@ class WXBot:
 
 
     def __init__(self):
     def __init__(self):
         self.DEBUG = False
         self.DEBUG = False
+        self.SCHEDULE_INTV = 5
         self.uuid = ''
         self.uuid = ''
         self.base_uri = ''
         self.base_uri = ''
         self.redirect_uri = ''
         self.redirect_uri = ''
@@ -57,7 +76,12 @@ class WXBot:
         self.sync_key = []
         self.sync_key = []
         self.sync_host = ''
         self.sync_host = ''
 
 
-        self.session = requests.Session()
+        #文件缓存目录
+        self.temp_pwd  =  os.path.join(os.getcwd(),'temp')
+        if os.path.exists(self.temp_pwd) == False:
+            os.makedirs(self.temp_pwd)
+
+        self.session = SafeSession()
         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'}
 
 
@@ -78,7 +102,9 @@ class WXBot:
         self.special_list = []  # 特殊账号列表
         self.special_list = []  # 特殊账号列表
         self.encry_chat_room_id_list = []  # 存储群聊的EncryChatRoomId,获取群内成员头像时需要用到
         self.encry_chat_room_id_list = []  # 存储群聊的EncryChatRoomId,获取群内成员头像时需要用到
 
 
-        self.file_index = 0  # 发送文件消息时用到的的文件序号
+        self.file_index = 0
+
+        self.msg_queue = Queue()
 
 
     @staticmethod
     @staticmethod
     def to_unicode(string, encoding='utf-8'):
     def to_unicode(string, encoding='utf-8'):
@@ -102,7 +128,7 @@ class WXBot:
         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:
-            with open('contacts.json', 'w') as f:
+            with open(os.path.join(self.temp_pwd,'contacts.json'), 'w') as f:
                 f.write(r.text.encode('utf-8'))
                 f.write(r.text.encode('utf-8'))
         dic = json.loads(r.text)
         dic = json.loads(r.text)
         self.member_list = dic['MemberList']
         self.member_list = dic['MemberList']
@@ -133,7 +159,6 @@ class WXBot:
                 self.account_info['normal_member'][contact['UserName']] = {'type': 'group', 'info': contact}
                 self.account_info['normal_member'][contact['UserName']] = {'type': 'group', 'info': contact}
             elif contact['UserName'] == self.my_account['UserName']:  # 自己
             elif contact['UserName'] == self.my_account['UserName']:  # 自己
                 self.account_info['normal_member'][contact['UserName']] = {'type': 'self', 'info': contact}
                 self.account_info['normal_member'][contact['UserName']] = {'type': 'self', 'info': contact}
-                pass
             else:
             else:
                 self.contact_list.append(contact)
                 self.contact_list.append(contact)
                 self.account_info['normal_member'][contact['UserName']] = {'type': 'contact', 'info': contact}
                 self.account_info['normal_member'][contact['UserName']] = {'type': 'contact', 'info': contact}
@@ -147,19 +172,19 @@ class WXBot:
                         {'type': 'group_member', 'info': member, 'group': group}
                         {'type': 'group_member', 'info': member, 'group': group}
 
 
         if self.DEBUG:
         if self.DEBUG:
-            with open('contact_list.json', 'w') as f:
+            with open(os.path.join(self.temp_pwd,'contact_list.json'), 'w') as f:
                 f.write(json.dumps(self.contact_list))
                 f.write(json.dumps(self.contact_list))
-            with open('special_list.json', 'w') as f:
+            with open(os.path.join(self.temp_pwd,'special_list.json'), 'w') as f:
                 f.write(json.dumps(self.special_list))
                 f.write(json.dumps(self.special_list))
-            with open('group_list.json', 'w') as f:
+            with open(os.path.join(self.temp_pwd,'group_list.json'), 'w') as f:
                 f.write(json.dumps(self.group_list))
                 f.write(json.dumps(self.group_list))
-            with open('public_list.json', 'w') as f:
+            with open(os.path.join(self.temp_pwd,'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:
+            with open(os.path.join(self.temp_pwd,'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:
+            with open(os.path.join(self.temp_pwd,'group_users.json'), 'w') as f:
                 f.write(json.dumps(self.group_members))
                 f.write(json.dumps(self.group_members))
-            with open('account_info.json', 'w') as f:
+            with open(os.path.join(self.temp_pwd,'account_info.json'), 'w') as f:
                 f.write(json.dumps(self.account_info))
                 f.write(json.dumps(self.account_info))
         return True
         return True
 
 
@@ -207,24 +232,10 @@ class WXBot:
         return None
         return None
 
 
     def get_contact_info(self, 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
+        return self.account_info['normal_member'].get(uid)
 
 
     def get_group_member_info(self, 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
+        return self.account_info['group_member'].get(uid)
 
 
     def get_contact_name(self, uid):
     def get_contact_name(self, uid):
         info = self.get_contact_info(uid)
         info = self.get_contact_info(uid)
@@ -243,40 +254,6 @@ class WXBot:
         else:
         else:
             return name
             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
     @staticmethod
     def get_contact_prefer_name(name):
     def get_contact_prefer_name(name):
         if name is None:
         if name is None:
@@ -364,7 +341,7 @@ class WXBot:
         str_msg = ''
         str_msg = ''
         infos = []
         infos = []
         if len(segs) > 1:
         if len(segs) > 1:
-            for i in range(0, len(segs)-1):
+            for i in range(0, len(segs) - 1):
                 segs[i] += u'\u2005'
                 segs[i] += u'\u2005'
                 pm = re.search(u'@.*\u2005', segs[i]).group()
                 pm = re.search(u'@.*\u2005', segs[i]).group()
                 if pm:
                 if pm:
@@ -424,7 +401,7 @@ class WXBot:
             uid = uid[:-1]
             uid = uid[:-1]
             name = self.get_contact_prefer_name(self.get_contact_name(uid))
             name = self.get_contact_prefer_name(self.get_contact_name(uid))
             if not name:
             if not name:
-                name = self.get_group_member_prefer_name(self.get_group_member_name(uid, msg['FromUserName']))
+                name = self.get_group_member_prefer_name(self.get_group_member_name(msg['FromUserName'], uid))
             if not name:
             if not name:
                 name = 'unknown'
                 name = 'unknown'
             msg_content['user'] = {'id': uid, 'name': name}
             msg_content['user'] = {'id': uid, 'name': name}
@@ -464,15 +441,22 @@ class WXBot:
         elif mtype == 3:
         elif mtype == 3:
             msg_content['type'] = 3
             msg_content['type'] = 3
             msg_content['data'] = self.get_msg_img_url(msg_id)
             msg_content['data'] = self.get_msg_img_url(msg_id)
+            msg_content['img'] = self.session.get(msg_content['data']).content.encode('hex')
             if self.DEBUG:
             if self.DEBUG:
                 image = self.get_msg_img(msg_id)
                 image = self.get_msg_img(msg_id)
                 print '    %s[Image] %s' % (msg_prefix, image)
                 print '    %s[Image] %s' % (msg_prefix, image)
         elif mtype == 34:
         elif mtype == 34:
             msg_content['type'] = 4
             msg_content['type'] = 4
             msg_content['data'] = self.get_voice_url(msg_id)
             msg_content['data'] = self.get_voice_url(msg_id)
+            msg_content['voice'] = self.session.get(msg_content['data']).content.encode('hex')
             if self.DEBUG:
             if self.DEBUG:
                 voice = self.get_voice(msg_id)
                 voice = self.get_voice(msg_id)
                 print '    %s[Voice] %s' % (msg_prefix, voice)
                 print '    %s[Voice] %s' % (msg_prefix, voice)
+        elif mtype == 37:
+            msg_content['type'] = 37
+            msg_content['data'] = msg['RecommendInfo']
+            if self.DEBUG:
+                print '    %s[useradd] %s' % (msg_prefix,msg['RecommendInfo']['NickName'])
         elif mtype == 42:
         elif mtype == 42:
             msg_content['type'] = 5
             msg_content['type'] = 5
             info = msg['RecommendInfo']
             info = msg['RecommendInfo']
@@ -496,7 +480,6 @@ class WXBot:
                 print '    %s[Animation] %s' % (msg_prefix, msg_content['data'])
                 print '    %s[Animation] %s' % (msg_prefix, msg_content['data'])
         elif mtype == 49:
         elif mtype == 49:
             msg_content['type'] = 7
             msg_content['type'] = 7
-            app_msg_type = ''
             if msg['AppMsgType'] == 3:
             if msg['AppMsgType'] == 3:
                 app_msg_type = 'music'
                 app_msg_type = 'music'
             elif msg['AppMsgType'] == 5:
             elif msg['AppMsgType'] == 5:
@@ -509,7 +492,9 @@ class WXBot:
                                    'title': msg['FileName'],
                                    'title': msg['FileName'],
                                    'desc': self.search_content('des', content, 'xml'),
                                    'desc': self.search_content('des', content, 'xml'),
                                    'url': msg['Url'],
                                    'url': msg['Url'],
-                                   'from': self.search_content('appname', content, 'xml')}
+                                   'from': self.search_content('appname', content, 'xml'),
+                                   'content': msg.get('Content')  # 有的公众号会发一次性3 4条链接一个大图,如果只url那只能获取第一条,content里面有所有的链接
+                                   }
             if self.DEBUG:
             if self.DEBUG:
                 print '    %s[Share] %s' % (msg_prefix, app_msg_type)
                 print '    %s[Share] %s' % (msg_prefix, app_msg_type)
                 print '    --------------------------'
                 print '    --------------------------'
@@ -517,6 +502,7 @@ class WXBot:
                 print '    | desc: %s' % self.search_content('des', content, 'xml')
                 print '    | desc: %s' % self.search_content('des', content, 'xml')
                 print '    | link: %s' % msg['Url']
                 print '    | link: %s' % msg['Url']
                 print '    | from: %s' % self.search_content('appname', content, 'xml')
                 print '    | from: %s' % self.search_content('appname', content, 'xml')
+                print '    | content: %s' % (msg.get('content')[:20] if msg.get('content') else "unknown")
                 print '    --------------------------'
                 print '    --------------------------'
 
 
         elif mtype == 62:
         elif mtype == 62:
@@ -561,11 +547,21 @@ class WXBot:
         :param r: 原始微信消息
         :param r: 原始微信消息
         """
         """
         for msg in r['AddMsgList']:
         for msg in r['AddMsgList']:
-            msg_type_id = 99
             user = {'id': msg['FromUserName'], 'name': 'unknown'}
             user = {'id': msg['FromUserName'], 'name': 'unknown'}
             if msg['MsgType'] == 51:  # init message
             if msg['MsgType'] == 51:  # init message
                 msg_type_id = 0
                 msg_type_id = 0
                 user['name'] = 'system'
                 user['name'] = 'system'
+            elif msg['MsgType'] == 37:  # friend request
+                msg_type_id = 37
+                pass
+                # content = msg['Content']
+                # username = content[content.index('fromusername='): content.index('encryptusername')]
+                # username = username[username.index('"') + 1: username.rindex('"')]
+                # print u'[Friend Request]'
+                # print u'       Nickname:' + msg['RecommendInfo']['NickName']
+                # print u'       附加消息:'+msg['RecommendInfo']['Content']
+                # # print u'Ticket:'+msg['RecommendInfo']['Ticket'] # Ticket添加好友时要用
+                # print u'       微信号:'+username #未设置微信号的 腾讯会自动生成一段微信ID 但是无法通过搜索 搜索到此人
             elif msg['FromUserName'] == self.my_account['UserName']:  # Self
             elif msg['FromUserName'] == self.my_account['UserName']:  # Self
                 msg_type_id = 1
                 msg_type_id = 1
                 user['name'] = 'self'
                 user['name'] = 'self'
@@ -592,14 +588,14 @@ class WXBot:
             user['name'] = HTMLParser.HTMLParser().unescape(user['name'])
             user['name'] = HTMLParser.HTMLParser().unescape(user['name'])
 
 
             if self.DEBUG and msg_type_id != 0:
             if self.DEBUG and msg_type_id != 0:
-                print '[MSG] %s:' % user['name']
+                print u'[MSG] %s:' % user['name']
             content = self.extract_msg_content(msg_type_id, msg)
             content = self.extract_msg_content(msg_type_id, msg)
             message = {'msg_type_id': msg_type_id,
             message = {'msg_type_id': msg_type_id,
                        'msg_id': msg['MsgId'],
                        'msg_id': msg['MsgId'],
                        'content': content,
                        'content': content,
                        'to_user_id': msg['ToUserName'],
                        'to_user_id': msg['ToUserName'],
                        'user': user}
                        'user': user}
-            self.handle_msg_all(message)
+            self.msg_queue.put(message)
 
 
     def schedule(self):
     def schedule(self):
         """
         """
@@ -615,9 +611,7 @@ class WXBot:
             try:
             try:
                 [retcode, selector] = self.sync_check()
                 [retcode, selector] = self.sync_check()
                 # print '[DEBUG] sync_check:', retcode, selector
                 # print '[DEBUG] sync_check:', retcode, selector
-                if retcode == '-1' and selector == '-1':
-                    pass
-                elif retcode == '1100':  # 从微信客户端上登出
+                if retcode == '1100':  # 从微信客户端上登出
                     break
                     break
                 elif retcode == '1101':  # 从其它设备上登了网页微信
                 elif retcode == '1101':  # 从其它设备上登了网页微信
                     break
                     break
@@ -630,6 +624,10 @@ class WXBot:
                         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 == '4':  # 通讯录更新
+                        r = self.sync()
+                        if r is not None:
+                            self.get_contact()
                     elif selector == '6':  # 可能是红包
                     elif selector == '6':  # 可能是红包
                         r = self.sync()
                         r = self.sync()
                         if r is not None:
                         if r is not None:
@@ -647,13 +645,185 @@ class WXBot:
                             self.handle_msg(r)
                             self.handle_msg(r)
                 else:
                 else:
                     print '[DEBUG] sync_check:', retcode, selector
                     print '[DEBUG] sync_check:', retcode, selector
-                self.schedule()
-            except Exception,e:
+            except:
                 print '[ERROR] Except in proc_msg'
                 print '[ERROR] Except in proc_msg'
+                print format_exc()
             check_time = time.time() - check_time
             check_time = time.time() - check_time
             if check_time < 0.8:
             if check_time < 0.8:
                 time.sleep(1 - check_time)
                 time.sleep(1 - check_time)
 
 
+    def msg_thread_proc(self):
+        print '[INFO] Msg thread start'
+        while True:
+            if not self.msg_queue.empty():
+                msg = self.msg_queue.get()
+                self.handle_msg_all(msg)
+            else:
+                time.sleep(0.1)
+
+    def schedule_thread_proc(self):
+        print '[INFO] Schedule thread start'
+        while True:
+            check_time = time.time()
+            self.schedule()
+            check_time = time.time() - check_time
+            if check_time < self.SCHEDULE_INTV:
+                time.sleep(self.SCHEDULE_INTV - check_time)
+
+    def run(self):
+        self.get_uuid()
+        self.gen_qr_code(os.path.join(self.temp_pwd, 'wxqr.png'))
+        print '[INFO] Please use WeChat to scan the QR code .'
+
+        result = self.wait4login()
+        if result != SUCCESS:
+            print '[ERROR] Web WeChat login failed. failed code=%s' % (result,)
+            return
+
+        if self.login():
+            print '[INFO] Web WeChat login succeed .'
+        else:
+            print '[ERROR] Web WeChat login failed .'
+            return
+
+        if self.init():
+            print '[INFO] Web WeChat init succeed .'
+        else:
+            print '[INFO] Web WeChat init failed'
+            return
+        self.status_notify()
+        self.get_contact()
+        print '[INFO] Get %d contacts' % len(self.contact_list)
+        print '[INFO] Start to process messages .'
+
+        msg_thread = threading.Thread(target=self.msg_thread_proc)
+        msg_thread.setDaemon(True)
+        msg_thread.start()
+
+        msg_thread = threading.Thread(target=self.schedule_thread_proc)
+        msg_thread.setDaemon(True)
+        msg_thread.start()
+
+        self.proc_msg()
+
+    def apply_useradd_requests(self,RecommendInfo):
+        url = self.base_uri + '/webwxverifyuser?r='+str(int(time.time()))+'&lang=zh_CN'
+        params = {
+            "BaseRequest": self.base_request,
+            "Opcode": 3,
+            "VerifyUserListSize": 1,
+            "VerifyUserList": [
+                {
+                    "Value": RecommendInfo['UserName'],
+                    "VerifyUserTicket": RecommendInfo['Ticket']             }
+            ],
+            "VerifyContent": "",
+            "SceneListCount": 1,
+            "SceneList": [
+                33
+            ],
+            "skey": self.skey
+        }
+        headers = {'content-type': 'application/json; charset=UTF-8'}
+        data = json.dumps(params, ensure_ascii=False).encode('utf8')
+        try:
+            r = self.session.post(url, data=data, headers=headers)
+        except (ConnectionError, ReadTimeout):
+            return False
+        dic = r.json()
+        return dic['BaseResponse']['Ret'] == 0
+
+    def add_groupuser_to_friend_by_uid(self, uid, VerifyContent):
+        """
+        主动向群内人员打招呼,提交添加好友请求
+        uid-群内人员得uid   VerifyContent-好友招呼内容
+        慎用此接口!封号后果自负!慎用此接口!封号后果自负!慎用此接口!封号后果自负!
+        """
+        if self.is_contact(uid):
+            return True
+        url = self.base_uri + '/webwxverifyuser?r='+str(int(time.time()))+'&lang=zh_CN'
+        params ={
+            "BaseRequest": self.base_request,
+            "Opcode": 2,
+            "VerifyUserListSize": 1,
+            "VerifyUserList": [
+                {
+                    "Value": uid,
+                    "VerifyUserTicket": ""
+                }
+            ],
+            "VerifyContent": VerifyContent,
+            "SceneListCount": 1,
+            "SceneList": [
+                33
+            ],
+            "skey": self.skey
+        }
+        headers = {'content-type': 'application/json; charset=UTF-8'}
+        data = json.dumps(params, ensure_ascii=False).encode('utf8')
+        try:
+            r = self.session.post(url, data=data, headers=headers)
+        except (ConnectionError, ReadTimeout):
+            return False
+        dic = r.json()
+        return dic['BaseResponse']['Ret'] == 0
+
+    def add_friend_to_group(self,uid,group_name):
+        """
+        将好友加入到群聊中
+        """
+        gid = ''
+        #通过群名获取群id,群没保存到通讯录中的话无法添加哦
+        for group in self.group_list:
+            if group['NickName'] == group_name:
+                gid = group['UserName']
+        if gid == '':
+            return False
+        #通过群id判断uid是否在群中
+        for user in self.group_members[gid]:
+            if user['UserName'] == uid:
+                #已经在群里面了,不用加了
+                return True
+        url = self.base_uri + '/webwxupdatechatroom?fun=addmember&pass_ticket=%s' % self.pass_ticket
+        params ={
+            "AddMemberList": uid,
+            "ChatRoomName": gid,
+            "BaseRequest": self.base_request
+        }
+        headers = {'content-type': 'application/json; charset=UTF-8'}
+        data = json.dumps(params, ensure_ascii=False).encode('utf8')
+        try:
+            r = self.session.post(url, data=data, headers=headers)
+        except (ConnectionError, ReadTimeout):
+            return False
+        dic = r.json()
+        return dic['BaseResponse']['Ret'] == 0
+
+    def delete_user_from_group(self,uname,gid):
+        """
+        将群用户从群中剔除,只有群管理员有权限
+        """
+        uid = ""
+        for user in self.group_members[gid]:
+            if user['NickName'] == uname:
+                uid = user['UserName']
+        if uid == "":
+            return False
+        url = self.base_uri + '/webwxupdatechatroom?fun=delmember&pass_ticket=%s' % self.pass_ticket
+        params ={
+            "DelMemberList": uid,
+            "ChatRoomName": gid,
+            "BaseRequest": self.base_request
+        }
+        headers = {'content-type': 'application/json; charset=UTF-8'}
+        data = json.dumps(params, ensure_ascii=False).encode('utf8')
+        try:
+            r = self.session.post(url, data=data, headers=headers)
+        except (ConnectionError, ReadTimeout):
+            return False
+        dic = r.json()
+        return dic['BaseResponse']['Ret'] == 0
+
     def send_msg_by_uid(self, word, dst='filehelper'):
     def send_msg_by_uid(self, word, dst='filehelper'):
         url = self.base_uri + '/webwxsendmsg?pass_ticket=%s' % self.pass_ticket
         url = self.base_uri + '/webwxsendmsg?pass_ticket=%s' % self.pass_ticket
         msg_id = str(int(time.time() * 1000)) + str(random.random())[:5].replace('.', '')
         msg_id = str(int(time.time() * 1000)) + str(random.random())[:5].replace('.', '')
@@ -678,6 +848,100 @@ class WXBot:
         dic = r.json()
         dic = r.json()
         return dic['BaseResponse']['Ret'] == 0
         return dic['BaseResponse']['Ret'] == 0
 
 
+    def upload_media(self, fpath, is_img=False):
+        if not os.path.exists(fpath):
+            print '[ERROR] File not exists.'
+            return None
+        url_1 = 'https://file.wx.qq.com/cgi-bin/mmwebwx-bin/webwxuploadmedia?f=json'
+        url_2 = 'https://file2.wx.qq.com/cgi-bin/mmwebwx-bin/webwxuploadmedia?f=json'
+        flen = str(os.path.getsize(fpath))
+        ftype = mimetypes.guess_type(fpath)[0] or 'application/octet-stream'
+        files = {
+                'id': (None, 'WU_FILE_%s' % str(self.file_index)),
+                'name': (None, os.path.basename(fpath)),
+                'type': (None, ftype),
+                'lastModifiedDate': (None, time.strftime('%m/%d/%Y, %H:%M:%S GMT+0800 (CST)')),
+                'size': (None, flen),
+                'mediatype': (None, 'pic' if is_img else 'doc'),
+                'uploadmediarequest': (None, json.dumps({
+                    'BaseRequest': self.base_request,
+                    'ClientMediaId': int(time.time()),
+                    'TotalLen': flen,
+                    'StartPos': 0,
+                    'DataLen': flen,
+                    'MediaType': 4,
+                    })),
+                'webwx_data_ticket': (None, self.session.cookies['webwx_data_ticket']),
+                'pass_ticket': (None, self.pass_ticket),
+                'filename': (os.path.basename(fpath), open(fpath, 'rb'),ftype.split('/')[1]),
+                }
+        self.file_index += 1
+        try:
+            r = self.session.post(url_1, files=files)
+            if json.loads(r.text)['BaseResponse']['Ret'] != 0:
+                # 当file返回值不为0时则为上传失败,尝试第二服务器上传
+                r = self.session.post(url_2, files=files)
+            if json.loads(r.text)['BaseResponse']['Ret'] != 0:
+                print '[ERROR] Upload media failure.'
+                return None
+            mid = json.loads(r.text)['MediaId']
+            return mid
+        except Exception,e:
+            return None
+
+    def send_file_msg_by_uid(self, fpath, uid):
+        mid = self.upload_media(fpath)
+        if mid is None or not mid:
+            return False
+        url = self.base_uri + '/webwxsendappmsg?fun=async&f=json&pass_ticket=' + self.pass_ticket
+        msg_id = str(int(time.time() * 1000)) + str(random.random())[:5].replace('.', '')
+        data = {
+                'BaseRequest': self.base_request,
+                'Msg': {
+                    'Type': 6,
+                    'Content': ("<appmsg appid='wxeb7ec651dd0aefa9' sdkver=''><title>%s</title><des></des><action></action><type>6</type><content></content><url></url><lowurl></lowurl><appattach><totallen>%s</totallen><attachid>%s</attachid><fileext>%s</fileext></appattach><extinfo></extinfo></appmsg>" % (os.path.basename(fpath).encode('utf-8'), str(os.path.getsize(fpath)), mid, fpath.split('.')[-1])).encode('utf8'),
+                    'FromUserName': self.my_account['UserName'],
+                    'ToUserName': uid,
+                    'LocalID': msg_id,
+                    'ClientMsgId': msg_id, }, }
+        try:
+            r = self.session.post(url, data=json.dumps(data))
+            res = json.loads(r.text)
+            if res['BaseResponse']['Ret'] == 0:
+                return True
+            else:
+                return False
+        except Exception,e:
+            return False
+
+    def send_img_msg_by_uid(self, fpath, uid):
+        mid = self.upload_media(fpath, is_img=True)
+        if mid is None:
+            return False
+        url = self.base_uri + '/webwxsendmsgimg?fun=async&f=json'
+        data = {
+                'BaseRequest': self.base_request,
+                'Msg': {
+                    'Type': 3,
+                    'MediaId': mid,
+                    'FromUserName': self.my_account['UserName'],
+                    'ToUserName': uid,
+                    'LocalID': str(time.time() * 1e7),
+                    'ClientMsgId': str(time.time() * 1e7), }, }
+        if fpath[-4:] == '.gif':
+            url = self.base_uri + '/webwxsendemoticon?fun=sys'
+            data['Msg']['Type'] = 47
+            data['Msg']['EmojiFlag'] = 2
+        try:
+            r = self.session.post(url, data=json.dumps(data))
+            res = json.loads(r.text)
+            if res['BaseResponse']['Ret'] == 0:
+                return True
+            else:
+                return False
+        except Exception,e:
+            return False
+
     def get_user_id(self, name):
     def get_user_id(self, name):
         if name == '':
         if name == '':
             return None
             return None
@@ -689,6 +953,14 @@ class WXBot:
                 return contact['UserName']
                 return contact['UserName']
             elif 'DisplayName' in contact and contact['DisplayName'] == name:
             elif 'DisplayName' in contact and contact['DisplayName'] == name:
                 return contact['UserName']
                 return contact['UserName']
+        for group in self.group_list:
+            if 'RemarkName' in group and group['RemarkName'] == name:
+                return group['UserName']
+            if 'NickName' in group and group['NickName'] == name:
+                return group['UserName']
+            if 'DisplayName' in group and group['DisplayName'] == name:
+                return group['UserName']
+
         return ''
         return ''
 
 
     def send_msg(self, name, word, isfile=False):
     def send_msg(self, name, word, isfile=False):
@@ -729,33 +1001,6 @@ class WXBot:
                 return pm.group(1)
                 return pm.group(1)
         return 'unknown'
         return 'unknown'
 
 
-    def run(self):
-        self.get_uuid()
-        self.gen_qr_code('qr.png')
-        print '[INFO] Please use WeChat to scan the QR code .'
-
-        result = self.wait4login()
-        if result != SUCCESS:
-            print '[ERROR] Web WeChat login failed. failed code=%s'%(result, )
-            return
-
-        if self.login():
-            print '[INFO] Web WeChat login succeed .'
-        else:
-            print '[ERROR] Web WeChat login failed .'
-            return
-
-        if self.init():
-            print '[INFO] Web WeChat init succeed .'
-        else:
-            print '[INFO] Web WeChat init failed'
-            return
-        self.status_notify()
-        self.get_contact()
-        print '[INFO] Get %d contacts' % len(self.contact_list)
-        print '[INFO] Start to process messages .'
-        self.proc_msg()
-
     def get_uuid(self):
     def get_uuid(self):
         url = 'https://login.weixin.qq.com/jslogin'
         url = 'https://login.weixin.qq.com/jslogin'
         params = {
         params = {
@@ -825,14 +1070,14 @@ class WXBot:
                 self.base_uri = redirect_uri[:redirect_uri.rfind('/')]
                 self.base_uri = redirect_uri[:redirect_uri.rfind('/')]
                 return code
                 return code
             elif code == TIMEOUT:
             elif code == TIMEOUT:
-                print '[ERROR] WeChat login timeout. retry in %s secs later...'%(try_later_secs, )
+                print '[ERROR] WeChat login timeout. retry in %s secs later...' % (try_later_secs,)
 
 
-                tip = 1 # 重置
+                tip = 1  # 重置
                 retry_time -= 1
                 retry_time -= 1
                 time.sleep(try_later_secs)
                 time.sleep(try_later_secs)
             else:
             else:
                 print ('[ERROR] WeChat login exception return_code=%s. retry in %s secs later...' %
                 print ('[ERROR] WeChat login exception return_code=%s. retry in %s secs later...' %
-                        (code, try_later_secs))
+                       (code, try_later_secs))
                 tip = 1
                 tip = 1
                 retry_time -= 1
                 retry_time -= 1
                 time.sleep(try_later_secs)
                 time.sleep(try_later_secs)
@@ -927,7 +1172,7 @@ class WXBot:
             selector = pm.group(2)
             selector = pm.group(2)
             return [retcode, selector]
             return [retcode, selector]
         except:
         except:
-            return ['-1', '-1']
+            return [-1, -1]
 
 
     def sync(self):
     def sync(self):
         url = self.base_uri + '/webwxsync?sid=%s&skey=%s&lang=en_US&pass_ticket=%s' \
         url = self.base_uri + '/webwxsync?sid=%s&skey=%s&lang=en_US&pass_ticket=%s' \
@@ -949,100 +1194,6 @@ class WXBot:
         except:
         except:
             return None
             return None
 
 
-    def upload_media(self, fpath, is_img=False):
-        if not os.path.exists(fpath):
-            print '[ERROR] File not exists.'
-            return None
-        url_1 = 'https://file.wx.qq.com/cgi-bin/mmwebwx-bin/webwxuploadmedia?f=json'
-        url_2 = 'https://file2.wx.qq.com/cgi-bin/mmwebwx-bin/webwxuploadmedia?f=json'
-        flen = str(os.path.getsize(fpath))
-        ftype = mimetypes.guess_type(fpath)[0] or 'application/octet-stream'
-        files = {
-                'id': (None, 'WU_FILE_%s' % str(self.file_index)),
-                'name': (None, os.path.basename(fpath)),
-                'type': (None, ftype),
-                'lastModifiedDate': (None, time.strftime('%m/%d/%Y, %H:%M:%S GMT+0800 (CST)')),
-                'size': (None, flen),
-                'mediatype': (None, 'pic' if is_img else 'doc'),
-                'uploadmediarequest': (None, json.dumps({
-                    'BaseRequest': self.base_request,
-                    'ClientMediaId': int(time.time()),
-                    'TotalLen': flen,
-                    'StartPos': 0,
-                    'DataLen': flen,
-                    'MediaType': 4,
-                    })),
-                'webwx_data_ticket': (None, self.session.cookies['webwx_data_ticket']),
-                'pass_ticket': (None, self.pass_ticket),
-                'filename': (os.path.basename(fpath), open(fpath, 'rb'),ftype.split('/')[1]),
-                }
-        self.file_index += 1
-        try:
-            r = self.session.post(url_1, files=files)
-            if json.loads(r.text)['BaseResponse']['Ret'] != 0:
-                # 当file返回值不为0时则为上传失败,尝试第二服务器上传
-                r = self.session.post(url_2, files=files)
-            if json.loads(r.text)['BaseResponse']['Ret'] != 0:
-                print '[ERROR] Upload media failure.'
-                return None
-            mid = json.loads(r.text)['MediaId']
-            return mid
-        except Exception,e:
-            return None
-
-    def send_file_msg_by_uid(self, fpath, uid):
-        mid = self.upload_media(fpath)
-        if mid is None or not mid:
-            return False
-        url = self.base_uri + '/webwxsendappmsg?fun=async&f=json&pass_ticket=' + self.pass_ticket
-        msg_id = str(int(time.time() * 1000)) + str(random.random())[:5].replace('.', '')
-        data = {
-                'BaseRequest': self.base_request,
-                'Msg': {
-                    'Type': 6,
-                    'Content': ("<appmsg appid='wxeb7ec651dd0aefa9' sdkver=''><title>%s</title><des></des><action></action><type>6</type><content></content><url></url><lowurl></lowurl><appattach><totallen>%s</totallen><attachid>%s</attachid><fileext>%s</fileext></appattach><extinfo></extinfo></appmsg>" % (os.path.basename(fpath).encode('utf-8'), str(os.path.getsize(fpath)), mid, fpath.split('.')[-1])).encode('utf8'),
-                    'FromUserName': self.my_account['UserName'],
-                    'ToUserName': uid,
-                    'LocalID': msg_id,
-                    'ClientMsgId': msg_id, }, }
-        try:
-            r = self.session.post(url, data=json.dumps(data))
-            res = json.loads(r.text)
-            if res['BaseResponse']['Ret'] == 0:
-                return True
-            else:
-                return False
-        except Exception,e:
-            return False
-
-    def send_img_msg_by_uid(self, fpath, uid):
-        mid = self.upload_media(fpath, is_img=True)
-        if mid is None:
-            return False
-        url = self.base_uri + '/webwxsendmsgimg?fun=async&f=json'
-        data = {
-                'BaseRequest': self.base_request,
-                'Msg': {
-                    'Type': 3,
-                    'MediaId': mid,
-                    'FromUserName': self.my_account['UserName'],
-                    'ToUserName': uid,
-                    'LocalID': str(time.time() * 1e7),
-                    'ClientMsgId': str(time.time() * 1e7), }, }
-        if fpath[-4:] == '.gif':
-            url = self.base_uri + '/webwxsendemoticon?fun=sys'
-            data['Msg']['Type'] = 47
-            data['Msg']['EmojiFlag'] = 2
-        try:
-            r = self.session.post(url, data=json.dumps(data))
-            res = json.loads(r.text)
-            if res['BaseResponse']['Ret'] == 0:
-                return True
-            else:
-                return False
-        except Exception,e:
-            return False
-
     def get_icon(self, uid, gid=None):
     def get_icon(self, uid, gid=None):
         """
         """
         获取联系人或者群聊成员头像
         获取联系人或者群聊成员头像
@@ -1052,11 +1203,12 @@ class WXBot:
         if gid is None:
         if gid is None:
             url = self.base_uri + '/webwxgeticon?username=%s&skey=%s' % (uid, self.skey)
             url = self.base_uri + '/webwxgeticon?username=%s&skey=%s' % (uid, self.skey)
         else:
         else:
-            url = self.base_uri + '/webwxgeticon?username=%s&skey=%s&chatroomid=%s' % (uid, self.skey, self.encry_chat_room_id_list[gid])
+            url = self.base_uri + '/webwxgeticon?username=%s&skey=%s&chatroomid=%s' % (
+            uid, self.skey, self.encry_chat_room_id_list[gid])
         r = self.session.get(url)
         r = self.session.get(url)
         data = r.content
         data = r.content
         fn = 'icon_' + uid + '.jpg'
         fn = 'icon_' + uid + '.jpg'
-        with open(fn, 'wb') as f:
+        with open(os.path.join(self.temp_pwd,fn), 'wb') as f:
             f.write(data)
             f.write(data)
         return fn
         return fn
 
 
@@ -1069,7 +1221,7 @@ class WXBot:
         r = self.session.get(url)
         r = self.session.get(url)
         data = r.content
         data = r.content
         fn = 'head_' + uid + '.jpg'
         fn = 'head_' + uid + '.jpg'
-        with open(fn, 'wb') as f:
+        with open(os.path.join(self.temp_pwd,fn), 'wb') as f:
             f.write(data)
             f.write(data)
         return fn
         return fn
 
 
@@ -1086,7 +1238,7 @@ class WXBot:
         r = self.session.get(url)
         r = self.session.get(url)
         data = r.content
         data = r.content
         fn = 'img_' + msgid + '.jpg'
         fn = 'img_' + msgid + '.jpg'
-        with open(fn, 'wb') as f:
+        with open(os.path.join(self.temp_pwd,fn), 'wb') as f:
             f.write(data)
             f.write(data)
         return fn
         return fn
 
 
@@ -1103,6 +1255,23 @@ class WXBot:
         r = self.session.get(url)
         r = self.session.get(url)
         data = r.content
         data = r.content
         fn = 'voice_' + msgid + '.mp3'
         fn = 'voice_' + msgid + '.mp3'
-        with open(fn, 'wb') as f:
+        with open(os.path.join(self.temp_pwd,fn), 'wb') as f:
             f.write(data)
             f.write(data)
         return fn
         return fn
+
+    def set_remark_name(self, uid, name):  # 设置联系人的备注名
+        url = self.base_uri + '/webwxoplog?lang=zh_CN&pass_ticket=%s' % self.pass_ticket
+        remark_name = self.to_unicode(name)
+        params = {
+            'BaseRequest': self.base_request,
+            'CmdId': 2,
+            'RemarkName': remark_name,
+            'UserName': uid
+        }
+        try:
+            r = self.session.post(url, data=json.dumps(params), timeout=60)
+            r.encoding = 'utf-8'
+            dic = json.loads(r.text)
+            return dic['BaseResponse']['ErrMsg']
+        except:
+            return None