diff --git a/transWhat/bot.py b/transWhat/bot.py index 95cb058..f29ad3b 100644 --- a/transWhat/bot.py +++ b/transWhat/bot.py @@ -6,68 +6,68 @@ import time import os class Bot(): - def __init__(self, session, name = "Bot"): - self.session = session - self.name = name + def __init__(self, session, name = "Bot"): + self.session = session + self.name = name - self.commands = { - "help": self._help, - "groups": self._groups, - "getgroups": self._getgroups - } + self.commands = { + "help": self._help, + "groups": self._groups, + "getgroups": self._getgroups + } - def parse(self, message): - args = message.strip().split(" ") - cmd = args.pop(0) + def parse(self, message): + args = message.strip().split(" ") + cmd = args.pop(0) - if len(cmd) > 0 and cmd[0] == '\\': - try: - self.call(cmd[1:], args) - except KeyError: - self.send("invalid command") - except TypeError: - self.send("invalid syntax") - else: - self.send("a valid command starts with a backslash") + if len(cmd) > 0 and cmd[0] == '\\': + try: + self.call(cmd[1:], args) + except KeyError: + self.send("invalid command") + except TypeError: + self.send("invalid syntax") + else: + self.send("a valid command starts with a backslash") - def call(self, cmd, args = []): - func = self.commands[cmd.lower()] - spec = inspect.getargspec(func) - maxs = len(spec.args) - 1 - reqs = maxs - len(spec.defaults or []) - if (reqs > len(args)) or (len(args) > maxs): - raise TypeError() + def call(self, cmd, args = []): + func = self.commands[cmd.lower()] + spec = inspect.getargspec(func) + maxs = len(spec.args) - 1 + reqs = maxs - len(spec.defaults or []) + if (reqs > len(args)) or (len(args) > maxs): + raise TypeError() - thread = threading.Thread(target=func, args=tuple(args)) - thread.start() + thread = threading.Thread(target=func, args=tuple(args)) + thread.start() - def send(self, message): - self.session.backend.handleMessage(self.session.user, self.name, message) + def send(self, message): + self.session.backend.handleMessage(self.session.user, self.name, message) - # commands - def _help(self): - self.send("""following bot commands are available: -\\help show this message + # commands + def _help(self): + self.send("""following bot commands are available: +\\help show this message following user commands are available: -\\lastseen request last online timestamp from buddy +\\lastseen request last online timestamp from buddy following group commands are available -\\leave permanently leave group chat -\\groups print all attended groups -\\getgroups get current groups from WA""") +\\leave permanently leave group chat +\\groups print all attended groups +\\getgroups get current groups from WA""") - def _groups(self): - for group in self.session.groups: - buddy = self.session.groups[group].owner - try: - nick = self.session.buddies[buddy].nick - except KeyError: - nick = buddy + def _groups(self): + for group in self.session.groups: + buddy = self.session.groups[group].owner + try: + nick = self.session.buddies[buddy].nick + except KeyError: + nick = buddy - self.send(self.session.groups[group].id + "@" + self.session.backend.spectrum_jid + " " + self.session.groups[group].subject + " Owner: " + nick ) + self.send(self.session.groups[group].id + "@" + self.session.backend.spectrum_jid + " " + self.session.groups[group].subject + " Owner: " + nick ) - def _getgroups(self): - #self.session.call("group_getGroups", ("participating",)) - self.session.requestGroupsList(self.session._updateGroups) + def _getgroups(self): + #self.session.call("group_getGroups", ("participating",)) + self.session.requestGroupsList(self.session._updateGroups) diff --git a/transWhat/buddy.py b/transWhat/buddy.py index de012d1..f850669 100644 --- a/transWhat/buddy.py +++ b/transWhat/buddy.py @@ -7,207 +7,207 @@ import Spectrum2 from . import deferred class Buddy(): - def __init__(self, owner, number, nick, statusMsg, groups, image_hash): - self.nick = nick - self.owner = owner - self.number = "%s" % number - self.groups = groups - self.image_hash = image_hash if image_hash is not None else "" - self.statusMsg = u"" - self.lastseen = 0 - self.presence = 0 + def __init__(self, owner, number, nick, statusMsg, groups, image_hash): + self.nick = nick + self.owner = owner + self.number = "%s" % number + self.groups = groups + self.image_hash = image_hash if image_hash is not None else "" + self.statusMsg = u"" + self.lastseen = 0 + self.presence = 0 - def update(self, nick, groups, image_hash): - self.nick = nick - self.groups = groups - if image_hash is not None: - self.image_hash = image_hash + def update(self, nick, groups, image_hash): + self.nick = nick + self.groups = groups + if image_hash is not None: + self.image_hash = image_hash - def __str__(self): - # we must return str here - return str("%s (nick=%s)") % (self.number, self.nick) + def __str__(self): + # we must return str here + return str("%s (nick=%s)") % (self.number, self.nick) class BuddyList(dict): - def __init__(self, owner, backend, user, session): - self.owner = owner - self.backend = backend - self.session = session - self.user = user - self.logger = logging.getLogger(self.__class__.__name__) + def __init__(self, owner, backend, user, session): + self.owner = owner + self.backend = backend + self.session = session + self.user = user + self.logger = logging.getLogger(self.__class__.__name__) - def _load(self, buddies): - for buddy in buddies: - number = buddy.buddyName - nick = buddy.alias - statusMsg = buddy.statusMessage - groups = [g for g in buddy.group] - image_hash = buddy.iconHash - self[number] = Buddy(self.owner, number, nick, statusMsg, - groups, image_hash) + def _load(self, buddies): + for buddy in buddies: + number = buddy.buddyName + nick = buddy.alias + statusMsg = buddy.statusMessage + groups = [g for g in buddy.group] + image_hash = buddy.iconHash + self[number] = Buddy(self.owner, number, nick, statusMsg, + groups, image_hash) - self.logger.debug("Update roster") + self.logger.debug("Update roster") - contacts = self.keys() - contacts.remove('bot') + contacts = self.keys() + contacts.remove('bot') - self.session.sendSync(contacts, delta=False, interactive=True, - success=self.onSync) + self.session.sendSync(contacts, delta=False, interactive=True, + success=self.onSync) - self.logger.debug("Roster add: %s" % list(contacts)) + self.logger.debug("Roster add: %s" % list(contacts)) - for number in contacts: - buddy = self[number] - self.updateSpectrum(buddy) + for number in contacts: + buddy = self[number] + self.updateSpectrum(buddy) - def onSync(self, existing, nonexisting, invalid): - """We should only presence subscribe to existing numbers""" + def onSync(self, existing, nonexisting, invalid): + """We should only presence subscribe to existing numbers""" - for number in existing: - self.session.subscribePresence(number) - self.logger.debug("%s is requesting statuses of: %s" % (self.user, existing)) - self.session.requestStatuses(existing, success = self.onStatus) + for number in existing: + self.session.subscribePresence(number) + self.logger.debug("%s is requesting statuses of: %s" % (self.user, existing)) + self.session.requestStatuses(existing, success = self.onStatus) - self.logger.debug("Removing nonexisting buddies %s" % nonexisting) - for number in nonexisting: - self.remove(number) - try: del self[number] - except KeyError: self.logger.warn("non-existing buddy really didn't exist: %s" % number) + self.logger.debug("Removing nonexisting buddies %s" % nonexisting) + for number in nonexisting: + self.remove(number) + try: del self[number] + except KeyError: self.logger.warn("non-existing buddy really didn't exist: %s" % number) - self.logger.debug("Removing invalid buddies %s" % invalid) - for number in invalid: - self.remove(number) - try: del self[number] - except KeyError: self.logger.warn("non-existing buddy really didn't exist: %s" % number) + self.logger.debug("Removing invalid buddies %s" % invalid) + for number in invalid: + self.remove(number) + try: del self[number] + except KeyError: self.logger.warn("non-existing buddy really didn't exist: %s" % number) - def onStatus(self, contacts): - self.logger.debug("%s received statuses of: %s" % (self.user, contacts)) - for number, (status, time) in contacts.iteritems(): - try: buddy = self[number] - except KeyError: self.logger.warn("received status of buddy not in list: %s" % number) - if status is None: - buddy.statusMsg = "" - else: - buddy.statusMsg = status - self.updateSpectrum(buddy) + def onStatus(self, contacts): + self.logger.debug("%s received statuses of: %s" % (self.user, contacts)) + for number, (status, time) in contacts.iteritems(): + try: buddy = self[number] + except KeyError: self.logger.warn("received status of buddy not in list: %s" % number) + if status is None: + buddy.statusMsg = "" + else: + buddy.statusMsg = status + self.updateSpectrum(buddy) - def load(self, buddies): - if self.session.loggedIn: - self._load(buddies) - else: - self.session.loginQueue.append(lambda: self._load(buddies)) + def load(self, buddies): + if self.session.loggedIn: + self._load(buddies) + else: + self.session.loginQueue.append(lambda: self._load(buddies)) - def update(self, number, nick, groups, image_hash): - if number in self: - buddy = self[number] - buddy.update(nick, groups, image_hash) - else: - buddy = Buddy(self.owner, number, nick, "", groups, image_hash) - self[number] = buddy - self.logger.debug("Roster add: %s" % buddy) - self.session.sendSync([number], delta = True, interactive = True) - self.session.subscribePresence(number) - self.session.requestStatuses([number], success = self.onStatus) - if image_hash == "" or image_hash is None: - self.requestVCard(number) - self.updateSpectrum(buddy) - return buddy + def update(self, number, nick, groups, image_hash): + if number in self: + buddy = self[number] + buddy.update(nick, groups, image_hash) + else: + buddy = Buddy(self.owner, number, nick, "", groups, image_hash) + self[number] = buddy + self.logger.debug("Roster add: %s" % buddy) + self.session.sendSync([number], delta = True, interactive = True) + self.session.subscribePresence(number) + self.session.requestStatuses([number], success = self.onStatus) + if image_hash == "" or image_hash is None: + self.requestVCard(number) + self.updateSpectrum(buddy) + return buddy - def updateSpectrum(self, buddy): - if buddy.presence == 0: - status = Spectrum2.protocol_pb2.STATUS_NONE - elif buddy.presence == 'unavailable': - status = Spectrum2.protocol_pb2.STATUS_AWAY - else: - status = Spectrum2.protocol_pb2.STATUS_ONLINE + def updateSpectrum(self, buddy): + if buddy.presence == 0: + status = Spectrum2.protocol_pb2.STATUS_NONE + elif buddy.presence == 'unavailable': + status = Spectrum2.protocol_pb2.STATUS_AWAY + else: + status = Spectrum2.protocol_pb2.STATUS_ONLINE - statusmsg = buddy.statusMsg - if buddy.lastseen != 0: - timestamp = time.localtime(buddy.lastseen) - statusmsg += time.strftime("\n Last seen: %a, %d %b %Y %H:%M:%S", timestamp) + statusmsg = buddy.statusMsg + if buddy.lastseen != 0: + timestamp = time.localtime(buddy.lastseen) + statusmsg += time.strftime("\n Last seen: %a, %d %b %Y %H:%M:%S", timestamp) - iconHash = buddy.image_hash if buddy.image_hash is not None else "" + iconHash = buddy.image_hash if buddy.image_hash is not None else "" - self.logger.debug("Updating buddy %s (%s) in %s, image_hash = %s" % - (buddy.nick, buddy.number, buddy.groups, iconHash)) - self.logger.debug("Status Message: %s" % statusmsg) - self.backend.handleBuddyChanged(self.user, buddy.number, buddy.nick, - buddy.groups, status, statusMessage=statusmsg, iconHash=iconHash) + self.logger.debug("Updating buddy %s (%s) in %s, image_hash = %s" % + (buddy.nick, buddy.number, buddy.groups, iconHash)) + self.logger.debug("Status Message: %s" % statusmsg) + self.backend.handleBuddyChanged(self.user, buddy.number, buddy.nick, + buddy.groups, status, statusMessage=statusmsg, iconHash=iconHash) - def remove(self, number): - try: - buddy = self[number] - del self[number] - self.backend.handleBuddyChanged(self.user, number, "", [], - Spectrum2.protocol_pb2.STATUS_NONE) - self.backend.handleBuddyRemoved(self.user, number) - self.session.unsubscribePresence(number) - # TODO Sync remove - return buddy - except KeyError: - return None + def remove(self, number): + try: + buddy = self[number] + del self[number] + self.backend.handleBuddyChanged(self.user, number, "", [], + Spectrum2.protocol_pb2.STATUS_NONE) + self.backend.handleBuddyRemoved(self.user, number) + self.session.unsubscribePresence(number) + # TODO Sync remove + return buddy + except KeyError: + return None - def requestVCard(self, buddy, ID=None): - if "/" in buddy: - room, nick = buddy.split("/") - group = self.session.groups[room] - buddynr = None - for othernumber, othernick in group.participants.iteritems(): - if othernick == nick: - buddynr = othernumber - break - if buddynr is None: - return - else: - buddynr = buddy - + def requestVCard(self, buddy, ID=None): + if "/" in buddy: + room, nick = buddy.split("/") + group = self.session.groups[room] + buddynr = None + for othernumber, othernick in group.participants.iteritems(): + if othernick == nick: + buddynr = othernumber + break + if buddynr is None: + return + else: + buddynr = buddy + - if buddynr == self.user or buddynr == self.user.split('@')[0]: - buddynr = self.session.legacyName + if buddynr == self.user or buddynr == self.user.split('@')[0]: + buddynr = self.session.legacyName - # Get profile picture - self.logger.debug('Requesting profile picture of %s' % buddynr) - response = deferred.Deferred() - # Error probably means image doesn't exist - error = deferred.Deferred() - self.session.requestProfilePicture(buddynr, onSuccess=response.run, - onFailure=error.run) - response = response.arg(0) + # Get profile picture + self.logger.debug('Requesting profile picture of %s' % buddynr) + response = deferred.Deferred() + # Error probably means image doesn't exist + error = deferred.Deferred() + self.session.requestProfilePicture(buddynr, onSuccess=response.run, + onFailure=error.run) + response = response.arg(0) - pictureData = response.pictureData() - # Send VCard - if ID != None: - deferred.call(self.logger.debug, 'Sending VCard (%s) with image id %s: %s' % - (ID, response.pictureId(), pictureData.then(base64.b64encode))) - deferred.call(self.backend.handleVCard, self.user, ID, buddy, "", "", - pictureData) - # If error - error.when(self.logger.debug, 'Sending VCard (%s) without image' % ID) - error.when(self.backend.handleVCard, self.user, ID, buddy, "", "", "") + pictureData = response.pictureData() + # Send VCard + if ID != None: + deferred.call(self.logger.debug, 'Sending VCard (%s) with image id %s: %s' % + (ID, response.pictureId(), pictureData.then(base64.b64encode))) + deferred.call(self.backend.handleVCard, self.user, ID, buddy, "", "", + pictureData) + # If error + error.when(self.logger.debug, 'Sending VCard (%s) without image' % ID) + error.when(self.backend.handleVCard, self.user, ID, buddy, "", "", "") - # Send image hash - if not buddynr == self.session.legacyName: - try: - obuddy = self[buddynr] - nick = obuddy.nick - groups = obuddy.groups - except KeyError: - nick = "" - groups = [] + # Send image hash + if not buddynr == self.session.legacyName: + try: + obuddy = self[buddynr] + nick = obuddy.nick + groups = obuddy.groups + except KeyError: + nick = "" + groups = [] - def sha1hash(data): - hashlib.sha1(data).hexdigest() + def sha1hash(data): + hashlib.sha1(data).hexdigest() - image_hash = pictureData.then(sha1hash) + image_hash = pictureData.then(sha1hash) - deferred.call(self.logger.debug, 'Image hash is %s' % image_hash) - deferred.call(self.update, buddynr, nick, groups, image_hash) - # No image - error.when(self.logger.debug, 'No image') - error.when(self.update, buddynr, nick, groups, '') + deferred.call(self.logger.debug, 'Image hash is %s' % image_hash) + deferred.call(self.update, buddynr, nick, groups, image_hash) + # No image + error.when(self.logger.debug, 'No image') + error.when(self.update, buddynr, nick, groups, '') - def refresh(self, number): - self.session.unsubscribePresence(number) - self.session.subscribePresence(number) - self.requestVCard(number) - self.session.requestStatuses([number], success = self.onStatus) + def refresh(self, number): + self.session.unsubscribePresence(number) + self.session.subscribePresence(number) + self.requestVCard(number) + self.session.requestStatuses([number], success = self.onStatus) diff --git a/transWhat/deferred.py b/transWhat/deferred.py index e270c5d..692b905 100644 --- a/transWhat/deferred.py +++ b/transWhat/deferred.py @@ -1,139 +1,139 @@ from functools import partial class Deferred(object): - """ - Represents a delayed computation. This is a more elegant way to deal with - callbacks. + """ + Represents a delayed computation. This is a more elegant way to deal with + callbacks. - A Deferred object can be thought of as a computation whose value is yet to - be determined. We can manipulate the Deferred as if it where a regular - value by using the then method. Computations dependent on the Deferred will - only proceed when the run method is called. + A Deferred object can be thought of as a computation whose value is yet to + be determined. We can manipulate the Deferred as if it where a regular + value by using the then method. Computations dependent on the Deferred will + only proceed when the run method is called. - Attributes of a Deferred can be accessed directly as methods. The result of - calling these functions will be Deferred. + Attributes of a Deferred can be accessed directly as methods. The result of + calling these functions will be Deferred. - Example: - image = Deferred() - getImageWithCallback(image.run) - image.then(displayFunc) + Example: + image = Deferred() + getImageWithCallback(image.run) + image.then(displayFunc) - colors = Deferred() - colors.append('blue') - colors.then(print) - colors.run(['red', 'green']) #=> ['red', 'green', 'blue'] - """ + colors = Deferred() + colors.append('blue') + colors.then(print) + colors.run(['red', 'green']) #=> ['red', 'green', 'blue'] + """ - def __init__(self): - self.subscribers = [] - self.computed = False - self.args = None - self.kwargs = None + def __init__(self): + self.subscribers = [] + self.computed = False + self.args = None + self.kwargs = None - def run(self, *args, **kwargs): - """ - Give a value to the deferred. Calling this method more than once will - result in a DeferredHasValue exception to be raised. - """ - if self.computed: - raise DeferredHasValue("Deferred object already has a value.") - else: - self.args = args - self.kwargs = kwargs - for func, deferred in self.subscribers: - deferred.run(func(*args, **kwargs)) - self.computed = True + def run(self, *args, **kwargs): + """ + Give a value to the deferred. Calling this method more than once will + result in a DeferredHasValue exception to be raised. + """ + if self.computed: + raise DeferredHasValue("Deferred object already has a value.") + else: + self.args = args + self.kwargs = kwargs + for func, deferred in self.subscribers: + deferred.run(func(*args, **kwargs)) + self.computed = True - def then(self, func): - """ - Apply func to Deferred value. Returns a Deferred whose value will be - the result of applying func. - """ - result = Deferred() - if self.computed: - result.run(func(*self.args, **self.kwargs)) - else: - self.subscribers.append((func, result)) - return result + def then(self, func): + """ + Apply func to Deferred value. Returns a Deferred whose value will be + the result of applying func. + """ + result = Deferred() + if self.computed: + result.run(func(*self.args, **self.kwargs)) + else: + self.subscribers.append((func, result)) + return result - def arg(self, n): - """ - Returns the nth positional argument of a deferred as a deferred + def arg(self, n): + """ + Returns the nth positional argument of a deferred as a deferred - Args: - n - the index of the positional argument - """ - def helper(*args, **kwargs): - return args[n] - return self.then(helper) + Args: + n - the index of the positional argument + """ + def helper(*args, **kwargs): + return args[n] + return self.then(helper) - def when(self, func, *args, **kwargs): - """ Calls when func(*args, **kwargs) when deferred gets a value """ - def helper(*args2, **kwargs2): - func(*args, **kwargs) - return self.then(helper) + def when(self, func, *args, **kwargs): + """ Calls when func(*args, **kwargs) when deferred gets a value """ + def helper(*args2, **kwargs2): + func(*args, **kwargs) + return self.then(helper) - def __getattr__(self, method_name): - return getattr(Then(self), method_name) + def __getattr__(self, method_name): + return getattr(Then(self), method_name) class Then(object): - """ - Allows you to call methods on a Deferred. + """ + Allows you to call methods on a Deferred. - Example: - colors = Deferred() - Then(colors).append('blue') - colors.run(['red', 'green']) - colors.then(print) #=> ['red', 'green', 'blue'] - """ - def __init__(self, deferred): - self.deferred = deferred + Example: + colors = Deferred() + Then(colors).append('blue') + colors.run(['red', 'green']) + colors.then(print) #=> ['red', 'green', 'blue'] + """ + def __init__(self, deferred): + self.deferred = deferred - def __getattr__(self, name): - def tryCall(obj, *args, **kwargs): - if callable(obj): - return obj(*args, **kwargs) - else: - return obj - def helper(*args, **kwargs): - func = (lambda x: tryCall(getattr(x, name), *args, **kwargs)) - return self.deferred.then(func) - return helper + def __getattr__(self, name): + def tryCall(obj, *args, **kwargs): + if callable(obj): + return obj(*args, **kwargs) + else: + return obj + def helper(*args, **kwargs): + func = (lambda x: tryCall(getattr(x, name), *args, **kwargs)) + return self.deferred.then(func) + return helper def call(func, *args, **kwargs): - """ - Call a function with deferred arguments + """ + Call a function with deferred arguments - Example: - colors = Deferred() - colors.append('blue') - colors.run(['red', 'green']) - call(print, colors) #=> ['red', 'green', 'blue'] - call(print, 'hi', colors) #=> hi ['red', 'green', 'blue'] - """ - for i, c in enumerate(args): - if isinstance(c, Deferred): - # Function without deferred arguments - normalfunc = partial(func, *args[:i]) - # Function with deferred and possibly deferred arguments - def restfunc(*arg2, **kwarg2): - apply_deferred = partial(normalfunc, *arg2, **kwarg2) - return call(apply_deferred, *args[i + 1:], **kwargs) - return c.then(restfunc) - items = kwargs.items() - for i, (k, v) in enumerate(items): - if isinstance(v, Deferred): - # Function without deferred arguments - normalfunc = partial(func, *args, **dict(items[:i])) - # Function with deferred and possibly deferred arguments - def restfunc2(*arg2, **kwarg2): - apply_deferred = partial(normalfunc, *arg2, **kwarg2) - return call(apply_deferred, **dict(items[i + 1:])) - return v.then(restfunc2) - # No items deferred - return func(*args, **kwargs) + Example: + colors = Deferred() + colors.append('blue') + colors.run(['red', 'green']) + call(print, colors) #=> ['red', 'green', 'blue'] + call(print, 'hi', colors) #=> hi ['red', 'green', 'blue'] + """ + for i, c in enumerate(args): + if isinstance(c, Deferred): + # Function without deferred arguments + normalfunc = partial(func, *args[:i]) + # Function with deferred and possibly deferred arguments + def restfunc(*arg2, **kwarg2): + apply_deferred = partial(normalfunc, *arg2, **kwarg2) + return call(apply_deferred, *args[i + 1:], **kwargs) + return c.then(restfunc) + items = kwargs.items() + for i, (k, v) in enumerate(items): + if isinstance(v, Deferred): + # Function without deferred arguments + normalfunc = partial(func, *args, **dict(items[:i])) + # Function with deferred and possibly deferred arguments + def restfunc2(*arg2, **kwarg2): + apply_deferred = partial(normalfunc, *arg2, **kwarg2) + return call(apply_deferred, **dict(items[i + 1:])) + return v.then(restfunc2) + # No items deferred + return func(*args, **kwargs) class DeferredHasValue(Exception): - def __init__(self, string): - super(DeferredHasValue, self).__init__(string) + def __init__(self, string): + super(DeferredHasValue, self).__init__(string) diff --git a/transWhat/group.py b/transWhat/group.py index 271d050..29733ef 100644 --- a/transWhat/group.py +++ b/transWhat/group.py @@ -2,83 +2,83 @@ import Spectrum2 class Group(): - def __init__(self, id, owner, subject, subjectOwner, backend, user): - self.id = id - self.subject = subject - self.subjectOwner = subjectOwner - self.owner = owner - self.joined = False - self.backend = backend - self.user = user + def __init__(self, id, owner, subject, subjectOwner, backend, user): + self.id = id + self.subject = subject + self.subjectOwner = subjectOwner + self.owner = owner + self.joined = False + self.backend = backend + self.user = user - self.nick = "me" - # Participants is a number -> nickname dict - self.participants = {} + self.nick = "me" + # Participants is a number -> nickname dict + self.participants = {} - def addParticipants(self, participants, buddies, yourNumber): - """ - Adds participants to the group. + def addParticipants(self, participants, buddies, yourNumber): + """ + Adds participants to the group. - Args: - - participants: (Iterable) phone numbers of participants - - buddies: (dict) Used to get the nicknames of the participants - - yourNumber: The number you are using - """ - for jid in participants: - number = jid.split('@')[0] - try: - nick = buddies[number].nick - except KeyError: - nick = number - if number == yourNumber: - nick = self.nick - if nick == "": - nick = number - self.participants[number] = nick + Args: + - participants: (Iterable) phone numbers of participants + - buddies: (dict) Used to get the nicknames of the participants + - yourNumber: The number you are using + """ + for jid in participants: + number = jid.split('@')[0] + try: + nick = buddies[number].nick + except KeyError: + nick = number + if number == yourNumber: + nick = self.nick + if nick == "": + nick = number + self.participants[number] = nick - def sendParticipantsToSpectrum(self, yourNumber): - for number, nick in self.participants.iteritems(): - if number == self.owner: - flags = Spectrum2.protocol_pb2.PARTICIPANT_FLAG_MODERATOR - else: - flags = Spectrum2.protocol_pb2.PARTICIPANT_FLAG_NONE - if number == yourNumber: - flags = flags | Spectrum2.protocol_pb2.PARTICIPANT_FLAG_ME - - try: - self._updateParticipant(number, flags, Spectrum2.protocol_pb2.STATUS_ONLINE, - self.backend.sessions[self.user].buddies[number].image_hash) - except KeyError: - self._updateParticipant(number, flags, Spectrum2.protocol_pb2.STATUS_ONLINE) + def sendParticipantsToSpectrum(self, yourNumber): + for number, nick in self.participants.iteritems(): + if number == self.owner: + flags = Spectrum2.protocol_pb2.PARTICIPANT_FLAG_MODERATOR + else: + flags = Spectrum2.protocol_pb2.PARTICIPANT_FLAG_NONE + if number == yourNumber: + flags = flags | Spectrum2.protocol_pb2.PARTICIPANT_FLAG_ME + + try: + self._updateParticipant(number, flags, Spectrum2.protocol_pb2.STATUS_ONLINE, + self.backend.sessions[self.user].buddies[number].image_hash) + except KeyError: + self._updateParticipant(number, flags, Spectrum2.protocol_pb2.STATUS_ONLINE) - def removeParticipants(self, participants): - for jid in participants: - number = jid.split('@')[0] - nick = self.participants[number] - flags = Spectrum2.protocol_pb2.PARTICIPANT_FLAG_NONE - self._updateParticipant(number, flags, Spectrum2.protocol_pb2.STATUS_NONE) - del self.participants[number] + def removeParticipants(self, participants): + for jid in participants: + number = jid.split('@')[0] + nick = self.participants[number] + flags = Spectrum2.protocol_pb2.PARTICIPANT_FLAG_NONE + self._updateParticipant(number, flags, Spectrum2.protocol_pb2.STATUS_NONE) + del self.participants[number] - def leaveRoom(self): - for number in self.participants: - nick = self.participants[number] - flags = Spectrum2.protocol_pb2.PARTICIPANT_FLAG_ROOM_NOT_FOUND - self._updateParticipant(number, flags, Spectrum2.protocol_pb2.STATUS_NONE) + def leaveRoom(self): + for number in self.participants: + nick = self.participants[number] + flags = Spectrum2.protocol_pb2.PARTICIPANT_FLAG_ROOM_NOT_FOUND + self._updateParticipant(number, flags, Spectrum2.protocol_pb2.STATUS_NONE) - def changeNick(self, number, new_nick): - if self.participants[number] == new_nick: - return - if number == self.owner: - flags = Spectrum2.protocol_pb2.PARTICIPANT_FLAG_MODERATOR - else: - flags = Spectrum2.protocol_pb2.PARTICIPANT_FLAG_NONE - self._updateParticipant(number, flags, Spectrum2.protocol_pb2.STATUS_ONLINE, new_nick) - self.participants[number] = new_nick + def changeNick(self, number, new_nick): + if self.participants[number] == new_nick: + return + if number == self.owner: + flags = Spectrum2.protocol_pb2.PARTICIPANT_FLAG_MODERATOR + else: + flags = Spectrum2.protocol_pb2.PARTICIPANT_FLAG_NONE + self._updateParticipant(number, flags, Spectrum2.protocol_pb2.STATUS_ONLINE, new_nick) + self.participants[number] = new_nick - def _updateParticipant(self, number, flags, status, imageHash = "", newNick = ""): - nick = self.participants[number] - # Notice the status message is the buddy's number - if self.joined: - self.backend.handleParticipantChanged( - self.user, nick, self.id, flags, - status, number, newname = newNick, iconHash = imageHash) + def _updateParticipant(self, number, flags, status, imageHash = "", newNick = ""): + nick = self.participants[number] + # Notice the status message is the buddy's number + if self.joined: + self.backend.handleParticipantChanged( + self.user, nick, self.id, flags, + status, number, newname = newNick, iconHash = imageHash) diff --git a/transWhat/registersession.py b/transWhat/registersession.py index 74bfd8f..2d244bd 100644 --- a/transWhat/registersession.py +++ b/transWhat/registersession.py @@ -8,142 +8,142 @@ from . import threadutils class RegisterSession(YowsupApp): - """ - A dummy Session object that is used to register a user to whatsapp - """ - WANT_CC = 0 - WANT_SMS = 1 - def __init__(self, backend, user, legacyName, extra): - self.user = user - self.number = legacyName - self.backend = backend - self.countryCode = '' - self.logger = logging.getLogger(self.__class__.__name__) - self.state = self.WANT_CC + """ + A dummy Session object that is used to register a user to whatsapp + """ + WANT_CC = 0 + WANT_SMS = 1 + def __init__(self, backend, user, legacyName, extra): + self.user = user + self.number = legacyName + self.backend = backend + self.countryCode = '' + self.logger = logging.getLogger(self.__class__.__name__) + self.state = self.WANT_CC - def login(self, password=""): - self.backend.handleConnected(self.user) - self.backend.handleBuddyChanged(self.user, 'bot', 'bot', - ['Admin'], Spectrum2.protocol_pb2.STATUS_ONLINE) - self.backend.handleMessage(self.user, 'bot', - 'Please enter your country code') + def login(self, password=""): + self.backend.handleConnected(self.user) + self.backend.handleBuddyChanged(self.user, 'bot', 'bot', + ['Admin'], Spectrum2.protocol_pb2.STATUS_ONLINE) + self.backend.handleMessage(self.user, 'bot', + 'Please enter your country code') - def sendMessageToWA(self, buddy, message, ID='', xhtml=''): - if buddy == 'bot' and self.state == self.WANT_CC: - try: - country_code = int(message.strip()) - except ValueError: - self.backend.handleMessage(self.user, 'bot', - 'Country code must be a number') - else: # Succeded in decoding country code - country_code = "%s" % country_code - if country_code != self.number[:len(country_code)]: - self.backend.handleMessage(self.user, - 'bot', 'Number does not start with provided country code') - else: - self.backend.handleMessage(self.user, 'bot', 'Requesting sms code') - self.logger.debug('Requesting SMS code for %s' % self.user) - self.countryCode = country_code - self._requestSMSCodeNonBlock() - elif buddy == 'bot' and self.state == self.WANT_SMS: - code = message.strip() - if self._checkSMSFormat(code): - self._requestPassword(code) - else: - self.backend.handleMessage(self.user, - 'bot', 'Invalid code. Must be of the form XXX-XXX.') - else: - self.logger.warn('Unauthorised user (%s) attempting to send messages' % - self.user) - self.backend.handleMessage(self.user, buddy, - 'You are not logged in yet. You can only send messages to bot.') + def sendMessageToWA(self, buddy, message, ID='', xhtml=''): + if buddy == 'bot' and self.state == self.WANT_CC: + try: + country_code = int(message.strip()) + except ValueError: + self.backend.handleMessage(self.user, 'bot', + 'Country code must be a number') + else: # Succeded in decoding country code + country_code = "%s" % country_code + if country_code != self.number[:len(country_code)]: + self.backend.handleMessage(self.user, + 'bot', 'Number does not start with provided country code') + else: + self.backend.handleMessage(self.user, 'bot', 'Requesting sms code') + self.logger.debug('Requesting SMS code for %s' % self.user) + self.countryCode = country_code + self._requestSMSCodeNonBlock() + elif buddy == 'bot' and self.state == self.WANT_SMS: + code = message.strip() + if self._checkSMSFormat(code): + self._requestPassword(code) + else: + self.backend.handleMessage(self.user, + 'bot', 'Invalid code. Must be of the form XXX-XXX.') + else: + self.logger.warn('Unauthorised user (%s) attempting to send messages' % + self.user) + self.backend.handleMessage(self.user, buddy, + 'You are not logged in yet. You can only send messages to bot.') - def _checkSMSFormat(self, sms): - splitting = sms.split('-') - if len(splitting) != 2: - return False - a, b = splitting - if len(a) != 3 and len(b) != 3: - return False - try: - int(a) - int(b) - except ValueError: - return False - return True + def _checkSMSFormat(self, sms): + splitting = sms.split('-') + if len(splitting) != 2: + return False + a, b = splitting + if len(a) != 3 and len(b) != 3: + return False + try: + int(a) + int(b) + except ValueError: + return False + return True - def _requestSMSCodeNonBlock(self): - number = self.number[len(self.countryCode):] - threadFunc = lambda: self.requestSMSCode(self.countryCode, number) - threadutils.runInThread(threadFunc, self._confirmation) - self.backend.handleMessage(self.user, 'bot', 'SMS Code Sent') + def _requestSMSCodeNonBlock(self): + number = self.number[len(self.countryCode):] + threadFunc = lambda: self.requestSMSCode(self.countryCode, number) + threadutils.runInThread(threadFunc, self._confirmation) + self.backend.handleMessage(self.user, 'bot', 'SMS Code Sent') - def _confirmation(self, result): - self.state = self.WANT_SMS - resultStr = self._resultToString(result) - self.backend.handleMessage(self.user, 'bot', 'Response:') - self.backend.handleMessage(self.user, 'bot', resultStr) - self.backend.handleMessage(self.user, 'bot', 'Please enter SMS Code') + def _confirmation(self, result): + self.state = self.WANT_SMS + resultStr = self._resultToString(result) + self.backend.handleMessage(self.user, 'bot', 'Response:') + self.backend.handleMessage(self.user, 'bot', resultStr) + self.backend.handleMessage(self.user, 'bot', 'Please enter SMS Code') - def _requestPassword(self, smsCode): - cc = self.countryCode - number = self.number[len(cc):] - threadFunc = lambda: self.requestPassword(cc, number, smsCode) - threadutils.runInThread(threadFunc, self._gotPassword) - self.backend.handleMessage(self.user, 'bot', 'Getting Password') + def _requestPassword(self, smsCode): + cc = self.countryCode + number = self.number[len(cc):] + threadFunc = lambda: self.requestPassword(cc, number, smsCode) + threadutils.runInThread(threadFunc, self._gotPassword) + self.backend.handleMessage(self.user, 'bot', 'Getting Password') - def _gotPassword(self, result): - resultStr = self._resultToString(result) - self.backend.handleMessage(self.user, 'bot', 'Response:') - self.backend.handleMessage(self.user, 'bot', resultStr) - self.backend.handleMessage(self.user, 'bot', 'Logging you in') - password = result['pw'] - self.backend.relogin(self.user, self.number, password, None) + def _gotPassword(self, result): + resultStr = self._resultToString(result) + self.backend.handleMessage(self.user, 'bot', 'Response:') + self.backend.handleMessage(self.user, 'bot', resultStr) + self.backend.handleMessage(self.user, 'bot', 'Logging you in') + password = result['pw'] + self.backend.relogin(self.user, self.number, password, None) - def _resultToString(self, result): - unistr = str if sys.version_info >= (3, 0) else unicode - out = [] - for k, v in result.items(): - if v is None: - continue - out.append("%s: %s" % (k, v)) + def _resultToString(self, result): + unistr = str if sys.version_info >= (3, 0) else unicode + out = [] + for k, v in result.items(): + if v is None: + continue + out.append("%s: %s" % (k, v)) - return "\n".join(out) + return "\n".join(out) - # Dummy methods. Whatsapp backend might call these, but they should have no - # effect - def logout(self): - pass + # Dummy methods. Whatsapp backend might call these, but they should have no + # effect + def logout(self): + pass - def joinRoom(self, room, nickname): - pass + def joinRoom(self, room, nickname): + pass - def leaveRoom(self, room): - pass + def leaveRoom(self, room): + pass - def changeStatusMessage(self, statusMessage): - pass + def changeStatusMessage(self, statusMessage): + pass - def changeStatus(self, status): - pass + def changeStatus(self, status): + pass - def loadBuddies(self, buddies): - pass + def loadBuddies(self, buddies): + pass - def updateBuddy(self, buddies): - pass + def updateBuddy(self, buddies): + pass - def removeBuddy(self, buddies): - pass + def removeBuddy(self, buddies): + pass - def sendTypingStarted(self, buddy): - pass + def sendTypingStarted(self, buddy): + pass - def sendTypingStopped(self, buddy): - pass + def sendTypingStopped(self, buddy): + pass - def requestVCard(self, buddy, ID): - pass + def requestVCard(self, buddy, ID): + pass - def setProfilePicture(self, previewPicture, fullPicture = None): - pass + def setProfilePicture(self, previewPicture, fullPicture = None): + pass diff --git a/transWhat/session.py b/transWhat/session.py index d8f32d4..f07c512 100644 --- a/transWhat/session.py +++ b/transWhat/session.py @@ -15,824 +15,824 @@ from .bot import Bot from .yowsupwrapper import YowsupApp def ago(secs): - periods = ["second", "minute", "hour", "day", "week", "month", "year", "decade"] - lengths = [60, 60, 24, 7,4.35, 12, 10] + periods = ["second", "minute", "hour", "day", "week", "month", "year", "decade"] + lengths = [60, 60, 24, 7,4.35, 12, 10] - j = 0 - diff = secs + j = 0 + diff = secs - while diff >= lengths[j]: - diff /= lengths[j] - diff = round(diff) - j += 1 + while diff >= lengths[j]: + diff /= lengths[j] + diff = round(diff) + j += 1 - period = periods[j] - if diff > 1: period += "s" + period = periods[j] + if diff > 1: period += "s" - return "%d %s ago" % (diff, period) + return "%d %s ago" % (diff, period) class MsgIDs: - def __init__(self, xmppId, waId): - self.xmppId = xmppId - self.waId = waId - self.cnt = 0 + def __init__(self, xmppId, waId): + self.xmppId = xmppId + self.waId = waId + self.cnt = 0 class Session(YowsupApp): - broadcast_prefix = '\U0001F4E2 ' - - def __init__(self, backend, user, legacyName, extra): - super(Session, self).__init__() - self.logger = logging.getLogger(self.__class__.__name__) - self.logger.info("Created: %s" % legacyName) - - self.backend = backend - self.user = user - self.legacyName = legacyName - - self.status = Spectrum2.protocol_pb2.STATUS_NONE - self.statusMessage = '' - - self.groups = {} - self.gotGroupList = False - # Functions to exectute when logged in via yowsup - self.loginQueue = [] - self.joinRoomQueue = [] - self.presenceRequested = [] - self.offlineQueue = [] - self.msgIDs = { } - self.groupOfflineQueue = { } - self.loggedIn = False - self.recvMsgIDs = [] - - self.timer = None - self.password = None - self.initialized = False - self.lastMsgId = None - self.synced = False - - self.buddies = BuddyList(self.legacyName, self.backend, self.user, self) - self.bot = Bot(self) - - self.imgMsgId = None - self.imgPath = "" - self.imgBuddy = None - self.imgType = "" - - - def __del__(self): # handleLogoutRequest - self.logout() - - def logout(self): - self.logger.info("%s logged out" % self.user) - super(Session, self).logout() - self.loggedIn = False - - def login(self, password): - self.logger.info("%s attempting login" % self.user) - self.password = password - self.shouldBeConncted = True - super(Session, self).login(self.legacyName, self.password) - - def _shortenGroupId(self, gid): - # FIXME: might have problems if number begins with 0 - return gid -# return '-'.join(hex(int(s))[2:] for s in gid.split('-')) - - def _lengthenGroupId(self, gid): - return gid - # FIXME: might have problems if number begins with 0 -# return '-'.join(str(int(s, 16)) for s in gid.split('-')) - - def updateRoomList(self): - rooms = [] - text = [] - for room, group in self.groups.iteritems(): - rooms.append([self._shortenGroupId(room), group.subject]) - text.append(self._shortenGroupId(room) + '@' + self.backend.spectrum_jid + ' :' + group.subject) - - self.logger.debug("Got rooms: %s" % rooms) - self.backend.handleRoomList(rooms) - message = "Note, you are a participant of the following groups:\n" + \ - "\n".join(text) + "\nIf you do not join them you will lose messages" - #self.bot.send(message) - - def _updateGroups(self, response, _): - self.logger.debug('Received groups list %s' % response) - groups = response.getGroups() - for group in groups: - room = group.getId() - # ensure self.groups[room] exists - if room not in self.groups: - owner = group.getOwner().split('@')[0] - subjectOwner = group.getSubjectOwner().split('@')[0] - subject = group.getSubject() - self.groups[room] = Group(room, owner, subject, subjectOwner, - self.backend, self.user) - # add/update room participants - self.groups[room].addParticipants(group.getParticipants().keys(), - self.buddies, self.legacyName) - self.gotGroupList = True - # join rooms - while self.joinRoomQueue: - self.joinRoom(*self.joinRoomQueue.pop(0)) - # deliver queued offline messages - for room in self.groupOfflineQueue: - while self.groupOfflineQueue[room]: - msg = self.groupOfflineQueue[room].pop(0) - self.backend.handleMessage(self.user, room, msg[1], msg[0], "", - msg[2]) - self.logger.debug("Send queued group message to: %s %s %s" % - (msg[0], msg[1], msg[2])) - # pass update to backend - self.updateRoomList() - - def joinRoom(self, room, nick): - if not self.gotGroupList: - self.joinRoomQueue.append((room, nick)) - return - room = self._lengthenGroupId(room) - if room in self.groups: - self.logger.info("Joining room: %s room=%s, nick=%s" % - (self.legacyName, room, nick)) - - group = self.groups[room] - group.joined = True - group.nick = nick - group.participants[self.legacyName] = nick - try: - ownerNick = group.participants[group.subjectOwner] - except KeyError: - ownerNick = group.subjectOwner - - group.sendParticipantsToSpectrum(self.legacyName) - self.backend.handleSubject(self.user, self._shortenGroupId(room), - group.subject, ownerNick) - self.logger.debug("Room subject: room=%s, subject=%s" % - (room, group.subject)) - self.backend.handleRoomNicknameChanged( - self.user, self._shortenGroupId(room), group.subject - ) - else: - self.logger.warn("Room doesn't exist: %s" % room) - - def leaveRoom(self, room): - if room in self.groups: - self.logger.info("Leaving room: %s room=%s" % (self.legacyName, room)) - group = self.groups[room] - group.joined = False - else: - self.logger.warn("Room doesn't exist: %s. Unable to leave." % room) - - def _lastSeen(self, number, seconds): - self.logger.debug("Last seen %s at %s seconds" % (number, seconds)) - if seconds < 60: - self.onPresenceAvailable(number) - else: - self.onPresenceUnavailable(number) - def sendReadReceipts(self, buddy): - for _id, _from, participant, t in self.recvMsgIDs: - if _from.split('@')[0] == buddy: - self.sendReceipt(_id, _from, 'read', participant) - self.recvMsgIDs.remove((_id, _from, participant, t)) - self.logger.debug("Send read receipt to %s (ID: %s)", _from, _id) - - # Called by superclass - def onAuthSuccess(self, status, kind, creation, - expiration, props, nonce, t): - self.logger.info("Auth success: %s" % self.user) - - self.backend.handleConnected(self.user) - self.backend.handleBuddyChanged(self.user, "bot", self.bot.name, - ["Admin"], Spectrum2.protocol_pb2.STATUS_ONLINE) - # Initialisation? - self.requestPrivacyList() - self.requestClientConfig() - self.requestServerProperties() - # ? - - self.logger.debug('Requesting groups list') - self.requestGroupsList(self._updateGroups) - # self.requestBroadcastList() - - # This should handle, sync, statuses, and presence - self.sendPresence(True) - for func in self.loginQueue: - func() - - if self.initialized == False: - self.sendOfflineMessages() - #self.bot.call("welcome") - self.initialized = True - - self.loggedIn = True - - # Called by superclass - def onAuthFailed(self, reason): - self.logger.info("Auth failed: %s (%s)" % (self.user, reason)) - self.backend.handleDisconnected(self.user, 0, reason) - self.password = None - self.loggedIn = False - - # Called by superclass - def onDisconnect(self): - self.logger.debug('Disconnected') - self.backend.handleDisconnected(self.user, 0, 'Disconnected for unknown reasons') - - # Called by superclass - def onReceipt(self, _id, _from, timestamp, type, participant, offline, items): - self.logger.debug("received receipt, sending ack: %s" % - [ _id, _from, timestamp, type, participant, offline, items ] - ) - try: - number = _from.split('@')[0] - self.backend.handleMessageAck(self.user, number, self.msgIDs[_id].xmppId) - self.msgIDs[_id].cnt = self.msgIDs[_id].cnt + 1 - if self.msgIDs[_id].cnt == 2: - del self.msgIDs[_id] - except KeyError: - self.logger.error("Message %s not found. Unable to send ack" % _id) - - # Called by superclass - def onAck(self, _id, _class, _from, timestamp): - self.logger.debug('received ack: %s' % [ _id, _class, _from, timestamp ]) - - # Called by superclass - def onTextMessage(self, _id, _from, to, notify, timestamp, participant, - offline, retry, body): - buddy = _from.split('@')[0] - messageContent = body - self.sendReceipt(_id, _from, None, participant) - self.recvMsgIDs.append((_id, _from, participant, timestamp)) - self.logger.info("Message received from %s to %s: %s (at ts=%s)" % - (buddy, self.legacyName, messageContent, timestamp)) - - if participant is not None: # Group message or broadcast - partname = participant.split('@')[0] - if _from.split('@')[1] == 'broadcast': # Broadcast message - message = self.broadcast_prefix + messageContent - self.sendMessageToXMPP(partname, message, timestamp) - else: # Group message - if notify is None: - notify = "" - self.sendGroupMessageToXMPP(buddy, partname, messageContent, - timestamp, notify) - else: - self.sendMessageToXMPP(buddy, messageContent, timestamp) - - # Called by superclass - def onImage(self, image): - if image.caption is None: - image.caption = '' - - self.onMedia(image, "image") - - - # Called by superclass - def onAudio(self, audio): - self.onMedia(audio, "audio") - - - # Called by superclass - def onVideo(self, video): - self.onMedia(video, "video") - - - def onMedia(self, media, type): - self.logger.debug('Received %s message: %s' % (type, media)) - buddy = media._from.split('@')[0] - participant = media.participant - caption = '' - - if media.isEncrypted(): - self.logger.debug('Received encrypted media message') - if self.backend.specConf is not None and self.backend.specConf.__getitem__("service.web_directory") is not None and self.backend.specConf.__getitem__("service.web_url") is not None : - ipath = "/" + str(media.timestamp) + media.getExtension() - - with open(self.backend.specConf.__getitem__("service.web_directory") + ipath,"wb") as f: - f.write(media.getMediaContent()) - url = self.backend.specConf.__getitem__("service.web_url") + ipath - else: - self.logger.warn('Received encrypted media: web storage not set in config!') - url = media.url - - else: - url = media.url - - if type == 'image': - caption = media.caption - - if participant is not None: # Group message - partname = participant.split('@')[0] - if media._from.split('@')[1] == 'broadcast': # Broadcast message - self.sendMessageToXMPP(partname, self.broadcast_prefix, media.timestamp) - self.sendMessageToXMPP(partname, url, media.timestamp) - self.sendMessageToXMPP(partname, caption, media.timestamp) - else: # Group message - self.sendGroupMessageToXMPP(buddy, partname, url, media.timestamp) - self.sendGroupMessageToXMPP(buddy, partname, caption, media.timestamp) - else: - self.sendMessageToXMPP(buddy, url, media.timestamp) - self.sendMessageToXMPP(buddy, caption, media.timestamp) - - self.sendReceipt(media._id, media._from, None, media.participant) - self.recvMsgIDs.append((media._id, media._from, media.participant, media.timestamp)) - - def onLocation(self, location): - buddy = location._from.split('@')[0] - latitude = location.getLatitude() - longitude = location.getLongitude() - url = location.getLocationURL() - participant = location.participant - latlong = 'geo:' + latitude + ',' + longitude - - self.logger.debug("Location received from %s: %s, %s", (buddy, latitude, longitude)) - - if participant is not None: # Group message - partname = participant.split('@')[0] - if location._from.split('@')[1] == 'broadcast': # Broadcast message - self.sendMessageToXMPP(partname, self.broadcast_prefix, location.timestamp) - if url is not None: - self.sendMessageToXMPP(partname, url, location.timestamp) - self.sendMessageToXMPP(partname, latlong, location.timestamp) - else: # Group message - if url is not None: - self.sendGroupMessageToXMPP(buddy, partname, url, location.timestamp) - self.sendGroupMessageToXMPP(buddy, partname, latlong, location.timestamp) - else: - if url is not None: - self.sendMessageToXMPP(buddy, url, location.timestamp) - self.sendMessageToXMPP(buddy, latlong, location.timestamp) - self.sendReceipt(location._id, location._from, None, location.participant) - self.recvMsgIDs.append((location._id, location._from, location.participant, location.timestamp)) - - - - # Called by superclass - def onVCard(self, _id, _from, name, card_data, to, notify, timestamp, participant): - self.logger.debug('received VCard: %s' % - [ _id, _from, name, card_data, to, notify, timestamp, participant ] - ) - message = "Received VCard (not implemented yet)" - buddy = _from.split("@")[0] - if participant is not None: # Group message - partname = participant.split('@')[0] - if _from.split('@')[1] == 'broadcast': # Broadcast message - message = self.broadcast_prefix + message - self.sendMessageToXMPP(partname, message, timestamp) - else: # Group message - self.sendGroupMessageToXMPP(buddy, partname, message, timestamp) - else: - self.sendMessageToXMPP(buddy, message, timestamp) -# self.sendMessageToXMPP(buddy, card_data) - #self.transferFile(buddy, str(name), card_data) - self.sendReceipt(_id, _from, None, participant) - self.recvMsgIDs.append((_id, _from, participant, timestamp)) - - - def transferFile(self, buddy, name, data): - # Not working - self.logger.debug('transfering file: %s' % name) - self.backend.handleFTStart(self.user, buddy, name, len(data)) - self.backend.handleFTData(0, data) - self.backend.handleFTFinish(self.user, buddy, name, len(data), 0) - - # Called by superclass - def onContactTyping(self, buddy): - self.logger.info("Started typing: %s" % buddy) - if buddy != 'bot': - self.sendPresence(True) - self.backend.handleBuddyTyping(self.user, buddy) - - if self.timer != None: - self.timer.cancel() - - # Called by superclass - def onContactPaused(self, buddy): - self.logger.info("Paused typing: %s" % buddy) - if buddy != 'bot': - self.backend.handleBuddyTyped(self.user, buddy) - self.timer = threading.Timer(3, self.backend.handleBuddyStoppedTyping, - (self.user, buddy)).start() - - # Called by superclass - def onAddedToGroup(self, group): - self.logger.debug("Added to group: %s" % group) - room = group.getGroupId() - owner = group.getCreatorJid(full = False) - subjectOwner = group.getSubjectOwnerJid(full = False) - subject = group.getSubject() - - self.groups[room] = Group(room, owner, subject, subjectOwner, self.backend, self.user) - self.groups[room].addParticipants(group.getParticipants(), self.buddies, self.legacyName) - self.bot.send("You have been added to group: %s@%s (%s)" - % (self._shortenGroupId(room), subject, self.backend.spectrum_jid)) - - # Called by superclass - def onParticipantsAddedToGroup(self, group): - self.logger.debug("Participants added to group: %s" % group) - room = group.getGroupId().split('@')[0] - self.groups[room].addParticipants(group.getParticipants(), self.buddies, self.legacyName) - self.groups[room].sendParticipantsToSpectrum(self.legacyName) - - # Called by superclass - def onSubjectChanged(self, room, subject, subjectOwner, timestamp): - self.logger.debug( - "onSubjectChange(rrom=%s, subject=%s, subjectOwner=%s, timestamp=%s)" % - (room, subject, subjectOwner, timestamp) - ) - try: - group = self.groups[room] - except KeyError: - self.logger.error("Subject of non-existant group (%s) changed" % group) - else: - group.subject = subject - group.subjectOwner = subjectOwner - if not group.joined: - # We have not joined group so we should not send subject - return - self.backend.handleSubject(self.user, room, subject, subjectOwner) - self.backend.handleRoomNicknameChanged(self.user, room, subject) - - # Called by superclass - def onParticipantsRemovedFromGroup(self, room, participants): - self.logger.debug("Participants removed from group: %s, %s" % - (room, participants)) - self.groups[room].removeParticipants(participants) - - # Called by superclass - def onContactStatusChanged(self, number, status): - self.logger.debug("%s changed their status to %s" % (number, status)) - try: - buddy = self.buddies[number] - buddy.statusMsg = status - self.buddies.updateSpectrum(buddy) - except KeyError: - self.logger.debug("%s not in buddy list" % number) - - # Called by superclass - def onContactPictureChanged(self, number): - self.logger.debug("%s changed their profile picture" % number) - self.buddies.requestVCard(number) - - # Called by superclass - def onContactAdded(self, number, nick): - self.logger.debug("Adding new contact %s (%s)" % (nick, number)) - self.updateBuddy(number, nick, []) - - # Called by superclass - def onContactRemoved(self, number): - self.logger.debug("Removing contact %s" % number) - self.removeBuddy(number) - - def onContactUpdated(self, oldnumber, newnumber): - self.logger.debug("Contact has changed number from %s to %s" % - (oldnumber, newnumber)) - if newnumber in self.buddies: - self.logger.warn("Contact %s exists, just updating" % newnumber) - self.buddies.refresh(newnumber) - try: - buddy = self.buddies[oldnumber] - except KeyError: - self.logger.warn("Old contact (%s) not found. Adding new contact (%s)" % - (oldnumber, newnumber)) - nick = "" - else: - self.removeBuddy(buddy.number) - nick = buddy.nick - self.updateBuddy(newnumber, nick, []) - - def onPresenceReceived(self, _type, name, jid, lastseen): - self.logger.info("Presence received: %s %s %s %s" % (_type, name, jid, lastseen)) - buddy = jid.split("@")[0] - try: - buddy = self.buddies[buddy] - except KeyError: - # Sometimes whatsapp send our own presence - if buddy != self.legacyName: - self.logger.error("Buddy not found: %s" % buddy) - return - - if (lastseen == buddy.lastseen) and (_type == buddy.presence): - return - - if ((lastseen != "deny") and (lastseen != None) and (lastseen != "none")): - buddy.lastseen = int(lastseen) - if (_type == None): - buddy.lastseen = time.time() - - buddy.presence = _type - - if _type == "unavailable": - self.onPresenceUnavailable(buddy) - else: - self.onPresenceAvailable(buddy) - - def onPresenceAvailable(self, buddy): - self.logger.info("Is available: %s" % buddy) - self.buddies.updateSpectrum(buddy) - - def onPresenceUnavailable(self, buddy): - self.logger.info("Is unavailable: %s" % buddy) - self.buddies.updateSpectrum(buddy) - - # spectrum RequestMethods - def sendTypingStarted(self, buddy): - if buddy != "bot": - self.logger.info("Started typing: %s to %s" % (self.legacyName, buddy)) - self.sendTyping(buddy, True) - self.sendReadReceipts(buddy) - # If he is typing he is present - # I really don't know where else to put this. - # Ideally, this should be sent if the user is looking at his client - self.sendPresence(True) - - def sendTypingStopped(self, buddy): - if buddy != "bot": - self.logger.info("Stopped typing: %s to %s" % (self.legacyName, buddy)) - self.sendTyping(buddy, False) - self.sendReadReceipts(buddy) - - def sendImage(self, message, ID, to): - if (".jpg" in message.lower()): - imgType = "jpg" - if (".webp" in message.lower()): - imgType = "webp" - - success = deferred.Deferred() - error = deferred.Deferred() - self.downloadMedia(message, success.run, error.run) - - # Success - path = success.arg(0) - call(self.logger.info, "Success: Image downloaded to %s" % path) - pathWithExt = path.then(lambda p: p + "." + imgType) - call(os.rename, path, pathWithExt) - pathJpg = path.then(lambda p: p + ".jpg") - if imgType != "jpg": - im = call(Image.open, pathWithExt) - call(im.save, pathJpg) - call(os.remove, pathWithExt) - call(self.logger.info, "Sending image to %s" % to) - waId = deferred.Deferred() - call(super(Session, self).sendImage, to, pathJpg, onSuccess = waId.run) - call(self.setWaId, ID, waId) - waId.when(call, os.remove, pathJpg) - waId.when(self.logger.info, "Image sent") - - # Error - error.when(self.logger.info, "Download Error. Sending message as is.") - waId = error.when(self.sendTextMessage, to, message) - call(self.setWaId, ID, waId) - - def setWaId(self, XmppId, waId): - self.msgIDs[waId] = MsgIDs(XmppId, waId) - - def sendMessageToWA(self, sender, message, ID, xhtml=""): - self.logger.info("Message sent from %s to %s: %s (xhtml=%s)" % - (self.legacyName, sender, message, xhtml)) - - self.sendReadReceipts(sender) - - if sender == "bot": - self.bot.parse(message) - elif "-" in sender: # group msg - if "/" in sender: # directed at single user - room, nick = sender.split("/") - group = self.groups[room] - number = None - for othernumber, othernick in group.participants.iteritems(): - if othernick == nick: - number = othernumber - break - if number is not None: - self.logger.debug("Private message sent from %s to %s" % (self.legacyName, number)) - waId = self.sendTextMessage(number + '@s.whatsapp.net', message) - self.msgIDs[waId] = MsgIDs( ID, waId) - else: - self.logger.error("Attempted to send private message to non-existent user") - self.logger.debug("%s to %s in %s" % (self.legacyName, nick, room)) - else: - room = sender - if message[0] == '\\' and message[:1] != '\\\\': - self.logger.debug("Executing command %s in %s" % (message, room)) - self.executeCommand(message, room) - else: - try: - group = self.groups[self._lengthenGroupId(room)] - self.logger.debug("Group Message from %s to %s Groups: %s" % - (group.nick , group , self.groups)) - self.backend.handleMessage( - self.user, room, message, group.nick, xhtml=xhtml - ) - except KeyError: - self.logger.error('Group not found: %s' % room) - - if (".jpg" in message.lower()) or (".webp" in message.lower()): - self.sendImage(message, ID, room + '@g.us') - elif "geo:" in message.lower(): - self._sendLocation(room + "@g.us", message, ID) - else: - self.sendTextMessage(room + '@g.us', message) - else: # private msg - buddy = sender - if message.split(" ").pop(0) == "\\lastseen": - self.presenceRequested.append(buddy) - self._requestLastSeen(buddy) - elif message.split(" ").pop(0) == "\\gpp": - self.sendMessageToXMPP(buddy, "Fetching Profile Picture") - self.requestVCard(buddy) - elif (".jpg" in message.lower()) or (".webp" in message.lower()): - self.sendImage(message, ID, buddy + "@s.whatsapp.net") - elif "geo:" in message.lower(): - self._sendLocation(buddy + "@s.whatsapp.net", message, ID) - else: - waId = self.sendTextMessage(sender + '@s.whatsapp.net', message) - self.msgIDs[waId] = MsgIDs(ID, waId) - - # self.logger.info("WA Message send to %s with ID %s", buddy, waId) - - def executeCommand(self, command, room): - if command == '\\leave': - self.logger.debug("Leaving room %s", room) - self.leaveGroup(room) # Leave group on whatsapp side - group = self.groups[room] - group.leaveRoom() # Delete Room on spectrum side - del self.groups[room] - - def _requestLastSeen(self, buddy): - def onSuccess(buddy, lastseen): - timestamp = time.localtime(time.localtime()-lastseen) - timestring = time.strftime("%a, %d %b %Y %H:%M:%S", timestamp) - self.sendMessageToXMPP(buddy, "%s (%s) %s" % (timestring, ago(lastseen), str(lastseen))) - - def onError(errorIqEntity, originalIqEntity): - self.sendMessageToXMPP(errorIqEntity.getFrom(), "LastSeen Error") - - self.requestLastSeen(buddy, onSuccess, onError) - - def _sendLocation(self, buddy, message, ID): - latitude,longitude = message.split(':')[1].split(',') - waId = self.sendLocation(buddy, float(latitude), float(longitude)) - self.msgIDs[waId] = MsgIDs(ID, waId) - self.logger.info("WA Location Message send to %s with ID %s", buddy, waId) - - def sendMessageToXMPP(self, buddy, messageContent, timestamp = "", nickname = ""): - if timestamp: - timestamp = time.strftime("%Y%m%dT%H%M%S", time.gmtime(timestamp)) - - if self.initialized == False: - self.logger.debug("Message queued from %s to %s: %s" % - (buddy, self.legacyName, messageContent)) - self.offlineQueue.append((buddy, messageContent, timestamp)) - else: - self.logger.debug("Message sent from %s to %s: %s" % ( - buddy, self.legacyName, messageContent)) - self.backend.handleMessage(self.user, buddy, messageContent, "", - "", timestamp) - - def sendGroupMessageToXMPP(self, room, number, messageContent, timestamp = "", defaultname = ""): - if timestamp: - timestamp = time.strftime("%Y%m%dT%H%M%S", time.gmtime(timestamp)) - - if self.initialized == False: - self.logger.debug("Group message queued from %s to %s: %s" % - (number, room, messageContent)) - - if room not in self.groupOfflineQueue: - self.groupOfflineQueue[room] = [ ] - - self.groupOfflineQueue[room].append( - (number, messageContent, timestamp) - ) - else: - self.logger.debug("Group message sent from %s to %s: %s" % - (number, room, messageContent)) - try: - group = self.groups[room] - # Update nickname - try: - if defaultname != "" and group.participants[number] == number: - group.changeNick(number, defaultname) - if self.buddies[number].nick != "": - group.changeNick(number, self.buddies[number].nick) - except KeyError: - pass - nick = group.participants[number] - if group.joined: - self.backend.handleMessage(self.user, room, messageContent, - nick, "", timestamp) - else: - self.bot.send("You have received a message in group: %s@%s" - % (room, self.backend.spectrum_jid)) - self.bot.send("Join the group in order to reply") - self.bot.send("%s: %s" % (nick, messageContent)) - except KeyError: - self.logger.warn("Group is not in group list") - self.backend.handleMessage(self.user, self._shortenGroupId(room), - messageContent, number, "", timestamp) - - - def changeStatus(self, status): - if status != self.status: - self.logger.info("Status changed: %s" % status) - self.status = status - - if status == Spectrum2.protocol_pb2.STATUS_ONLINE \ - or status == Spectrum2.protocol_pb2.STATUS_FFC: - self.sendPresence(True) - else: - self.sendPresence(False) - - def changeStatusMessage(self, statusMessage): - if (statusMessage != self.statusMessage) or (self.initialized == False): - self.statusMessage = statusMessage - self.setStatus(statusMessage) - self.logger.info("Status message changed: %s" % statusMessage) - - #if self.initialized == False: - # self.sendOfflineMessages() - # self.bot.call("welcome") - # self.initialized = True - - def sendOfflineMessages(self): - # Flush Queues - while self.offlineQueue: - msg = self.offlineQueue.pop(0) - self.backend.handleMessage(self.user, msg[0], msg[1], "", "", msg[2]) - - # Called when user logs in to initialize the roster - def loadBuddies(self, buddies): - self.buddies.load(buddies) - - # also for adding a new buddy - def updateBuddy(self, buddy, nick, groups, image_hash = None): - if buddy != "bot": - self.buddies.update(buddy, nick, groups, image_hash) - - def removeBuddy(self, buddy): - if buddy != "bot": - self.logger.info("Buddy removed: %s" % buddy) - self.buddies.remove(buddy) - - def requestVCard(self, buddy, ID=None): - self.buddies.requestVCard(buddy, ID) - - def createThumb(self, size=100, raw=False): - img = Image.open(self.imgPath) - width, height = img.size - img_thumbnail = self.imgPath + '_thumbnail' - - if width > height: - nheight = float(height) / width * size - nwidth = size - else: - nwidth = float(width) / height * size - nheight = size - - img.thumbnail((nwidth, nheight), Image.ANTIALIAS) - img.save(img_thumbnail, 'JPEG') - - with open(img_thumbnail, 'rb') as imageFile: - raw = base64.b64encode(imageFile.read()) - - return raw - - # Not used - def onLocationReceived(self, messageId, jid, name, preview, latitude, longitude, receiptRequested, isBroadcast): - buddy = jid.split("@")[0] - self.logger.info("Location received from %s: %s, %s" % (buddy, latitude, longitude)) - - url = "http://maps.google.de?%s" % urllib.urlencode({ "q": "%s %s" % (latitude, longitude) }) - self.sendMessageToXMPP(buddy, utils.shorten(url)) - if receiptRequested: - self.call("message_ack", (jid, messageId)) - - def onGroupSubjectReceived(self, messageId, gjid, jid, subject, timestamp, receiptRequested): - room = gjid.split("@")[0] - buddy = jid.split("@")[0] - - self.backend.handleSubject(self.user, room, subject, buddy) - if receiptRequested: - self.call("subject_ack", (gjid, messageId)) - - # Yowsup Notifications - def onGroupParticipantRemoved(self, gjid, jid, author, timestamp, messageId, receiptRequested): - room = gjid.split("@")[0] - buddy = jid.split("@")[0] - - self.logger.info("Removed %s from room %s" % (buddy, room)) - - self.backend.handleParticipantChanged(self.user, buddy, room, Spectrum2.protocol_pb2.PARTICIPANT_FLAG_NONE, Spectrum2.protocol_pb2.STATUS_NONE) # TODO - - if receiptRequested: self.call("notification_ack", (gjid, messageId)) - - def onContactProfilePictureUpdated(self, jid, timestamp, messageId, pictureId, receiptRequested): - # TODO - if receiptRequested: - self.call("notification_ack", (jid, messageId)) - - def onGroupPictureUpdated(self, jid, author, timestamp, messageId, pictureId, receiptRequested): - # TODO - if receiptRequested: - self.call("notification_ack", (jid, messageId)) + broadcast_prefix = '\U0001F4E2 ' + + def __init__(self, backend, user, legacyName, extra): + super(Session, self).__init__() + self.logger = logging.getLogger(self.__class__.__name__) + self.logger.info("Created: %s" % legacyName) + + self.backend = backend + self.user = user + self.legacyName = legacyName + + self.status = Spectrum2.protocol_pb2.STATUS_NONE + self.statusMessage = '' + + self.groups = {} + self.gotGroupList = False + # Functions to exectute when logged in via yowsup + self.loginQueue = [] + self.joinRoomQueue = [] + self.presenceRequested = [] + self.offlineQueue = [] + self.msgIDs = { } + self.groupOfflineQueue = { } + self.loggedIn = False + self.recvMsgIDs = [] + + self.timer = None + self.password = None + self.initialized = False + self.lastMsgId = None + self.synced = False + + self.buddies = BuddyList(self.legacyName, self.backend, self.user, self) + self.bot = Bot(self) + + self.imgMsgId = None + self.imgPath = "" + self.imgBuddy = None + self.imgType = "" + + + def __del__(self): # handleLogoutRequest + self.logout() + + def logout(self): + self.logger.info("%s logged out" % self.user) + super(Session, self).logout() + self.loggedIn = False + + def login(self, password): + self.logger.info("%s attempting login" % self.user) + self.password = password + self.shouldBeConncted = True + super(Session, self).login(self.legacyName, self.password) + + def _shortenGroupId(self, gid): + # FIXME: might have problems if number begins with 0 + return gid +# return '-'.join(hex(int(s))[2:] for s in gid.split('-')) + + def _lengthenGroupId(self, gid): + return gid + # FIXME: might have problems if number begins with 0 +# return '-'.join(str(int(s, 16)) for s in gid.split('-')) + + def updateRoomList(self): + rooms = [] + text = [] + for room, group in self.groups.iteritems(): + rooms.append([self._shortenGroupId(room), group.subject]) + text.append(self._shortenGroupId(room) + '@' + self.backend.spectrum_jid + ' :' + group.subject) + + self.logger.debug("Got rooms: %s" % rooms) + self.backend.handleRoomList(rooms) + message = "Note, you are a participant of the following groups:\n" + \ + "\n".join(text) + "\nIf you do not join them you will lose messages" + #self.bot.send(message) + + def _updateGroups(self, response, _): + self.logger.debug('Received groups list %s' % response) + groups = response.getGroups() + for group in groups: + room = group.getId() + # ensure self.groups[room] exists + if room not in self.groups: + owner = group.getOwner().split('@')[0] + subjectOwner = group.getSubjectOwner().split('@')[0] + subject = group.getSubject() + self.groups[room] = Group(room, owner, subject, subjectOwner, + self.backend, self.user) + # add/update room participants + self.groups[room].addParticipants(group.getParticipants().keys(), + self.buddies, self.legacyName) + self.gotGroupList = True + # join rooms + while self.joinRoomQueue: + self.joinRoom(*self.joinRoomQueue.pop(0)) + # deliver queued offline messages + for room in self.groupOfflineQueue: + while self.groupOfflineQueue[room]: + msg = self.groupOfflineQueue[room].pop(0) + self.backend.handleMessage(self.user, room, msg[1], msg[0], "", + msg[2]) + self.logger.debug("Send queued group message to: %s %s %s" % + (msg[0], msg[1], msg[2])) + # pass update to backend + self.updateRoomList() + + def joinRoom(self, room, nick): + if not self.gotGroupList: + self.joinRoomQueue.append((room, nick)) + return + room = self._lengthenGroupId(room) + if room in self.groups: + self.logger.info("Joining room: %s room=%s, nick=%s" % + (self.legacyName, room, nick)) + + group = self.groups[room] + group.joined = True + group.nick = nick + group.participants[self.legacyName] = nick + try: + ownerNick = group.participants[group.subjectOwner] + except KeyError: + ownerNick = group.subjectOwner + + group.sendParticipantsToSpectrum(self.legacyName) + self.backend.handleSubject(self.user, self._shortenGroupId(room), + group.subject, ownerNick) + self.logger.debug("Room subject: room=%s, subject=%s" % + (room, group.subject)) + self.backend.handleRoomNicknameChanged( + self.user, self._shortenGroupId(room), group.subject + ) + else: + self.logger.warn("Room doesn't exist: %s" % room) + + def leaveRoom(self, room): + if room in self.groups: + self.logger.info("Leaving room: %s room=%s" % (self.legacyName, room)) + group = self.groups[room] + group.joined = False + else: + self.logger.warn("Room doesn't exist: %s. Unable to leave." % room) + + def _lastSeen(self, number, seconds): + self.logger.debug("Last seen %s at %s seconds" % (number, seconds)) + if seconds < 60: + self.onPresenceAvailable(number) + else: + self.onPresenceUnavailable(number) + def sendReadReceipts(self, buddy): + for _id, _from, participant, t in self.recvMsgIDs: + if _from.split('@')[0] == buddy: + self.sendReceipt(_id, _from, 'read', participant) + self.recvMsgIDs.remove((_id, _from, participant, t)) + self.logger.debug("Send read receipt to %s (ID: %s)", _from, _id) + + # Called by superclass + def onAuthSuccess(self, status, kind, creation, + expiration, props, nonce, t): + self.logger.info("Auth success: %s" % self.user) + + self.backend.handleConnected(self.user) + self.backend.handleBuddyChanged(self.user, "bot", self.bot.name, + ["Admin"], Spectrum2.protocol_pb2.STATUS_ONLINE) + # Initialisation? + self.requestPrivacyList() + self.requestClientConfig() + self.requestServerProperties() + # ? + + self.logger.debug('Requesting groups list') + self.requestGroupsList(self._updateGroups) + # self.requestBroadcastList() + + # This should handle, sync, statuses, and presence + self.sendPresence(True) + for func in self.loginQueue: + func() + + if self.initialized == False: + self.sendOfflineMessages() + #self.bot.call("welcome") + self.initialized = True + + self.loggedIn = True + + # Called by superclass + def onAuthFailed(self, reason): + self.logger.info("Auth failed: %s (%s)" % (self.user, reason)) + self.backend.handleDisconnected(self.user, 0, reason) + self.password = None + self.loggedIn = False + + # Called by superclass + def onDisconnect(self): + self.logger.debug('Disconnected') + self.backend.handleDisconnected(self.user, 0, 'Disconnected for unknown reasons') + + # Called by superclass + def onReceipt(self, _id, _from, timestamp, type, participant, offline, items): + self.logger.debug("received receipt, sending ack: %s" % + [ _id, _from, timestamp, type, participant, offline, items ] + ) + try: + number = _from.split('@')[0] + self.backend.handleMessageAck(self.user, number, self.msgIDs[_id].xmppId) + self.msgIDs[_id].cnt = self.msgIDs[_id].cnt + 1 + if self.msgIDs[_id].cnt == 2: + del self.msgIDs[_id] + except KeyError: + self.logger.error("Message %s not found. Unable to send ack" % _id) + + # Called by superclass + def onAck(self, _id, _class, _from, timestamp): + self.logger.debug('received ack: %s' % [ _id, _class, _from, timestamp ]) + + # Called by superclass + def onTextMessage(self, _id, _from, to, notify, timestamp, participant, + offline, retry, body): + buddy = _from.split('@')[0] + messageContent = body + self.sendReceipt(_id, _from, None, participant) + self.recvMsgIDs.append((_id, _from, participant, timestamp)) + self.logger.info("Message received from %s to %s: %s (at ts=%s)" % + (buddy, self.legacyName, messageContent, timestamp)) + + if participant is not None: # Group message or broadcast + partname = participant.split('@')[0] + if _from.split('@')[1] == 'broadcast': # Broadcast message + message = self.broadcast_prefix + messageContent + self.sendMessageToXMPP(partname, message, timestamp) + else: # Group message + if notify is None: + notify = "" + self.sendGroupMessageToXMPP(buddy, partname, messageContent, + timestamp, notify) + else: + self.sendMessageToXMPP(buddy, messageContent, timestamp) + + # Called by superclass + def onImage(self, image): + if image.caption is None: + image.caption = '' + + self.onMedia(image, "image") + + + # Called by superclass + def onAudio(self, audio): + self.onMedia(audio, "audio") + + + # Called by superclass + def onVideo(self, video): + self.onMedia(video, "video") + + + def onMedia(self, media, type): + self.logger.debug('Received %s message: %s' % (type, media)) + buddy = media._from.split('@')[0] + participant = media.participant + caption = '' + + if media.isEncrypted(): + self.logger.debug('Received encrypted media message') + if self.backend.specConf is not None and self.backend.specConf.__getitem__("service.web_directory") is not None and self.backend.specConf.__getitem__("service.web_url") is not None : + ipath = "/" + str(media.timestamp) + media.getExtension() + + with open(self.backend.specConf.__getitem__("service.web_directory") + ipath,"wb") as f: + f.write(media.getMediaContent()) + url = self.backend.specConf.__getitem__("service.web_url") + ipath + else: + self.logger.warn('Received encrypted media: web storage not set in config!') + url = media.url + + else: + url = media.url + + if type == 'image': + caption = media.caption + + if participant is not None: # Group message + partname = participant.split('@')[0] + if media._from.split('@')[1] == 'broadcast': # Broadcast message + self.sendMessageToXMPP(partname, self.broadcast_prefix, media.timestamp) + self.sendMessageToXMPP(partname, url, media.timestamp) + self.sendMessageToXMPP(partname, caption, media.timestamp) + else: # Group message + self.sendGroupMessageToXMPP(buddy, partname, url, media.timestamp) + self.sendGroupMessageToXMPP(buddy, partname, caption, media.timestamp) + else: + self.sendMessageToXMPP(buddy, url, media.timestamp) + self.sendMessageToXMPP(buddy, caption, media.timestamp) + + self.sendReceipt(media._id, media._from, None, media.participant) + self.recvMsgIDs.append((media._id, media._from, media.participant, media.timestamp)) + + def onLocation(self, location): + buddy = location._from.split('@')[0] + latitude = location.getLatitude() + longitude = location.getLongitude() + url = location.getLocationURL() + participant = location.participant + latlong = 'geo:' + latitude + ',' + longitude + + self.logger.debug("Location received from %s: %s, %s", (buddy, latitude, longitude)) + + if participant is not None: # Group message + partname = participant.split('@')[0] + if location._from.split('@')[1] == 'broadcast': # Broadcast message + self.sendMessageToXMPP(partname, self.broadcast_prefix, location.timestamp) + if url is not None: + self.sendMessageToXMPP(partname, url, location.timestamp) + self.sendMessageToXMPP(partname, latlong, location.timestamp) + else: # Group message + if url is not None: + self.sendGroupMessageToXMPP(buddy, partname, url, location.timestamp) + self.sendGroupMessageToXMPP(buddy, partname, latlong, location.timestamp) + else: + if url is not None: + self.sendMessageToXMPP(buddy, url, location.timestamp) + self.sendMessageToXMPP(buddy, latlong, location.timestamp) + self.sendReceipt(location._id, location._from, None, location.participant) + self.recvMsgIDs.append((location._id, location._from, location.participant, location.timestamp)) + + + + # Called by superclass + def onVCard(self, _id, _from, name, card_data, to, notify, timestamp, participant): + self.logger.debug('received VCard: %s' % + [ _id, _from, name, card_data, to, notify, timestamp, participant ] + ) + message = "Received VCard (not implemented yet)" + buddy = _from.split("@")[0] + if participant is not None: # Group message + partname = participant.split('@')[0] + if _from.split('@')[1] == 'broadcast': # Broadcast message + message = self.broadcast_prefix + message + self.sendMessageToXMPP(partname, message, timestamp) + else: # Group message + self.sendGroupMessageToXMPP(buddy, partname, message, timestamp) + else: + self.sendMessageToXMPP(buddy, message, timestamp) +# self.sendMessageToXMPP(buddy, card_data) + #self.transferFile(buddy, str(name), card_data) + self.sendReceipt(_id, _from, None, participant) + self.recvMsgIDs.append((_id, _from, participant, timestamp)) + + + def transferFile(self, buddy, name, data): + # Not working + self.logger.debug('transfering file: %s' % name) + self.backend.handleFTStart(self.user, buddy, name, len(data)) + self.backend.handleFTData(0, data) + self.backend.handleFTFinish(self.user, buddy, name, len(data), 0) + + # Called by superclass + def onContactTyping(self, buddy): + self.logger.info("Started typing: %s" % buddy) + if buddy != 'bot': + self.sendPresence(True) + self.backend.handleBuddyTyping(self.user, buddy) + + if self.timer != None: + self.timer.cancel() + + # Called by superclass + def onContactPaused(self, buddy): + self.logger.info("Paused typing: %s" % buddy) + if buddy != 'bot': + self.backend.handleBuddyTyped(self.user, buddy) + self.timer = threading.Timer(3, self.backend.handleBuddyStoppedTyping, + (self.user, buddy)).start() + + # Called by superclass + def onAddedToGroup(self, group): + self.logger.debug("Added to group: %s" % group) + room = group.getGroupId() + owner = group.getCreatorJid(full = False) + subjectOwner = group.getSubjectOwnerJid(full = False) + subject = group.getSubject() + + self.groups[room] = Group(room, owner, subject, subjectOwner, self.backend, self.user) + self.groups[room].addParticipants(group.getParticipants(), self.buddies, self.legacyName) + self.bot.send("You have been added to group: %s@%s (%s)" + % (self._shortenGroupId(room), subject, self.backend.spectrum_jid)) + + # Called by superclass + def onParticipantsAddedToGroup(self, group): + self.logger.debug("Participants added to group: %s" % group) + room = group.getGroupId().split('@')[0] + self.groups[room].addParticipants(group.getParticipants(), self.buddies, self.legacyName) + self.groups[room].sendParticipantsToSpectrum(self.legacyName) + + # Called by superclass + def onSubjectChanged(self, room, subject, subjectOwner, timestamp): + self.logger.debug( + "onSubjectChange(rrom=%s, subject=%s, subjectOwner=%s, timestamp=%s)" % + (room, subject, subjectOwner, timestamp) + ) + try: + group = self.groups[room] + except KeyError: + self.logger.error("Subject of non-existant group (%s) changed" % group) + else: + group.subject = subject + group.subjectOwner = subjectOwner + if not group.joined: + # We have not joined group so we should not send subject + return + self.backend.handleSubject(self.user, room, subject, subjectOwner) + self.backend.handleRoomNicknameChanged(self.user, room, subject) + + # Called by superclass + def onParticipantsRemovedFromGroup(self, room, participants): + self.logger.debug("Participants removed from group: %s, %s" % + (room, participants)) + self.groups[room].removeParticipants(participants) + + # Called by superclass + def onContactStatusChanged(self, number, status): + self.logger.debug("%s changed their status to %s" % (number, status)) + try: + buddy = self.buddies[number] + buddy.statusMsg = status + self.buddies.updateSpectrum(buddy) + except KeyError: + self.logger.debug("%s not in buddy list" % number) + + # Called by superclass + def onContactPictureChanged(self, number): + self.logger.debug("%s changed their profile picture" % number) + self.buddies.requestVCard(number) + + # Called by superclass + def onContactAdded(self, number, nick): + self.logger.debug("Adding new contact %s (%s)" % (nick, number)) + self.updateBuddy(number, nick, []) + + # Called by superclass + def onContactRemoved(self, number): + self.logger.debug("Removing contact %s" % number) + self.removeBuddy(number) + + def onContactUpdated(self, oldnumber, newnumber): + self.logger.debug("Contact has changed number from %s to %s" % + (oldnumber, newnumber)) + if newnumber in self.buddies: + self.logger.warn("Contact %s exists, just updating" % newnumber) + self.buddies.refresh(newnumber) + try: + buddy = self.buddies[oldnumber] + except KeyError: + self.logger.warn("Old contact (%s) not found. Adding new contact (%s)" % + (oldnumber, newnumber)) + nick = "" + else: + self.removeBuddy(buddy.number) + nick = buddy.nick + self.updateBuddy(newnumber, nick, []) + + def onPresenceReceived(self, _type, name, jid, lastseen): + self.logger.info("Presence received: %s %s %s %s" % (_type, name, jid, lastseen)) + buddy = jid.split("@")[0] + try: + buddy = self.buddies[buddy] + except KeyError: + # Sometimes whatsapp send our own presence + if buddy != self.legacyName: + self.logger.error("Buddy not found: %s" % buddy) + return + + if (lastseen == buddy.lastseen) and (_type == buddy.presence): + return + + if ((lastseen != "deny") and (lastseen != None) and (lastseen != "none")): + buddy.lastseen = int(lastseen) + if (_type == None): + buddy.lastseen = time.time() + + buddy.presence = _type + + if _type == "unavailable": + self.onPresenceUnavailable(buddy) + else: + self.onPresenceAvailable(buddy) + + def onPresenceAvailable(self, buddy): + self.logger.info("Is available: %s" % buddy) + self.buddies.updateSpectrum(buddy) + + def onPresenceUnavailable(self, buddy): + self.logger.info("Is unavailable: %s" % buddy) + self.buddies.updateSpectrum(buddy) + + # spectrum RequestMethods + def sendTypingStarted(self, buddy): + if buddy != "bot": + self.logger.info("Started typing: %s to %s" % (self.legacyName, buddy)) + self.sendTyping(buddy, True) + self.sendReadReceipts(buddy) + # If he is typing he is present + # I really don't know where else to put this. + # Ideally, this should be sent if the user is looking at his client + self.sendPresence(True) + + def sendTypingStopped(self, buddy): + if buddy != "bot": + self.logger.info("Stopped typing: %s to %s" % (self.legacyName, buddy)) + self.sendTyping(buddy, False) + self.sendReadReceipts(buddy) + + def sendImage(self, message, ID, to): + if (".jpg" in message.lower()): + imgType = "jpg" + if (".webp" in message.lower()): + imgType = "webp" + + success = deferred.Deferred() + error = deferred.Deferred() + self.downloadMedia(message, success.run, error.run) + + # Success + path = success.arg(0) + call(self.logger.info, "Success: Image downloaded to %s" % path) + pathWithExt = path.then(lambda p: p + "." + imgType) + call(os.rename, path, pathWithExt) + pathJpg = path.then(lambda p: p + ".jpg") + if imgType != "jpg": + im = call(Image.open, pathWithExt) + call(im.save, pathJpg) + call(os.remove, pathWithExt) + call(self.logger.info, "Sending image to %s" % to) + waId = deferred.Deferred() + call(super(Session, self).sendImage, to, pathJpg, onSuccess = waId.run) + call(self.setWaId, ID, waId) + waId.when(call, os.remove, pathJpg) + waId.when(self.logger.info, "Image sent") + + # Error + error.when(self.logger.info, "Download Error. Sending message as is.") + waId = error.when(self.sendTextMessage, to, message) + call(self.setWaId, ID, waId) + + def setWaId(self, XmppId, waId): + self.msgIDs[waId] = MsgIDs(XmppId, waId) + + def sendMessageToWA(self, sender, message, ID, xhtml=""): + self.logger.info("Message sent from %s to %s: %s (xhtml=%s)" % + (self.legacyName, sender, message, xhtml)) + + self.sendReadReceipts(sender) + + if sender == "bot": + self.bot.parse(message) + elif "-" in sender: # group msg + if "/" in sender: # directed at single user + room, nick = sender.split("/") + group = self.groups[room] + number = None + for othernumber, othernick in group.participants.iteritems(): + if othernick == nick: + number = othernumber + break + if number is not None: + self.logger.debug("Private message sent from %s to %s" % (self.legacyName, number)) + waId = self.sendTextMessage(number + '@s.whatsapp.net', message) + self.msgIDs[waId] = MsgIDs( ID, waId) + else: + self.logger.error("Attempted to send private message to non-existent user") + self.logger.debug("%s to %s in %s" % (self.legacyName, nick, room)) + else: + room = sender + if message[0] == '\\' and message[:1] != '\\\\': + self.logger.debug("Executing command %s in %s" % (message, room)) + self.executeCommand(message, room) + else: + try: + group = self.groups[self._lengthenGroupId(room)] + self.logger.debug("Group Message from %s to %s Groups: %s" % + (group.nick , group , self.groups)) + self.backend.handleMessage( + self.user, room, message, group.nick, xhtml=xhtml + ) + except KeyError: + self.logger.error('Group not found: %s' % room) + + if (".jpg" in message.lower()) or (".webp" in message.lower()): + self.sendImage(message, ID, room + '@g.us') + elif "geo:" in message.lower(): + self._sendLocation(room + "@g.us", message, ID) + else: + self.sendTextMessage(room + '@g.us', message) + else: # private msg + buddy = sender + if message.split(" ").pop(0) == "\\lastseen": + self.presenceRequested.append(buddy) + self._requestLastSeen(buddy) + elif message.split(" ").pop(0) == "\\gpp": + self.sendMessageToXMPP(buddy, "Fetching Profile Picture") + self.requestVCard(buddy) + elif (".jpg" in message.lower()) or (".webp" in message.lower()): + self.sendImage(message, ID, buddy + "@s.whatsapp.net") + elif "geo:" in message.lower(): + self._sendLocation(buddy + "@s.whatsapp.net", message, ID) + else: + waId = self.sendTextMessage(sender + '@s.whatsapp.net', message) + self.msgIDs[waId] = MsgIDs(ID, waId) + + # self.logger.info("WA Message send to %s with ID %s", buddy, waId) + + def executeCommand(self, command, room): + if command == '\\leave': + self.logger.debug("Leaving room %s", room) + self.leaveGroup(room) # Leave group on whatsapp side + group = self.groups[room] + group.leaveRoom() # Delete Room on spectrum side + del self.groups[room] + + def _requestLastSeen(self, buddy): + def onSuccess(buddy, lastseen): + timestamp = time.localtime(time.localtime()-lastseen) + timestring = time.strftime("%a, %d %b %Y %H:%M:%S", timestamp) + self.sendMessageToXMPP(buddy, "%s (%s) %s" % (timestring, ago(lastseen), str(lastseen))) + + def onError(errorIqEntity, originalIqEntity): + self.sendMessageToXMPP(errorIqEntity.getFrom(), "LastSeen Error") + + self.requestLastSeen(buddy, onSuccess, onError) + + def _sendLocation(self, buddy, message, ID): + latitude,longitude = message.split(':')[1].split(',') + waId = self.sendLocation(buddy, float(latitude), float(longitude)) + self.msgIDs[waId] = MsgIDs(ID, waId) + self.logger.info("WA Location Message send to %s with ID %s", buddy, waId) + + def sendMessageToXMPP(self, buddy, messageContent, timestamp = "", nickname = ""): + if timestamp: + timestamp = time.strftime("%Y%m%dT%H%M%S", time.gmtime(timestamp)) + + if self.initialized == False: + self.logger.debug("Message queued from %s to %s: %s" % + (buddy, self.legacyName, messageContent)) + self.offlineQueue.append((buddy, messageContent, timestamp)) + else: + self.logger.debug("Message sent from %s to %s: %s" % ( + buddy, self.legacyName, messageContent)) + self.backend.handleMessage(self.user, buddy, messageContent, "", + "", timestamp) + + def sendGroupMessageToXMPP(self, room, number, messageContent, timestamp = "", defaultname = ""): + if timestamp: + timestamp = time.strftime("%Y%m%dT%H%M%S", time.gmtime(timestamp)) + + if self.initialized == False: + self.logger.debug("Group message queued from %s to %s: %s" % + (number, room, messageContent)) + + if room not in self.groupOfflineQueue: + self.groupOfflineQueue[room] = [ ] + + self.groupOfflineQueue[room].append( + (number, messageContent, timestamp) + ) + else: + self.logger.debug("Group message sent from %s to %s: %s" % + (number, room, messageContent)) + try: + group = self.groups[room] + # Update nickname + try: + if defaultname != "" and group.participants[number] == number: + group.changeNick(number, defaultname) + if self.buddies[number].nick != "": + group.changeNick(number, self.buddies[number].nick) + except KeyError: + pass + nick = group.participants[number] + if group.joined: + self.backend.handleMessage(self.user, room, messageContent, + nick, "", timestamp) + else: + self.bot.send("You have received a message in group: %s@%s" + % (room, self.backend.spectrum_jid)) + self.bot.send("Join the group in order to reply") + self.bot.send("%s: %s" % (nick, messageContent)) + except KeyError: + self.logger.warn("Group is not in group list") + self.backend.handleMessage(self.user, self._shortenGroupId(room), + messageContent, number, "", timestamp) + + + def changeStatus(self, status): + if status != self.status: + self.logger.info("Status changed: %s" % status) + self.status = status + + if status == Spectrum2.protocol_pb2.STATUS_ONLINE \ + or status == Spectrum2.protocol_pb2.STATUS_FFC: + self.sendPresence(True) + else: + self.sendPresence(False) + + def changeStatusMessage(self, statusMessage): + if (statusMessage != self.statusMessage) or (self.initialized == False): + self.statusMessage = statusMessage + self.setStatus(statusMessage) + self.logger.info("Status message changed: %s" % statusMessage) + + #if self.initialized == False: + # self.sendOfflineMessages() + # self.bot.call("welcome") + # self.initialized = True + + def sendOfflineMessages(self): + # Flush Queues + while self.offlineQueue: + msg = self.offlineQueue.pop(0) + self.backend.handleMessage(self.user, msg[0], msg[1], "", "", msg[2]) + + # Called when user logs in to initialize the roster + def loadBuddies(self, buddies): + self.buddies.load(buddies) + + # also for adding a new buddy + def updateBuddy(self, buddy, nick, groups, image_hash = None): + if buddy != "bot": + self.buddies.update(buddy, nick, groups, image_hash) + + def removeBuddy(self, buddy): + if buddy != "bot": + self.logger.info("Buddy removed: %s" % buddy) + self.buddies.remove(buddy) + + def requestVCard(self, buddy, ID=None): + self.buddies.requestVCard(buddy, ID) + + def createThumb(self, size=100, raw=False): + img = Image.open(self.imgPath) + width, height = img.size + img_thumbnail = self.imgPath + '_thumbnail' + + if width > height: + nheight = float(height) / width * size + nwidth = size + else: + nwidth = float(width) / height * size + nheight = size + + img.thumbnail((nwidth, nheight), Image.ANTIALIAS) + img.save(img_thumbnail, 'JPEG') + + with open(img_thumbnail, 'rb') as imageFile: + raw = base64.b64encode(imageFile.read()) + + return raw + + # Not used + def onLocationReceived(self, messageId, jid, name, preview, latitude, longitude, receiptRequested, isBroadcast): + buddy = jid.split("@")[0] + self.logger.info("Location received from %s: %s, %s" % (buddy, latitude, longitude)) + + url = "http://maps.google.de?%s" % urllib.urlencode({ "q": "%s %s" % (latitude, longitude) }) + self.sendMessageToXMPP(buddy, utils.shorten(url)) + if receiptRequested: + self.call("message_ack", (jid, messageId)) + + def onGroupSubjectReceived(self, messageId, gjid, jid, subject, timestamp, receiptRequested): + room = gjid.split("@")[0] + buddy = jid.split("@")[0] + + self.backend.handleSubject(self.user, room, subject, buddy) + if receiptRequested: + self.call("subject_ack", (gjid, messageId)) + + # Yowsup Notifications + def onGroupParticipantRemoved(self, gjid, jid, author, timestamp, messageId, receiptRequested): + room = gjid.split("@")[0] + buddy = jid.split("@")[0] + + self.logger.info("Removed %s from room %s" % (buddy, room)) + + self.backend.handleParticipantChanged(self.user, buddy, room, Spectrum2.protocol_pb2.PARTICIPANT_FLAG_NONE, Spectrum2.protocol_pb2.STATUS_NONE) # TODO + + if receiptRequested: self.call("notification_ack", (gjid, messageId)) + + def onContactProfilePictureUpdated(self, jid, timestamp, messageId, pictureId, receiptRequested): + # TODO + if receiptRequested: + self.call("notification_ack", (jid, messageId)) + + def onGroupPictureUpdated(self, jid, author, timestamp, messageId, pictureId, receiptRequested): + # TODO + if receiptRequested: + self.call("notification_ack", (jid, messageId)) diff --git a/transWhat/threadutils.py b/transWhat/threadutils.py index 4053d05..a8ff461 100644 --- a/transWhat/threadutils.py +++ b/transWhat/threadutils.py @@ -5,16 +5,16 @@ import threading eventQueue = queue.Queue() def runInThread(threadFunc, callback): - """ - Executes threadFunc in a new thread. The result of threadFunc will be - pass as the first argument to callback. callback will be called in the main - thread. - """ - def helper(): - # Execute threadfunc in new thread - result = threadFunc() - # Queue callback to be call in main thread - eventQueue.put(lambda: callback(result)) + """ + Executes threadFunc in a new thread. The result of threadFunc will be + pass as the first argument to callback. callback will be called in the main + thread. + """ + def helper(): + # Execute threadfunc in new thread + result = threadFunc() + # Queue callback to be call in main thread + eventQueue.put(lambda: callback(result)) - thread = threading.Thread(target=helper) - thread.start() + thread = threading.Thread(target=helper) + thread.start() diff --git a/transWhat/transwhat.py b/transWhat/transwhat.py index cfb3915..ed3da79 100755 --- a/transWhat/transwhat.py +++ b/transWhat/transwhat.py @@ -21,8 +21,8 @@ parser.add_argument('--log', type=str) parser.add_argument('--host', type=str, required=True) parser.add_argument('--port', type=int, required=True) parser.add_argument('--service.backend_id', metavar="ID", type=int, required=True) -parser.add_argument('config', type=str) parser.add_argument('-j', type=str, metavar="JID", required=True) +parser.add_argument('config', type=str) args, unknown = parser.parse_known_args() @@ -75,14 +75,16 @@ def main(): try: asyncore.loop(timeout=1.0, count=10, use_poll = True) try: - callback = YowStack._YowStack__detachedQueue.get(False) #doesn't block + callback = YowStack._YowStack__detachedQueue.get(False) # doesn't block callback() except queue.Empty: pass else: break + if closed: break + while True: try: callback = threadutils.eventQueue.get_nowait() diff --git a/transWhat/whatsappbackend.py b/transWhat/whatsappbackend.py index 8133573..f26103e 100644 --- a/transWhat/whatsappbackend.py +++ b/transWhat/whatsappbackend.py @@ -5,140 +5,140 @@ from .session import Session from .registersession import RegisterSession class WhatsAppBackend(Spectrum2.Backend): - def __init__(self, io, spectrum_jid, specConf): - Spectrum2.Backend.__init__(self) - self.logger = logging.getLogger(self.__class__.__name__) - self.io = io - self.specConf = specConf - self.sessions = { } - self.spectrum_jid = spectrum_jid - # Used to prevent duplicate messages - self.lastMsgId = {} + def __init__(self, io, spectrum_jid, specConf): + Spectrum2.Backend.__init__(self) + self.logger = logging.getLogger(self.__class__.__name__) + self.io = io + self.specConf = specConf + self.sessions = { } + self.spectrum_jid = spectrum_jid + # Used to prevent duplicate messages + self.lastMsgId = {} - self.logger.debug("Backend started") + self.logger.debug("Backend started") - # RequestsHandlers - def handleLoginRequest(self, user, legacyName, password, extra): - self.logger.debug("handleLoginRequest(user=%s, legacyName=%s)" % (user, legacyName)) - # Key word means we should register a new password - if password == 'register': - if user not in self.sessions: - self.sessions[user] = RegisterSession(self, user, legacyName, extra) - else: - if user not in self.sessions: - self.sessions[user] = Session(self, user, legacyName, extra) + # RequestsHandlers + def handleLoginRequest(self, user, legacyName, password, extra): + self.logger.debug("handleLoginRequest(user=%s, legacyName=%s)" % (user, legacyName)) + # Key word means we should register a new password + if password == 'register': + if user not in self.sessions: + self.sessions[user] = RegisterSession(self, user, legacyName, extra) + else: + if user not in self.sessions: + self.sessions[user] = Session(self, user, legacyName, extra) - self.sessions[user].login(password) + self.sessions[user].login(password) - def handleLogoutRequest(self, user, legacyName): - self.logger.debug("handleLogoutRequest(user=%s, legacyName=%s)" % (user, legacyName)) - if user in self.sessions: - self.sessions[user].logout() - del self.sessions[user] + def handleLogoutRequest(self, user, legacyName): + self.logger.debug("handleLogoutRequest(user=%s, legacyName=%s)" % (user, legacyName)) + if user in self.sessions: + self.sessions[user].logout() + del self.sessions[user] - def handleMessageSendRequest(self, user, buddy, message, xhtml="", ID=""): - self.logger.debug("handleMessageSendRequest(user=%s, buddy=%s, message=%s, xhtml=%s, ID=%s)" % - ( user, buddy, message, xhtml, ID)) - # For some reason spectrum occasionally sends to identical messages to - # a buddy, one to the bare jid and one to the /bot resource. This - # causes duplicate messages. Thus we should not send consecutive - # messages with the same id - if ID == '': - self.sessions[user].sendMessageToWA(buddy, message, ID, xhtml) - elif user not in self.lastMsgId or self.lastMsgId[user] != ID: - self.sessions[user].sendMessageToWA(buddy, message, ID, xhtml) - self.lastMsgId[user] = ID + def handleMessageSendRequest(self, user, buddy, message, xhtml="", ID=""): + self.logger.debug("handleMessageSendRequest(user=%s, buddy=%s, message=%s, xhtml=%s, ID=%s)" % + ( user, buddy, message, xhtml, ID)) + # For some reason spectrum occasionally sends to identical messages to + # a buddy, one to the bare jid and one to the /bot resource. This + # causes duplicate messages. Thus we should not send consecutive + # messages with the same id + if ID == '': + self.sessions[user].sendMessageToWA(buddy, message, ID, xhtml) + elif user not in self.lastMsgId or self.lastMsgId[user] != ID: + self.sessions[user].sendMessageToWA(buddy, message, ID, xhtml) + self.lastMsgId[user] = ID - def handleJoinRoomRequest(self, user, room, nickname, pasword): - self.logger.debug("handleJoinRoomRequest(user=%s, room=%s, nickname=%s)" % (user, room, nickname)) - self.sessions[user].joinRoom(room, nickname) + def handleJoinRoomRequest(self, user, room, nickname, pasword): + self.logger.debug("handleJoinRoomRequest(user=%s, room=%s, nickname=%s)" % (user, room, nickname)) + self.sessions[user].joinRoom(room, nickname) - def handleLeaveRoomRequest(self, user, room): - self.logger.debug("handleLeaveRoomRequest(user=%s, room=%s)" % (user, room)) - self.sessions[user].leaveRoom(room) + def handleLeaveRoomRequest(self, user, room): + self.logger.debug("handleLeaveRoomRequest(user=%s, room=%s)" % (user, room)) + self.sessions[user].leaveRoom(room) - def handleStatusChangeRequest(self, user, status, statusMessage): - self.logger.debug("handleStatusChangeRequest(user=%s, status=%d, statusMessage=%s)" % (user, status, statusMessage)) - self.sessions[user].changeStatusMessage(statusMessage) - self.sessions[user].changeStatus(status) + def handleStatusChangeRequest(self, user, status, statusMessage): + self.logger.debug("handleStatusChangeRequest(user=%s, status=%d, statusMessage=%s)" % (user, status, statusMessage)) + self.sessions[user].changeStatusMessage(statusMessage) + self.sessions[user].changeStatus(status) - def handleBuddies(self, buddies): - """Called when user logs in. Used to initialize roster.""" - self.logger.debug("handleBuddies(buddies=%s)" % buddies) - buddies = [b for b in buddies.buddy] - if len(buddies) > 0: - user = buddies[0].userName - self.sessions[user].loadBuddies(buddies) + def handleBuddies(self, buddies): + """Called when user logs in. Used to initialize roster.""" + self.logger.debug("handleBuddies(buddies=%s)" % buddies) + buddies = [b for b in buddies.buddy] + if len(buddies) > 0: + user = buddies[0].userName + self.sessions[user].loadBuddies(buddies) - def handleBuddyUpdatedRequest(self, user, buddy, nick, groups): - self.logger.debug("handleBuddyUpdatedRequest(user=%s, buddy=%s, nick=%s, groups=%s)" % (user, buddy, nick, groups)) - self.sessions[user].updateBuddy(buddy, nick, groups) + def handleBuddyUpdatedRequest(self, user, buddy, nick, groups): + self.logger.debug("handleBuddyUpdatedRequest(user=%s, buddy=%s, nick=%s, groups=%s)" % (user, buddy, nick, groups)) + self.sessions[user].updateBuddy(buddy, nick, groups) - def handleBuddyRemovedRequest(self, user, buddy, groups): - self.logger.debug("handleBuddyRemovedRequest(user=%s, buddy=%s, groups=%s)" % (user, buddy, groups)) - self.sessions[user].removeBuddy(buddy) + def handleBuddyRemovedRequest(self, user, buddy, groups): + self.logger.debug("handleBuddyRemovedRequest(user=%s, buddy=%s, groups=%s)" % (user, buddy, groups)) + self.sessions[user].removeBuddy(buddy) - def handleTypingRequest(self, user, buddy): - self.logger.debug("handleTypingRequest(user=%s, buddy=%s)" % (user, buddy)) - self.sessions[user].sendTypingStarted(buddy) + def handleTypingRequest(self, user, buddy): + self.logger.debug("handleTypingRequest(user=%s, buddy=%s)" % (user, buddy)) + self.sessions[user].sendTypingStarted(buddy) - def handleTypedRequest(self, user, buddy): - self.logger.debug("handleTypedRequest(user=%s, buddy=%s)" % (user, buddy)) - self.sessions[user].sendTypingStopped(buddy) + def handleTypedRequest(self, user, buddy): + self.logger.debug("handleTypedRequest(user=%s, buddy=%s)" % (user, buddy)) + self.sessions[user].sendTypingStopped(buddy) - def handleStoppedTypingRequest(self, user, buddy): - self.logger.debug("handleStoppedTypingRequest(user=%s, buddy=%s)" % (user, buddy)) - self.sessions[user].sendTypingStopped(buddy) + def handleStoppedTypingRequest(self, user, buddy): + self.logger.debug("handleStoppedTypingRequest(user=%s, buddy=%s)" % (user, buddy)) + self.sessions[user].sendTypingStopped(buddy) - def handleVCardRequest(self, user, buddy, ID): - self.logger.debug("handleVCardRequest(user=%s, buddy=%s, ID=%s)" % (user, buddy, ID)) - self.sessions[user].requestVCard(buddy, ID) + def handleVCardRequest(self, user, buddy, ID): + self.logger.debug("handleVCardRequest(user=%s, buddy=%s, ID=%s)" % (user, buddy, ID)) + self.sessions[user].requestVCard(buddy, ID) - def handleVCardUpdatedRequest(self, user, photo, nickname): - self.logger.debug("handleVCardUpdatedRequest(user=%s, nickname=%s)" % (user, nickname)) - self.sessions[user].setProfilePicture(photo) + def handleVCardUpdatedRequest(self, user, photo, nickname): + self.logger.debug("handleVCardUpdatedRequest(user=%s, nickname=%s)" % (user, nickname)) + self.sessions[user].setProfilePicture(photo) - def handleBuddyBlockToggled(self, user, buddy, blocked): - self.logger.debug("handleBuddyBlockedToggled(user=%s, buddy=%s, blocked=%s)" % (user, buddy, blocked)) + def handleBuddyBlockToggled(self, user, buddy, blocked): + self.logger.debug("handleBuddyBlockedToggled(user=%s, buddy=%s, blocked=%s)" % (user, buddy, blocked)) - def relogin(self, user, legacyName, password, extra): - """ - Used to re-initialize the session object. Used when finished with - registration session and the user needs to login properly - """ - self.logger.debug("relogin(user=%s, legacyName=%s)" % (user, legacyName)) - # Change password in spectrum database - self.handleQuery('register %s %s %s' % (user, legacyName, password)) - # Key word means we should register a new password - if password == 'register': # This shouldn't happen, but just in case - self.sessions[user] = RegisterSession(self, user, legacyName, extra) - else: - self.sessions[user] = Session(self, user, legacyName, extra) - self.sessions[user].login(password) + def relogin(self, user, legacyName, password, extra): + """ + Used to re-initialize the session object. Used when finished with + registration session and the user needs to login properly + """ + self.logger.debug("relogin(user=%s, legacyName=%s)" % (user, legacyName)) + # Change password in spectrum database + self.handleQuery('register %s %s %s' % (user, legacyName, password)) + # Key word means we should register a new password + if password == 'register': # This shouldn't happen, but just in case + self.sessions[user] = RegisterSession(self, user, legacyName, extra) + else: + self.sessions[user] = Session(self, user, legacyName, extra) + self.sessions[user].login(password) - # TODO - def handleAttentionRequest(self, user, buddy, message): - pass + # TODO + def handleAttentionRequest(self, user, buddy, message): + pass - def handleFTStartRequest(self, user, buddy, fileName, size, ftID): - self.logger.debug('File send request %s, for user %s, from %s, size: %s' % - (fileName, user, buddy, size)) + def handleFTStartRequest(self, user, buddy, fileName, size, ftID): + self.logger.debug('File send request %s, for user %s, from %s, size: %s' % + (fileName, user, buddy, size)) - def handleFTFinishRequest(self, user, buddy, fileName, size, ftID): - pass + def handleFTFinishRequest(self, user, buddy, fileName, size, ftID): + pass - def handleFTPauseRequest(self, ftID): - pass + def handleFTPauseRequest(self, ftID): + pass - def handleFTContinueRequest(self, ftID): - pass + def handleFTContinueRequest(self, ftID): + pass - def handleRawXmlRequest(self, xml): - pass + def handleRawXmlRequest(self, xml): + pass - def handleMessageAckRequest(self, user, legacyName, ID = 0): - self.logger.info("Meassage ACK request for %s !!" % legacyName) + def handleMessageAckRequest(self, user, legacyName, ID = 0): + self.logger.info("Meassage ACK request for %s !!" % legacyName) - def sendData(self, data): - self.io.sendData(data) + def sendData(self, data): + self.io.sendData(data) diff --git a/transWhat/yowsupwrapper.py b/transWhat/yowsupwrapper.py index a96aa27..3f74708 100644 --- a/transWhat/yowsupwrapper.py +++ b/transWhat/yowsupwrapper.py @@ -8,26 +8,26 @@ from yowsup.common import YowConstants from yowsup.layers import YowLayerEvent, YowParallelLayer # Layers -from yowsup.layers.axolotl import AxolotlSendLayer, AxolotlControlLayer, AxolotlReceivelayer -from yowsup.layers.auth import 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.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 -from yowsup.layers.axolotl.props import PROP_IDENTITY_AUTOTRUST +from yowsup.layers.axolotl import AxolotlSendLayer, AxolotlControlLayer, AxolotlReceivelayer +from yowsup.layers.auth import 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.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 +from yowsup.layers.axolotl.props import PROP_IDENTITY_AUTOTRUST # ProtocolEntities from yowsup.layers.protocol_acks.protocolentities import * @@ -52,805 +52,805 @@ 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, - YowNetworkLayer - ) - - self.logger = logging.getLogger(self.__class__.__name__) - stackBuilder = YowStackBuilder() - - self.stack = stackBuilder \ - .pushDefaultLayers() \ - .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(PROP_IDENTITY_AUTOTRUST, True) -# 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, asynchronous=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, - ) - ) + 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, + YowNetworkLayer + ) + + self.logger = logging.getLogger(self.__class__.__name__) + stackBuilder = YowStackBuilder() + + self.stack = stackBuilder \ + .pushDefaultLayers() \ + .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(PROP_IDENTITY_AUTOTRUST, True) +# 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, asynchronous=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' + 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 + 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.location - 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('success') + def onAuthSuccess(self, entity): + # entity is SuccessProtocolEntity + status = entity.location + 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('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('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('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('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('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('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) + @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)