diff --git a/.gitignore b/.gitignore
index 969df946..c0ca475b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,6 +3,7 @@ dist/*
tests/*
itchat.egg-info/*
*.pyc
+*.pkl
*.swp
test.py
itchat.pkl
diff --git a/README.md b/README.md
index 1ba9e11c..de8f7e00 100644
--- a/README.md
+++ b/README.md
@@ -59,36 +59,61 @@ itchat.run()
## 进阶应用
+### 特殊的字典使用方式
+
+通过打印itchat的用户以及注册消息的参数,可以发现这些值都是字典。
+
+但实际上itchat精心构造了相应的消息、用户、群聊、公众号类。
+
+其所有的键值都可以通过这一方式访问:
+
+```python
+@itchat.msg_register(TEXT)
+def _(msg):
+ # equals to print(msg['FromUserName'])
+ print(msg.fromUserName)
+```
+
+属性名为键值首字母小写后的内容。
+
+```python
+author = itchat.search_friends(nickName='LittleCoder')[0]
+author.send('greeting, littlecoder!')
+```
+
### 各类型消息的注册
通过如下代码,微信已经可以就日常的各种信息进行获取与回复。
```python
-#coding=utf8
import itchat, time
from itchat.content import *
@itchat.msg_register([TEXT, MAP, CARD, NOTE, SHARING])
def text_reply(msg):
- itchat.send('%s: %s' % (msg['Type'], msg['Text']), msg['FromUserName'])
+ msg.user.send('%s: %s' % (msg.type, msg.text))
@itchat.msg_register([PICTURE, RECORDING, ATTACHMENT, VIDEO])
def download_files(msg):
- msg['Text'](msg['FileName'])
- return '@%s@%s' % ({'Picture': 'img', 'Video': 'vid'}.get(msg['Type'], 'fil'), msg['FileName'])
+ msg.download(msg.fileName)
+ typeSymbol = {
+ PICTURE: 'img',
+ VIDEO: 'vid', }.get(msg.type, 'fil')
+ return '@%s@%s' % (typeSymbol, msg.fileName)
@itchat.msg_register(FRIENDS)
def add_friend(msg):
- itchat.add_friend(**msg['Text']) # 该操作会自动将新好友的消息录入,不需要重载通讯录
- itchat.send_msg('Nice to meet you!', msg['RecommendInfo']['UserName'])
+ msg.user.verify()
+ msg.user.send('Nice to meet you!')
@itchat.msg_register(TEXT, isGroupChat=True)
def text_reply(msg):
- if msg['isAt']:
- itchat.send(u'@%s\u2005I received: %s' % (msg['ActualNickName'], msg['Content']), msg['FromUserName'])
+ if msg.isAt:
+ msg.user.send(u'@%s\u2005I received: %s' % (
+ msg.actualNickName, msg.text))
itchat.auto_login(True)
-itchat.run()
+itchat.run(True)
```
### 命令行二维码
@@ -234,6 +259,8 @@ A: 有些账号是天生无法给自己的账号发送信息的,建议使用`f
## 类似项目
+[youfou/wxpy][youfou-wxpy]: 优秀的api包装和配套插件,微信机器人/优雅的微信个人号API
+
[liuwons/wxBot][liuwons-wxBot]: 类似的基于Python的微信机器人
[zixia/wechaty][zixia-wechaty]: 基于Javascript(ES6)的微信个人账号机器人NodeJS框架/库
@@ -267,6 +294,7 @@ A: 有些账号是天生无法给自己的账号发送信息的,建议使用`f
[littlecodersh]: https://github.com/littlecodersh
[tempdban]: https://github.com/tempdban
[Chyroc]: https://github.com/Chyroc
+[youfou-wxpy]: https://github.com/youfou/wxpy
[liuwons-wxBot]: https://github.com/liuwons/wxBot
[zixia-wechaty]: https://github.com/zixia/wechaty
[Mojo-Weixin]: https://github.com/sjdy521/Mojo-Weixin
diff --git a/itchat/components/contact.py b/itchat/components/contact.py
index 1fc98bec..95bd3b60 100644
--- a/itchat/components/contact.py
+++ b/itchat/components/contact.py
@@ -6,7 +6,7 @@
from .. import config, utils
from ..returnvalues import ReturnValue
-from ..storage import contact_change
+from ..storage import contact_change, templates
logger = logging.getLogger('itchat')
@@ -99,8 +99,8 @@ def update_friend(self, userName):
return r if len(r) != 1 else r[0]
def update_info_dict(oldInfoDict, newInfoDict):
- '''
- only normal values will be updated here
+ ''' only normal values will be updated here
+ because newInfoDict is normal dict, so it's not necessary to consider templates
'''
for k, v in newInfoDict.items():
if any((isinstance(v, t) for t in (tuple, list, dict))):
@@ -130,8 +130,8 @@ def update_local_chatrooms(core, l):
if oldChatroom:
update_info_dict(oldChatroom, chatroom)
# - update other values
- memberList, oldMemberList = (c.get('MemberList', [])
- for c in (chatroom, oldChatroom))
+ memberList = chatroom.get('MemberList', [])
+ oldMemberList = oldChatroom.memberList
if memberList:
for member in memberList:
oldMember = utils.search_dict_list(
@@ -141,17 +141,19 @@ def update_local_chatrooms(core, l):
else:
oldMemberList.append(member)
else:
- oldChatroom = chatroom
- core.chatroomList.append(chatroom)
+ oldChatroom = templates.wrap_user_dict(chatroom)
+ core.chatroomList.append(oldChatroom)
# delete useless members
if len(chatroom['MemberList']) != len(oldChatroom['MemberList']) and \
chatroom['MemberList']:
existsUserNames = [member['UserName'] for member in chatroom['MemberList']]
delList = []
for i, member in enumerate(oldChatroom['MemberList']):
- if member['UserName'] not in existsUserNames: delList.append(i)
+ if member['UserName'] not in existsUserNames:
+ delList.append(i)
delList.sort(reverse=True)
- for i in delList: del oldChatroom['MemberList'][i]
+ for i in delList:
+ del oldChatroom['MemberList'][i]
# - update OwnerUin
if oldChatroom.get('ChatRoomOwner') and oldChatroom.get('MemberList'):
oldChatroom['OwnerUin'] = utils.search_dict_list(oldChatroom['MemberList'],
@@ -184,8 +186,8 @@ def update_local_friends(core, l):
utils.emoji_formatter(friend, 'NickName')
if 'DisplayName' in friend:
utils.emoji_formatter(friend, 'DisplayName')
- if 'RemarkName' in member:
- utils.emoji_formatter(member, 'RemarkName')
+ if 'RemarkName' in friend:
+ utils.emoji_formatter(friend, 'RemarkName')
oldInfoDict = utils.search_dict_list(
fullList, 'UserName', friend['UserName'])
if oldInfoDict is None:
diff --git a/itchat/components/hotreload.py b/itchat/components/hotreload.py
index f0a40d04..d34d25ee 100644
--- a/itchat/components/hotreload.py
+++ b/itchat/components/hotreload.py
@@ -5,6 +5,7 @@
from ..config import VERSION
from ..returnvalues import ReturnValue
+from ..storage import templates
from .contact import update_local_chatrooms, update_local_friends
from .messages import produce_msg
@@ -50,6 +51,8 @@ def load_login_status(self, fileDir,
'ErrMsg': 'cached status ignored because of version',
'Ret': -1005, }})
self.loginInfo = j['loginInfo']
+ self.loginInfo['User'] = templates.User(self.loginInfo['User'])
+ self.loginInfo['User'].core = self
self.s.cookies = requests.utils.cookiejar_from_dict(j['cookies'])
self.storageClass.loads(j['storage'])
msgList, contactList = self.get_msg()
diff --git a/itchat/components/login.py b/itchat/components/login.py
index c35d1ecd..7736427c 100644
--- a/itchat/components/login.py
+++ b/itchat/components/login.py
@@ -9,6 +9,7 @@
from .. import config, utils
from ..returnvalues import ReturnValue
+from ..storage.templates import wrap_user_dict
from .contact import update_local_chatrooms, update_local_friends
from .messages import produce_msg
@@ -182,7 +183,7 @@ def web_init(self):
# deal with login info
utils.emoji_formatter(dic['User'], 'NickName')
self.loginInfo['InviteStartCount'] = int(dic['InviteStartCount'])
- self.loginInfo['User'] = utils.struct_friend_info(dic['User'])
+ self.loginInfo['User'] = wrap_user_dict(utils.struct_friend_info(dic['User']))
self.memberList.append(self.loginInfo['User'])
self.loginInfo['SyncKey'] = dic['SyncKey']
self.loginInfo['synckey'] = '|'.join(['%s_%s' % (item['Key'], item['Val'])
@@ -247,6 +248,7 @@ def maintain_loop():
else:
otherList.append(contact)
chatroomMsg = update_local_chatrooms(self, chatroomList)
+ chatroomMsg['User'] = self.loginInfo['User']
self.msgList.put(chatroomMsg)
update_local_friends(self, otherList)
retryCount = 0
diff --git a/itchat/components/messages.py b/itchat/components/messages.py
index 9f3cd10d..c376dc59 100644
--- a/itchat/components/messages.py
+++ b/itchat/components/messages.py
@@ -1,438 +1,509 @@
-import os, time, re, io
-import json
-import mimetypes, hashlib
-import traceback, logging
-from collections import OrderedDict
-
-import requests
-
-from .. import config, utils
-from ..returnvalues import ReturnValue
-from .contact import update_local_uin
-
-logger = logging.getLogger('itchat')
-
-def load_messages(core):
- core.send_raw_msg = send_raw_msg
- core.send_msg = send_msg
- core.upload_file = upload_file
- core.send_file = send_file
- core.send_image = send_image
- core.send_video = send_video
- core.send = send
-
-def get_download_fn(core, url, msgId):
- def download_fn(downloadDir=None):
- params = {
- 'msgid': msgId,
- 'skey': core.loginInfo['skey'],}
- headers = { 'User-Agent' : config.USER_AGENT }
- r = core.s.get(url, params=params, stream=True, headers = headers)
- tempStorage = io.BytesIO()
- for block in r.iter_content(1024):
- tempStorage.write(block)
- if downloadDir is None:
- return tempStorage.getvalue()
- with open(downloadDir, 'wb') as f:
- f.write(tempStorage.getvalue())
- tempStorage.seek(0)
- return ReturnValue({'BaseResponse': {
- 'ErrMsg': 'Successfully downloaded',
- 'Ret': 0, },
- 'PostFix': utils.get_image_postfix(tempStorage.read(20)), })
- return download_fn
-
-def produce_msg(core, msgList):
- ''' for messages types
- * 40 msg, 43 videochat, 50 VOIPMSG, 52 voipnotifymsg
- * 53 webwxvoipnotifymsg, 9999 sysnotice
- '''
- rl = []
- srl = [40, 43, 50, 52, 53, 9999]
- for m in msgList:
- if '@@' in m['FromUserName'] or '@@' in m['ToUserName']:
- produce_group_chat(core, m)
- else:
- utils.msg_formatter(m, 'Content')
- if m['MsgType'] == 1: # words
- if m['Url']:
- regx = r'(.+?\(.+?\))'
- data = re.search(regx, m['Content'])
- data = 'Map' if data is None else data.group(1)
- msg = {
- 'Type': 'Map',
- 'Text': data,}
- else:
- msg = {
- 'Type': 'Text',
- 'Text': m['Content'],}
- elif m['MsgType'] == 3 or m['MsgType'] == 47: # picture
- download_fn = get_download_fn(core,
- '%s/webwxgetmsgimg' % core.loginInfo['url'], m['NewMsgId'])
- msg = {
- 'Type' : 'Picture',
- 'FileName' : '%s.%s' % (time.strftime('%y%m%d-%H%M%S', time.localtime()),
- 'png' if m['MsgType'] == 3 else 'gif'),
- 'Text' : download_fn, }
- elif m['MsgType'] == 34: # voice
- download_fn = get_download_fn(core,
- '%s/webwxgetvoice' % core.loginInfo['url'], m['NewMsgId'])
- msg = {
- 'Type': 'Recording',
- 'FileName' : '%s.mp3' % time.strftime('%y%m%d-%H%M%S', time.localtime()),
- 'Text': download_fn,}
- elif m['MsgType'] == 37: # friends
- msg = {
- 'Type': 'Friends',
- 'Text': {
- 'status' : m['Status'],
- 'userName' : m['RecommendInfo']['UserName'],
- 'verifyContent' : m['Ticket'],
- 'autoUpdate' : m['RecommendInfo'], }, }
- elif m['MsgType'] == 42: # name card
- msg = {
- 'Type': 'Card',
- 'Text': m['RecommendInfo'], }
- elif m['MsgType'] in (43, 62): # tiny video
- msgId = m['MsgId']
- def download_video(videoDir=None):
- url = '%s/webwxgetvideo' % core.loginInfo['url']
- params = {
- 'msgid': msgId,
- 'skey': core.loginInfo['skey'],}
- headers = {'Range': 'bytes=0-', 'User-Agent' : config.USER_AGENT }
- r = core.s.get(url, params=params, headers=headers, stream=True)
- tempStorage = io.BytesIO()
- for block in r.iter_content(1024):
- tempStorage.write(block)
- if videoDir is None:
- return tempStorage.getvalue()
- with open(videoDir, 'wb') as f:
- f.write(tempStorage.getvalue())
- return ReturnValue({'BaseResponse': {
- 'ErrMsg': 'Successfully downloaded',
- 'Ret': 0, }})
- msg = {
- 'Type': 'Video',
- 'FileName' : '%s.mp4' % time.strftime('%y%m%d-%H%M%S', time.localtime()),
- 'Text': download_video, }
- elif m['MsgType'] == 49: # sharing
- if m['AppMsgType'] == 6:
- rawMsg = m
- cookiesList = {name:data for name,data in core.s.cookies.items()}
- def download_atta(attaDir=None):
- url = core.loginInfo['fileUrl'] + '/webwxgetmedia'
- params = {
- 'sender': rawMsg['FromUserName'],
- 'mediaid': rawMsg['MediaId'],
- 'filename': rawMsg['FileName'],
- 'fromuser': core.loginInfo['wxuin'],
- 'pass_ticket': 'undefined',
- 'webwx_data_ticket': cookiesList['webwx_data_ticket'],}
- headers = { 'User-Agent' : config.USER_AGENT }
- r = core.s.get(url, params=params, stream=True, headers=headers)
- tempStorage = io.BytesIO()
- for block in r.iter_content(1024):
- tempStorage.write(block)
- if attaDir is None:
- return tempStorage.getvalue()
- with open(attaDir, 'wb') as f:
- f.write(tempStorage.getvalue())
- return ReturnValue({'BaseResponse': {
- 'ErrMsg': 'Successfully downloaded',
- 'Ret': 0, }})
- msg = {
- 'Type': 'Attachment',
- 'Text': download_atta, }
- elif m['AppMsgType'] == 8:
- download_fn = get_download_fn(core,
- '%s/webwxgetmsgimg' % core.loginInfo['url'], m['NewMsgId'])
- msg = {
- 'Type' : 'Picture',
- 'FileName' : '%s.gif' % (
- time.strftime('%y%m%d-%H%M%S', time.localtime())),
- 'Text' : download_fn, }
- elif m['AppMsgType'] == 17:
- msg = {
- 'Type': 'Note',
- 'Text': m['FileName'], }
- elif m['AppMsgType'] == 2000:
- regx = r'\[CDATA\[(.+?)\][\s\S]+?\[CDATA\[(.+?)\]'
- data = re.search(regx, m['Content'])
- if data:
- data = data.group(2).split(u'\u3002')[0]
- else:
- data = 'You may found detailed info in Content key.'
- msg = {
- 'Type': 'Note',
- 'Text': data, }
- else:
- msg = {
- 'Type': 'Sharing',
- 'Text': m['FileName'], }
- elif m['MsgType'] == 51: # phone init
- msg = update_local_uin(core, m)
- elif m['MsgType'] == 10000:
- msg = {
- 'Type': 'Note',
- 'Text': m['Content'],}
- elif m['MsgType'] == 10002:
- regx = r'\[CDATA\[(.+?)\]\]'
- data = re.search(regx, m['Content'])
- data = 'System message' if data is None else data.group(1).replace('\\', '')
- msg = {
- 'Type': 'Note',
- 'Text': data, }
- elif m['MsgType'] in srl:
- msg = {
- 'Type': 'Useless',
- 'Text': 'UselessMsg', }
- else:
- logger.debug('Useless message received: %s\n%s' % (m['MsgType'], str(m)))
- msg = {
- 'Type': 'Useless',
- 'Text': 'UselessMsg', }
- m = dict(m, **msg)
- rl.append(m)
- return rl
-
-def produce_group_chat(core, msg):
- r = re.match('(@[0-9a-z]*?):
(.*)$', msg['Content'])
- if r:
- actualUserName, content = r.groups()
- chatroomUserName = msg['FromUserName']
- elif msg['FromUserName'] == core.storageClass.userName:
- actualUserName = core.storageClass.userName
- content = msg['Content']
- chatroomUserName = msg['ToUserName']
- else:
- msg['ActualUserName'] = core.storageClass.userName
- msg['ActualNickName'] = core.storageClass.nickName
- msg['isAt'] = False
- utils.msg_formatter(msg, 'Content')
- return
- chatroom = core.storageClass.search_chatrooms(userName=chatroomUserName)
- member = utils.search_dict_list((chatroom or {}).get(
- 'MemberList') or [], 'UserName', actualUserName)
- if member is None:
- chatroom = core.update_chatroom(msg['FromUserName'])
- member = utils.search_dict_list((chatroom or {}).get(
- 'MemberList') or [], 'UserName', actualUserName)
- if member is None:
- logger.debug('chatroom member fetch failed with %s' % actualUserName)
- msg['ActualNickName'] = ''
- msg['isAt'] = False
- else:
- msg['ActualNickName'] = member['DisplayName'] or member['NickName']
- atFlag = '@' + (chatroom['self']['DisplayName']
- or core.storageClass.nickName)
- msg['isAt'] = (
- (atFlag + (u'\u2005' if u'\u2005' in msg['Content'] else ' '))
- in msg['Content'] or msg['Content'].endswith(atFlag))
- msg['ActualUserName'] = actualUserName
- msg['Content'] = content
- utils.msg_formatter(msg, 'Content')
-
-def send_raw_msg(self, msgType, content, toUserName):
- url = '%s/webwxsendmsg' % self.loginInfo['url']
- data = {
- 'BaseRequest': self.loginInfo['BaseRequest'],
- 'Msg': {
- 'Type': msgType,
- 'Content': content,
- 'FromUserName': self.storageClass.userName,
- 'ToUserName': (toUserName if toUserName else self.storageClass.userName),
- 'LocalID': int(time.time() * 1e4),
- 'ClientMsgId': int(time.time() * 1e4),
- },
- 'Scene': 0, }
- headers = { 'ContentType': 'application/json; charset=UTF-8', 'User-Agent' : config.USER_AGENT }
- r = self.s.post(url, headers=headers,
- data=json.dumps(data, ensure_ascii=False).encode('utf8'))
- return ReturnValue(rawResponse=r)
-
-def send_msg(self, msg='Test Message', toUserName=None):
- logger.debug('Request to send a text message to %s: %s' % (toUserName, msg))
- r = self.send_raw_msg(1, msg, toUserName)
- return r
-
-def upload_file(self, fileDir, isPicture=False, isVideo=False,
- toUserName='filehelper'):
- logger.debug('Request to upload a %s: %s' % (
- 'picture' if isPicture else 'video' if isVideo else 'file', fileDir))
- if not utils.check_file(fileDir):
- return ReturnValue({'BaseResponse': {
- 'ErrMsg': 'No file found in specific dir',
- 'Ret': -1002, }})
- fileSize = os.path.getsize(fileDir)
- fileSymbol = 'pic' if isPicture else 'video' if isVideo else'doc'
- with open(fileDir, 'rb') as f:
- fileMd5 = hashlib.md5(f.read()).hexdigest()
- file_ = open(fileDir, 'rb')
- chunks = int((fileSize - 1) / 524288) + 1
- clientMediaId = int(time.time() * 1e4)
- uploadMediaRequest = json.dumps(OrderedDict([
- ('UploadType', 2),
- ('BaseRequest', self.loginInfo['BaseRequest']),
- ('ClientMediaId', clientMediaId),
- ('TotalLen', fileSize),
- ('StartPos', 0),
- ('DataLen', fileSize),
- ('MediaType', 4),
- ('FromUserName', self.storageClass.userName),
- ('ToUserName', toUserName),
- ('FileMd5', fileMd5)]
- ), separators = (',', ':'))
- r = {'BaseResponse': {'Ret': -1005, 'ErrMsg': 'Empty file detected'}}
- for chunk in range(chunks):
- r = upload_chunk_file(self, fileDir, fileSymbol, fileSize,
- file_, chunk, chunks, uploadMediaRequest)
- file_.close()
- if isinstance(r, dict):
- return ReturnValue(r)
- return ReturnValue(rawResponse=r)
-
-def upload_chunk_file(core, fileDir, fileSymbol, fileSize,
- file, chunk, chunks, uploadMediaRequest):
- url = core.loginInfo.get('fileUrl', core.loginInfo['url']) + \
- '/webwxuploadmedia?f=json'
- # save it on server
- cookiesList = {name:data for name,data in core.s.cookies.items()}
- fileType = mimetypes.guess_type(fileDir)[0] or 'application/octet-stream'
- files = OrderedDict([
- ('id', (None, 'WU_FILE_0')),
- ('name', (None, os.path.basename(fileDir))),
- ('type', (None, fileType)),
- ('lastModifiedDate', (None, time.strftime('%a %b %d %Y %H:%M:%S GMT+0800 (CST)'))),
- ('size', (None, str(fileSize))),
- ('chunks', (None, None)),
- ('chunk', (None, None)),
- ('mediatype', (None, fileSymbol)),
- ('uploadmediarequest', (None, uploadMediaRequest)),
- ('webwx_data_ticket', (None, cookiesList['webwx_data_ticket'])),
- ('pass_ticket', (None, core.loginInfo['pass_ticket'])),
- ('filename' , (os.path.basename(fileDir), file.read(524288), 'application/octet-stream'))])
- if chunks == 1:
- del files['chunk']; del files['chunks']
- else:
- files['chunk'], files['chunks'] = (None, str(chunk)), (None, str(chunks))
- headers = { 'User-Agent' : config.USER_AGENT }
- return requests.post(url, files=files, headers=headers)
-
-def send_file(self, fileDir, toUserName=None, mediaId=None):
- logger.debug('Request to send a file(mediaId: %s) to %s: %s' % (
- mediaId, toUserName, fileDir))
- if toUserName is None: toUserName = self.storageClass.userName
- if mediaId is None:
- r = self.upload_file(fileDir)
- if r:
- mediaId = r['MediaId']
- else:
- return r
- url = '%s/webwxsendappmsg?fun=async&f=json' % self.loginInfo['url']
- data = {
- 'BaseRequest': self.loginInfo['BaseRequest'],
- 'Msg': {
- 'Type': 6,
- 'Content': ("%s"%os.path.basename(fileDir) +
- "6" +
- "%s%s"%(str(os.path.getsize(fileDir)), mediaId) +
- "%s"%os.path.splitext(fileDir)[1].replace('.','')),
- 'FromUserName': self.storageClass.userName,
- 'ToUserName': toUserName,
- 'LocalID': int(time.time() * 1e4),
- 'ClientMsgId': int(time.time() * 1e4), },
- 'Scene': 0, }
- headers = {
- 'User-Agent': config.USER_AGENT,
- 'Content-Type': 'application/json;charset=UTF-8', }
- r = self.s.post(url, headers=headers,
- data=json.dumps(data, ensure_ascii=False).encode('utf8'))
- return ReturnValue(rawResponse=r)
-
-def send_image(self, fileDir, toUserName=None, mediaId=None):
- logger.debug('Request to send a image(mediaId: %s) to %s: %s' % (
- mediaId, toUserName, fileDir))
- if toUserName is None: toUserName = self.storageClass.userName
- if mediaId is None:
- r = self.upload_file(fileDir, isPicture=not fileDir[-4:] == '.gif')
- if r:
- mediaId = r['MediaId']
- else:
- return r
- url = '%s/webwxsendmsgimg?fun=async&f=json' % self.loginInfo['url']
- data = {
- 'BaseRequest': self.loginInfo['BaseRequest'],
- 'Msg': {
- 'Type': 3,
- 'MediaId': mediaId,
- 'FromUserName': self.storageClass.userName,
- 'ToUserName': toUserName,
- 'LocalID': int(time.time() * 1e4),
- 'ClientMsgId': int(time.time() * 1e4), },
- 'Scene': 0, }
- if fileDir[-4:] == '.gif':
- url = '%s/webwxsendemoticon?fun=sys' % self.loginInfo['url']
- data['Msg']['Type'] = 47
- data['Msg']['EmojiFlag'] = 2
- headers = {
- 'User-Agent': config.USER_AGENT,
- 'Content-Type': 'application/json;charset=UTF-8', }
- r = self.s.post(url, headers=headers,
- data=json.dumps(data, ensure_ascii=False).encode('utf8'))
- return ReturnValue(rawResponse=r)
-
-def send_video(self, fileDir=None, toUserName=None, mediaId=None):
- logger.debug('Request to send a video(mediaId: %s) to %s: %s' % (
- mediaId, toUserName, fileDir))
- if toUserName is None: toUserName = self.storageClass.userName
- if mediaId is None:
- r = self.upload_file(fileDir, isVideo=True)
- if r:
- mediaId = r['MediaId']
- else:
- return r
- url = '%s/webwxsendvideomsg?fun=async&f=json&pass_ticket=%s' % (
- self.loginInfo['url'], self.loginInfo['pass_ticket'])
- data = {
- 'BaseRequest': self.loginInfo['BaseRequest'],
- 'Msg': {
- 'Type' : 43,
- 'MediaId' : mediaId,
- 'FromUserName' : self.storageClass.userName,
- 'ToUserName' : toUserName,
- 'LocalID' : int(time.time() * 1e4),
- 'ClientMsgId' : int(time.time() * 1e4), },
- 'Scene': 0, }
- headers = {
- 'User-Agent' : config.USER_AGENT,
- 'Content-Type': 'application/json;charset=UTF-8', }
- r = self.s.post(url, headers=headers,
- data=json.dumps(data, ensure_ascii=False).encode('utf8'))
- return ReturnValue(rawResponse=r)
-
-def send(self, msg, toUserName=None, mediaId=None):
- if not msg:
- r = ReturnValue({'BaseResponse': {
- 'ErrMsg': 'No message.',
- 'Ret': -1005, }})
- elif msg[:5] == '@fil@':
- if mediaId is None:
- r = self.send_file(msg[5:], toUserName)
- else:
- r = self.send_file(msg[5:], toUserName, mediaId)
- elif msg[:5] == '@img@':
- if mediaId is None:
- r = self.send_image(msg[5:], toUserName)
- else:
- r = self.send_image(msg[5:], toUserName, mediaId)
- elif msg[:5] == '@msg@':
- r = self.send_msg(msg[5:], toUserName)
- elif msg[:5] == '@vid@':
- if mediaId is None:
- r = self.send_video(msg[5:], toUserName)
- else:
- r = self.send_video(msg[5:], toUserName, mediaId)
- else:
- r = self.send_msg(msg, toUserName)
- return r
+import os, time, re, io
+import json
+import mimetypes, hashlib
+import traceback, logging
+from collections import OrderedDict
+
+import requests
+
+from .. import config, utils
+from ..returnvalues import ReturnValue
+from ..storage import templates
+from .contact import update_local_uin
+
+logger = logging.getLogger('itchat')
+
+def load_messages(core):
+ core.send_raw_msg = send_raw_msg
+ core.send_msg = send_msg
+ core.upload_file = upload_file
+ core.send_file = send_file
+ core.send_image = send_image
+ core.send_video = send_video
+ core.send = send
+
+def get_download_fn(core, url, msgId):
+ def download_fn(downloadDir=None):
+ params = {
+ 'msgid': msgId,
+ 'skey': core.loginInfo['skey'],}
+ headers = { 'User-Agent' : config.USER_AGENT }
+ r = core.s.get(url, params=params, stream=True, headers = headers)
+ tempStorage = io.BytesIO()
+ for block in r.iter_content(1024):
+ tempStorage.write(block)
+ if downloadDir is None:
+ return tempStorage.getvalue()
+ with open(downloadDir, 'wb') as f:
+ f.write(tempStorage.getvalue())
+ tempStorage.seek(0)
+ return ReturnValue({'BaseResponse': {
+ 'ErrMsg': 'Successfully downloaded',
+ 'Ret': 0, },
+ 'PostFix': utils.get_image_postfix(tempStorage.read(20)), })
+ return download_fn
+
+def produce_msg(core, msgList):
+ ''' for messages types
+ * 40 msg, 43 videochat, 50 VOIPMSG, 52 voipnotifymsg
+ * 53 webwxvoipnotifymsg, 9999 sysnotice
+ '''
+ rl = []
+ srl = [40, 43, 50, 52, 53, 9999]
+ for m in msgList:
+ # get actual opposite
+ if m['FromUserName'] == core.storageClass.userName:
+ actualOpposite = m['ToUserName']
+ else:
+ actualOpposite = m['FromUserName']
+ # produce basic message
+ if '@@' in m['FromUserName'] or '@@' in m['ToUserName']:
+ produce_group_chat(core, m)
+ else:
+ utils.msg_formatter(m, 'Content')
+ # set user of msg
+ if '@@' in actualOpposite:
+ m['User'] = core.search_chatrooms(userName=actualOpposite) or \
+ templates.Chatroom({'UserName': actualOpposite})
+ # we don't need to update chatroom here because we have
+ # updated once when producing basic message
+ elif actualOpposite in ('filehelper', 'fmessage'):
+ m['User'] = templates.User({'UserName': actualOpposite})
+ else:
+ m['User'] = core.search_mps(userName=actualOpposite) or \
+ core.search_friends(userName=actualOpposite) or \
+ templates.User(userName=actualOpposite)
+ # by default we think there may be a user missing not a mp
+ m['User'].core = core
+ if m['MsgType'] == 1: # words
+ if m['Url']:
+ regx = r'(.+?\(.+?\))'
+ data = re.search(regx, m['Content'])
+ data = 'Map' if data is None else data.group(1)
+ msg = {
+ 'Type': 'Map',
+ 'Text': data,}
+ else:
+ msg = {
+ 'Type': 'Text',
+ 'Text': m['Content'],}
+ elif m['MsgType'] == 3 or m['MsgType'] == 47: # picture
+ download_fn = get_download_fn(core,
+ '%s/webwxgetmsgimg' % core.loginInfo['url'], m['NewMsgId'])
+ msg = {
+ 'Type' : 'Picture',
+ 'FileName' : '%s.%s' % (time.strftime('%y%m%d-%H%M%S', time.localtime()),
+ 'png' if m['MsgType'] == 3 else 'gif'),
+ 'Text' : download_fn, }
+ elif m['MsgType'] == 34: # voice
+ download_fn = get_download_fn(core,
+ '%s/webwxgetvoice' % core.loginInfo['url'], m['NewMsgId'])
+ msg = {
+ 'Type': 'Recording',
+ 'FileName' : '%s.mp3' % time.strftime('%y%m%d-%H%M%S', time.localtime()),
+ 'Text': download_fn,}
+ elif m['MsgType'] == 37: # friends
+ m['User']['UserName'] = m['RecommendInfo']['UserName']
+ msg = {
+ 'Type': 'Friends',
+ 'Text': {
+ 'status' : m['Status'],
+ 'userName' : m['RecommendInfo']['UserName'],
+ 'verifyContent' : m['Ticket'],
+ 'autoUpdate' : m['RecommendInfo'], }, }
+ m['User'].verifyDict = msg['Text']
+ elif m['MsgType'] == 42: # name card
+ msg = {
+ 'Type': 'Card',
+ 'Text': m['RecommendInfo'], }
+ elif m['MsgType'] in (43, 62): # tiny video
+ msgId = m['MsgId']
+ def download_video(videoDir=None):
+ url = '%s/webwxgetvideo' % core.loginInfo['url']
+ params = {
+ 'msgid': msgId,
+ 'skey': core.loginInfo['skey'],}
+ headers = {'Range': 'bytes=0-', 'User-Agent' : config.USER_AGENT }
+ r = core.s.get(url, params=params, headers=headers, stream=True)
+ tempStorage = io.BytesIO()
+ for block in r.iter_content(1024):
+ tempStorage.write(block)
+ if videoDir is None:
+ return tempStorage.getvalue()
+ with open(videoDir, 'wb') as f:
+ f.write(tempStorage.getvalue())
+ return ReturnValue({'BaseResponse': {
+ 'ErrMsg': 'Successfully downloaded',
+ 'Ret': 0, }})
+ msg = {
+ 'Type': 'Video',
+ 'FileName' : '%s.mp4' % time.strftime('%y%m%d-%H%M%S', time.localtime()),
+ 'Text': download_video, }
+ elif m['MsgType'] == 49: # sharing
+ if m['AppMsgType'] == 6:
+ rawMsg = m
+ cookiesList = {name:data for name,data in core.s.cookies.items()}
+ def download_atta(attaDir=None):
+ url = core.loginInfo['fileUrl'] + '/webwxgetmedia'
+ params = {
+ 'sender': rawMsg['FromUserName'],
+ 'mediaid': rawMsg['MediaId'],
+ 'filename': rawMsg['FileName'],
+ 'fromuser': core.loginInfo['wxuin'],
+ 'pass_ticket': 'undefined',
+ 'webwx_data_ticket': cookiesList['webwx_data_ticket'],}
+ headers = { 'User-Agent' : config.USER_AGENT }
+ r = core.s.get(url, params=params, stream=True, headers=headers)
+ tempStorage = io.BytesIO()
+ for block in r.iter_content(1024):
+ tempStorage.write(block)
+ if attaDir is None:
+ return tempStorage.getvalue()
+ with open(attaDir, 'wb') as f:
+ f.write(tempStorage.getvalue())
+ return ReturnValue({'BaseResponse': {
+ 'ErrMsg': 'Successfully downloaded',
+ 'Ret': 0, }})
+ msg = {
+ 'Type': 'Attachment',
+ 'Text': download_atta, }
+ elif m['AppMsgType'] == 8:
+ download_fn = get_download_fn(core,
+ '%s/webwxgetmsgimg' % core.loginInfo['url'], m['NewMsgId'])
+ msg = {
+ 'Type' : 'Picture',
+ 'FileName' : '%s.gif' % (
+ time.strftime('%y%m%d-%H%M%S', time.localtime())),
+ 'Text' : download_fn, }
+ elif m['AppMsgType'] == 17:
+ msg = {
+ 'Type': 'Note',
+ 'Text': m['FileName'], }
+ elif m['AppMsgType'] == 2000:
+ regx = r'\[CDATA\[(.+?)\][\s\S]+?\[CDATA\[(.+?)\]'
+ data = re.search(regx, m['Content'])
+ if data:
+ data = data.group(2).split(u'\u3002')[0]
+ else:
+ data = 'You may found detailed info in Content key.'
+ msg = {
+ 'Type': 'Note',
+ 'Text': data, }
+ else:
+ msg = {
+ 'Type': 'Sharing',
+ 'Text': m['FileName'], }
+ elif m['MsgType'] == 51: # phone init
+ msg = update_local_uin(core, m)
+ elif m['MsgType'] == 10000:
+ msg = {
+ 'Type': 'Note',
+ 'Text': m['Content'],}
+ elif m['MsgType'] == 10002:
+ regx = r'\[CDATA\[(.+?)\]\]'
+ data = re.search(regx, m['Content'])
+ data = 'System message' if data is None else data.group(1).replace('\\', '')
+ msg = {
+ 'Type': 'Note',
+ 'Text': data, }
+ elif m['MsgType'] in srl:
+ msg = {
+ 'Type': 'Useless',
+ 'Text': 'UselessMsg', }
+ else:
+ logger.debug('Useless message received: %s\n%s' % (m['MsgType'], str(m)))
+ msg = {
+ 'Type': 'Useless',
+ 'Text': 'UselessMsg', }
+ m = dict(m, **msg)
+ rl.append(m)
+ return rl
+
+def produce_group_chat(core, msg):
+ r = re.match('(@[0-9a-z]*?):
(.*)$', msg['Content'])
+ if r:
+ actualUserName, content = r.groups()
+ chatroomUserName = msg['FromUserName']
+ elif msg['FromUserName'] == core.storageClass.userName:
+ actualUserName = core.storageClass.userName
+ content = msg['Content']
+ chatroomUserName = msg['ToUserName']
+ else:
+ msg['ActualUserName'] = core.storageClass.userName
+ msg['ActualNickName'] = core.storageClass.nickName
+ msg['IsAt'] = False
+ utils.msg_formatter(msg, 'Content')
+ return
+ chatroom = core.storageClass.search_chatrooms(userName=chatroomUserName)
+ member = utils.search_dict_list((chatroom or {}).get(
+ 'MemberList') or [], 'UserName', actualUserName)
+ if member is None:
+ chatroom = core.update_chatroom(msg['FromUserName'])
+ member = utils.search_dict_list((chatroom or {}).get(
+ 'MemberList') or [], 'UserName', actualUserName)
+ if member is None:
+ logger.debug('chatroom member fetch failed with %s' % actualUserName)
+ msg['ActualNickName'] = ''
+ msg['isAt'] = False
+ else:
+ msg['ActualNickName'] = member['DisplayName'] or member['NickName']
+ atFlag = '@' + (chatroom['self']['DisplayName']
+ or core.storageClass.nickName)
+ msg['IsAt'] = (
+ (atFlag + (u'\u2005' if u'\u2005' in msg['Content'] else ' '))
+ in msg['Content'] or msg['Content'].endswith(atFlag))
+ msg['ActualUserName'] = actualUserName
+ msg['Content'] = content
+ utils.msg_formatter(msg, 'Content')
+
+def send_raw_msg(self, msgType, content, toUserName):
+ url = '%s/webwxsendmsg' % self.loginInfo['url']
+ data = {
+ 'BaseRequest': self.loginInfo['BaseRequest'],
+ 'Msg': {
+ 'Type': msgType,
+ 'Content': content,
+ 'FromUserName': self.storageClass.userName,
+ 'ToUserName': (toUserName if toUserName else self.storageClass.userName),
+ 'LocalID': int(time.time() * 1e4),
+ 'ClientMsgId': int(time.time() * 1e4),
+ },
+ 'Scene': 0, }
+ headers = { 'ContentType': 'application/json; charset=UTF-8', 'User-Agent' : config.USER_AGENT }
+ r = self.s.post(url, headers=headers,
+ data=json.dumps(data, ensure_ascii=False).encode('utf8'))
+ return ReturnValue(rawResponse=r)
+
+def send_msg(self, msg='Test Message', toUserName=None):
+ logger.debug('Request to send a text message to %s: %s' % (toUserName, msg))
+ r = self.send_raw_msg(1, msg, toUserName)
+ return r
+
+def _prepare_file(fileDir, file_=None):
+ fileDict = {}
+ if file_:
+ if hasattr(file_, 'read'):
+ file_ = file_.read()
+ else:
+ return ReturnValue({'BaseResponse': {
+ 'ErrMsg': 'file_ param should be opened file',
+ 'Ret': -1005, }})
+ else:
+ if not utils.check_file(fileDir):
+ return ReturnValue({'BaseResponse': {
+ 'ErrMsg': 'No file found in specific dir',
+ 'Ret': -1002, }})
+ with open(fileDir, 'rb') as f:
+ file_ = f.read()
+ fileDict['fileSize'] = len(file_)
+ fileDict['fileMd5'] = hashlib.md5(file_).hexdigest()
+ fileDict['file_'] = io.BytesIO(file_)
+ return fileDict
+
+def upload_file(self, fileDir, isPicture=False, isVideo=False,
+ toUserName='filehelper', file_=None, preparedFile=None):
+ logger.debug('Request to upload a %s: %s' % (
+ 'picture' if isPicture else 'video' if isVideo else 'file', fileDir))
+ if not preparedFile:
+ preparedFile = _prepare_file(fileDir, file_)
+ if not preparedFile:
+ return preparedFile
+ fileSize, fileMd5, file_ = \
+ preparedFile['fileSize'], preparedFile['fileMd5'], preparedFile['file_']
+ fileSymbol = 'pic' if isPicture else 'video' if isVideo else'doc'
+ chunks = int((fileSize - 1) / 524288) + 1
+ clientMediaId = int(time.time() * 1e4)
+ uploadMediaRequest = json.dumps(OrderedDict([
+ ('UploadType', 2),
+ ('BaseRequest', self.loginInfo['BaseRequest']),
+ ('ClientMediaId', clientMediaId),
+ ('TotalLen', fileSize),
+ ('StartPos', 0),
+ ('DataLen', fileSize),
+ ('MediaType', 4),
+ ('FromUserName', self.storageClass.userName),
+ ('ToUserName', toUserName),
+ ('FileMd5', fileMd5)]
+ ), separators = (',', ':'))
+ r = {'BaseResponse': {'Ret': -1005, 'ErrMsg': 'Empty file detected'}}
+ for chunk in range(chunks):
+ r = upload_chunk_file(self, fileDir, fileSymbol, fileSize,
+ file_, chunk, chunks, uploadMediaRequest)
+ file_.close()
+ if isinstance(r, dict):
+ return ReturnValue(r)
+ return ReturnValue(rawResponse=r)
+
+def upload_chunk_file(core, fileDir, fileSymbol, fileSize,
+ file_, chunk, chunks, uploadMediaRequest):
+ url = core.loginInfo.get('fileUrl', core.loginInfo['url']) + \
+ '/webwxuploadmedia?f=json'
+ # save it on server
+ cookiesList = {name:data for name,data in core.s.cookies.items()}
+ fileType = mimetypes.guess_type(fileDir)[0] or 'application/octet-stream'
+ files = OrderedDict([
+ ('id', (None, 'WU_FILE_0')),
+ ('name', (None, os.path.basename(fileDir))),
+ ('type', (None, fileType)),
+ ('lastModifiedDate', (None, time.strftime('%a %b %d %Y %H:%M:%S GMT+0800 (CST)'))),
+ ('size', (None, str(fileSize))),
+ ('chunks', (None, None)),
+ ('chunk', (None, None)),
+ ('mediatype', (None, fileSymbol)),
+ ('uploadmediarequest', (None, uploadMediaRequest)),
+ ('webwx_data_ticket', (None, cookiesList['webwx_data_ticket'])),
+ ('pass_ticket', (None, core.loginInfo['pass_ticket'])),
+ ('filename' , (os.path.basename(fileDir), file_.read(524288), 'application/octet-stream'))])
+ if chunks == 1:
+ del files['chunk']; del files['chunks']
+ else:
+ files['chunk'], files['chunks'] = (None, str(chunk)), (None, str(chunks))
+ headers = { 'User-Agent' : config.USER_AGENT }
+ return requests.post(url, files=files, headers=headers)
+
+def send_file(self, fileDir, toUserName=None, mediaId=None, file_=None):
+ logger.debug('Request to send a file(mediaId: %s) to %s: %s' % (
+ mediaId, toUserName, fileDir))
+ if hasattr(fileDir, 'read'):
+ return ReturnValue({'BaseResponse': {
+ 'ErrMsg': 'fileDir param should not be an opened file in send_file',
+ 'Ret': -1005, }})
+ if toUserName is None:
+ toUserName = self.storageClass.userName
+ if mediaId is None:
+ preparedFile = _prepare_file(fileDir, file_)
+ if not preparedFile:
+ return preparedFile
+ fileSize = preparedFile['fileSize']
+ r = self.upload_file(fileDir, preparedFile=preparedFile)
+ if r:
+ mediaId = r['MediaId']
+ else:
+ return r
+ url = '%s/webwxsendappmsg?fun=async&f=json' % self.loginInfo['url']
+ data = {
+ 'BaseRequest': self.loginInfo['BaseRequest'],
+ 'Msg': {
+ 'Type': 6,
+ 'Content': ("%s" % os.path.basename(fileDir) +
+ "6" +
+ "%s%s" % (str(fileSize), mediaId) +
+ "%s" % os.path.splitext(fileDir)[1].replace('.','')),
+ 'FromUserName': self.storageClass.userName,
+ 'ToUserName': toUserName,
+ 'LocalID': int(time.time() * 1e4),
+ 'ClientMsgId': int(time.time() * 1e4), },
+ 'Scene': 0, }
+ headers = {
+ 'User-Agent': config.USER_AGENT,
+ 'Content-Type': 'application/json;charset=UTF-8', }
+ r = self.s.post(url, headers=headers,
+ data=json.dumps(data, ensure_ascii=False).encode('utf8'))
+ return ReturnValue(rawResponse=r)
+
+def send_image(self, fileDir=None, toUserName=None, mediaId=None, file_=None):
+ logger.debug('Request to send a image(mediaId: %s) to %s: %s' % (
+ mediaId, toUserName, fileDir))
+ if fileDir or file_:
+ if hasattr(fileDir, 'read'):
+ file_, fileDir = fileDir, None
+ if fileDir is None:
+ fileDir = 'tmp.jpg' # specific fileDir to send gifs
+ else:
+ return ReturnValue({'BaseResponse': {
+ 'ErrMsg': 'Either fileDir or file_ should be specific',
+ 'Ret': -1005, }})
+ if toUserName is None:
+ toUserName = self.storageClass.userName
+ if mediaId is None:
+ r = self.upload_file(fileDir, isPicture=not fileDir[-4:] == '.gif', file_=file_)
+ if r:
+ mediaId = r['MediaId']
+ else:
+ return r
+ url = '%s/webwxsendmsgimg?fun=async&f=json' % self.loginInfo['url']
+ data = {
+ 'BaseRequest': self.loginInfo['BaseRequest'],
+ 'Msg': {
+ 'Type': 3,
+ 'MediaId': mediaId,
+ 'FromUserName': self.storageClass.userName,
+ 'ToUserName': toUserName,
+ 'LocalID': int(time.time() * 1e4),
+ 'ClientMsgId': int(time.time() * 1e4), },
+ 'Scene': 0, }
+ if fileDir[-4:] == '.gif':
+ url = '%s/webwxsendemoticon?fun=sys' % self.loginInfo['url']
+ data['Msg']['Type'] = 47
+ data['Msg']['EmojiFlag'] = 2
+ headers = {
+ 'User-Agent': config.USER_AGENT,
+ 'Content-Type': 'application/json;charset=UTF-8', }
+ r = self.s.post(url, headers=headers,
+ data=json.dumps(data, ensure_ascii=False).encode('utf8'))
+ return ReturnValue(rawResponse=r)
+
+def send_video(self, fileDir=None, toUserName=None, mediaId=None, file_=None):
+ logger.debug('Request to send a video(mediaId: %s) to %s: %s' % (
+ mediaId, toUserName, fileDir))
+ if fileDir or file_:
+ if hasattr(fileDir, 'read'):
+ file_, fileDir = fileDir, None
+ if fileDir is None:
+ fileDir = 'tmp.mp4' # specific fileDir to send other formats
+ else:
+ return ReturnValue({'BaseResponse': {
+ 'ErrMsg': 'Either fileDir or file_ should be specific',
+ 'Ret': -1005, }})
+ if toUserName is None:
+ toUserName = self.storageClass.userName
+ if mediaId is None:
+ r = self.upload_file(fileDir, isVideo=True, file_=file_)
+ if r:
+ mediaId = r['MediaId']
+ else:
+ return r
+ url = '%s/webwxsendvideomsg?fun=async&f=json&pass_ticket=%s' % (
+ self.loginInfo['url'], self.loginInfo['pass_ticket'])
+ data = {
+ 'BaseRequest': self.loginInfo['BaseRequest'],
+ 'Msg': {
+ 'Type' : 43,
+ 'MediaId' : mediaId,
+ 'FromUserName' : self.storageClass.userName,
+ 'ToUserName' : toUserName,
+ 'LocalID' : int(time.time() * 1e4),
+ 'ClientMsgId' : int(time.time() * 1e4), },
+ 'Scene': 0, }
+ headers = {
+ 'User-Agent' : config.USER_AGENT,
+ 'Content-Type': 'application/json;charset=UTF-8', }
+ r = self.s.post(url, headers=headers,
+ data=json.dumps(data, ensure_ascii=False).encode('utf8'))
+ return ReturnValue(rawResponse=r)
+
+def send(self, msg, toUserName=None, mediaId=None):
+ if not msg:
+ r = ReturnValue({'BaseResponse': {
+ 'ErrMsg': 'No message.',
+ 'Ret': -1005, }})
+ elif msg[:5] == '@fil@':
+ if mediaId is None:
+ r = self.send_file(msg[5:], toUserName)
+ else:
+ r = self.send_file(msg[5:], toUserName, mediaId)
+ elif msg[:5] == '@img@':
+ if mediaId is None:
+ r = self.send_image(msg[5:], toUserName)
+ else:
+ r = self.send_image(msg[5:], toUserName, mediaId)
+ elif msg[:5] == '@msg@':
+ r = self.send_msg(msg[5:], toUserName)
+ elif msg[:5] == '@vid@':
+ if mediaId is None:
+ r = self.send_video(msg[5:], toUserName)
+ else:
+ r = self.send_video(msg[5:], toUserName, mediaId)
+ else:
+ r = self.send_msg(msg, toUserName)
+ return r
diff --git a/itchat/components/register.py b/itchat/components/register.py
index a07c1111..86a37fcc 100644
--- a/itchat/components/register.py
+++ b/itchat/components/register.py
@@ -6,6 +6,7 @@
from ..log import set_logging
from ..utils import test_connect
+from ..storage import templates
logger = logging.getLogger('itchat')
@@ -46,19 +47,12 @@ def configured_reply(self):
except Queue.Empty:
pass
else:
- if msg['FromUserName'] == self.storageClass.userName:
- actualOpposite = msg['ToUserName']
- else:
- actualOpposite = msg['FromUserName']
- if '@@' in actualOpposite:
- replyFn = self.functionDict['GroupChat'].get(msg['Type'])
- elif self.search_mps(userName=msg['FromUserName']):
- replyFn = self.functionDict['MpChat'].get(msg['Type'])
- elif '@' in actualOpposite or \
- actualOpposite in ('filehelper', 'fmessage'):
+ if isinstance(msg['User'], templates.User):
replyFn = self.functionDict['FriendChat'].get(msg['Type'])
- else:
+ elif isinstance(msg['User'], templates.MassivePlatform):
replyFn = self.functionDict['MpChat'].get(msg['Type'])
+ elif isinstance(msg['User'], templates.Chatroom):
+ replyFn = self.functionDict['GroupChat'].get(msg['Type'])
if replyFn is None:
r = None
else:
@@ -72,7 +66,7 @@ def configured_reply(self):
def msg_register(self, msgType, isFriendChat=False, isGroupChat=False, isMpChat=False):
''' a decorator constructor
return a specific decorator based on information given '''
- if not isinstance(msgType, list):
+ if not (isinstance(msgType, list) or isinstance(msgType, tuple)):
msgType = [msgType]
def _msg_register(fn):
for _msgType in msgType:
@@ -84,6 +78,7 @@ def _msg_register(fn):
self.functionDict['MpChat'][_msgType] = fn
if not any((isFriendChat, isGroupChat, isMpChat)):
self.functionDict['FriendChat'][_msgType] = fn
+ return fn
return _msg_register
def run(self, debug=False, blockThread=True):
diff --git a/itchat/config.py b/itchat/config.py
index ab17ffbe..515ea463 100644
--- a/itchat/config.py
+++ b/itchat/config.py
@@ -1,9 +1,9 @@
-import os, platform
-
-VERSION = '1.2.33'
-BASE_URL = 'https://login.weixin.qq.com'
-OS = platform.system() #Windows, Linux, Darwin
-DIR = os.getcwd()
-DEFAULT_QR = 'QR.png'
-
-USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.71 Safari/537.36'
+import os, platform
+
+VERSION = '1.3.1'
+BASE_URL = 'https://login.weixin.qq.com'
+OS = platform.system() #Windows, Linux, Darwin
+DIR = os.getcwd()
+DEFAULT_QR = 'QR.png'
+
+USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.71 Safari/537.36'
diff --git a/itchat/core.py b/itchat/core.py
index dea80903..ba384ced 100644
--- a/itchat/core.py
+++ b/itchat/core.py
@@ -18,7 +18,7 @@ def __init__(self):
- failing is failing
'''
self.alive, self.isLogging = False, False
- self.storageClass = storage.Storage()
+ self.storageClass = storage.Storage(self)
self.memberList = self.storageClass.memberList
self.mpList = self.storageClass.mpList
self.chatroomList = self.storageClass.chatroomList
@@ -313,7 +313,7 @@ def send_msg(self, msg='Test Message', toUserName=None):
'''
raise NotImplementedError()
def upload_file(self, fileDir, isPicture=False, isVideo=False,
- toUserName='filehelper'):
+ toUserName='filehelper', file_=None, preparedFile=None):
''' upload file to server and get mediaId
for options
- fileDir: dir for file ready for upload
@@ -325,7 +325,7 @@ def upload_file(self, fileDir, isPicture=False, isVideo=False,
it is defined in components/messages.py
'''
raise NotImplementedError()
- def send_file(self, fileDir, toUserName=None, mediaId=None):
+ def send_file(self, fileDir, toUserName=None, mediaId=None, file_=None):
''' send attachment
for options
- fileDir: dir for file ready for upload
@@ -335,7 +335,7 @@ def send_file(self, fileDir, toUserName=None, mediaId=None):
it is defined in components/messages.py
'''
raise NotImplementedError()
- def send_image(self, fileDir, toUserName=None, mediaId=None):
+ def send_image(self, fileDir=None, toUserName=None, mediaId=None, file_=None):
''' send image
for options
- fileDir: dir for file ready for upload
@@ -346,7 +346,7 @@ def send_image(self, fileDir, toUserName=None, mediaId=None):
it is defined in components/messages.py
'''
raise NotImplementedError()
- def send_video(self, fileDir=None, toUserName=None, mediaId=None):
+ def send_video(self, fileDir=None, toUserName=None, mediaId=None, file_=None):
''' send video
for options
- fileDir: dir for file ready for upload
diff --git a/itchat/returnvalues.py b/itchat/returnvalues.py
index e9643d08..b9600bd6 100644
--- a/itchat/returnvalues.py
+++ b/itchat/returnvalues.py
@@ -4,6 +4,23 @@
TRANSLATE = 'Chinese'
class ReturnValue(dict):
+ ''' turn return value of itchat into a boolean value
+ for requests:
+ ..code::python
+
+ import requests
+ r = requests.get('http://httpbin.org/get')
+ print(ReturnValue(rawResponse=r)
+
+ for normal dict:
+ ..code::python
+
+ returnDict = {
+ 'BaseResponse': {
+ 'Ret': 0,
+ 'ErrMsg': 'My error msg', }, }
+ print(ReturnValue(returnDict))
+ '''
def __init__(self, returnValueDict={}, rawResponse=None):
if rawResponse:
try:
@@ -14,7 +31,8 @@ def __init__(self, returnValueDict={}, rawResponse=None):
'Ret': -1004,
'ErrMsg': 'Unexpected return value', },
'Data': rawResponse.content, }
- for k, v in returnValueDict.items(): self[k] = v
+ for k, v in returnValueDict.items():
+ self[k] = v
if not 'BaseResponse' in self:
self['BaseResponse'] = {
'ErrMsg': 'no BaseResponse in raw response',
@@ -45,6 +63,7 @@ def __repr__(self):
-1003: u'服务器拒绝连接',
-1004: u'服务器返回异常值',
-1005: u'参数错误',
+ -1006: u'无效操作',
0: u'请求成功',
},
}
diff --git a/itchat/storage.py b/itchat/storage/__init__.py
similarity index 63%
rename from itchat/storage.py
rename to itchat/storage/__init__.py
index 5bfff6f0..8010c9a1 100644
--- a/itchat/storage.py
+++ b/itchat/storage/__init__.py
@@ -1,10 +1,11 @@
import os, time, copy
-try:
- import Queue
-except ImportError:
- import queue as Queue
from threading import Lock
+from .messagequeue import Queue
+from .templates import (
+ ContactList, AbstractUserDict, User,
+ MassivePlatform, Chatroom, ChatroomMember)
+
def contact_change(fn):
def _contact_change(core, *args, **kwargs):
with core.storageClass.updateLock:
@@ -12,32 +13,41 @@ def _contact_change(core, *args, **kwargs):
return _contact_change
class Storage(object):
- def __init__(self):
+ def __init__(self, core):
self.userName = None
self.nickName = None
self.updateLock = Lock()
- self.memberList = []
- self.mpList = []
- self.chatroomList = []
- self.msgList = Queue.Queue(-1)
+ self.memberList = ContactList()
+ self.mpList = ContactList()
+ self.chatroomList = ContactList()
+ self.msgList = Queue(-1)
self.lastInputUserName = None
+ self.memberList.set_default_value(contactClass=User)
+ self.memberList.core = core
+ self.mpList.set_default_value(contactClass=MassivePlatform)
+ self.mpList.core = core
+ self.chatroomList.set_default_value(contactClass=Chatroom)
+ self.chatroomList.core = core
def dumps(self):
return {
'userName' : self.userName,
'nickName' : self.nickName,
- 'memberList' : self.memberList,
- 'mpList' : self.mpList,
- 'chatroomList' : self.chatroomList,
+ 'memberList' : [dict(member) for member in self.memberList],
+ 'mpList' : [dict(mp) for mp in self.mpList],
+ 'chatroomList' : [dict(chatroom) for chatroom in self.chatroomList],
'lastInputUserName' : self.lastInputUserName, }
def loads(self, j):
- self.userName = j.get('userName', None)
- self.nickName = j.get('nickName', None)
+ self.userName = j.get('userName', None)
+ self.nickName = j.get('nickName', None)
del self.memberList[:]
- for i in j.get('memberList', []): self.memberList.append(i)
+ for i in j.get('memberList', []):
+ self.memberList.append(i)
del self.mpList[:]
- for i in j.get('mpList', []): self.mpList.append(i)
+ for i in j.get('mpList', []):
+ self.mpList.append(i)
del self.chatroomList[:]
- for i in j.get('chatroomList', []): self.chatroomList.append(i)
+ for i in j.get('chatroomList', []):
+ self.chatroomList.append(i)
self.lastInputUserName = j.get('lastInputUserName', None)
def search_friends(self, name=None, userName=None, remarkName=None, nickName=None,
wechatAccount=None):
@@ -75,19 +85,23 @@ def search_chatrooms(self, name=None, userName=None):
with self.updateLock:
if userName is not None:
for m in self.chatroomList:
- if m['UserName'] == userName: return copy.deepcopy(m)
+ if m['UserName'] == userName:
+ return copy.deepcopy(m)
elif name is not None:
matchList = []
for m in self.chatroomList:
- if name in m['NickName']: matchList.append(copy.deepcopy(m))
+ if name in m['NickName']:
+ matchList.append(copy.deepcopy(m))
return matchList
def search_mps(self, name=None, userName=None):
with self.updateLock:
if userName is not None:
for m in self.mpList:
- if m['UserName'] == userName: return copy.deepcopy(m)
+ if m['UserName'] == userName:
+ return copy.deepcopy(m)
elif name is not None:
matchList = []
for m in self.mpList:
- if name in m['NickName']: matchList.append(copy.deepcopy(m))
+ if name in m['NickName']:
+ matchList.append(copy.deepcopy(m))
return matchList
diff --git a/itchat/storage/messagequeue.py b/itchat/storage/messagequeue.py
new file mode 100644
index 00000000..c1f6ae71
--- /dev/null
+++ b/itchat/storage/messagequeue.py
@@ -0,0 +1,26 @@
+try:
+ import Queue as queue
+except ImportError:
+ import queue
+
+class Queue(queue.Queue):
+ def put(self, message):
+ if 'IsAt' in message:
+ message['isAt'] = message['IsAt']
+ queue.Queue.put(self, Message(message))
+
+class Message(dict):
+ def download(self, fileName):
+ if hasattr(self.text, '__call__'):
+ return self.text(fileName)
+ else:
+ return b''
+ def __getattr__(self, value):
+ value = value[0].upper() + value[1:]
+ return self.get(value, '')
+ def __str__(self):
+ return '{%s}' % ', '.join(
+ ['%s: %s' % (repr(k),repr(v)) for k,v in self.items()])
+ def __repr__(self):
+ return '<%s: %s>' % (self.__class__.__name__.split('.')[-1],
+ self.__str__())
diff --git a/itchat/storage/templates.py b/itchat/storage/templates.py
new file mode 100644
index 00000000..70bf0d56
--- /dev/null
+++ b/itchat/storage/templates.py
@@ -0,0 +1,254 @@
+import logging, copy, pickle
+
+from ..returnvalues import ReturnValue
+
+logger = logging.getLogger('itchat')
+
+class UnInitializedItchat(object):
+ def _raise_error(self, *args, **kwargs):
+ logger.warning('An itchat instance is called before initialized')
+ def __getattr__(self, value):
+ return self._raise_error
+
+fakeItchat = UnInitializedItchat()
+
+class ContactList(list):
+ ''' when a dict is append, init function will be called to format that dict
+ '''
+ def __init__(self, *args, **kwargs):
+ super(ContactList, self).__init__(*args, **kwargs)
+ self.contactInitFn = None
+ self.contactClass = User
+ self.core = fakeItchat
+ def set_default_value(self, initFunction=None, contactClass=None):
+ if hasattr(initFunction, '__call__'):
+ self.contactInitFn = initFunction
+ if hasattr(contactClass, '__call__'):
+ self.contactClass = contactClass
+ def append(self, value):
+ contact = self.contactClass(value)
+ contact.core = self.core
+ if self.contactInitFn is not None:
+ contact = self.contactInitFn(contact) or contact
+ super(ContactList, self).append(contact)
+ def __deepcopy__(self, memo):
+ return self.__class__([copy.deepcopy(v) for v in self])
+ def __getstate__(self):
+ return [pickle.dumps(v) for v in self]
+ def __setstate__(self, state):
+ for v in state:
+ super(ContactList, self).append(pickle.loads(v))
+ def __str__(self):
+ return '[%s]' % ', '.join([repr(v) for v in self])
+ def __repr__(self):
+ return '<%s: %s>' % (self.__class__.__name__.split('.')[-1],
+ self.__str__())
+
+fakeContactList = ContactList
+
+class AbstractUserDict(dict):
+ def __init__(self, *args, **kwargs):
+ super(AbstractUserDict, self).__init__(*args, **kwargs)
+ self.core = fakeItchat
+ def update(self):
+ return ReturnValue({'BaseResponse': {
+ 'Ret': -1006,
+ 'ErrMsg': '%s can not be updated' % \
+ self.__class__.__name__, }, })
+ def set_alias(self, alias):
+ return ReturnValue({'BaseResponse': {
+ 'Ret': -1006,
+ 'ErrMsg': '%s can not set alias' % \
+ self.__class__.__name__, }, })
+ def set_pinned(self, isPinned=True):
+ return ReturnValue({'BaseResponse': {
+ 'Ret': -1006,
+ 'ErrMsg': '%s can not be pinned' % \
+ self.__class__.__name__, }, })
+ def verify(self):
+ return ReturnValue({'BaseResponse': {
+ 'Ret': -1006,
+ 'ErrMsg': '%s do not need verify' % \
+ self.__class__.__name__, }, })
+ def get_head_image(self, imageDir=None):
+ return self.core.get_head_img(self.userName, picDir=imageDir)
+ def delete_member(self, userName):
+ return ReturnValue({'BaseResponse': {
+ 'Ret': -1006,
+ 'ErrMsg': '%s can not delete member' % \
+ self.__class__.__name__, }, })
+ def add_member(self, userName):
+ return ReturnValue({'BaseResponse': {
+ 'Ret': -1006,
+ 'ErrMsg': '%s can not add member' % \
+ self.__class__.__name__, }, })
+ def send_raw_msg(self, msgType, content):
+ return self.core.send_raw_msg(msgType, content, self.userName)
+ def send_msg(self, msg='Test Message'):
+ return self.core.send_msg(msgType, content, self.userName)
+ def send_file(self, fileDir, mediaId=None):
+ return self.core.send_file(fileDir, self.userName, mediaId)
+ def send_image(self, fileDir, mediaId=None):
+ return self.core.send_image(fileDir, self.userName, mediaId)
+ def send_video(self, fileDir=None, mediaId=None):
+ return self.core.send_video(fileDir, self.userName, mediaId)
+ def send(self, msg, mediaId=None):
+ return self.core.send(msg, self.userName, mediaId)
+ def search_member(self, name=None, userName=None, remarkName=None, nickName=None,
+ wechatAccount=None):
+ return ReturnValue({'BaseResponse': {
+ 'Ret': -1006,
+ 'ErrMsg': '%s do not have members' % \
+ self.__class__.__name__, }, })
+ def __getattr__(self, value):
+ value = value[0].upper() + value[1:]
+ return self.get(value, '')
+ def __deepcopy__(self, memo):
+ r = self.__class__({
+ copy.deepcopy(k, memo): copy.deepcopy(v, memo)
+ for k, v in self.items()})
+ r.core = self.core
+ return r
+ def __getstate__(self):
+ return dict(self)
+ def __setstate__(self, state):
+ for k, v in state.items():
+ self[k] = v
+ def __str__(self):
+ return '{%s}' % ', '.join(
+ ['%s: %s' % (repr(k),repr(v)) for k,v in self.items()])
+ def __repr__(self):
+ return '<%s: %s>' % (self.__class__.__name__.split('.')[-1],
+ self.__str__())
+
+class User(AbstractUserDict):
+ def __init__(self, *args, **kwargs):
+ super(User, self).__init__(*args, **kwargs)
+ self.verifyDict = {}
+ self.memberList = fakeContactList
+ def update(self):
+ return self.core.update_friend(self.userName)
+ def set_alias(self, alias):
+ return self.core.set_alias(self.userName, alias)
+ def set_pinned(self, isPinned=True):
+ return self.core.set_pinned(self.userName, isPinned)
+ def verify(self):
+ return self.core.add_friend(**self.verifyDict)
+ def __deepcopy__(self, memo):
+ r = super(User, self).__deepcopy__(memo)
+ r.verifyDict = copy.deepcopy(self.verifyDict)
+ return r
+
+class MassivePlatform(AbstractUserDict):
+ def __init__(self, *args, **kwargs):
+ super(MassivePlatform, self).__init__(*args, **kwargs)
+ self.memberList = fakeContactList
+
+class Chatroom(AbstractUserDict):
+ def __init__(self, *args, **kwargs):
+ super(Chatroom, self).__init__(*args, **kwargs)
+ memberList = ContactList()
+ def init_fn(d):
+ d.chatroom = self
+ memberList.set_default_value(init_fn, ChatroomMember)
+ for rawMember in self.memberList:
+ memberList.append(rawMember)
+ self['MemberList'] = memberList
+ def update(self, detailedMember=False):
+ return self.core.update_chatroom(self.userName, detailedMember)
+ def set_alias(self, alias):
+ return self.core.set_chatroom_name(self.userName, alias)
+ def set_pinned(self, isPinned=True):
+ return self.core.set_pinned(self.userName, isPinned)
+ def delete_member(self, userName):
+ return self.core.delete_member_from_chatroom(self.userName, userName)
+ def add_member(self, userName):
+ return self.core.add_member_into_chatroom(self.userName, userName)
+ def search_member(self, name=None, userName=None, remarkName=None, nickName=None,
+ wechatAccount=None):
+ with self.core.storageClass.updateLock:
+ if (name or userName or remarkName or nickName or wechatAccount) is None:
+ return None
+ elif userName: # return the only userName match
+ for m in self.memberList:
+ if m.userName == userName:
+ return copy.deepcopy(m)
+ else:
+ matchDict = {
+ 'RemarkName' : remarkName,
+ 'NickName' : nickName,
+ 'Alias' : wechatAccount, }
+ for k in ('RemarkName', 'NickName', 'Alias'):
+ if matchDict[k] is None:
+ del matchDict[k]
+ if name: # select based on name
+ contact = []
+ for m in self.memberList:
+ if any([m.get(k) == name for k in ('RemarkName', 'NickName', 'Alias')]):
+ contact.append(m)
+ else:
+ contact = self.memberList[:]
+ if matchDict: # select again based on matchDict
+ friendList = []
+ for m in contact:
+ if all([m.get(k) == v for k, v in matchDict.items()]):
+ friendList.append(m)
+ return copy.deepcopy(friendList)
+ else:
+ return copy.deepcopy(contact)
+
+class ChatroomMember(AbstractUserDict):
+ def __init__(self, *args, **kwargs):
+ super(AbstractUserDict, self).__init__(*args, **kwargs)
+ self.core = fakeItchat
+ self.chatroom = self.fakeChatroom
+ def get_head_image(self, imageDir=None):
+ return self.core.get_head_img(self.userName, self.chatroom.userName, picDir=imageDir)
+ def delete_member(self, userName):
+ return self.core.delete_member_from_chatroom(self.chatroom.userName, self.userName)
+ def send_raw_msg(self, msgType, content):
+ return ReturnValue({'BaseResponse': {
+ 'Ret': -1006,
+ 'ErrMsg': '%s can not send message directly' % \
+ self.__class__.__name__, }, })
+ def send_msg(self, msg='Test Message'):
+ return ReturnValue({'BaseResponse': {
+ 'Ret': -1006,
+ 'ErrMsg': '%s can not send message directly' % \
+ self.__class__.__name__, }, })
+ def send_file(self, fileDir, mediaId=None):
+ return ReturnValue({'BaseResponse': {
+ 'Ret': -1006,
+ 'ErrMsg': '%s can not send message directly' % \
+ self.__class__.__name__, }, })
+ def send_image(self, fileDir, mediaId=None):
+ return ReturnValue({'BaseResponse': {
+ 'Ret': -1006,
+ 'ErrMsg': '%s can not send message directly' % \
+ self.__class__.__name__, }, })
+ def send_video(self, fileDir=None, mediaId=None):
+ return ReturnValue({'BaseResponse': {
+ 'Ret': -1006,
+ 'ErrMsg': '%s can not send message directly' % \
+ self.__class__.__name__, }, })
+ def send(self, msg, mediaId=None):
+ return ReturnValue({'BaseResponse': {
+ 'Ret': -1006,
+ 'ErrMsg': '%s can not send message directly' % \
+ self.__class__.__name__, }, })
+ def __deepcopy__(self, memo):
+ r = super(ChatroomMember, self).__deepcopy__(memo)
+ r.core = self.core
+ return r
+
+ChatroomMember.fakeChatroom = Chatroom()
+
+def wrap_user_dict(d):
+ userName = d.get('UserName')
+ if '@@' in userName:
+ r = Chatroom(d)
+ elif d.get('VerifyFlag', 8) & 8 == 0:
+ r = User(d)
+ else:
+ r = MassivePlatform(d)
+ return r
diff --git a/itchat/utils.py b/itchat/utils.py
index a0a61a2e..202de6e7 100644
--- a/itchat/utils.py
+++ b/itchat/utils.py
@@ -106,7 +106,8 @@ def search_dict_list(l, key, value):
''' Search a list of dict
* return dict with specific value & key '''
for i in l:
- if i.get(key) == value: return i
+ if i.get(key) == value:
+ return i
def print_line(msg, oneLine = False):
if oneLine: