diff --git a/README.md b/README.md index 387c9d039..4e7b0035a 100644 --- a/README.md +++ b/README.md @@ -2,93 +2,26 @@ -## Updates (September 07, 2015) -Yowsup v2.4 is out, See [release notes](https://github.com/tgalal/yowsup/releases/tag/v2.4) +## Updates (December 14, 2015) +Yowsup v2.4.48 is out, See [release notes](https://github.com/tgalal/yowsup/releases/tag/v2.4.48) -### 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) -Yowsup v2.3.183, see [release notes](https://github.com/tgalal/yowsup/releases/tag/v2.3.183) - -### Updates (July 21, 2015) -Yowsup v2.3.167, see [release notes](https://github.com/tgalal/yowsup/releases/tag/v2.3.167) - -### Updates (June 23, 2015) -Yowsup v2.3.123, see [release notes](https://github.com/tgalal/yowsup/releases/tag/v2.3.123) - -### Updates (May 29, 2015) - -Yowsup v2.3.84, see [release notes](https://github.com/tgalal/yowsup/releases/tag/v2.3.84) - -### Updates (February 17, 2015) - -Yowsup 2.2.78 is out, see [release notes](https://github.com/tgalal/yowsup/releases/tag/v2.2.78). - -### Updates (January 12, 2015) - -Yowsup 2.2.15 is out. - -Image upload and send is now implemented. [Read here](https://github.com/tgalal/yowsup/wiki/Upload-and-send-Media) how to integrate in your code, or try it out with yowsup-cli - -[Read full release notes](https://github.com/tgalal/yowsup/releases/tag/v2.2.15) - -### Updates (January 1, 2015) - -Happy new year! - -P.S: Yowsup's [license changed](https://github.com/tgalal/yowsup#license) to GPLv3 (previously the MIT License was used). - -### (December 31, 2014) - -New features land in yowsup. You will need to re-install to pull new dependencies. Follow the [updated install instructions for your OS](#installation-updated-dec-31-2014). - -A couple of highlights: - -### End-to-End encryption - -Yowsup now implements the new end-to-end encryption recently introduced in WhatsApp (AKA axolotl) (AKA textsecure). To activate in demos pass in '--moxie' switch. -Example: - - > yowsup-cli demos --config /path/to/config --yowsup --moxie - -This will make messages communication with WhatsApp platforms which support this feature encrypted. At the moment it's only Android, which means yowsup got this feature even before official WhatsApp clients on other platforms. - -For platforms which do not support encryption, they will get plaintext messages as usual. - -More technical details about axolotl in yowsup [here](https://github.com/tgalal/yowsup/wiki/End-to-End-encryption) - -### New Send client demo - -Use the send client demo in yowsup-cli to login, send a message and exit. - - > yowsup-cli demos --config /path/to/config --send NUMBER "Hello world" - ========================================================== ## Yowsup opened WhatsApp service under platforms! Yowsup is a python library that enables you build application which use WhatsApp service. Yowsup has been used to create an unofficial WhatsApp client Nokia N9 through the Wazapp project which was in use by 200K + users as well as another fully featured unofficial client for Blackberry 10 -## What's new in Yowsup 2 - -Everything! The old library code was so messed up that I was disgusted just by looking at it. I rewrote the library from ground up with a much more robust, extensible architecture and a much simpler and easier to read code. - -__For devs, the update is breaking for any old code. While old code will stay in "legacy" branch for a while, it's advised that you upgrade your code. Unless your code is a full fledged WhatsApp application, migrating won't be a hard task.__ +## Quickstart -Here is what you need to know about Yowsup 2.0 to get started: (Or quickly [jump to installation](#installation)): - - * **[The new architecture](https://github.com/tgalal/yowsup/wiki/Architecture)** + * **[yowsup's architecture](https://github.com/tgalal/yowsup/wiki/Architecture)** * **[Create a sample app](https://github.com/tgalal/yowsup/wiki/Sample-Application)** - * **[yowsup-cli 2.0](https://github.com/tgalal/yowsup/wiki/yowsup-cli-2.0)** + * **[yowsup-cli](https://github.com/tgalal/yowsup/wiki/yowsup-cli-2.0)** * **[Yowsup development, debugging, maintainance and sanity](https://github.com/tgalal/yowsup/wiki/Yowsup-development,-debugging,-maintainance-and-sanity)** - -## Installation -(Updated Jan 12, 2015) +## Installation - Requires python2.6+, or python3.0 + - - Required python packages: python-dateutil, + - Required python packages: python-dateutil, - Required python packages for end-to-end encryption: protobuf, pycrypto, python-axolotl-curve25519 - Required python packages for yowsup-cli: argparse, readline (or pyreadline for windows), pillow (for sending images) @@ -110,14 +43,14 @@ Because of a bug with python-dateutil package you might get permission error for ``` python setup.py install ``` -Administrators privileges might be required, if so then run with 'sudo' +Administrators privileges might be required, if so then run with 'sudo' ### Windows - Install [mingw](http://www.mingw.org/) compiler - Add mingw to your PATH - In PYTHONPATH\Lib\distutils create a file called distutils.cfg and add these lines: - + ``` [build] compiler=mingw32 @@ -126,7 +59,7 @@ compiler=mingw32 - Install [zlib](http://www.zlib.net/) - ```python setup.py install``` -If pycrypto fails to install with some "chmod error". You can install it separately using something like +If pycrypto fails to install with some "chmod error". You can install it separately using something like ```easy_install http://www.voidspace.org.uk/downloads/pycrypto26/pycrypto-2.6.win32-py2.7.exe``` or for python3 from: @@ -139,8 +72,8 @@ and then rerun the install command again Special thanks to: -- [CODeRUS](https://github.com/CODeRUS) -- [mgp25](https://github.com/mgp25) +- [CODeRUS](https://github.com/CODeRUS) +- [mgp25](https://github.com/mgp25) - [SikiFn](https://github.com/SikiFn) - [0xTryCatch](https://github.com/0xTryCatch) - [shirioko](https://github.com/shirioko) diff --git a/yowsup/__init__.py b/yowsup/__init__.py index c9bd9263a..efb69c560 100644 --- a/yowsup/__init__.py +++ b/yowsup/__init__.py @@ -1,2 +1,2 @@ -__version__ = "2.4" +__version__ = "2.4.48" __author__ = "Tarek Galal" diff --git a/yowsup/common/tools.py b/yowsup/common/tools.py index d414de260..003b4960b 100644 --- a/yowsup/common/tools.py +++ b/yowsup/common/tools.py @@ -85,13 +85,13 @@ class TimeTools: def parseIso(iso): d=datetime.datetime(*map(int, re.split('[^\d]', iso)[:-1])) return d - - @staticmethod + + @staticmethod def utcToLocal(dt): utc = tz.gettz('UTC') local = tz.tzlocal() dtUtc = dt.replace(tzinfo=utc) - + return dtUtc.astimezone(local) @staticmethod @@ -128,6 +128,9 @@ def scaleImage(infile, outfile, imageFormat, width, height): if ModuleTools.INSTALLED_PIL(): from PIL import Image im = Image.open(infile) + #Convert P mode images + if im.mode != "RGB": + im = im.convert("RGB") im.thumbnail((width, height)) im.save(outfile, imageFormat) return True @@ -148,10 +151,11 @@ def getImageDimensions(imageFile): @staticmethod def generatePreviewFromImage(image): fd, path = tempfile.mkstemp() - fileObj = os.fdopen(fd, "rb+") + preview = None - if ImageTools.scaleImage(image, fileObj, "JPEG", YowConstants.PREVIEW_WIDTH, YowConstants.PREVIEW_HEIGHT): + if ImageTools.scaleImage(image, path, "JPEG", YowConstants.PREVIEW_WIDTH, YowConstants.PREVIEW_HEIGHT): + fileObj = os.fdopen(fd, "rb+") fileObj.seek(0) preview = fileObj.read() - fileObj.close() - return preview \ No newline at end of file + fileObj.close() + return preview diff --git a/yowsup/demos/cli/layer.py b/yowsup/demos/cli/layer.py index 186eb2e38..6e70f5228 100644 --- a/yowsup/demos/cli/layer.py +++ b/yowsup/demos/cli/layer.py @@ -47,6 +47,7 @@ def __init__(self): self.connected = False self.username = None self.sendReceipts = True + self.sendRead = True self.disconnectAction = self.__class__.DISCONNECT_ACTION_PROMPT self.credentials = None @@ -223,6 +224,35 @@ def onError(errorIqEntity, originalIqEntity): else: logger.error("Python PIL library is not installed, can't set profile picture") + @clicmd("Get profile privacy") + def profile_getPrivacy(self): + if self.assertConnected(): + def onSuccess(resultIqEntity, originalIqEntity): + self.output("Profile privacy is: %s" %(resultIqEntity)) + + def onError(errorIqEntity, originalIqEntity): + logger.error("Error getting profile privacy") + + iq = GetPrivacyIqProtocolEntity() + self._sendIq(iq, onSuccess, onError) + + @clicmd("Profile privacy. value=all|contacts|none names=profile|status|last. Names are comma separated, defaults to all.") + def profile_setPrivacy(self, value="all", names=None): + if self.assertConnected(): + def onSuccess(resultIqEntity, originalIqEntity): + self.output("Profile privacy set to: %s" %(resultIqEntity)) + + def onError(errorIqEntity, originalIqEntity): + logger.error("Error setting profile privacy") + try: + names = [name for name in names.split(',')] if names else None + iq = SetPrivacyIqProtocolEntity(value, names) + self._sendIq(iq, onSuccess, onError) + except Exception as inst: + self.output(inst.message) + return self.print_usage() + + ########### groups @clicmd("List all groups you belong to", 5) @@ -395,6 +425,12 @@ def state_typing(self, jid): entity = OutgoingChatstateProtocolEntity(ChatstateProtocolEntity.STATE_TYPING, self.aliasToJid(jid)) self.toLower(entity) + @clicmd("Request contacts statuses") + def statuses_get(self, contacts): + if self.assertConnected(): + entity = GetStatusesIqProtocolEntity([self.aliasToJid(c) for c in contacts.split(',')]) + self.toLower(entity) + @clicmd("Send paused state") def state_paused(self, jid): if self.assertConnected(): @@ -492,8 +528,8 @@ def onMessage(self, message): self.output(output, tag = None, prompt = not self.sendReceipts) if self.sendReceipts: - self.toLower(message.ack()) - self.output("Sent delivered receipt", tag = "Message %s" % message.getId()) + self.toLower(message.ack(self.sendRead)) + self.output("Sent delivered receipt"+" and Read" if self.sendRead else "", tag = "Message %s" % message.getId()) def getTextMessageBody(self, message): @@ -504,7 +540,7 @@ def getMediaMessageBody(self, message): return self.getDownloadableMediaMessageBody(message) else: return "[Media Type: %s]" % message.getMediaType() - + def getDownloadableMediaMessageBody(self, message): return "[Media Type:{media_type}, Size:{media_size}, URL:{media_url}]".format( @@ -570,4 +606,3 @@ def __str__(self): @clicmd("Print this message") def help(self): self.print_usage() - diff --git a/yowsup/demos/contacts/stack.py b/yowsup/demos/contacts/stack.py index 2dbae86bb..0ecb29454 100644 --- a/yowsup/demos/contacts/stack.py +++ b/yowsup/demos/contacts/stack.py @@ -9,8 +9,7 @@ from yowsup.layers.protocol_acks import YowAckProtocolLayer from yowsup.layers.logger import YowLoggerLayer from yowsup.layers.protocol_contacts import YowContactsIqProtocolLayer -from yowsup.common import YowConstants -from yowsup import env +from yowsup.layers import YowParallelLayer class YowsupSyncStack(object): def __init__(self, credentials, contacts, encryptionEnabled = False): @@ -24,7 +23,7 @@ def __init__(self, credentials, contacts, encryptionEnabled = False): from yowsup.layers.axolotl import YowAxolotlLayer layers = ( SyncLayer, - (YowAuthenticationProtocolLayer, YowContactsIqProtocolLayer, YowReceiptProtocolLayer, YowAckProtocolLayer), + YowParallelLayer([YowAuthenticationProtocolLayer, YowContactsIqProtocolLayer, YowReceiptProtocolLayer, YowAckProtocolLayer]), YowAxolotlLayer, YowLoggerLayer, YowCoderLayer, @@ -35,7 +34,7 @@ def __init__(self, credentials, contacts, encryptionEnabled = False): else: layers = ( SyncLayer, - (YowAuthenticationProtocolLayer, YowContactsIqProtocolLayer, YowReceiptProtocolLayer, YowAckProtocolLayer), + YowParallelLayer([YowAuthenticationProtocolLayer, YowContactsIqProtocolLayer, YowReceiptProtocolLayer, YowAckProtocolLayer]), YowLoggerLayer, YowCoderLayer, YowCryptLayer, diff --git a/yowsup/demos/echoclient/stack.py b/yowsup/demos/echoclient/stack.py index d8448d291..af14f5d4d 100644 --- a/yowsup/demos/echoclient/stack.py +++ b/yowsup/demos/echoclient/stack.py @@ -12,8 +12,7 @@ from yowsup.layers.logger import YowLoggerLayer from yowsup.layers.protocol_iq import YowIqProtocolLayer from yowsup.layers.protocol_calls import YowCallsProtocolLayer -from yowsup.common import YowConstants -from yowsup import env +from yowsup.layers import YowParallelLayer class YowsupEchoStack(object): def __init__(self, credentials, encryptionEnabled = False): @@ -21,7 +20,7 @@ def __init__(self, credentials, encryptionEnabled = False): from yowsup.layers.axolotl import YowAxolotlLayer layers = ( EchoLayer, - (YowAuthenticationProtocolLayer, YowMessagesProtocolLayer, YowReceiptProtocolLayer, YowAckProtocolLayer, YowMediaProtocolLayer, YowIqProtocolLayer, YowCallsProtocolLayer), + YowParallelLayer([YowAuthenticationProtocolLayer, YowMessagesProtocolLayer, YowReceiptProtocolLayer, YowAckProtocolLayer, YowMediaProtocolLayer, YowIqProtocolLayer, YowCallsProtocolLayer]), YowAxolotlLayer, YowLoggerLayer, YowCoderLayer, @@ -32,7 +31,7 @@ def __init__(self, credentials, encryptionEnabled = False): else: layers = ( EchoLayer, - (YowAuthenticationProtocolLayer, YowMessagesProtocolLayer, YowReceiptProtocolLayer, YowAckProtocolLayer, YowMediaProtocolLayer, YowIqProtocolLayer, YowCallsProtocolLayer), + YowParallelLayer([YowAuthenticationProtocolLayer, YowMessagesProtocolLayer, YowReceiptProtocolLayer, YowAckProtocolLayer, YowMediaProtocolLayer, YowIqProtocolLayer, YowCallsProtocolLayer]), YowLoggerLayer, YowCoderLayer, YowCryptLayer, diff --git a/yowsup/demos/sendclient/stack.py b/yowsup/demos/sendclient/stack.py index 5d78aac90..8d63ee116 100644 --- a/yowsup/demos/sendclient/stack.py +++ b/yowsup/demos/sendclient/stack.py @@ -9,8 +9,8 @@ from yowsup.layers.protocol_receipts import YowReceiptProtocolLayer from yowsup.layers.protocol_acks import YowAckProtocolLayer from yowsup.layers.logger import YowLoggerLayer -from yowsup.common import YowConstants -from yowsup import env +from yowsup.layers import YowParallelLayer + class YowsupSendStack(object): def __init__(self, credentials, messages, encryptionEnabled = False): @@ -24,7 +24,7 @@ def __init__(self, credentials, messages, encryptionEnabled = False): from yowsup.layers.axolotl import YowAxolotlLayer layers = ( SendLayer, - (YowAuthenticationProtocolLayer, YowMessagesProtocolLayer, YowReceiptProtocolLayer, YowAckProtocolLayer), + YowParallelLayer([YowAuthenticationProtocolLayer, YowMessagesProtocolLayer, YowReceiptProtocolLayer, YowAckProtocolLayer]), YowAxolotlLayer, YowLoggerLayer, YowCoderLayer, @@ -35,7 +35,7 @@ def __init__(self, credentials, messages, encryptionEnabled = False): else: layers = ( SendLayer, - (YowAuthenticationProtocolLayer, YowMessagesProtocolLayer, YowReceiptProtocolLayer, YowAckProtocolLayer), + YowParallelLayer([YowAuthenticationProtocolLayer, YowMessagesProtocolLayer, YowReceiptProtocolLayer, YowAckProtocolLayer]), YowLoggerLayer, YowCoderLayer, YowCryptLayer, diff --git a/yowsup/env/env_android.py b/yowsup/env/env_android.py index 2dfd424e6..cc859773a 100644 --- a/yowsup/env/env_android.py +++ b/yowsup/env/env_android.py @@ -1,25 +1,27 @@ from .env import YowsupEnv import base64 import hashlib + + class AndroidYowsupEnv(YowsupEnv): _SIGNATURE = "MIIDMjCCAvCgAwIBAgIETCU2pDALBgcqhkjOOAQDBQAwfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFDASBgNV" \ - "BAcTC1NhbnRhIENsYXJhMRYwFAYDVQQKEw1XaGF0c0FwcCBJbmMuMRQwEgYDVQQLEwtFbmdpbmVlcmluZzEUMBIGA1UEAxMLQnJ" \ - "pYW4gQWN0b24wHhcNMTAwNjI1MjMwNzE2WhcNNDQwMjE1MjMwNzE2WjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5" \ - "pYTEUMBIGA1UEBxMLU2FudGEgQ2xhcmExFjAUBgNVBAoTDVdoYXRzQXBwIEluYy4xFDASBgNVBAsTC0VuZ2luZWVyaW5nMRQwEg" \ - "YDVQQDEwtCcmlhbiBBY3RvbjCCAbgwggEsBgcqhkjOOAQBMIIBHwKBgQD9f1OBHXUSKVLfSpwu7OTn9hG3UjzvRADDHj+AtlEm" \ - "aUVdQCJR+1k9jVj6v8X1ujD2y5tVbNeBO4AdNG/yZmC3a5lQpaSfn+gEexAiwk+7qdf+t8Yb+DtX58aophUPBPuD9tPFHsMCN" \ - "VQTWhaRMvZ1864rYdcq7/IiAxmd0UgBxwIVAJdgUI8VIwvMspK5gqLrhAvwWBz1AoGBAPfhoIXWmz3ey7yrXDa4V7l5lK+7+jr" \ - "qgvlXTAs9B4JnUVlXjrrUWU/mcQcQgYC0SRZxI+hMKBYTt88JMozIpuE8FnqLVHyNKOCjrh4rs6Z1kW6jfwv6ITVi8ftiegEkO" \ - "8yk8b6oUZCJqIPf4VrlnwaSi2ZegHtVJWQBTDv+z0kqA4GFAAKBgQDRGYtLgWh7zyRtQainJfCpiaUbzjJuhMgo4fVWZIvXHaS" \ - "HBU1t5w//S0lDK2hiqkj8KpMWGywVov9eZxZy37V26dEqr/c2m5qZ0E+ynSu7sqUD7kGx/zeIcGT0H+KAVgkGNQCo5Uc0koLRW" \ - "YHNtYoIvt5R3X6YZylbPftF/8ayWTALBgcqhkjOOAQDBQADLwAwLAIUAKYCp0d6z4QQdyN74JDfQ2WCyi8CFDUM4CaNB+ceVXd" \ - "KtOrNTQcc0e+t" - - _MD5_CLASSES = "MYFrqqEFgD/DG5Z9E+zaSA==" + "BAcTC1NhbnRhIENsYXJhMRYwFAYDVQQKEw1XaGF0c0FwcCBJbmMuMRQwEgYDVQQLEwtFbmdpbmVlcmluZzEUMBIGA1UEAxMLQnJ" \ + "pYW4gQWN0b24wHhcNMTAwNjI1MjMwNzE2WhcNNDQwMjE1MjMwNzE2WjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5" \ + "pYTEUMBIGA1UEBxMLU2FudGEgQ2xhcmExFjAUBgNVBAoTDVdoYXRzQXBwIEluYy4xFDASBgNVBAsTC0VuZ2luZWVyaW5nMRQwEg" \ + "YDVQQDEwtCcmlhbiBBY3RvbjCCAbgwggEsBgcqhkjOOAQBMIIBHwKBgQD9f1OBHXUSKVLfSpwu7OTn9hG3UjzvRADDHj+AtlEm" \ + "aUVdQCJR+1k9jVj6v8X1ujD2y5tVbNeBO4AdNG/yZmC3a5lQpaSfn+gEexAiwk+7qdf+t8Yb+DtX58aophUPBPuD9tPFHsMCN" \ + "VQTWhaRMvZ1864rYdcq7/IiAxmd0UgBxwIVAJdgUI8VIwvMspK5gqLrhAvwWBz1AoGBAPfhoIXWmz3ey7yrXDa4V7l5lK+7+jr" \ + "qgvlXTAs9B4JnUVlXjrrUWU/mcQcQgYC0SRZxI+hMKBYTt88JMozIpuE8FnqLVHyNKOCjrh4rs6Z1kW6jfwv6ITVi8ftiegEkO" \ + "8yk8b6oUZCJqIPf4VrlnwaSi2ZegHtVJWQBTDv+z0kqA4GFAAKBgQDRGYtLgWh7zyRtQainJfCpiaUbzjJuhMgo4fVWZIvXHaS" \ + "HBU1t5w//S0lDK2hiqkj8KpMWGywVov9eZxZy37V26dEqr/c2m5qZ0E+ynSu7sqUD7kGx/zeIcGT0H+KAVgkGNQCo5Uc0koLRW" \ + "YHNtYoIvt5R3X6YZylbPftF/8ayWTALBgcqhkjOOAQDBQADLwAwLAIUAKYCp0d6z4QQdyN74JDfQ2WCyi8CFDUM4CaNB+ceVXd" \ + "KtOrNTQcc0e+t" + + _MD5_CLASSES = "vIDGGxIcIxeBP0GoG8yL8g==" _KEY = "/UIGKU1FVQa+ATM2A0za7G2KI9S/CwPYjgAbc67v7ep42eO/WeTLx1lb1cHwxpsEgF4+PmYpLd2YpGUdX/A2JQitsHzDwgcdBpUf7psX1BU=" - _VERSION = "2.12.287" - _OS_NAME= "Android" + _VERSION = "2.12.357" + _OS_NAME = "Android" _OS_VERSION = "4.3" _DEVICE_NAME = "GalaxyS3" _AXOLOTL = True diff --git a/yowsup/layers/__init__.py b/yowsup/layers/__init__.py index a59954761..c36e0369e 100644 --- a/yowsup/layers/__init__.py +++ b/yowsup/layers/__init__.py @@ -156,7 +156,7 @@ def __init__(self, sublayers = None): def getLayerInterface(self, YowLayerClass): for s in self.sublayers: if s.__class__ == YowLayerClass: - return s + return s.getLayerInterface() def setStack(self, stack): super(YowParallelLayer, self).setStack(stack) diff --git a/yowsup/layers/auth/layer_interface_authentication.py b/yowsup/layers/auth/layer_interface_authentication.py index 49d72f668..ae3b29fde 100644 --- a/yowsup/layers/auth/layer_interface_authentication.py +++ b/yowsup/layers/auth/layer_interface_authentication.py @@ -2,7 +2,7 @@ class YowAuthenticationProtocolLayerInterface(YowLayerInterface): def setCredentials(self, phone, password): - self._layer.setCredentials(phone, password) + self._layer.setCredentials((phone, password)) def getUsername(self, full = False): - return self._layer.getUsername(full) \ No newline at end of file + return self._layer.getUsername(full) diff --git a/yowsup/layers/coder/encoder.py b/yowsup/layers/coder/encoder.py index d80225962..70c067194 100644 --- a/yowsup/layers/coder/encoder.py +++ b/yowsup/layers/coder/encoder.py @@ -30,9 +30,12 @@ def protocolTreeNodeToBytes(self, node): def writeInternal(self, node, data): - x = 1 + (0 if node.attributes is None else len(node.attributes) * 2) + (0 if not node.hasChildren() else 1) + (0 if node.data is None else 1) + x = 1 + \ + (0 if node.attributes is None else len(node.attributes) * 2) + \ + (0 if not node.hasChildren() else 1) + \ + (0 if node.data is None else 1) - self.writeListStart(1 + (0 if node.attributes is None else len(node.attributes) * 2) + (0 if not node.hasChildren() else 1) + (0 if node.data is None else 1), data) + self.writeListStart(x, data) self.writeString(node.tag, data) self.writeAttributes(node.attributes, data); @@ -140,4 +143,3 @@ def writeJid(self, user, server, data): else: self.writeToken(0, data) self.writeString(server, data) - diff --git a/yowsup/layers/protocol_contacts/layer.py b/yowsup/layers/protocol_contacts/layer.py index 3c566ba76..4685597f4 100644 --- a/yowsup/layers/protocol_contacts/layer.py +++ b/yowsup/layers/protocol_contacts/layer.py @@ -28,7 +28,11 @@ def recvNotification(self, node): def recvIq(self, node): if node["type"] == "result" and node.getChild("sync"): self.toUpper(ResultSyncIqProtocolEntity.fromProtocolTreeNode(node)) + elif node["type"] == "result" and node.getChild("status"): + self.toUpper(ResultStatusesIqProtocolEntity.fromProtocolTreeNode(node)) def sendIq(self, entity): if entity.getXmlns() == "urn:xmpp:whatsapp:sync": self.toLower(entity.toProtocolTreeNode()) + elif entity.getXmlns() == GetStatusesIqProtocolEntity.XMLNS: + self.toLower(entity.toProtocolTreeNode()) diff --git a/yowsup/layers/protocol_contacts/protocolentities/__init__.py b/yowsup/layers/protocol_contacts/protocolentities/__init__.py index b9e5d352e..ca6f461e9 100644 --- a/yowsup/layers/protocol_contacts/protocolentities/__init__.py +++ b/yowsup/layers/protocol_contacts/protocolentities/__init__.py @@ -5,3 +5,5 @@ from .notification_contact_remove import RemoveContactNotificationProtocolEntity from .notification_contact_update import UpdateContactNotificationProtocolEntity from .notificiation_contacts_sync import ContactsSyncNotificationProtocolEntity +from .iq_statuses_get import GetStatusesIqProtocolEntity +from .iq_statuses_result import ResultStatusesIqProtocolEntity diff --git a/yowsup/layers/protocol_contacts/protocolentities/iq_statuses_get.py b/yowsup/layers/protocol_contacts/protocolentities/iq_statuses_get.py new file mode 100644 index 000000000..34bc8f5df --- /dev/null +++ b/yowsup/layers/protocol_contacts/protocolentities/iq_statuses_get.py @@ -0,0 +1,46 @@ +from yowsup.layers.protocol_iq.protocolentities import IqProtocolEntity +from yowsup.structs import ProtocolTreeNode + +class GetStatusesIqProtocolEntity(IqProtocolEntity): + XMLNS = "status" + + def __init__(self, jids, _id = None): + """ + Request the statuses of users. Should be sent once after login. + + Args: + - jids: A list of jids representing the users whose statuses you are + trying to get. + """ + super(GetStatusesIqProtocolEntity, self).__init__(self.__class__.XMLNS, _id, _type = "get", to = "s.whatsapp.net") + self.setGetStatusesProps(jids) + + def setGetStatusesProps(self, jids): + assert type(jids) is list, "jids must be a list of jids" + self.jids = jids + + def __str__(self): + out = super(GetStatusesIqProtocolEntity, self).__str__() + out += "Numbers: %s\n" % (",".join(self.numbers)) + return out + + def toProtocolTreeNode(self): + users = [ProtocolTreeNode("user", {'jid': jid}) for jid in self.jids] + + node = super(GetStatusesIqProtocolEntity, self).toProtocolTreeNode() + statusNode = ProtocolTreeNode("status", None, users) + node.addChild(statusNode) + + return node + + @staticmethod + def fromProtocolTreeNode(node): + entity = IqProtocolEntity.fromProtocolTreeNode(node) + entity.__class__ = GetStatusesIqProtocolEntity + statusNode = node.getChild("status") + userNodes = statusNode.getAllChildren() + jids = [user['jid'] for user in userNodes] + + entity.setGetStatusesProps(jids) + + return entity diff --git a/yowsup/layers/protocol_contacts/protocolentities/iq_statuses_result.py b/yowsup/layers/protocol_contacts/protocolentities/iq_statuses_result.py new file mode 100644 index 000000000..75d2ab410 --- /dev/null +++ b/yowsup/layers/protocol_contacts/protocolentities/iq_statuses_result.py @@ -0,0 +1,52 @@ +from yowsup.structs import ProtocolTreeNode +from .iq_sync import SyncIqProtocolEntity +from yowsup.layers.protocol_iq.protocolentities import IqProtocolEntity + +class ResultStatusesIqProtocolEntity(IqProtocolEntity): + ''' + + + + {status message} + HEX:{status message in hex} + + + {status message} + HEX:{status message in hex} + + + + ''' + XMLNS = 'status' + def __init__(self, _id, _from, statuses): + super(ResultStatusesIqProtocolEntity, self).__init__(self.__class__.XMLNS, _id, 'result', _from=_from) + setResultStatusesProps(statusesp) + + def setResultStatusesProps(self, statuses): + assert type(statuses) is dict, "statuses must be dict" + self.statuses = statuses + + def __str__(self): + out = super(ResultStatusesIqProtocolEntity, self).__str__() + out += "Statuses: %s\n" % ','.join(jid + '(' + str(v) + ')' for jid, v in self.statuses.iteritems()) + return out + + def toProtocolTreeNode(self): + node = super(ResultStatusesIqProtocolEntity, self).toProtcolTreeNode() + users = [ProtocolTreeNode('user', {'jid': jid, 't': t}, None, status) for jid, (status, t) in self.statuses.iteritems()] + statusNode = ProtocolTreeNode('status', None, users) + node.addChild(statusNode) + return node + + @staticmethod + def fromProtocolTreeNode(node): + statusNode = node.getChild('status') + users = statusNode.getAllChildren() + statuses = dict() + for user in users: + statuses[user['jid']] = (user.getData(), user['t']) + + entity = IqProtocolEntity.fromProtocolTreeNode(node) + entity.__class__ = ResultStatusesIqProtocolEntity + entity.setResultStatusesProps(statuses) + return entity diff --git a/yowsup/layers/protocol_contacts/protocolentities/iq_sync.py b/yowsup/layers/protocol_contacts/protocolentities/iq_sync.py index 1678962e3..174c6f8f4 100644 --- a/yowsup/layers/protocol_contacts/protocolentities/iq_sync.py +++ b/yowsup/layers/protocol_contacts/protocolentities/iq_sync.py @@ -7,7 +7,7 @@ class SyncIqProtocolEntity(IqProtocolEntity): ''' @@ -20,7 +20,7 @@ def __init__(self, _type, _id = None, sid = None, index = 0, last = True): self.setSyncProps(sid, index, last) def setSyncProps(self, sid, index, last): - self.sid = sid if sid else str((time.time() + 11644477200) * 10000000) + self.sid = sid if sid else str((int(time.time()) + 11644477200) * 10000000) self.index = int(index) self.last = last @@ -33,7 +33,7 @@ def __str__(self): return out def toProtocolTreeNode(self): - + syncNodeAttrs = { "sid": self.sid, "index": str(self.index), @@ -58,6 +58,6 @@ def fromProtocolTreeNode(node): syncNode.getAttributeValue("index"), syncNode.getAttributeValue("last") ) - - return entity + + return entity diff --git a/yowsup/layers/protocol_contacts/protocolentities/iq_sync_get.py b/yowsup/layers/protocol_contacts/protocolentities/iq_sync_get.py index bbdb3914c..95c18128e 100644 --- a/yowsup/layers/protocol_contacts/protocolentities/iq_sync_get.py +++ b/yowsup/layers/protocol_contacts/protocolentities/iq_sync_get.py @@ -17,7 +17,7 @@ class GetSyncIqProtocolEntity(SyncIqProtocolEntity): @@ -53,7 +53,7 @@ def __str__(self): return out def toProtocolTreeNode(self): - + users = [ProtocolTreeNode("user", {}, None, number) for number in self.numbers] node = super(GetSyncIqProtocolEntity, self).toProtocolTreeNode() @@ -76,5 +76,5 @@ def fromProtocolTreeNode(node): syncNode.getAttributeValue("mode"), syncNode.getAttributeValue("context"), ) - + return entity diff --git a/yowsup/layers/protocol_groups/protocolentities/notification_groups.py b/yowsup/layers/protocol_groups/protocolentities/notification_groups.py index d0c7c32d9..4e6398f69 100644 --- a/yowsup/layers/protocol_groups/protocolentities/notification_groups.py +++ b/yowsup/layers/protocol_groups/protocolentities/notification_groups.py @@ -21,10 +21,10 @@ def getParticipant(self, full = True): return self._participant if full else self._participant.split('@')[0] def getGroupId(self): - return self._id + return self.groupId def setGroupId(self, groupId): - self._id = groupId + self.groupId = groupId def __str__(self): diff --git a/yowsup/layers/protocol_media/protocolentities/message_media_downloadable_video.py b/yowsup/layers/protocol_media/protocolentities/message_media_downloadable_video.py index ef1ee74fe..71d847d1e 100644 --- a/yowsup/layers/protocol_media/protocolentities/message_media_downloadable_video.py +++ b/yowsup/layers/protocol_media/protocolentities/message_media_downloadable_video.py @@ -80,8 +80,10 @@ def toProtocolTreeNode(self): mediaNode.setAttribute("encoding", self.encoding) mediaNode.setAttribute("fps", self.fps) mediaNode.setAttribute("height", self.height) - mediaNode.setAttribute("seconds", self.seconds) - mediaNode.setAttribute("vbitrate", self.vbitrate) + if self.seconds is not None: + mediaNode.setAttribute("seconds", self.seconds) + if self.vbitrate is not None: + mediaNode.setAttribute("vbitrate", self.vbitrate) mediaNode.setAttribute("vcodec", self.vcodec) mediaNode.setAttribute("width", self.width) if self.caption is not None: diff --git a/yowsup/layers/protocol_profiles/layer.py b/yowsup/layers/protocol_profiles/layer.py index d86d5414a..00aad53c5 100644 --- a/yowsup/layers/protocol_profiles/layer.py +++ b/yowsup/layers/protocol_profiles/layer.py @@ -21,10 +21,18 @@ def sendIq(self, entity): self._sendIq(entity, self.onDeletePictureResult, self.onDeletePictureError) elif entity.getXmlns() == "status": self._sendIq(entity, self.onSetStatusResult, self.onSetStatusError) + elif entity.getXmlns() == "privacy": + self._sendIq(entity, self.onPrivacyResult, self.onPrivacyError) def recvIq(self, node): pass + def onPrivacyResult(self, resultNode, originIqRequestEntity): + self.toUpper(ResultPrivacyIqProtocolEntity.fromProtocolTreeNode(resultNode)) + + def onPrivacyError(self, errorNode, originalIqRequestEntity): + self.toUpper(ErrorIqProtocolEntity.fromProtocolTreeNode(errorNode)) + def onSetStatusResult(self, resultNode, originIqRequestEntity): self.toUpper(ResultIqProtocolEntity.fromProtocolTreeNode(resultNode)) @@ -48,4 +56,3 @@ def onDeletePictureResult(self, resultNode, originalIqRequestEntity): def onDeletePictureError(self, errorNode, originalIqRequestEntity): self.toUpper(ErrorIqProtocolEntity.fromProtocolTreeNode(errorNode)) - diff --git a/yowsup/layers/protocol_profiles/protocolentities/__init__.py b/yowsup/layers/protocol_profiles/protocolentities/__init__.py index 9d29af033..427ef6b6c 100644 --- a/yowsup/layers/protocol_profiles/protocolentities/__init__.py +++ b/yowsup/layers/protocol_profiles/protocolentities/__init__.py @@ -4,3 +4,6 @@ from .iq_picture_get_result import ResultGetPictureIqProtocolEntity from .iq_pictures_list import ListPicturesIqProtocolEntity from .iq_picture_set import SetPictureIqProtocolEntity +from .iq_privacy_set import SetPrivacyIqProtocolEntity +from .iq_privacy_get import GetPrivacyIqProtocolEntity +from .iq_privacy_result import ResultPrivacyIqProtocolEntity diff --git a/yowsup/layers/protocol_profiles/protocolentities/iq_privacy_get.py b/yowsup/layers/protocol_profiles/protocolentities/iq_privacy_get.py new file mode 100644 index 000000000..ed6446101 --- /dev/null +++ b/yowsup/layers/protocol_profiles/protocolentities/iq_privacy_get.py @@ -0,0 +1,27 @@ +from yowsup.layers.protocol_iq.protocolentities import IqProtocolEntity +from yowsup.structs import ProtocolTreeNode + +''' + + + + +''' + +class GetPrivacyIqProtocolEntity(IqProtocolEntity): + XMLNS = "privacy" + def __init__(self): + super(GetPrivacyIqProtocolEntity, self).__init__(self.__class__.XMLNS, _type="get") + + def toProtocolTreeNode(self): + node = super(GetPrivacyIqProtocolEntity, self).toProtocolTreeNode() + queryNode = ProtocolTreeNode(self.__class__.XMLNS) + node.addChild(queryNode) + return node + + @staticmethod + def fromProtocolTreeNode(node): + assert node.getChild(GetPrivacyIqProtocolEntity.XMLNS) is not None, "Not a get privacy iq node %s" % node + entity = IqProtocolEntity.fromProtocolTreeNode(node) + entity.__class__ = GetPrivacyIqProtocolEntity + return entity diff --git a/yowsup/layers/protocol_profiles/protocolentities/iq_privacy_result.py b/yowsup/layers/protocol_profiles/protocolentities/iq_privacy_result.py new file mode 100644 index 000000000..1b2a86008 --- /dev/null +++ b/yowsup/layers/protocol_profiles/protocolentities/iq_privacy_result.py @@ -0,0 +1,50 @@ +from yowsup.structs import ProtocolTreeNode +from yowsup.layers.protocol_iq.protocolentities import ResultIqProtocolEntity + +''' + + + + + + + + + + +''' + +class ResultPrivacyIqProtocolEntity(ResultIqProtocolEntity): + NODE_PRIVACY="privacy" + + def __init__(self, privacy): + super(ResultPrivacyIqProtocolEntity, self).__init__() + self.setProps(privacy) + + def setProps(self, privacy): + assert type(privacy) is dict, "Privacy must be a dict {name => value}" + self.privacy = privacy + + def __str__(self): + out = super(ResultPrivacyIqProtocolEntity, self).__str__() + out += "Privacy settings\n" + for name, value in self.privacy.items(): + out += "Category %s --> %s\n" % (name, value) + return out + + def toProtocolTreeNode(self): + node = super(ResultPrivacyIqProtocolEntity, self).toProtocolTreeNode() + queryNode = ProtocolTreeNode(self.__class__.NODE_PRIVACY) + node.addChild(queryNode) + return node + + @staticmethod + def fromProtocolTreeNode(node): + entity = super(ResultPrivacyIqProtocolEntity, ResultPrivacyIqProtocolEntity).fromProtocolTreeNode(node) + entity.__class__ = ResultPrivacyIqProtocolEntity + privacyNode = node.getChild(ResultPrivacyIqProtocolEntity.NODE_PRIVACY) + privacy = {} + for categoryNode in privacyNode.getAllChildren(): + privacy[categoryNode["name"]] = categoryNode["value"] + entity.setProps(privacy) + return entity diff --git a/yowsup/layers/protocol_profiles/protocolentities/iq_privacy_set.py b/yowsup/layers/protocol_profiles/protocolentities/iq_privacy_set.py new file mode 100644 index 000000000..597989fda --- /dev/null +++ b/yowsup/layers/protocol_profiles/protocolentities/iq_privacy_set.py @@ -0,0 +1,69 @@ +from yowsup.layers.protocol_iq.protocolentities import IqProtocolEntity +from yowsup.structs import ProtocolTreeNode + +''' + + + + + + + + +''' + +class SetPrivacyIqProtocolEntity(IqProtocolEntity): + NAMES = ["status", "profile", "last"] + VALUES = ["all", "contacts", "none"] + XMLNS = "privacy" + + def __init__(self, value="all", names = None): + # names can be a string with some element in VALUES or an array with strings with elements in VALUES + # by default, all names are used + super(SetPrivacyIqProtocolEntity, self).__init__(self.__class__.XMLNS, _type="set") + self.setNames(names) + self.setValue(value) + + @staticmethod + def checkValidNames(names): + names = names if names else SetPrivacyIqProtocolEntity.NAMES + if not type(names) is list: + names = [names] + + for name in names: + if not name in SetPrivacyIqProtocolEntity.NAMES: + raise Exception("Name should be in: '" + "', '".join(SetPrivacyIqProtocolEntity.NAMES) + "' but is '" + name + "'") + return names + + @staticmethod + def checkValidValue(value): + if not value in SetPrivacyIqProtocolEntity.VALUES: + raise Exception("Value should be in: '" + "', '".join(SetPrivacyIqProtocolEntity.VALUES) + "' but is '" + value + "'") + return value + + def setNames(self, names): + self.names = SetPrivacyIqProtocolEntity.checkValidNames(names) + + def setValue(self, value): + self.value = SetPrivacyIqProtocolEntity.checkValidValue(value) + + def toProtocolTreeNode(self): + node = super(SetPrivacyIqProtocolEntity, self).toProtocolTreeNode() + queryNode = ProtocolTreeNode(self.__class__.XMLNS) + for name in self.names: + listNode = ProtocolTreeNode("category", {"name": name, "value": self.value}) + queryNode.addChild(listNode) + node.addChild(queryNode) + return node + + @staticmethod + def fromProtocolTreeNode(node): + entity = IqProtocolEntity.fromProtocolTreeNode(node) + entity.__class__ = SetPrivacyIqProtocolEntity + privacyNode = node.getChild(SetPrivacyIqProtocolEntity.XMLNS) + names = [] + for categoryNode in privacyNode.getAllChildren(): + names.append(categoryNode["name"]) + entity.setNames(names) + entity.setValue("all") + return entity diff --git a/yowsup/layers/protocol_profiles/protocolentities/test_iq_privacy_get.py b/yowsup/layers/protocol_profiles/protocolentities/test_iq_privacy_get.py new file mode 100644 index 000000000..073ee441a --- /dev/null +++ b/yowsup/layers/protocol_profiles/protocolentities/test_iq_privacy_get.py @@ -0,0 +1,11 @@ +from yowsup.layers.protocol_iq.protocolentities.test_iq import IqProtocolEntityTest +from yowsup.layers.protocol_profiles.protocolentities import GetPrivacyIqProtocolEntity +from yowsup.structs import ProtocolTreeNode + +entity = GetPrivacyIqProtocolEntity() + +class GetPrivacyIqProtocolEntityTest(IqProtocolEntityTest): + def setUp(self): + super(GetPrivacyIqProtocolEntityTest, self).setUp() + self.ProtocolEntity = GetPrivacyIqProtocolEntity + self.node = entity.toProtocolTreeNode() diff --git a/yowsup/layers/protocol_profiles/protocolentities/test_iq_privacy_result.py b/yowsup/layers/protocol_profiles/protocolentities/test_iq_privacy_result.py new file mode 100644 index 000000000..5da45b054 --- /dev/null +++ b/yowsup/layers/protocol_profiles/protocolentities/test_iq_privacy_result.py @@ -0,0 +1,11 @@ +from yowsup.layers.protocol_iq.protocolentities.test_iq import IqProtocolEntityTest +from yowsup.layers.protocol_profiles.protocolentities import ResultPrivacyIqProtocolEntity +from yowsup.structs import ProtocolTreeNode + +entity = ResultPrivacyIqProtocolEntity({"profile":"all","last":"none","status":"contacts"}) + +class ResultPrivacyIqProtocolEntityTest(IqProtocolEntityTest): + def setUp(self): + super(ResultPrivacyIqProtocolEntityTest, self).setUp() + self.ProtocolEntity = ResultPrivacyIqProtocolEntity + self.node = entity.toProtocolTreeNode() diff --git a/yowsup/layers/protocol_profiles/protocolentities/test_iq_privacy_set.py b/yowsup/layers/protocol_profiles/protocolentities/test_iq_privacy_set.py new file mode 100644 index 000000000..b1d0dda6e --- /dev/null +++ b/yowsup/layers/protocol_profiles/protocolentities/test_iq_privacy_set.py @@ -0,0 +1,11 @@ +from yowsup.layers.protocol_iq.protocolentities.test_iq import IqProtocolEntityTest +from yowsup.layers.protocol_profiles.protocolentities import SetPrivacyIqProtocolEntity +from yowsup.structs import ProtocolTreeNode + +entity = SetPrivacyIqProtocolEntity("all", ["profile","last","status"]) + +class SetPrivacyIqProtocolEntityTest(IqProtocolEntityTest): + def setUp(self): + super(SetPrivacyIqProtocolEntityTest, self).setUp() + self.ProtocolEntity = SetPrivacyIqProtocolEntity + self.node = entity.toProtocolTreeNode() diff --git a/yowsup/stacks/yowstack.py b/yowsup/stacks/yowstack.py index c17a0850c..b360fe623 100644 --- a/yowsup/stacks/yowstack.py +++ b/yowsup/stacks/yowstack.py @@ -152,7 +152,7 @@ def receive(self, data): self.__stackInstances[0].receive(data) def setCredentials(self, credentials): - self.getLayerInterface(YowAuthenticationProtocolLayer).setCredentials(credentials) + self.getLayerInterface(YowAuthenticationProtocolLayer).setCredentials(*credentials) def addLayer(self, layerClass): self.__stack.push(layerClass) diff --git a/yowsup/structs/protocoltreenode.py b/yowsup/structs/protocoltreenode.py index c63446d43..f6f1ce9da 100644 --- a/yowsup/structs/protocoltreenode.py +++ b/yowsup/structs/protocoltreenode.py @@ -60,8 +60,13 @@ def toString(self): except UnicodeDecodeError: out += binascii.hexlify(self.data) else: - out += "%s" % self.data - + try: + out += "%s" % self.data + except UnicodeDecodeError: + try: + out += "%s" % self.data.decode() + except UnicodeDecodeError: + out += binascii.hexlify(self.data) if type(self.data) is str and sys.version_info >= (3,0): out += "\nHEX3:%s\n" % binascii.hexlify(self.data.encode('latin-1'))