df305b8491
Picture data was stored in the session object. This means that if you send multiple pictures within a short period of time the last picture would overwrite the data of the other pictures. This would cause pictures to be lost. Now picture data is stored in closures
865 lines
27 KiB
Python
865 lines
27 KiB
Python
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_notifications.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_privacy.protocolentities import *
|
|
from yowsup.layers.protocol_receipts.protocolentities import *
|
|
from yowsup.layers.protocol_iq.protocolentities import *
|
|
from yowsup.layers.protocol_media.mediauploader import MediaUploader
|
|
from yowsup.layers.protocol_media.mediadownloader import MediaDownloader
|
|
|
|
|
|
# Registration
|
|
|
|
from yowsup.registration import WACodeRequest
|
|
from yowsup.registration import WARegRequest
|
|
|
|
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 downloadMedia(self, url, onSuccess = None, onFailure = None):
|
|
downloader = MediaDownloader(onSuccess, onFailure)
|
|
downloader.download(url)
|
|
|
|
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 sendImage(self, jid, path, caption = None, onSuccess = None, onFailure = None):
|
|
entity = RequestUploadIqProtocolEntity(RequestUploadIqProtocolEntity.MEDIA_TYPE_IMAGE, filePath=path)
|
|
successFn = lambda successEntity, originalEntity: self.onRequestUploadResult(jid, path, successEntity, originalEntity, caption, onSuccess, onFailure)
|
|
errorFn = lambda errorEntity, originalEntity: self.onRequestUploadError(jid, path, errorEntity, originalEntity)
|
|
|
|
self.sendIq(entity, successFn, errorFn)
|
|
|
|
def onRequestUploadResult(self, jid, filePath, resultRequestUploadIqProtocolEntity, requestUploadIqProtocolEntity, caption = None, onSuccess=None, onFailure=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, onSuccess, onFailure)
|
|
ownNumber = self.stack.getLayerInterface(YowAuthenticationProtocolLayer).getUsername(full=False)
|
|
mediaUploader = MediaUploader(jid, ownNumber, 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, onSuccess = None, onFailure = None):
|
|
entity = ImageDownloadableMediaMessageProtocolEntity.fromFilePath(filePath, url, ip, to, caption = caption)
|
|
self.sendEntity(entity)
|
|
#self.msgIDs[entity.getId()] = MsgIDs(self.imgMsgId, entity.getId())
|
|
if onSuccess is not None:
|
|
onSuccess(entity.getId())
|
|
return entity.getId()
|
|
|
|
|
|
def doSendAudio(self, filePath, url, to, ip = None, caption = None, onSuccess = None, onFailure = None):
|
|
entity = AudioDownloadableMediaMessageProtocolEntity.fromFilePath(filePath, url, ip, to)
|
|
self.sendEntity(entity)
|
|
#self.msgIDs[entity.getId()] = MsgIDs(self.imgMsgId, entity.getId())
|
|
if onSuccess is not None:
|
|
onSuccess(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
|
|
"""
|
|
self.logger.debug("Subscribing to Presence updates from %s", (phone_number))
|
|
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 leaveGroup(self, group):
|
|
"""
|
|
Permanently leave a WhatsApp group
|
|
|
|
Args:
|
|
- group: (str) the group id (e.g. 27831788123-144024456)
|
|
"""
|
|
entity = LeaveGroupsIqProtocolEntity([group + '@g.us'])
|
|
self.sendEntity(entity)
|
|
|
|
def setStatus(self, statusText):
|
|
"""
|
|
Send status to whatsapp
|
|
|
|
Args:
|
|
- statusTest: (str) Your whatsapp status
|
|
"""
|
|
iq = SetStatusIqProtocolEntity(statusText)
|
|
self.sendIq(iq)
|
|
|
|
def setProfilePicture(self, previewPicture, fullPicture = None):
|
|
"""
|
|
Requests profile picture of whatsapp user
|
|
Args:
|
|
- previewPicture: (bytes) The preview picture
|
|
- fullPicture: (bytes) The full profile picture
|
|
"""
|
|
if fullPicture == None:
|
|
fullPicture = previewPicture
|
|
ownJid = self.stack.getLayerInterface(YowAuthenticationProtocolLayer).getUsername(full = True)
|
|
iq = SetPictureIqProtocolEntity(ownJid, previewPicture, fullPicture)
|
|
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, success = None, failure = None):
|
|
"""
|
|
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
|
|
- success: (func) - Callback; Takes three arguments: existing numbers,
|
|
non-existing numbers, invalid numbers.
|
|
"""
|
|
mode = GetSyncIqProtocolEntity.MODE_DELTA if delta else GetSyncIqProtocolEntity.MODE_FULL
|
|
context = GetSyncIqProtocolEntity.CONTEXT_INTERACTIVE if interactive else GetSyncIqProtocolEntity.CONTEXT_REGISTRATION
|
|
# International contacts must be preceded by a plus. Other numbers are
|
|
# considered local.
|
|
contacts = ['+' + c for c in contacts]
|
|
iq = GetSyncIqProtocolEntity(contacts, mode, context)
|
|
def onSuccess(response, request):
|
|
# Remove leading plus
|
|
if success is not None:
|
|
existing = [s[1:] for s in response.inNumbers.keys()]
|
|
nonexisting = [s[1:] for s in response.outNumbers.keys()]
|
|
invalid = [s[1:] for s in response.invalidNumbers]
|
|
success(existing, nonexisting, invalid)
|
|
|
|
self.sendIq(iq, onSuccess = onSuccess, onError = failure)
|
|
|
|
def requestClientConfig(self, success = None, failure = None):
|
|
"""I'm not sure what this does, but it might be required on first login."""
|
|
iq = PushIqProtocolEntity()
|
|
self.sendIq(iq, onSuccess = success, onError = failure)
|
|
|
|
|
|
def requestPrivacyList(self, success = None, failure = None):
|
|
"""I'm not sure what this does, but it might be required on first login."""
|
|
iq = PrivacyListIqProtocolEntity()
|
|
self.sendIq(iq, onSuccess = success, onError = failure)
|
|
|
|
def requestServerProperties(self, success = None, failure = None):
|
|
"""I'm not sure what this does, but it might be required on first login."""
|
|
iq = PropsIqProtocolEntity()
|
|
self.sendIq(iq, onSuccess = success, onError = failure)
|
|
|
|
def requestStatuses(self, contacts, success = None, failure = None):
|
|
"""
|
|
Request the statuses of a number of users.
|
|
|
|
Args:
|
|
- contacts: ([str]) the phone numbers of users whose statuses you
|
|
wish to request
|
|
- success: (func) called when request is successful
|
|
- failure: (func) called when request has failed
|
|
"""
|
|
iq = GetStatusesIqProtocolEntity([c + '@s.whatsapp.net' for c in contacts])
|
|
def onSuccess(response, request):
|
|
if success is not None:
|
|
self.logger.debug("Received Statuses %s", response)
|
|
s = {}
|
|
for k, v in response.statuses.iteritems():
|
|
s[k.split('@')[0]] = v
|
|
success(s)
|
|
|
|
self.sendIq(iq, onSuccess = onSuccess, onError = failure)
|
|
|
|
|
|
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 requestSMSCode(self, countryCode, phoneNumber):
|
|
"""
|
|
Request an sms regitration code. WARNING: this function is blocking
|
|
|
|
Args:
|
|
countryCode: The country code of the phone you wish to register
|
|
phoneNumber: phoneNumber of the phone you wish to register without
|
|
the country code.
|
|
"""
|
|
request = WACodeRequest(countryCode, phoneNumber)
|
|
return request.send()
|
|
|
|
def requestPassword(self, countryCode, phoneNumber, smsCode):
|
|
"""
|
|
Request a password. WARNING: this function is blocking
|
|
|
|
Args:
|
|
countryCode: The country code of the phone you wish to register
|
|
phoneNumber: phoneNumber of the phone you wish to register without
|
|
the country code.
|
|
smsCode: The sms code that you asked for previously
|
|
"""
|
|
smsCode = smsCode.replace('-', '')
|
|
request = WARegRequest(countryCode, phoneNumber, smsCode)
|
|
return request.send()
|
|
|
|
|
|
|
|
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 onParticipantsRemovedFromGroup(self, group, participants):
|
|
"""Called when participants have been removed from a group
|
|
|
|
Args:
|
|
- group: (str) id of the group (e.g. 27831788123-144024456)
|
|
- participants: (list) jids of participants that are removed
|
|
"""
|
|
pass
|
|
|
|
def onSubjectChanged(self, group, subject, subjectOwner, timestamp):
|
|
"""Called when someone changes the grousp subject
|
|
|
|
Args:
|
|
- group: (str) id of the group (e.g. 27831788123-144024456)
|
|
- subject: (str) the new subject
|
|
- subjectOwner: (str) the number of the person who changed the subject
|
|
- timestamp: (str) time the subject was changed
|
|
"""
|
|
pass
|
|
|
|
def onContactStatusChanged(self, number, status):
|
|
"""Called when a contacts changes their status
|
|
|
|
Args:
|
|
number: (str) the number of the contact who changed their status
|
|
status: (str) the new status
|
|
"""
|
|
pass
|
|
|
|
def onContactPictureChanged(self, number):
|
|
"""Called when a contact changes their profile picture
|
|
Args
|
|
number: (str) the number of the contact who changed their picture
|
|
"""
|
|
pass
|
|
|
|
def onContactRemoved(self, number):
|
|
"""Called when a contact has been removed
|
|
|
|
Args:
|
|
number: (str) the number of the contact who has been removed
|
|
"""
|
|
pass
|
|
|
|
def onContactAdded(self, number, nick):
|
|
"""Called when a contact has been added
|
|
|
|
Args:
|
|
number: (str) contacts number
|
|
nick: (str) contacts nickname
|
|
"""
|
|
pass
|
|
|
|
def onContactUpdated(self, oldNumber, newNumber):
|
|
"""Called when a contact has changed their number
|
|
|
|
Args:
|
|
oldNumber: (str) the number the contact previously used
|
|
newNumber: (str) the new number of the contact
|
|
"""
|
|
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): %s", type(entity), entity)
|
|
self.toLower(entity.ack())
|
|
if isinstance(entity, CreateGroupsNotificationProtocolEntity):
|
|
self.caller.onAddedToGroup(entity)
|
|
elif isinstance(entity, AddGroupsNotificationProtocolEntity):
|
|
self.caller.onParticipantsAddedToGroup(entity)
|
|
elif isinstance(entity, RemoveGroupsNotificationProtocolEntity):
|
|
self.caller.onParticipantsRemovedFromGroup(
|
|
entity.getGroupId().split('@')[0],
|
|
entity.getParticipants().keys()
|
|
)
|
|
elif isinstance(entity, SubjectGroupsNotificationProtocolEntity):
|
|
self.caller.onSubjectChanged(
|
|
entity.getGroupId().split('@')[0],
|
|
entity.getSubject(),
|
|
entity.getSubjectOwner(full=False),
|
|
entity.getSubjectTimestamp()
|
|
)
|
|
elif isinstance(entity, StatusNotificationProtocolEntity):
|
|
self.caller.onContactStatusChanged(
|
|
entity._from.split('@')[0],
|
|
entity.status
|
|
)
|
|
elif isinstance(entity, SetPictureNotificationProtocolEntity):
|
|
self.caller.onContactPictureChanged(entity.setJid.split('@')[0])
|
|
elif isinstance(entity, DeletePictureNotificationProtocolEntity):
|
|
self.caller.onContactPictureChanged(entity.deleteJid.split('@')[0])
|
|
elif isinstance(entity, RemoveContactNotificationProtocolEntity):
|
|
self.caller.onContactRemoved(entity.contactJid.split('@')[0])
|
|
elif isinstance(entity, AddContactNotificationProtocolEntity):
|
|
self.caller.onContactAdded(
|
|
entity.contactJid.split('@')[0],
|
|
entity.notify
|
|
)
|
|
elif isinstance(entity, UpdateContactNotificationProtocolEntity):
|
|
self.caller.onContactUpdated(
|
|
entity._from.split('@')[0],
|
|
entity.contactJid.split('@')[0],
|
|
)
|
|
|
|
@ProtocolEntityCallback('message')
|
|
def onMessageReceived(self, entity):
|
|
self.logger.debug("Received Message: %s", 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)
|