Skip to content

Commit

Permalink
New media send interface
Browse files Browse the repository at this point in the history
Added image media message builder
Initial support for encrypted media (recv ok)
Moved proto to protocol_messages
  • Loading branch information
tgalal committed Apr 24, 2016
1 parent 5b09719 commit 0f18179
Show file tree
Hide file tree
Showing 10 changed files with 216 additions and 129 deletions.
2 changes: 1 addition & 1 deletion yowsup/layers/axolotl/layer.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from yowsup.layers.network.layer import YowNetworkLayer
from yowsup.layers.auth.layer_authentication import YowAuthenticationProtocolLayer
from yowsup.layers.protocol_acks.protocolentities import OutgoingAckProtocolEntity
from yowsup.layers.axolotl.e2e_pb2 import *
from yowsup.layers.protocol_messages.proto.wa_pb2 import *
from yowsup.layers.axolotl.store.sqlite.liteaxolotlstore import LiteAxolotlStore
from yowsup.layers.axolotl.protocolentities import *
from yowsup.structs import ProtocolTreeNode
Expand Down
97 changes: 41 additions & 56 deletions yowsup/layers/interface/interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@
from yowsup.layers.auth import YowAuthenticationProtocolLayer
from yowsup.layers.protocol_receipts.protocolentities import OutgoingReceiptProtocolEntity
from yowsup.layers.protocol_acks.protocolentities import IncomingAckProtocolEntity
from yowsup.layers.network.layer import YowNetworkLayer
from yowsup.layers import EventCallback
from yowsup.layers.axolotl.layer import YowAxolotlLayer
from yowsup.layers.protocol_media.protocolentities.iq_requestupload import RequestUploadIqProtocolEntity
from yowsup.layers.protocol_media.protocolentities.iq_requestupload_result import ResultRequestUploadIqProtocolEntity
from yowsup.layers.protocol_media.mediauploader import MediaUploader
from yowsup.layers.axolotl.layer import YowAxolotlLayer
import inspect
import logging
logger = logging.getLogger(__name__)

class ProtocolEntityCallback(object):
def __init__(self, entityType):
Expand All @@ -17,15 +18,12 @@ def __init__(self, entityType):
def __call__(self, fn):
fn.entity_callback = self.entityType
return fn


class YowInterfaceLayer(YowLayer):

PROP_RECONNECT_ON_STREAM_ERR = "org.openwhatsapp.yowsup.prop.interface.reconnect_on_stream_error"
class YowInterfaceLayer(YowLayer):

def __init__(self):
super(YowInterfaceLayer, self).__init__()
self.reconnect = False
self.entity_callbacks = {}
self.iqRegistry = {}
# self.receiptsRegistry = {}
Expand All @@ -35,40 +33,13 @@ def __init__(self):
fname = m[0]
fn = m[1]
self.entity_callbacks[fn.entity_callback] = getattr(self, fname)


def _sendIq(self, iqEntity, onSuccess = None, onError = None):
assert iqEntity.getTag() == "iq", "Expected *IqProtocolEntity in _sendIq, got %s" % iqEntity.getTag()
self.iqRegistry[iqEntity.getId()] = (iqEntity, onSuccess, onError)
self.toLower(iqEntity)

# def _sendReceipt(self, outgoingReceiptProtocolEntity, onAck = None):
# assert outgoingReceiptProtocolEntity.__class__ == OutgoingReceiptProtocolEntity,\
# "Excepted OutgoingReceiptProtocolEntity in _sendReceipt, got %s" % outgoingReceiptProtocolEntity.__class__
# self.receiptsRegistry[outgoingReceiptProtocolEntity.getId()] = (outgoingReceiptProtocolEntity, onAck)
# self.toLower(outgoingReceiptProtocolEntity)

