import logging from yowsup import env from yowsup.stacks import YowStack from yowsup.common import YowConstants from yowsup.layers import YowLayerEvent, YowParallelLayer from yowsup.layers.auth import AuthError # Layers from yowsup.layers.axolotl import YowAxolotlLayer from yowsup.layers.auth import YowCryptLayer, YowAuthenticationProtocolLayer from yowsup.layers.coder import YowCoderLayer from yowsup.layers.logger import YowLoggerLayer from yowsup.layers.network import YowNetworkLayer from yowsup.layers.protocol_messages import YowMessagesProtocolLayer from yowsup.layers.stanzaregulator import YowStanzaRegulator from yowsup.layers.protocol_media import YowMediaProtocolLayer from yowsup.layers.protocol_acks import YowAckProtocolLayer from yowsup.layers.protocol_receipts import YowReceiptProtocolLayer from yowsup.layers.protocol_groups import YowGroupsProtocolLayer from yowsup.layers.protocol_presence import YowPresenceProtocolLayer from yowsup.layers.protocol_ib import YowIbProtocolLayer from yowsup.layers.protocol_notifications import YowNotificationsProtocolLayer from yowsup.layers.protocol_iq import YowIqProtocolLayer from yowsup.layers.protocol_contacts import YowContactsIqProtocolLayer from yowsup.layers.protocol_chatstate import YowChatstateProtocolLayer from yowsup.layers.protocol_privacy import YowPrivacyProtocolLayer from yowsup.layers.protocol_profiles import YowProfilesProtocolLayer from yowsup.layers.protocol_calls import YowCallsProtocolLayer # ProtocolEntities from yowsup.layers.protocol_acks.protocolentities import * from yowsup.layers.protocol_chatstate.protocolentities import * from yowsup.layers.protocol_contacts.protocolentities import * from yowsup.layers.protocol_groups.protocolentities import * from yowsup.layers.protocol_media.protocolentities import * from yowsup.layers.protocol_messages.protocolentities import * from yowsup.layers.protocol_presence.protocolentities import * from yowsup.layers.protocol_profiles.protocolentities import * from yowsup.layers.protocol_receipts.protocolentities import * from yowsup.layers.protocol_media.mediauploader import MediaUploader from functools import partial #from session import MsgIDs class YowsupApp(object): def __init__(self): env.CURRENT_ENV = env.S40YowsupEnv() layers = (YowsupAppLayer, YowParallelLayer((YowAuthenticationProtocolLayer, YowMessagesProtocolLayer, YowReceiptProtocolLayer, YowAckProtocolLayer, YowMediaProtocolLayer, YowIbProtocolLayer, YowIqProtocolLayer, YowNotificationsProtocolLayer, YowContactsIqProtocolLayer, YowChatstateProtocolLayer, YowCallsProtocolLayer, YowPrivacyProtocolLayer, YowProfilesProtocolLayer, YowGroupsProtocolLayer, YowPresenceProtocolLayer)), YowAxolotlLayer, YowCoderLayer, YowCryptLayer, YowStanzaRegulator, YowNetworkLayer ) self.logger = logging.getLogger(self.__class__.__name__) self.stack = YowStack(layers) self.stack.broadcastEvent( YowLayerEvent(YowsupAppLayer.EVENT_START, caller = self) ) def login(self, username, password): """Login to yowsup Should result in onAuthSuccess or onAuthFailure to be called. Args: - username: (str) username in the form of 1239482382 (country code and cellphone number) - password: (str) base64 encoded password """ self.stack.setProp(YowAuthenticationProtocolLayer.PROP_CREDENTIALS, (username, password)) self.stack.setProp(YowNetworkLayer.PROP_ENDPOINT, YowConstants.ENDPOINTS[0]) self.stack.setProp(YowCoderLayer.PROP_DOMAIN, YowConstants.DOMAIN) self.stack.setProp(YowCoderLayer.PROP_RESOURCE, env.CURRENT_ENV.getResource()) # self.stack.setProp(YowIqProtocolLayer.PROP_PING_INTERVAL, 5) try: self.stack.broadcastEvent( YowLayerEvent(YowNetworkLayer.EVENT_STATE_CONNECT)) except TypeError as e: # Occurs when password is not correctly formated self.onAuthFailure('password not base64 encoded') # try: # self.stack.loop(timeout=0.5, discrete=0.5) # except AuthError as e: # For some reason Yowsup throws an exception # self.onAuthFailure("%s" % e) def logout(self): """ Logout from whatsapp """ self.stack.broadcastEvent(YowLayerEvent(YowNetworkLayer.EVENT_STATE_DISCONNECT)) def sendReceipt(self, _id, _from, read, participant): """ Send a receipt (delivered: double-tick, read: blue-ticks) Args: - _id: id of message received - _from: jid of person who sent the message - read: ('read' or None) None is just delivered, 'read' is read - participant """ receipt = OutgoingReceiptProtocolEntity(_id, _from, read, participant) self.sendEntity(receipt) def sendTextMessage(self, to, message): """ Sends a text message Args: - to: (xxxxxxxxxx@s.whatsapp.net) who to send the message to - message: (str) the body of the message """ messageEntity = TextMessageProtocolEntity(message, to = to) self.sendEntity(messageEntity) return messageEntity.getId() def sendLocation(self, to, latitude, longitude): messageEntity = LocationMediaMessageProtocolEntity(latitude,longitude, None, None, "raw", to = to) self.sendEntity(messageEntity) return messageEntity.getId() def image_send(self, jid, path, caption = None): entity = RequestUploadIqProtocolEntity(RequestUploadIqProtocolEntity.MEDIA_TYPE_IMAGE, filePath=path) successFn = lambda successEntity, originalEntity: self.onRequestUploadResult(jid, path, successEntity, originalEntity, caption) errorFn = lambda errorEntity, originalEntity: self.onRequestUploadError(jid, path, errorEntity, originalEntity) self.sendIq(entity, successFn, errorFn) def onRequestUploadResult(self, jid, filePath, resultRequestUploadIqProtocolEntity, requestUploadIqProtocolEntity, caption = None): if requestUploadIqProtocolEntity.mediaType == RequestUploadIqProtocolEntity.MEDIA_TYPE_AUDIO: doSendFn = self._doSendAudio else: doSendFn = self._doSendImage if resultRequestUploadIqProtocolEntity.isDuplicate(): doSendFn(filePath, resultRequestUploadIqProtocolEntity.getUrl(), jid, resultRequestUploadIqProtocolEntity.getIp(), caption) else: successFn = lambda filePath, jid, url: doSendFn(filePath, url, jid, resultRequestUploadIqProtocolEntity.getIp(), caption) mediaUploader = MediaUploader(jid, self.legacyName, filePath, resultRequestUploadIqProtocolEntity.getUrl(), resultRequestUploadIqProtocolEntity.getResumeOffset(), successFn, self.onUploadError, self.onUploadProgress, async=False) mediaUploader.start() def onRequestUploadError(self, jid, path, errorRequestUploadIqProtocolEntity, requestUploadIqProtocolEntity): self.logger.error("Request upload for file %s for %s failed" % (path, jid)) def onUploadError(self, filePath, jid, url): #logger.error("Upload file %s to %s for %s failed!" % (filePath, url, jid)) self.logger.error("Upload Error!") def onUploadProgress(self, filePath, jid, url, progress): #sys.stdout.write("%s => %s, %d%% \r" % (os.path.basename(filePath), jid, progress)) #sys.stdout.flush() pass def doSendImage(self, filePath, url, to, ip = None, caption = None): entity = ImageDownloadableMediaMessageProtocolEntity.fromFilePath(filePath, url, ip, to, caption = caption) self.sendEntity(entity) #self.msgIDs[entity.getId()] = MsgIDs(self.imgMsgId, entity.getId()) return entity.getId() def doSendAudio(self, filePath, url, to, ip = None, caption = None): entity = AudioDownloadableMediaMessageProtocolEntity.fromFilePath(filePath, url, ip, to) self.sendEntity(entity) #self.msgIDs[entity.getId()] = MsgIDs(self.imgMsgId, entity.getId()) return entity.getId() def sendPresence(self, available): """ Send presence to whatsapp Args: - available: (boolean) True if available false otherwise """ if available: self.sendEntity(AvailablePresenceProtocolEntity()) else: self.sendEntity(UnavailablePresenceProtocolEntity()) def subscribePresence(self, phone_number): """ Subscribe to presence updates from phone_number Args: - phone_number: (str) The cellphone number of the person to subscribe to """ jid = phone_number + '@s.whatsapp.net' entity = SubscribePresenceProtocolEntity(jid) self.sendEntity(entity) def unsubscribePresence(self, phone_number): """ Unsubscribe to presence updates from phone_number Args: - phone_number: (str) The cellphone number of the person to unsubscribe from """ jid = phone_number + '@s.whatsapp.net' entity = UnsubscribePresenceProtocolEntity(jid) self.sendEntity(entity) def setStatus(self, statusText): """ Send status to whatsapp Args: - statusTest: (str) Your whatsapp status """ iq = SetStatusIqProtocolEntity(statusText) self.sendIq(iq) def sendTyping(self, phoneNumber, typing): """ Notify buddy using phoneNumber that you are typing to him Args: - phoneNumber: (str) cellphone number of the buddy you are typing to. - typing: (bool) True if you are typing, False if you are not """ jid = phoneNumber + '@s.whatsapp.net' if typing: state = OutgoingChatstateProtocolEntity( ChatstateProtocolEntity.STATE_TYPING, jid ) else: state = OutgoingChatstateProtocolEntity( ChatstateProtocolEntity.STATE_PAUSED, jid ) self.sendEntity(state) def sendSync(self, contacts, delta = False, interactive = True): """ You need to sync new contacts before you interact with them, failure to do so could result in a temporary ban. Args: - contacts: ([str]) a list of phone numbers of the contacts you wish to sync - delta: (bool; default: False) If true only send new contacts to sync, if false you should send your full contact list. - interactive: (bool; default: True) Set to false if you are sure this is the first time registering """ # TODO: Implement callbacks mode = GetSyncIqProtocolEntity.MODE_DELTA if delta else GetSyncIqProtocolEntity.MODE_FULL context = GetSyncIqProtocolEntity.CONTEXT_INTERACTIVE if interactive else GetSyncIqProtocolEntity.CONTEXT_REGISTRATION iq = GetSyncIqProtocolEntity(contacts, mode, context) self.sendIq(iq) def requestLastSeen(self, phoneNumber, success = None, failure = None): """ Requests when user was last seen. Args: - phone_number: (str) the phone number of the user - success: (func) called when request is successfully processed. The first argument is the number, second argument is the seconds since last seen. - failure: (func) called when request has failed """ iq = LastseenIqProtocolEntity(phoneNumber + '@s.whatsapp.net') self.sendIq(iq, onSuccess = partial(self._lastSeenSuccess, success), onError = failure) def _lastSeenSuccess(self, success, response, request): success(response._from.split('@')[0], response.seconds) def requestProfilePicture(self, phoneNumber, onSuccess = None, onFailure = None): """ Requests profile picture of whatsapp user Args: - phoneNumber: (str) the phone number of the user - onSuccess: (func) called when request is successfully processed. - onFailure: (func) called when request has failed """ iq = GetPictureIqProtocolEntity(phoneNumber + '@s.whatsapp.net') self.sendIq(iq, onSuccess = onSuccess, onError = onFailure) def requestGroupsList(self, onSuccess = None, onFailure = None): iq = ListGroupsIqProtocolEntity() self.sendIq(iq, onSuccess = onSuccess, onError = onFailure) def requestGroupInfo(self, group, onSuccess = None, onFailure = None): """ Request info on a specific group (includes participants, subject, owner etc.) Args: - group: (str) the group id in the form of xxxxxxxxx-xxxxxxxx - onSuccess: (func) called when request is successfully processed. - onFailure: (func) called when request is has failed """ iq = InfoGroupsIqProtocolEntity(group + '@g.us') self.sendIq(iq, onSuccess = onSuccess, onError = onFailure) def onAuthSuccess(self, status, kind, creation, expiration, props, nonce, t): """ Called when login is successful. Args: - status - kind - creation - expiration - props - nonce - t """ pass def onAuthFailure(self, reason): """ Called when login is a failure Args: - reason: (str) Reason for the login failure """ pass def onReceipt(self, _id, _from, timestamp, type, participant, offline, items): """ Called when a receipt is received (double tick or blue tick) Args - _id - _from - timestamp - type: Is 'read' for blue ticks and None for double-ticks - participant: (dxxxxxxxxxx@s.whatsapp.net) delivered to or read by this participant in group - offline: (True, False or None) - items """ pass def onAck(self, _id,_class, _from, timestamp): """ Called when Ack is received Args: - _id - _class: ('message', 'receipt' or something else?) - _from - timestamp """ pass def onPresenceReceived(self, _type, name, _from, last): """ Called when presence (e.g. available, unavailable) is received from whatsapp Args: - _type: (str) 'available' or 'unavailable' - _name - _from - _last """ pass def onDisconnect(self): """ Called when disconnected from whatsapp """ def onContactTyping(self, number): """ Called when contact starts to type Args: - number: (str) cellphone number of contact """ pass def onContactPaused(self, number): """ Called when contact stops typing Args: - number: (str) cellphone number of contact """ pass def onTextMessage(self, _id, _from, to, notify, timestamp, participant, offline, retry, body): """ Called when text message is received Args: - _id: - _from: (str) jid of of sender - to: - notify: (str) human readable name of _from (e.g. John Smith) - timestamp: - participant: (str) jid of user who sent the message in a groupchat - offline: - retry: - body: The content of the message """ pass def onImage(self, entity): """ Called when image message is received Args: - entity: ImageDownloadableMediaMessageProtocolEntity """ pass def onAudio(self, entity): """ Called when audio message is received Args: - entity: AudioDownloadableMediaMessageProtocolEntity """ pass def onVideo(self, entity): """ Called when video message is received Args: - entity: VideoDownloadableMediaMessageProtocolEntity """ pass def onLocation(self, entity): """ Called when location message is received Args: - entity: LocationMediaMessageProtocolEntity """ pass def onVCard(self, _id, _from, name, card_data, to, notify, timestamp, participant): """ Called when VCard message is received Args: - _id: (str) id of entity - _from: - name: - card_data: - to: - notify: - timestamp: - participant: """ pass def onAddedToGroup(self, entity): """Called when the user has been added to a new group""" pass def onParticipantsAddedToGroup(self, entity): """Called when participants have been added to a group""" pass def sendEntity(self, entity): """Sends an entity down the stack (as if YowsupAppLayer called toLower)""" self.stack.broadcastEvent(YowLayerEvent(YowsupAppLayer.TO_LOWER_EVENT, entity = entity )) def sendIq(self, iq, onSuccess = None, onError = None): self.stack.broadcastEvent( YowLayerEvent( YowsupAppLayer.SEND_IQ, iq = iq, success = onSuccess, failure = onError, ) ) from yowsup.layers.interface import YowInterfaceLayer, ProtocolEntityCallback class YowsupAppLayer(YowInterfaceLayer): EVENT_START = 'transwhat.event.YowsupAppLayer.start' TO_LOWER_EVENT = 'transwhat.event.YowsupAppLayer.toLower' SEND_IQ = 'transwhat.event.YowsupAppLayer.sendIq' def onEvent(self, layerEvent): # We cannot pass instance varaibles in through init, so we use an event # instead # Return False if you want the event to propogate down the stack # return True otherwise if layerEvent.getName() == YowsupAppLayer.EVENT_START: self.caller = layerEvent.getArg('caller') self.logger = logging.getLogger(self.__class__.__name__) return True elif layerEvent.getName() == YowNetworkLayer.EVENT_STATE_DISCONNECTED: self.caller.onDisconnect() return True elif layerEvent.getName() == YowsupAppLayer.TO_LOWER_EVENT: self.toLower(layerEvent.getArg('entity')) return True elif layerEvent.getName() == YowsupAppLayer.SEND_IQ: iq = layerEvent.getArg('iq') success = layerEvent.getArg('success') failure = layerEvent.getArg('failure') self._sendIq(iq, success, failure) return True return False @ProtocolEntityCallback('success') def onAuthSuccess(self, entity): # entity is SuccessProtocolEntity status = entity.status kind = entity.kind creation = entity.creation expiration = entity.expiration props = entity.props nonce = entity.nonce t = entity.t # I don't know what this is self.caller.onAuthSuccess(status, kind, creation, expiration, props, nonce, t) @ProtocolEntityCallback('failure') def onAuthFailure(self, entity): # entity is FailureProtocolEntity reason = entity.reason self.caller.onAuthFailure(reason) @ProtocolEntityCallback('receipt') def onReceipt(self, entity): """Sends ack automatically""" # entity is IncomingReceiptProtocolEntity ack = OutgoingAckProtocolEntity(entity.getId(), 'receipt', entity.getType(), entity.getFrom()) self.toLower(ack) _id = entity._id _from = entity._from timestamp = entity.timestamp type = entity.type participant = entity.participant offline = entity.offline items = entity.items self.caller.onReceipt(_id, _from, timestamp, type, participant, offline, items) @ProtocolEntityCallback('ack') def onAck(self, entity): # entity is IncomingAckProtocolEntity self.caller.onAck( entity._id, entity._class, entity._from, entity.timestamp ) @ProtocolEntityCallback('notification') def onNotification(self, entity): """ Sends ack automatically """ self.logger.debug("Received notification: %s", entity) self.toLower(entity.ack()) if isinstance(entity, CreateGroupsNotificationProtocolEntity): self.caller.onAddedToGroup(entity) elif isinstance(entity, AddGroupsNotificationProtocolEntity): self.caller.onParticipantsAddedToGroup(entity) @ProtocolEntityCallback('message') def onMessageReceived(self, entity): if entity.getType() == MessageProtocolEntity.MESSAGE_TYPE_TEXT: self.caller.onTextMessage( entity._id, entity._from, entity.to, entity.notify, entity.timestamp, entity.participant, entity.offline, entity.retry, entity.body ) elif entity.getType() == MessageProtocolEntity.MESSAGE_TYPE_MEDIA: if isinstance(entity, ImageDownloadableMediaMessageProtocolEntity): # There is just way too many fields to pass them into the # function self.caller.onImage(entity) elif isinstance(entity, AudioDownloadableMediaMessageProtocolEntity): self.caller.onAudio(entity) elif isinstance(entity, VideoDownloadableMediaMessageProtocolEntity): self.caller.onVideo(entity) elif isinstance(entity, VCardMediaMessageProtocolEntity): self.caller.onVCard( entity._id, entity._from, entity.name, entity.card_data, entity.to, entity.notify, entity.timestamp, entity.participant ) elif isinstance(entity, LocationMediaMessageProtocolEntity): self.caller.onLocation(entity) @ProtocolEntityCallback('presence') def onPresenceReceived(self, presence): _type = presence.getType() name = presence.getName() _from = presence.getFrom() last = presence.getLast() self.caller.onPresenceReceived(_type, name, _from, last) @ProtocolEntityCallback('chatstate') def onChatstate(self, chatstate): number = chatstate._from.split('@')[0] if chatstate.getState() == ChatstateProtocolEntity.STATE_TYPING: self.caller.onContactTyping(number) else: self.caller.onContactPaused(number)