-
-
Notifications
You must be signed in to change notification settings - Fork 2.2k
Sample Application
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:
- 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.
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.
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)
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)
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)
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)
This is the code that will initialize the stack with the new EchoLayer on top.
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()\
.push(EchoLayer)\
.build()
stack.setCredentials(credentials)
stack.broadcastEvent(YowLayerEvent(YowNetworkLayer.EVENT_STATE_CONNECT)) #sending the connect signal
stack.loop() #this is the program mainloop
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 AxolotlControlLayer, AxolotlSendLayer, AxolotlReceivelayer
from yowsup.env import YowsupEnv
CREDENTIALS = ("phone", "password") # replace with your phone and password
if __name__== "__main__":
layers = (
EchoLayer,
YowParallelLayer([YowAuthenticationProtocolLayer, YowMessagesProtocolLayer, YowReceiptProtocolLayer,
YowAckProtocolLayer]),
AxolotlControlLayer,
YowParallelLayer((AxolotlSendLayer, AxolotlReceivelayer)),
) + 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