|
- import os, time, re, io
- import json
- import mimetypes, hashlib
- import logging
- from collections import OrderedDict
-
-
- 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
- core.revoke = revoke
-
- async def get_download_fn(core, url, msgId):
- async 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']
- async 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'] == 0: # chat history
- msg = {
- 'Type': 'Note',
- 'Text': m['Content'], }
- elif m['AppMsgType'] == 6:
- rawMsg = m
- cookiesList = {name:data for name,data in core.s.cookies.items()}
- async 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]*?):<br/>(.*)$', 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(chatroomUserName)
- 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.get('DisplayName', '') or member['NickName']
- atFlag = '@' + (chatroom['Self'].get('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')
-
- async 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)
-
- async def send_msg(self, msg='Test Message', toUserName=None):
- logger.debug('Request to send a text message to %s: %s' % (toUserName, msg))
- r = await 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'
- fileName = utils.quote(os.path.basename(fileDir))
- files = OrderedDict([
- ('id', (None, 'WU_FILE_0')),
- ('name', (None, fileName)),
- ('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' , (fileName, 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 core.s.post(url, files=files, headers=headers, timeout=config.TIMEOUT)
-
- async 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
- preparedFile = _prepare_file(fileDir, file_)
- if not preparedFile:
- return preparedFile
- fileSize = preparedFile['fileSize']
- if mediaId is None:
- 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': ("<appmsg appid='wxeb7ec651dd0aefa9' sdkver=''><title>%s</title>" % os.path.basename(fileDir) +
- "<des></des><action></action><type>6</type><content></content><url></url><lowurl></lowurl>" +
- "<appattach><totallen>%s</totallen><attachid>%s</attachid>" % (str(fileSize), mediaId) +
- "<fileext>%s</fileext></appattach><extinfo></extinfo></appmsg>" % 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)
-
- async 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)
-
- async 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)
-
- async 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 = await self.send_file(msg[5:], toUserName)
- else:
- r = await self.send_file(msg[5:], toUserName, mediaId)
- elif msg[:5] == '@img@':
- if mediaId is None:
- r = await self.send_image(msg[5:], toUserName)
- else:
- r = await self.send_image(msg[5:], toUserName, mediaId)
- elif msg[:5] == '@msg@':
- r = await self.send_msg(msg[5:], toUserName)
- elif msg[:5] == '@vid@':
- if mediaId is None:
- r = await self.send_video(msg[5:], toUserName)
- else:
- r = await self.send_video(msg[5:], toUserName, mediaId)
- else:
- r = await self.send_msg(msg, toUserName)
- return r
-
- async def revoke(self, msgId, toUserName, localId=None):
- url = '%s/webwxrevokemsg' % self.loginInfo['url']
- data = {
- 'BaseRequest': self.loginInfo['BaseRequest'],
- "ClientMsgId": localId or str(time.time() * 1e3),
- "SvrMsgId": msgId,
- "ToUserName": toUserName}
- 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)
|