Sample Application

Albert Chin edited this page Sep 14, 2016 · 13 revisions

Writing a simple Yowsup Echo client

As an example for how to integrate Yowsup in your project, we're going to go through the steps for creating an Echo Yowsup client. That is, your yowsup app is running, it receives a message from some contact or group, and it just sends it back.

First make sure you have yowsup in your PYTHONPATH. If you don't know how to do that, just follow those installation steps

You might as well want to take a quick look on the Stacks and Layers architecture that yowsup uses.

Now we get started:

File structure

  • PROJECT_DIR/run.py
  • PROJECT_DIR/layer.py

For writing a Yowsup project from scratch you'd need at least 2 files (well we can put everything in 1 file but that wouldn't be so readable would it).

Inside layer.py we are going to define our new Echo Layer. Inside the run.py we are going to initialize the yowsup stack with the Echo layer on top.

For the sake of clarity, I'm going to split each adjustments to each file into a separate step.

layer.py

For a project to use Yowsup, it needs to integrate itself as a layer in the Yowsup stack. So this is where we define our EchoLayer.

layer.py I

Because we know the layer is going to be placed in the stack directly above the protocol layers, we know it's going to receive data of type ProtocolEntity. However, since we are only looking for incoming messages, we can filter those by checking the return value of 'protocolEntity.getTag()`. And then we let the 'onMessage' method take over.

To send the message back we create a TextMessageProtocolEntity object and send it to the layer below using 'toLower' method.

from yowsup.layers.protocol_messages.protocolentities  import TextMessageProtocolEntity
from yowsup.layers                                     import YowLayer

class EchoLayer(YowLayer):

    def receive(self, protocolEntity):
        if protocolEntity.getTag() == "message":
            self.onMessage(protocolEntity)

    def onMessage(self, messageProtocolEntity):

        outgoingMessageProtocolEntity = TextMessageProtocolEntity(
            messageProtocolEntity.getBody(),
            to = messageProtocolEntity.getFrom()
        )

        self.toLower(outgoingMessageProtocolEntity)

layer.py II

You can simplify your code if you subclass YowInterfaceLayer instead of YowLayer. A YowInterfaceLayer assumes it's placed directly above Protocol Layers in the stack, so it expects sending and receiving ProtocolEntity. With that you can also use the '@ProtocolEntityCallback' decorator it provides. With this you don't need to implement the 'receive', it will make YowInterfaceLayer automatigically handle the checks and invoke the appropriate methods for you.

Here is the updated layer.py:

from yowsup.layers.interface                           import YowInterfaceLayer, ProtocolEntityCallback
from yowsup.layers.protocol_messages.protocolentities  import TextMessageProtocolEntity

class EchoLayer(YowInterfaceLayer):

    @ProtocolEntityCallback("message")
    def onMessage(self, messageProtocolEntity):

        outgoingMessageProtocolEntity = TextMessageProtocolEntity(
            messageProtocolEntity.getBody(),
            to = messageProtocolEntity.getFrom()
        )

        self.toLower(outgoingMessageProtocolEntity)

layer.py III

While echo of the above implementations for the EchoLayer is enough to get the Echo client functioning, you will notice everytime you reconnect using Yowsup you will receive the same messages you previously received. This is because you need to send a receipt to WhatsApp including the Id for the received messages. This is what makes double ticks appear in WhatsApp on the sender's phone.

from yowsup.layers.interface                           import YowInterfaceLayer, ProtocolEntityCallback
from yowsup.layers.protocol_messages.protocolentities  import TextMessageProtocolEntity
from yowsup.layers.protocol_receipts.protocolentities  import OutgoingReceiptProtocolEntity

class EchoLayer(YowInterfaceLayer):

    @ProtocolEntityCallback("message")
    def onMessage(self, messageProtocolEntity):
        receipt = OutgoingReceiptProtocolEntity(messageProtocolEntity.getId(), messageProtocolEntity.getFrom(), 'read', messageProtocolEntity.getParticipant())

        outgoingMessageProtocolEntity = TextMessageProtocolEntity(
            messageProtocolEntity.getBody(),
            to = messageProtocolEntity.getFrom()
        )

        self.toLower(receipt)
        self.toLower(outgoingMessageProtocolEntity)

layer.py IV

When you send a message (in our case echoing back the message), we receive a receipt from WhatsApp indicating delivery to server and to recipient. We need to send an 'Ack' for that receipt to prevent receiving this receipt over and over, and to prevent WhatsApp sending us "stream:error" and closing the connection for not sending those acks.

And so the final EchoLayer class becomes:

from yowsup.layers.interface                           import YowInterfaceLayer, ProtocolEntityCallback
from yowsup.layers.protocol_messages.protocolentities  import TextMessageProtocolEntity
from yowsup.layers.protocol_receipts.protocolentities  import OutgoingReceiptProtocolEntity
from yowsup.layers.protocol_acks.protocolentities      import OutgoingAckProtocolEntity



class EchoLayer(YowInterfaceLayer):

    @ProtocolEntityCallback("message")
    def onMessage(self, messageProtocolEntity):
        #send receipt otherwise we keep receiving the same message over and over

        if True:
            receipt = OutgoingReceiptProtocolEntity(messageProtocolEntity.getId(), messageProtocolEntity.getFrom(), 'read', messageProtocolEntity.getParticipant())

            outgoingMessageProtocolEntity = TextMessageProtocolEntity(
                messageProtocolEntity.getBody(),
                to = messageProtocolEntity.getFrom())

            self.toLower(receipt)
            self.toLower(outgoingMessageProtocolEntity)

    @ProtocolEntityCallback("receipt")
    def onReceipt(self, entity):
        ack = OutgoingAckProtocolEntity(entity.getId(), "receipt", entity.getType(), entity.getFrom())
        self.toLower(ack)

run.py

This is the code that will initialize the stack with the new EchoLayer on top.

run.py I

Here we create the stack and we manually include all layers. For a description of what each layer does, see layers description. For the protocol layers, our EchoLayer is interested only in Authentication (YowAuthenticationrotocolLayer), sending and receiving text messages (YowMessagesProtocolLayer), sending and receiving receipts (YowReceiptProtocolLayer) and sending Acks for receipts (YowAckLayer). If we were interested for example in media messages, we would have added YowMediaProtocolLayer.

from yowsup.stacks import  YowStackBuilder
from layer import EchoLayer
from yowsup.layers.auth import AuthError
from yowsup.layers import YowLayerEvent
from yowsup.layers.network import YowNetworkLayer
from yowsup.env import YowsupEnv

credentials = ("phone", "password") # replace with your phone and password

if __name__==  "__main__":
    stackBuilder = YowStackBuilder()

    stack = stackBuilder\
        .pushDefaultLayers(True)\
        .push(EchoLayer)\
        .build()

    stack.setCredentials(credentials)
    stack.broadcastEvent(YowLayerEvent(YowNetworkLayer.EVENT_STATE_CONNECT))   #sending the connect signal
    stack.loop() #this is the program mainloop

run.py II

Remark (added 2016-09-xx): Please note that the following is a bit out-dated. It is noted that at least one should update according to the commit "Split up axolotl into 3 layers"

We can simplify this even more by using predefined stacks

from layer import EchoLayer
from yowsup.layers                             import YowParallelLayer
from yowsup.layers.auth                        import YowAuthenticationProtocolLayer
from yowsup.layers.protocol_messages           import YowMessagesProtocolLayer
from yowsup.layers.protocol_receipts           import YowReceiptProtocolLayer
from yowsup.layers.protocol_acks               import YowAckProtocolLayer
from yowsup.layers.network                     import YowNetworkLayer
from yowsup.layers.coder                       import YowCoderLayer
from yowsup.stacks import YowStack
from yowsup.common import YowConstants
from yowsup.layers import YowLayerEvent
from yowsup.stacks import YowStack, YOWSUP_CORE_LAYERS
from yowsup.layers.axolotl                     import YowAxolotlLayer
from yowsup.env import YowsupEnv


CREDENTIALS = ("phone", "password") # replace with your phone and password

if __name__==  "__main__":
    layers = (
        EchoLayer,
        YowParallelLayer([YowAuthenticationProtocolLayer, YowMessagesProtocolLayer, YowReceiptProtocolLayer,
                          YowAckProtocolLayer]), YowAxolotlLayer
    ) + YOWSUP_CORE_LAYERS

    stack = YowStack(layers)
    stack.setProp(YowAuthenticationProtocolLayer.PROP_CREDENTIALS, CREDENTIALS)         #setting credentials
    stack.setProp(YowNetworkLayer.PROP_ENDPOINT, YowConstants.ENDPOINTS[0])    #whatsapp server address
    stack.setProp(YowCoderLayer.PROP_DOMAIN, YowConstants.DOMAIN)              
    stack.setProp(YowCoderLayer.PROP_RESOURCE, YowsupEnv.getCurrent().getResource())          #info about us as WhatsApp client

    stack.broadcastEvent(YowLayerEvent(YowNetworkLayer.EVENT_STATE_CONNECT))   #sending the connect signal

    stack.loop() #this is the program mainloop

Now make sure Yowsup is in your python path and execute

python run.py

Once it outputs "Logged In", send messages from your phone and watch them being echoed back! For a fully working example checkout /yowsup/demos/echoclient