# def processReceiptsRegistry(self, incomingAckProtocolEntity):
# '''
# entity: IncomingAckProtocolEntity
# '''
#
# if incomingAckProtocolEntity.__class__ != IncomingAckProtocolEntity:
# return False
#
# receipt_id = incomingAckProtocolEntity.getId()
# if receipt_id in self.receiptsRegistry:
# originalReceiptEntity, ackClbk = self.receiptsRegistry[receipt_id]
# del self.receiptsRegistry[receipt_id]
#
# if ackClbk:
# ackClbk(incomingAckProtocolEntity, originalReceiptEntity)
#
# return True
#
# return False


def processIqRegistry(self, entity):
"""
:type entity: IqProtocolEntity
Expand Down Expand Up @@ -107,27 +78,41 @@ def receive(self, entity):
self.entity_callbacks[entityType](entity)
else:
self.toUpper(entity)

@ProtocolEntityCallback("stream:error")
def onStreamError(self, streamErrorEntity):
logger.error(streamErrorEntity)
if self.getProp(self.__class__.PROP_RECONNECT_ON_STREAM_ERR, True):
logger.info("Initiating reconnect")
self.reconnect = True
self.disconnect()

def _sendMediaMessage(self, builder, success, error = None, progress = None):
# axolotlIface = self.getLayerInterface(YowAxolotlLayer)
# if axolotlIface:
# axolotlIface.encryptMedia(builder)

iq = RequestUploadIqProtocolEntity(builder.mediaType, filePath = builder.getFilepath(), encrypted = builder.isEncrypted())
successFn = lambda resultEntity, requestUploadEntity: self.__onRequestUploadSuccess(resultEntity, requestUploadEntity, builder, success, error, progress)
errorFn = lambda errorEntity, requestUploadEntity: self.__onRequestUploadError(errorEntity, requestUploadEntity, error)
self._sendIq(iq, successFn, errorFn)

def __onRequestUploadSuccess(self, resultRequestUploadIqProtocolEntity, requestUploadEntity, builder, success, error = None, progress = None):
if(resultRequestUploadIqProtocolEntity.isDuplicate()):
return success(builder.build(resultRequestUploadIqProtocolEntity.getUrl(), resultRequestUploadIqProtocolEntity.getIp()))
else:
logger.warn("No reconnecting because property %s is not set" % self.__class__.PROP_RECONNECT_ON_STREAM_ERR)
self.toUpper(streamErrorEntity)

@EventCallback(YowNetworkLayer.EVENT_STATE_CONNECTED)
def onConnected(self, yowLayerEvent):
self.reconnect = False

@EventCallback(YowNetworkLayer.EVENT_STATE_DISCONNECTED)
def onDisconnected(self, yowLayerEvent):
if self.reconnect:
self.reconnect = False
self.connect()
successFn = lambda path, jid, url: self.__onMediaUploadSuccess(builder, url, resultRequestUploadIqProtocolEntity.getIp(), success)
errorFn = lambda path, jid, errorText: self.__onMediaUploadError(builder, errorText, error)

mediaUploader = MediaUploader(builder.jid, self.getOwnJid(), builder.getFilepath(),
resultRequestUploadIqProtocolEntity.getUrl(),
resultRequestUploadIqProtocolEntity.getResumeOffset(),
successFn, errorFn, progress, async=True)
mediaUploader.start()

def __onRequestUploadError(self, errorEntity, requestUploadEntity, builder, error = None):
if error:
return error(errorEntity.code, errorEntity.text, errorEntity.backoff)

def __onMediaUploadSuccess(self, builder, url, ip, successClbk):
messageNode = builder.build(url, ip)
return successClbk(messageNode)

def __onMediaUploadError(self, builder, errorText, errorClbk = None):
if errorClbk:
return errorClbk(0, errorText, 0)

def __str__(self):
return "Interface Layer"
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# from yowsup.layers.protocol_media import mediacipher
import tempfile
import os
class DownloadableMediaMessageBuilder(object):
def __init__(self, downloadbleMediaMessageClass, jid, filepath):
self.jid = jid
self.filepath = filepath
self.encryptedFilepath = None
self.cls = downloadbleMediaMessageClass
self.mediaKey = None
self.attributes = {}
self.mediaType = self.cls.__name__.split("DownloadableMediaMessageProtocolEntity")[0].lower() #ugly ?

# def encrypt(self):
# fd, encpath = tempfile.mkstemp()
# mediaKey = os.urandom(112)
# keys = mediacipher.getDerivedKeys(mediaKey)
# out = mediacipher.encryptImage(self.filepath, keys)
# with open(encImagePath, 'w') as outF:
# outF.write(out)
#
# self.mediaKey = mediaKey
# self.encryptedFilepath = encpath

# def decrypt(self):
# self.mediaKey = None
# self.encryptedFilePath = None


def setEncryptionData(self, mediaKey, encryptedFilepath):
self.mediaKey = mediaKey
self.encryptedFilepath = encryptedFilepath

def isEncrypted(self):
return self.encryptedFilepath is not None

def getFilepath(self):
return self.encryptedFilepath or self.filepath

def getOriginalFilepath(self):
return self.filepath

def set(self, key, val):
self.attributes[key] = val

def get(self, key, default = None):
if key in self.attributes:
return self.attributes[key]

return default

def getOrSet(self, key, func):
if not self.get(key):
self.set(key, func())

def build(self, url = None, ip = None):
if url:
self.set("url", url)
if ip:
self.set("ip", ip)
return self.cls.fromBuilder(self)
Original file line number Diff line number Diff line change
Expand Up @@ -4,27 +4,27 @@
import os
class DownloadableMediaMessageProtocolEntity(MediaMessageProtocolEntity):
'''
<message t="{{TIME_STAMP}}" from="{{CONTACT_JID}}"
<message t="{{TIME_STAMP}}" from="{{CONTACT_JID}}"
offline="{{OFFLINE}}" type="text" id="{{MESSAGE_ID}}" notify="{{NOTIFY_NAME}}">
<media type="{{DOWNLOADABLE_MEDIA_TYPE: (image | audio | video)}}"
mimetype="{{MIME_TYPE}}"
mimetype="{{MIME_TYPE}}"
filehash="{{FILE_HASH}}"
url="{{DOWNLOAD_URL}}"
url="{{DOWNLOAD_URL}}"
ip="{{IP}}"
size="{{MEDIA SIZE}}"
file="{{FILENAME}}"
file="{{FILENAME}}"
> {{THUMBNAIL_RAWDATA (JPEG?)}}
</media>
</message>
'''
def __init__(self, mediaType,
mimeType, fileHash, url, ip, size, fileName,
_id = None, _from = None, to = None, notify = None, timestamp = None,
mimeType, fileHash, url, ip, size, fileName, mediaKey = None,
_id = None, _from = None, to = None, notify = None, timestamp = None,
participant = None, preview = None, offline = None, retry = None):

super(DownloadableMediaMessageProtocolEntity, self).__init__(mediaType, _id, _from, to, notify, timestamp, participant, preview, offline, retry)
self.setDownloadableMediaProps(mimeType, fileHash, url, ip, size, fileName)
self.setDownloadableMediaProps(mimeType, fileHash, url, ip, size, fileName, mediaKey)

def __str__(self):
out = super(DownloadableMediaMessageProtocolEntity, self).__str__()
Expand All @@ -45,13 +45,14 @@ def getMediaUrl(self):
def getMimeType(self):
return self.mimeType

def setDownloadableMediaProps(self, mimeType, fileHash, url, ip, size, fileName):
def setDownloadableMediaProps(self, mimeType, fileHash, url, ip, size, fileName, mediaKey):
self.mimeType = mimeType
self.fileHash = fileHash
self.url = url
self.ip = ip
self.size = int(size)
self.fileName = fileName
self.mediaKey = mediaKey

def toProtocolTreeNode(self):
node = super(DownloadableMediaMessageProtocolEntity, self).toProtocolTreeNode()
Expand All @@ -63,9 +64,14 @@ def toProtocolTreeNode(self):
mediaNode.setAttribute("ip", self.ip)
mediaNode.setAttribute("size", str(self.size))
mediaNode.setAttribute("file", self.fileName)
if self.mediaKey:
mediaNode.setAttribute("mediakey", self.mediaKey)

return node

def isEncrypted(self):
return self.mediaKey is not None

@staticmethod
def fromProtocolTreeNode(node):
entity = MediaMessageProtocolEntity.fromProtocolTreeNode(node)
Expand All @@ -77,17 +83,18 @@ def fromProtocolTreeNode(node):
mediaNode.getAttributeValue("url"),
mediaNode.getAttributeValue("ip"),
mediaNode.getAttributeValue("size"),
mediaNode.getAttributeValue("file")
mediaNode.getAttributeValue("file"),
mediaNode.getAttributeValue("mediakey")
)
return entity

@staticmethod
def fromFilePath(fpath, url, mediaType, ip, to, mimeType = None, preview = None, filehash = None, filesize = None):
mimeType = mimeType or MimeTools.getMIME(fpath)
filehash = filehash or WATools.getFileHashForUpload(fpath)
size = filesize or os.path.getsize(fpath)
fileName = os.path.basename(fpath)

return DownloadableMediaMessageProtocolEntity(mediaType, mimeType, filehash, url, ip, size, fileName, to = to, preview = preview)


def fromBuilder(builder):
url = builder.get("url")
ip = builder.get("ip")
assert url, "Url is required"
mimeType = builder.get("mimetype", MimeTools.getMIME(builder.getOriginalFilepath())[0])
filehash = WATools.getFileHashForUpload(builder.getFilepath())
size = os.path.getsize(builder.getFilepath())
fileName = os.path.basename(builder.getFilepath())
return DownloadableMediaMessageProtocolEntity(builder.mediaType, mimeType, filehash, url, ip, size, fileName, to = builder.jid, preview = builder.get("preview"))
Original file line number Diff line number Diff line change
Expand Up @@ -2,33 +2,33 @@
from .message_media_downloadable import DownloadableMediaMessageProtocolEntity
class AudioDownloadableMediaMessageProtocolEntity(DownloadableMediaMessageProtocolEntity):
'''
<message t="{{TIME_STAMP}}" from="{{CONTACT_JID}}"
<message t="{{TIME_STAMP}}" from="{{CONTACT_JID}}"
offline="{{OFFLINE}}" type="text" id="{{MESSAGE_ID}}" notify="{{NOTIFY_NAME}}">
<media type="{{DOWNLOADABLE_MEDIA_TYPE: (image | audio | video)}}"
mimetype="{{MIME_TYPE}}"
mimetype="{{MIME_TYPE}}"
filehash="{{FILE_HASH}}"
url="{{DOWNLOAD_URL}}"
url="{{DOWNLOAD_URL}}"
ip="{{IP}}"
size="{{MEDIA SIZE}}"
file="{{FILENAME}}"
file="{{FILENAME}}"
encoding="{{ENCODING}}"
height="{{IMAGE_HEIGHT}}"
encoding="{{ENCODING}}"
height="{{IMAGE_HEIGHT}}"
width="{{IMAGE_WIDTH}}"
> {{THUMBNAIL_RAWDATA (JPEG?)}}
</media>
</message>
'''
def __init__(self,
mimeType, fileHash, url, ip, size, fileName,
mimeType, fileHash, url, ip, size, fileName,
abitrate, acodec, asampfreq, duration, encoding, origin, seconds,
_id = None, _from = None, to = None, notify = None, timestamp = None,
_id = None, _from = None, to = None, notify = None, timestamp = None,
participant = None, preview = None, offline = None, retry = None):

super(AudioDownloadableMediaMessageProtocolEntity, self).__init__("audio",
mimeType, fileHash, url, ip, size, fileName,
mimeType, fileHash, url, ip, size, fileName, None,
_id, _from, to, notify, timestamp, participant, preview, offline, retry)
self.setAudioProps(abitrate, acodec, asampfreq, duration, encoding, origin, seconds)

Expand Down
Loading

0 comments on commit 0f18179

Please sign in to comment.