52ec521ade
fixes #71
891 lines
28 KiB
Python
891 lines
28 KiB
Python
# use unicode encoding for all literals by default (for python2.x)
|
|
from __future__ import unicode_literals
|
|
|
|
__author__ = "Steffen Vogel"
|
|
__copyright__ = "Copyright 2015-2017, Steffen Vogel"
|
|
__license__ = "GPLv3"
|
|
__maintainer__ = "Steffen Vogel"
|
|
__email__ = "post@steffenvogel.de"
|
|
|
|
"""
|
|
This file is part of transWhat
|
|
|
|
transWhat is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
any later version.
|
|
|
|
transwhat is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with transWhat. If not, see <http://www.gnu.org/licenses/>.
|
|
"""
|
|
|
|
import logging
|
|
import os
|
|
|
|
from yowsup import env
|
|
from yowsup.env import S40YowsupEnv
|
|
from yowsup.stacks import YowStack
|
|
from yowsup.common import YowConstants
|
|
from yowsup.layers import YowLayerEvent, YowParallelLayer
|
|
from yowsup.layers.auth import AuthError
|
|
from yowsup.stacks import YowStack
|
|
from yowsup.stacks import YowStackBuilder
|
|
from yowsup.common import YowConstants
|
|
|
|
# Layers
|
|
from yowsup.layers.axolotl import AxolotlSendLayer, AxolotlControlLayer, AxolotlReceivelayer
|
|
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
|
|
|
|
class YowsupApp(object):
|
|
def __init__(self):
|
|
env.CURRENT_ENV = env.AndroidYowsupEnv()
|
|
|
|
layers = (YowsupAppLayer,
|
|
YowParallelLayer((YowAuthenticationProtocolLayer,
|
|
YowMessagesProtocolLayer,
|
|
YowReceiptProtocolLayer,
|
|
YowAckProtocolLayer,
|
|
YowMediaProtocolLayer,
|
|
YowIbProtocolLayer,
|
|
YowIqProtocolLayer,
|
|
YowNotificationsProtocolLayer,
|
|
YowContactsIqProtocolLayer,
|
|
YowChatstateProtocolLayer,
|
|
YowCallsProtocolLayer,
|
|
YowPrivacyProtocolLayer,
|
|
YowProfilesProtocolLayer,
|
|
YowGroupsProtocolLayer,
|
|
YowPresenceProtocolLayer)),
|
|
AxolotlControlLayer,
|
|
YowParallelLayer((AxolotlSendLayer, AxolotlReceivelayer)),
|
|
YowCoderLayer,
|
|
YowCryptLayer,
|
|
YowStanzaRegulator,
|
|
YowNetworkLayer
|
|
)
|
|
|
|
self.logger = logging.getLogger(self.__class__.__name__)
|
|
stackBuilder = YowStackBuilder()
|
|
|
|
self.stack = stackBuilder \
|
|
.pushDefaultLayers(True) \
|
|
.push(YowsupAppLayer) \
|
|
.build()
|
|
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(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
|
|
"""
|
|
self.logger.debug(u'Sending receipt to whatsapp: %s', [_id, _from, 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.encode('utf-8'), 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, onSuccess, onFailure)
|
|
else:
|
|
successFn = lambda filePath, jid, url: doSendFn(filePath, url.encode('ascii','ignore'), 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):
|
|
self.logger.error("Upload file %s to %s for %s failed!" % (filePath, url, jid))
|
|
|
|
def onUploadProgress(self, filePath, jid, url, progress):
|
|
self.logger.info("%s => %s, %d%% \r" % (os.path.basename(filePath), jid, progress))
|
|
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(entity.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" % unicode(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)
|