diff --git a/README.md b/README.md index eee64ae07..46a374d0b 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,10 @@ -## Updates (August 01, 2015) +## Updates (September 07, 2015) +Yowsup v2.4 is out, See [release notes](https://github.com/tgalal/yowsup/releases/tag/v2.4) + +### Updates (August 01, 2015) Yowsup v2.3.185 is out, contains fixes in axolotl integration. See [release notes](https://github.com/tgalal/yowsup/releases/tag/v2.3.185) ### Updates (July 27, 2015) diff --git a/yowsup-cli b/yowsup-cli index 7a29bb64f..010cece54 100755 --- a/yowsup-cli +++ b/yowsup-cli @@ -1,5 +1,5 @@ #!/usr/bin/env python -__version__ = "2.0.12" +__version__ = "2.0.13" __author__ = "Tarek Galal" import sys, argparse, yowsup, logging @@ -205,8 +205,7 @@ class DemosArgParser(YowArgParser): credentialsOpts.add_argument("-c", "--config", action="store", help = "Path to config file containing authentication info. For more info about config format use --help-config") - configGroup.add_argument("-m", "--moxie", action="store_true", help="Enable experimental support for the" - " new WhatsApp encryption") + configGroup.add_argument("-M", "--unmoxie", action="store_true", help="Disable E2E Encryption") cmdopts = self.add_argument_group("Command line interface demo") cmdopts.add_argument('-y', '--yowsup', action = "store_true", help = "Start the Yowsup command line client") @@ -224,9 +223,6 @@ class DemosArgParser(YowArgParser): def process(self): super(DemosArgParser, self).process() - if self.args["moxie"]: - logger.warning("--moxie/-m is deprecated and will be removed soon as e2e encryption is now almost mandatory.") - if self.args["yowsup"]: self.startCmdline() elif self.args["echo"]: @@ -255,7 +251,7 @@ class DemosArgParser(YowArgParser): if not credentials: print("Error: You must specify a configuration method") sys.exit(1) - stack = cli.YowsupCliStack(credentials, self.args["moxie"]) + stack = cli.YowsupCliStack(credentials, not self.args["unmoxie"]) stack.start() def startEcho(self): @@ -265,7 +261,7 @@ class DemosArgParser(YowArgParser): print("Error: You must specify a configuration method") sys.exit(1) try: - stack = echoclient.YowsupEchoStack(credentials, self.args["moxie"]) + stack = echoclient.YowsupEchoStack(credentials, not self.args["unmoxie"]) stack.start() except KeyboardInterrupt: print("\nYowsdown") @@ -280,7 +276,7 @@ class DemosArgParser(YowArgParser): try: stack = sendclient.YowsupSendStack(credentials, [([self.args["send"][0], self.args["send"][1]])], - self.args["moxie"]) + not self.args["unmoxie"]) stack.start() except KeyboardInterrupt: print("\nYowsdown") @@ -293,7 +289,7 @@ class DemosArgParser(YowArgParser): print("Error: You must specify a configuration method") sys.exit(1) try: - stack = contacts.YowsupSyncStack(credentials,self.args["sync"].split(','),self.args["moxie"]) + stack = contacts.YowsupSyncStack(credentials,self.args["sync"].split(','), not self.args["unmoxie"]) stack.start() except KeyboardInterrupt: print("\nYowsdown") @@ -326,4 +322,3 @@ if __name__ == "__main__": parser = modeDict[mode]() if not parser.process(): parser.print_help() - diff --git a/yowsup/__init__.py b/yowsup/__init__.py index d8d7cfc06..c9bd9263a 100644 --- a/yowsup/__init__.py +++ b/yowsup/__init__.py @@ -1,2 +1,2 @@ -__version__ = "2.3.185" +__version__ = "2.4" __author__ = "Tarek Galal" diff --git a/yowsup/demos/cli/layer.py b/yowsup/demos/cli/layer.py index 1754c11b2..186eb2e38 100644 --- a/yowsup/demos/cli/layer.py +++ b/yowsup/demos/cli/layer.py @@ -48,6 +48,7 @@ def __init__(self): self.username = None self.sendReceipts = True self.disconnectAction = self.__class__.DISCONNECT_ACTION_PROMPT + self.credentials = None #add aliases to make it user to use commands. for example you can then do: # /message send foobar "HI" @@ -77,6 +78,9 @@ def normalizeJid(self, number): return "%s@s.whatsapp.net" % number + def setCredentials(self, username, password): + self.getLayerInterface(YowAuthenticationProtocolLayer).setCredentials(username, password) + def onEvent(self, layerEvent): if layerEvent.getName() == self.__class__.EVENT_START: self.startInput() @@ -406,23 +410,20 @@ def contacts_sync(self, contacts): @clicmd("Disconnect") def disconnect(self): if self.assertConnected(): + self.broadcastEvent(YowLayerEvent(YowNetworkLayer.EVENT_STATE_DISCONNECT)) @clicmd("Quick login") def L(self): - return self.login(*self.getProp(YowAuthenticationProtocolLayer.PROP_CREDENTIALS)) - - @clicmd("Login to WhatsApp", 0) - def login(self, username, b64password): - if self.connected: return self.output("Already connected, disconnect first") + self.getLayerInterface(YowNetworkLayer).connect() + return True - self.getStack().setProp(YowAuthenticationProtocolLayer.PROP_CREDENTIALS, (username, b64password)) - connectEvent = YowLayerEvent(YowNetworkLayer.EVENT_STATE_CONNECT) - self.broadcastEvent(connectEvent) - return True #prompt will wait until notified - + @clicmd("Login to WhatsApp", 0) + def login(self, username, b64password): + self.setCredentials(username, b64password) + return self.L() ######## receive ######### diff --git a/yowsup/demos/cli/stack.py b/yowsup/demos/cli/stack.py index de06a2d59..715110a8e 100644 --- a/yowsup/demos/cli/stack.py +++ b/yowsup/demos/cli/stack.py @@ -1,9 +1,8 @@ -from yowsup.stacks import YowStack, YowStackBuilder +from yowsup.stacks import YowStackBuilder from .layer import YowsupCliLayer from yowsup.layers.auth import AuthError from yowsup.layers import YowLayerEvent -from yowsup import env -from yowsup.env import S40YowsupEnv +from yowsup.layers.auth import YowAuthenticationProtocolLayer import sys class YowsupCliStack(object): @@ -15,6 +14,7 @@ def __init__(self, credentials, encryptionEnabled = True): .push(YowsupCliLayer)\ .build() + # self.stack.setCredentials(credentials) self.stack.setCredentials(credentials) def start(self): @@ -27,4 +27,4 @@ def start(self): print("Auth Error, reason %s" % e) except KeyboardInterrupt: print("\nYowsdown") - sys.exit(0) \ No newline at end of file + sys.exit(0) diff --git a/yowsup/env/env_s40.py b/yowsup/env/env_s40.py index 5b312b2c1..8ef3d6602 100644 --- a/yowsup/env/env_s40.py +++ b/yowsup/env/env_s40.py @@ -2,11 +2,11 @@ import base64 import hashlib class S40YowsupEnv(YowsupEnv): - _VERSION = "2.12.89" + _VERSION = "2.12.96" _OS_NAME= "S40" _OS_VERSION = "14.26" _DEVICE_NAME = "Nokia302" - _TOKEN_STRING = "PdA2DJyKoUrwLw1Bg6EIhzh502dF9noR9uFCllGk1435688727801{phone}" + _TOKEN_STRING = "PdA2DJyKoUrwLw1Bg6EIhzh502dF9noR9uFCllGk1439921717185{phone}" _AXOLOTL = True def getVersion(self): @@ -34,4 +34,3 @@ def getUserAgent(self): OS_VERSION = self.getOSVersion(), DEVICE_NAME = self.getDeviceName() ) - diff --git a/yowsup/layers/__init__.py b/yowsup/layers/__init__.py index 8c86d4843..a59954761 100644 --- a/yowsup/layers/__init__.py +++ b/yowsup/layers/__init__.py @@ -33,6 +33,10 @@ class YowLayer(object): def __init__(self): self.setLayers(None, None) + self.interface = None + + def getLayerInterface(self, YowLayerClass = None): + return self.interface if YowLayerClass is None else self.__stack.getLayerInterface(YowLayerClass) def setStack(self, stack): self.__stack = stack @@ -149,6 +153,11 @@ def __init__(self, sublayers = None): s.emitEvent = self.subEmitEvent + def getLayerInterface(self, YowLayerClass): + for s in self.sublayers: + if s.__class__ == YowLayerClass: + return s + def setStack(self, stack): super(YowParallelLayer, self).setStack(stack) for s in self.sublayers: @@ -182,6 +191,10 @@ def onEvent(self, yowLayerEvent): def __str__(self): return " - ".join([l.__str__() for l in self.sublayers]) +class YowLayerInterface(object): + def __init__(self, layer): + self._layer = layer + class YowLayerTest(unittest.TestCase): def __init__(self, *args): diff --git a/yowsup/layers/auth/layer_authentication.py b/yowsup/layers/auth/layer_authentication.py index 1d239424a..af198d00c 100644 --- a/yowsup/layers/auth/layer_authentication.py +++ b/yowsup/layers/auth/layer_authentication.py @@ -1,4 +1,4 @@ -from yowsup.layers import YowLayer, YowLayerEvent, YowProtocolLayer +from yowsup.layers import YowLayerEvent, YowProtocolLayer from .keystream import KeyStream from yowsup.common.tools import TimeTools from .layer_crypt import YowCryptLayer @@ -6,6 +6,7 @@ from .autherror import AuthError from .protocolentities import * from yowsup.common.tools import StorageTools +from .layer_interface_authentication import YowAuthenticationProtocolLayerInterface import base64 class YowAuthenticationProtocolLayer(YowProtocolLayer): EVENT_LOGIN = "org.openwhatsapp.yowsup.event.auth.login" @@ -22,29 +23,38 @@ def __init__(self): "stream:error": (self.handleStreamError, None), } super(YowAuthenticationProtocolLayer, self).__init__(handleMap) - self.credentials = None + self.interface = YowAuthenticationProtocolLayerInterface(self) + self.credentials = None #left for backwards-compat + self._credentials = None #new style set def __str__(self): return "Authentication Layer" - def __getCredentials(self): - u, pb64 = self.getProp(YowAuthenticationProtocolLayer.PROP_CREDENTIALS) + def __getCredentials(self, credentials = None): + u, pb64 = credentials or self.getProp(YowAuthenticationProtocolLayer.PROP_CREDENTIALS) if type(pb64) is str: pb64 = pb64.encode() password = base64.b64decode(pb64) return (u, bytearray(password)) + def setCredentials(self, credentials): + self.setProp(YowAuthenticationProtocolLayer.PROP_CREDENTIALS, credentials) #keep for now + self._credentials = self.__getCredentials(credentials) + + def getUsername(self, full = False): + if self._credentials: + return self._credentials[0] if not full else ("%s@s.whatsapp.net" % self._credentials[0]) + else: + prop = self.getProp(YowAuthenticationProtocolLayer.PROP_CREDENTIALS) + return prop[0] if prop else None + def onEvent(self, event): if event.getName() == YowNetworkLayer.EVENT_STATE_CONNECTED: self.login() - elif event.getName() == YowNetworkLayer.EVENT_STATE_CONNECT: - self.credentials = self.__getCredentials() - if not self.credentials: - raise AuthError("Auth stopped connection signal as no credentials have been set") ## general methods def login(self): - + self.credentials = self._credentials or self.__getCredentials() self._sendFeatures() self._sendAuth() @@ -102,7 +112,7 @@ def _sendResponse(self,nonce): responseEntity = ResponseProtocolEntity(authBlob) #to prevent enr whole response - self.broadcastEvent(YowLayerEvent(YowCryptLayer.EVENT_KEYS_READY, keys = (inputKey, None))) + self.broadcastEvent(YowLayerEvent(YowCryptLayer.EVENT_KEYS_READY, keys = (inputKey, None))) self.entityToLower(responseEntity) self.broadcastEvent(YowLayerEvent(YowCryptLayer.EVENT_KEYS_READY, keys = (inputKey, outputKey))) #YowCryptLayer.setProp("outputKey", outputKey) diff --git a/yowsup/layers/auth/layer_crypt.py b/yowsup/layers/auth/layer_crypt.py index f0e6e8762..9d7822670 100644 --- a/yowsup/layers/auth/layer_crypt.py +++ b/yowsup/layers/auth/layer_crypt.py @@ -13,7 +13,7 @@ def __init__(self): self.keys = (None,None) def onEvent(self, yowLayerEvent): - if yowLayerEvent.getName() == YowNetworkLayer.EVENT_STATE_CONNECT: + if yowLayerEvent.getName() == YowNetworkLayer.EVENT_STATE_CONNECTED: self.keys = (None,None) elif yowLayerEvent.getName() == YowCryptLayer.EVENT_KEYS_READY: self.keys = yowLayerEvent.getArg("keys") diff --git a/yowsup/layers/auth/layer_interface_authentication.py b/yowsup/layers/auth/layer_interface_authentication.py new file mode 100644 index 000000000..49d72f668 --- /dev/null +++ b/yowsup/layers/auth/layer_interface_authentication.py @@ -0,0 +1,8 @@ +from yowsup.layers import YowLayerInterface + +class YowAuthenticationProtocolLayerInterface(YowLayerInterface): + def setCredentials(self, phone, password): + self._layer.setCredentials(phone, password) + + def getUsername(self, full = False): + return self._layer.getUsername(full) \ No newline at end of file diff --git a/yowsup/layers/axolotl/layer.py b/yowsup/layers/axolotl/layer.py index 6088980be..732211f67 100644 --- a/yowsup/layers/axolotl/layer.py +++ b/yowsup/layers/axolotl/layer.py @@ -4,6 +4,7 @@ from .store.sqlite.liteaxolotlstore import LiteAxolotlStore from axolotl.sessionbuilder import SessionBuilder from yowsup.layers.protocol_messages.protocolentities.message import MessageProtocolEntity +from yowsup.layers.protocol_receipts.protocolentities import OutgoingReceiptProtocolEntity from yowsup.layers.network.layer import YowNetworkLayer from yowsup.layers.auth.layer_authentication import YowAuthenticationProtocolLayer from axolotl.ecc.curve import Curve @@ -16,10 +17,12 @@ from .protocolentities import GetKeysIqProtocolEntity, ResultGetKeysIqProtocolEntity from axolotl.util.hexutil import HexUtil from axolotl.invalidmessageexception import InvalidMessageException +from axolotl.duplicatemessagexception import DuplicateMessageException from .protocolentities import EncryptNotification from yowsup.layers.protocol_acks.protocolentities import OutgoingAckProtocolEntity from axolotl.invalidkeyidexception import InvalidKeyIdException from axolotl.nosessionexception import NoSessionException +from axolotl.untrustedidentityexception import UntrustedIdentityException from .protocolentities.receipt_outgoing_retry import RetryOutgoingReceiptProtocolEntity import binascii import sys @@ -46,23 +49,33 @@ def __init__(self): self.skipEncJids = [] self.v2Jids = [] #people we're going to send v2 enc messages + @property + def store(self): + if self._store is None: + self.store = LiteAxolotlStore( + StorageTools.constructPath( + self.getProp( + YowAuthenticationProtocolLayer.PROP_CREDENTIALS)[0], + self.__class__._DB + ) + ) + self.state = self.__class__._STATE_HASKEYS if self.store.getLocalRegistrationId() is not None \ + else self.__class__._STATE_INIT + + return self._store + + @store.setter + def store(self, store): + self._store = store + def __str__(self): return "Axolotl Layer" ### store and state - def initStore(self): - self.store = LiteAxolotlStore( - StorageTools.constructPath( - self.getProp( - YowAuthenticationProtocolLayer.PROP_CREDENTIALS)[0], - self.__class__._DB - ) - ) - self.state = self.__class__._STATE_HASKEYS if self.store.getLocalRegistrationId() is not None \ - else self.__class__._STATE_INIT + def isInitState(self): - return self.state == self.__class__._STATE_INIT + return self.store == None or self.state == self.__class__._STATE_INIT def isGenKeysState(self): return self.state == self.__class__._STATE_GENKEYS @@ -72,8 +85,7 @@ def isGenKeysState(self): def onEvent(self, yowLayerEvent): if yowLayerEvent.getName() == self.__class__.EVENT_PREKEYS_SET: self.sendKeys(fresh=False) - elif yowLayerEvent.getName() == YowNetworkLayer.EVENT_STATE_CONNECT: - self.initStore() + elif yowLayerEvent.getName() == YowNetworkLayer.EVENT_STATE_CONNECTED: if self.isInitState(): self.setProp(YowAuthenticationProtocolLayer.PROP_PASSIVE, True) elif yowLayerEvent.getName() == YowAuthenticationProtocolLayer.EVENT_AUTHED: @@ -86,7 +98,9 @@ def onEvent(self, yowLayerEvent): #no need to traverse it to upper layers? self.setProp(YowAuthenticationProtocolLayer.PROP_PASSIVE, False) self.state = self.__class__._STATE_HASKEYS - self.broadcastEvent(YowLayerEvent(YowNetworkLayer.EVENT_STATE_CONNECT)) + self.getLayerInterface(YowNetworkLayer).connect() + else: + self.store = None def send(self, node): if node.tag == "message" and node["type"] == "text" and node["to"] not in self.skipEncJids: @@ -228,8 +242,14 @@ def handleEncMessage(self, node): self.pendingIncomingMessages[node["from"]].append(node) self._sendIq(entity, lambda a, b: self.onGetKeysResult(a, b, self.processPendingIncomingMessages), self.onGetKeysError) + except DuplicateMessageException as e: + logger.error(e) + logger.warning("Going to send the delivery receipt myself !") + self.toLower(OutgoingReceiptProtocolEntity(node["id"], node["from"]).toProtocolTreeNode()) - + except UntrustedIdentityException as e: + logger.error(e) + logger.warning("Ignoring message with untrusted identity") def handlePreKeyWhisperMessage(self, node): pkMessageProtocolEntity = EncryptedMessageProtocolEntity.fromProtocolTreeNode(node) diff --git a/yowsup/layers/interface/interface.py b/yowsup/layers/interface/interface.py index 4d41e5c00..c957a5b7d 100644 --- a/yowsup/layers/interface/interface.py +++ b/yowsup/layers/interface/interface.py @@ -2,6 +2,8 @@ from yowsup.layers.protocol_iq.protocolentities import IqProtocolEntity from yowsup.layers.network import YowNetworkLayer from yowsup.layers.auth import YowAuthenticationProtocolLayer +from yowsup.layers.protocol_receipts.protocolentities import OutgoingReceiptProtocolEntity +from yowsup.layers.protocol_acks.protocolentities import IncomingAckProtocolEntity import inspect class ProtocolEntityCallback(object): @@ -15,8 +17,10 @@ def __call__(self, fn): class YowInterfaceLayer(YowLayer): def __init__(self): + super(YowInterfaceLayer, self).__init__() self.callbacks = {} self.iqRegistry = {} + # self.receiptsRegistry = {} members = inspect.getmembers(self, predicate=inspect.ismethod) for m in members: if hasattr(m[1], "callback"): @@ -29,6 +33,33 @@ def _sendIq(self, iqEntity, onSuccess = None, onError = None): 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 @@ -48,14 +79,10 @@ def processIqRegistry(self, entity): return False def getOwnJid(self, full = True): - jid = self.getProp(YowAuthenticationProtocolLayer.PROP_CREDENTIALS)[0] - if jid: - return jid + "@s.whatsapp.net" if full else jid - return None + return self.getLayerInterface(YowAuthenticationProtocolLayer).getUsername(full) def connect(self): - loginEvent = YowLayerEvent(YowNetworkLayer.EVENT_STATE_CONNECT) - self.broadcastEvent(loginEvent) + self.getLayerInterface(YowNetworkLayer).connect() def disconnect(self): disconnectEvent = YowLayerEvent(YowNetworkLayer.EVENT_STATE_DISCONNECT) @@ -69,8 +96,9 @@ def receive(self, entity): entityType = entity.getTag() if entityType in self.callbacks: self.callbacks[entityType](entity) - + else: + self.toUpper(entity) + def __str__(self): return "Interface Layer" - diff --git a/yowsup/layers/network/layer.py b/yowsup/layers/network/layer.py index db5d468ca..168a6067a 100644 --- a/yowsup/layers/network/layer.py +++ b/yowsup/layers/network/layer.py @@ -1,5 +1,6 @@ from yowsup.layers import YowLayer, YowLayerEvent from yowsup.common.http.httpproxy import HttpProxy +from yowsup.layers.network.layer_interface import YowNetworkLayerInterface import asyncore, socket, logging logger = logging.getLogger(__name__) @@ -19,6 +20,7 @@ class YowNetworkLayer(YowLayer, asyncore.dispatcher_with_send): def __init__(self): YowLayer.__init__(self) + self.interface = YowNetworkLayerInterface(self) asyncore.dispatcher.__init__(self) httpProxy = HttpProxy.getFromEnviron() proxyHandler = None @@ -31,24 +33,34 @@ def onConnect(): proxyHandler = httpProxy.handler() proxyHandler.onConnect = onConnect self.proxyHandler = proxyHandler - + def onEvent(self, ev): if ev.getName() == YowNetworkLayer.EVENT_STATE_CONNECT: - self.create_socket(socket.AF_INET, socket.SOCK_STREAM) - self.out_buffer = bytearray() - endpoint = self.getProp(self.__class__.PROP_ENDPOINT) - logger.debug("Connecting to %s:%s" % endpoint) - if self.proxyHandler != None: - logger.debug("HttpProxy connect: %s:%d" % endpoint) - self.proxyHandler.connect(self, endpoint) - else: - self.connect(endpoint) + self.createConnection() return True elif ev.getName() == YowNetworkLayer.EVENT_STATE_DISCONNECT: - self.handle_close(ev.getArg("reason") or "Requested") + self.destroyConnection(ev.getArg("reason")) return True + def createConnection(self): + self.create_socket(socket.AF_INET, socket.SOCK_STREAM) + self.out_buffer = bytearray() + endpoint = self.getProp(self.__class__.PROP_ENDPOINT) + logger.debug("Connecting to %s:%s" % endpoint) + if self.proxyHandler != None: + logger.debug("HttpProxy connect: %s:%d" % endpoint) + self.proxyHandler.connect(self, endpoint) + else: + self.connect(endpoint) + + def destroyConnection(self, reason = None): + self.handle_close(reason or "Requested") + + def getStatus(self): + return self.connected + def handle_connect(self): + self.connected = True if self.proxyHandler != None: logger.debug("HttpProxy handle connect") self.proxyHandler.send(self) @@ -56,10 +68,11 @@ def handle_connect(self): self.emitEvent(YowLayerEvent(YowNetworkLayer.EVENT_STATE_CONNECTED)) def handle_close(self, reason = "Connection Closed"): + self.connected = False logger.debug("Disconnected, reason: %s" % reason) self.emitEvent(YowLayerEvent(self.__class__.EVENT_STATE_DISCONNECTED, reason = reason, detached=True)) self.close() - + def handle_error(self): raise @@ -73,8 +86,9 @@ def handle_read(self): self.receive(data) def send(self, data): - self.out_buffer = self.out_buffer + data - self.initiate_send() + if self.connected: + self.out_buffer = self.out_buffer + data + self.initiate_send() def receive(self, data): self.toUpper(data) diff --git a/yowsup/layers/network/layer_interface.py b/yowsup/layers/network/layer_interface.py new file mode 100644 index 000000000..a75594ebf --- /dev/null +++ b/yowsup/layers/network/layer_interface.py @@ -0,0 +1,10 @@ +from yowsup.layers import YowLayerInterface +class YowNetworkLayerInterface(YowLayerInterface): + def connect(self): + self._layer.createConnection() + + def disconnect(self): + self._layer.destroyConnection() + + def getStatus(self): + return self._layer.getStatus() \ No newline at end of file diff --git a/yowsup/layers/protocol_messages/protocolentities/message.py b/yowsup/layers/protocol_messages/protocolentities/message.py index 4d10b46ff..98e9f71c5 100644 --- a/yowsup/layers/protocol_messages/protocolentities/message.py +++ b/yowsup/layers/protocol_messages/protocolentities/message.py @@ -55,24 +55,21 @@ def toProtocolTreeNode(self): "id" : self._id, } - if not self.isOutgoing(): - attribs["t"] = str(self.timestamp) - - if self.offline is not None: - attribs["offline"] = "1" if self.offline else "0" - if self.isOutgoing(): attribs["to"] = self.to else: attribs["from"] = self._from - if self.notify: - attribs["notify"] = self.notify + attribs["t"] = str(self.timestamp) - if self.retry: - attribs["retry"] = str(self.retry) - if self.participant: - attribs["participant"] = self.participant + if self.offline is not None: + attribs["offline"] = "1" if self.offline else "0" + if self.notify: + attribs["notify"] = self.notify + if self.retry: + attribs["retry"] = str(self.retry) + if self.participant: + attribs["participant"] = self.participant xNode = None @@ -109,7 +106,6 @@ def forward(self, to, _id = None): OutgoingMessage.to = to OutgoingMessage._from = None OutgoingMessage._id = self._generateId() if _id is None else _id - OutgoingMessage.participant = None # very strange issue with group messages otherwise return OutgoingMessage @staticmethod diff --git a/yowsup/layers/protocol_receipts/protocolentities/receipt_outgoing.py b/yowsup/layers/protocol_receipts/protocolentities/receipt_outgoing.py index ffdf43536..33a11d246 100644 --- a/yowsup/layers/protocol_receipts/protocolentities/receipt_outgoing.py +++ b/yowsup/layers/protocol_receipts/protocolentities/receipt_outgoing.py @@ -13,20 +13,39 @@ class OutgoingReceiptProtocolEntity(ReceiptProtocolEntity): read - INCOMING - + multiple items: + + + + + + ''' - def __init__(self, _id, to, read = False, participant = None, callId = None): - super(OutgoingReceiptProtocolEntity, self).__init__(_id) - self.setOutgoingData(to, read, participant, callId) - def setOutgoingData(self, to, read, participant, callId): + def __init__(self, messageIds, to, read = False, participant = None, callId = None): + if type(messageIds) in (list, tuple): + if len(messageIds) > 1: + receiptId = self._generateId() + else: + receiptId = messageIds[0] + else: + receiptId = messageIds + messageIds = [messageIds] + + super(OutgoingReceiptProtocolEntity, self).__init__(receiptId) + self.setOutgoingData(messageIds, to, read, participant, callId) + + def setOutgoingData(self, messageIds, to, read, participant, callId): + self.messageIds = messageIds self.to = to self.read = read self.participant = participant self.callId = callId - + + def getMessageIds(self): + return self.messageIds + def toProtocolTreeNode(self): node = super(OutgoingReceiptProtocolEntity, self).toProtocolTreeNode() if self.read: @@ -38,7 +57,11 @@ def toProtocolTreeNode(self): node.addChild(offer) node.setAttribute("to", self.to) - node.setAttribute("t", str(int(time.time()))) + + if len(self.messageIds) > 1: + listNode = ProtocolTreeNode("list") + listNode.addChildren([ProtocolTreeNode("item", {"id": mId}) for mId in self.messageIds]) + node.addChild(listNode) return node @@ -47,13 +70,21 @@ def __str__(self): out += "To: \n%s" % self.to if self.read: out += "Type: \n%s" % "read" + out += "For: \n%s" % self.messageIds return out @staticmethod def fromProtocolTreeNode(node): + listNode = node.getChild("list") + messageIds = [] + if listNode: + messageIds = [child["id"] for child in listNode.getChildren()] + else: + messageIds = [node["id"]] + return OutgoingReceiptProtocolEntity( - node.getAttributeValue("id"), - node.getAttributeValue("to"), - node.getAttributeValue("type"), - node.getAttributeValue("participant") + messageIds, + node["to"], + node["type"] == "read", + node["participant"] ) diff --git a/yowsup/stacks/yowstack.py b/yowsup/stacks/yowstack.py index 324dbd508..c17a0850c 100644 --- a/yowsup/stacks/yowstack.py +++ b/yowsup/stacks/yowstack.py @@ -42,6 +42,10 @@ class YowStackBuilder(object): def __init__(self): self.layers = () + self._props = {} + + def setProp(self, key, value): + self._props[key] = value def pushDefaultLayers(self, axolotl = False): defaultLayers = YowStackBuilder.getDefaultLayers(axolotl) @@ -57,7 +61,7 @@ def pop(self): return self def build(self): - return YowStack(self.layers, reversed = False) + return YowStack(self.layers, reversed = False, props = self._props) @staticmethod def getDefaultLayers(axolotl = False, groups = True, media = True, privacy = True, profiles = True): @@ -119,20 +123,36 @@ class YowStack(object): __stack = [] __stackInstances = [] __detachedQueue = Queue.Queue() - def __init__(self, stackClassesArr = None, reversed = True): + def __init__(self, stackClassesArr = None, reversed = True, props = None): stackClassesArr = stackClassesArr or () self.__stack = stackClassesArr[::-1] if reversed else stackClassesArr self.__stackInstances = [] - self._construct() - self._props = {} + self._props = props or {} self.setProp(YowNetworkLayer.PROP_ENDPOINT, YowConstants.ENDPOINTS[random.randint(0,len(YowConstants.ENDPOINTS)-1)]) self.setProp(YowCoderLayer.PROP_DOMAIN, YowConstants.DOMAIN) self.setProp(YowCoderLayer.PROP_RESOURCE, env.CURRENT_ENV.getResource()) + self._construct() + + + def getLayerInterface(self, YowLayerClass): + for inst in self.__stackInstances: + if inst.__class__ == YowLayerClass: + return inst.getLayerInterface() + elif inst.__class__ == YowParallelLayer: + res = inst.getLayerInterface(YowLayerClass) + if res: + return res + def send(self, data): + self.__stackInstances[-1].send(data) + + def receive(self, data): + self.__stackInstances[0].receive(data) + def setCredentials(self, credentials): - self.setProp(YowAuthenticationProtocolLayer.PROP_CREDENTIALS, credentials) + self.getLayerInterface(YowAuthenticationProtocolLayer).setCredentials(credentials) def addLayer(self, layerClass): self.__stack.push(layerClass) @@ -202,4 +222,3 @@ def _construct(self): def getLayer(self, layerIndex): return self.__stackInstances[layerIndex] -