from Spectrum2 import protocol_pb2 import logging import time import utils import base64 import hashlib import deferred from deferred import call def sha1hash(data): hashlib.sha1(data).hexdigest() 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 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) 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 _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") contacts = self.keys() contacts.remove('bot') self.session.sendSync(contacts, delta=False, interactive=True, success=self.onSync) self.logger.debug("Roster add: %s" % list(contacts)) for number in contacts: buddy = self[number] self.updateSpectrum(buddy) 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) 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) 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 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 = protocol_pb2.STATUS_NONE elif buddy.presence == 'unavailable': status = protocol_pb2.STATUS_AWAY else: status = 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) 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) def remove(self, number): try: buddy = self[number] del self[number] self.backend.handleBuddyChanged(self.user, number, "", [], 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 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) pictureData = response.pictureData() # Send VCard if ID != None: call(self.logger.debug, 'Sending VCard (%s) with image id %s: %s' % (ID, response.pictureId(), pictureData.then(base64.b64encode))) 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 = [] image_hash = pictureData.then(sha1hash) call(self.logger.debug, 'Image hash is %s' % image_hash) 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)