From 4fd313e516c31bb66116a3ca88ad0643eef4617c Mon Sep 17 00:00:00 2001 From: moyamo Date: Sun, 6 Sep 2015 00:14:12 +0200 Subject: [PATCH] Download whatsapp profile pictures Everytime the user requests a VCard the profile pictures are updated. The sha1hash hash of the picture is calculated and stored in buddy. --- buddy.py | 31 +++++++++++++++++-------------- conf/schema.sql | 1 + session.py | 19 +++++++++++++++++-- utils.py | 4 ++++ whatsappbackend.py | 7 ++++--- yowsupwrapper.py | 13 ++++++++++++- 6 files changed, 55 insertions(+), 20 deletions(-) diff --git a/buddy.py b/buddy.py index 923327f..1ab9944 100644 --- a/buddy.py +++ b/buddy.py @@ -48,7 +48,7 @@ class Number(): class Buddy(): - def __init__(self, owner, number, nick, groups, id, db): + def __init__(self, owner, number, nick, groups, image_hash, id, db): self.id = id self.db = db @@ -56,14 +56,16 @@ class Buddy(): self.owner = owner self.number = number self.groups = groups + self.image_hash = image_hash - def update(self, nick, groups): + def update(self, nick, groups, image_hash): self.nick = nick self.groups = groups + self.image_hash = image_hash groups = u",".join(groups).encode("latin-1") cur = self.db.cursor() - cur.execute("UPDATE buddies SET nick = %s, groups = %s WHERE owner_id = %s AND buddy_id = %s", (self.nick, groups, self.owner.id, self.number.id)) + cur.execute("UPDATE buddies SET nick = %s, groups = %s, image_hash = %s WHERE owner_id = %s AND buddy_id = %s", (self.nick, groups, image_hash, self.owner.id, self.number.id)) self.db.commit() def delete(self): @@ -73,13 +75,13 @@ class Buddy(): self.id = None @staticmethod - def create(owner, number, nick, groups, db): + def create(owner, number, nick, groups, image_hash, db): groups = u",".join(groups).encode("latin-1") cur = db.cursor() - cur.execute("REPLACE buddies (owner_id, buddy_id, nick, groups) VALUES (%s, %s, %s, %s)", (owner.id, number.id, nick, groups)) + cur.execute("REPLACE buddies (owner_id, buddy_id, nick, groups, image_hash) VALUES (%s, %s, %s, %s, %s)", (owner.id, number.id, nick, groups, image_hash)) db.commit() - return Buddy(owner, number, nick, groups, cur.lastrowid, db) + return Buddy(owner, number, nick, groups, image_hash, cur.lastrowid, db) def __str__(self): return "%s (nick=%s, id=%s)" % (self.number, self.nick, self.id) @@ -99,7 +101,8 @@ class BuddyList(dict): n.number AS number, b.nick AS nick, b.groups AS groups, - n.state AS state + n.state AS state, + b.image_hash AS image_hash FROM buddies AS b LEFT JOIN numbers AS n ON b.buddy_id = n.id @@ -109,20 +112,20 @@ class BuddyList(dict): ORDER BY b.owner_id DESC""", self.owner.id) for i in range(cur.rowcount): - id, number, nick, groups, state = cur.fetchone() - self[number] = Buddy(self.owner, Number(number, state, self.db), nick.decode('latin1'), groups.split(","), id, self.db) + id, number, nick, groups, state, image_hash = cur.fetchone() + self[number] = Buddy(self.owner, Number(number, state, self.db), nick.decode('latin1'), groups.split(","), image_hash, id, self.db) - def update(self, number, nick, groups): + def update(self, number, nick, groups, image_hash): if number in self: buddy = self[number] - buddy.update(nick, groups) + buddy.update(nick, groups, image_hash) else: - buddy = self.add(number, nick, groups, 1) + buddy = self.add(number, nick, groups, 1, image_hash) return buddy - def add(self, number, nick, groups = [], state = 0): - return Buddy.create(self.owner, Number(number, state, self.db), nick, groups, self.db) + def add(self, number, nick, groups = [], state = 0, image_hash = ""): + return Buddy.create(self.owner, Number(number, state, self.db), nick, groups, image_hash, self.db) def remove(self, number): try: diff --git a/conf/schema.sql b/conf/schema.sql index 32441ca..f6fb0e7 100644 --- a/conf/schema.sql +++ b/conf/schema.sql @@ -13,6 +13,7 @@ CREATE TABLE IF NOT EXISTS `buddies` ( `buddy_id` int(11) NOT NULL, `nick` varchar(255) NOT NULL, `groups` varchar(255) NOT NULL, + `image_hash` varchar(40), PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; diff --git a/session.py b/session.py index 63baa5b..6281a08 100644 --- a/session.py +++ b/session.py @@ -110,6 +110,7 @@ class Session(YowsupApp): for number in add: buddy = self.buddies[number] self.subscribePresence(number) + self.backend.handleBuddyChanged(self.user, number, buddy.nick, buddy.groups, protocol_pb2.STATUS_NONE, iconHash = buddy.image_hash if buddy.image_hash is not None else "") self.requestLastSeen(number, self._lastSeen) def _lastSeen(self, number, seconds): @@ -368,9 +369,9 @@ class Session(YowsupApp): self.backend.handleMessage(self.user, msg[0], msg[1], "", "", msg[2]) # also for adding a new buddy - def updateBuddy(self, buddy, nick, groups): + def updateBuddy(self, buddy, nick, groups, image_hash =""): if buddy != "bot": - self.buddies.update(buddy, nick, groups) + self.buddies.update(buddy, nick, groups, image_hash) self.updateRoster() def removeBuddy(self, buddy): @@ -391,6 +392,20 @@ class Session(YowsupApp): self.backend.handleSubject(self.user, room, group.subject, group.subjectOwner) else: self.logger.warn("Room doesn't exist: %s", room) + + def requestVCard(self, buddy, ID): + + def onSuccess(response, request): + self.logger.debug('Sending VCard (%s) with image id %s', + ID, response.pictureId) + image_hash = utils.sha1hash(response.pictureData) + self.logger.debug('Image hash is %s', image_hash) + self.backend.handleVCard(self.user, ID, buddy, "", "", response.pictureData) + obuddy = self.buddies[buddy] + self.updateBuddy(buddy, obuddy.nick, obuddy.groups, image_hash) + + self.logger.debug('Requesting profile picture of %s', buddy) + self.requestProfilePicture(buddy, onSuccess = onSuccess) # Not used def onLocationReceived(self, messageId, jid, name, preview, latitude, longitude, receiptRequested, isBroadcast): diff --git a/utils.py b/utils.py index 71d6f38..c2e1ba6 100644 --- a/utils.py +++ b/utils.py @@ -26,6 +26,7 @@ import urllib import json import e4u import base64 +import hashlib def shorten(url): url = urllib.urlopen("http://d.0l.de/add.json?type=URL&rdata=%s" % urllib.quote(url)) @@ -61,3 +62,6 @@ def softToUni(message): def decodePassword(password): return base64.b64decode(bytes(password.encode("utf-8"))) + +def sha1hash(data): + return hashlib.sha1(data).hexdigest() diff --git a/whatsappbackend.py b/whatsappbackend.py index 95a0ae1..b7b9827 100644 --- a/whatsappbackend.py +++ b/whatsappbackend.py @@ -101,6 +101,10 @@ class WhatsAppBackend(SpectrumBackend): 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) + # TODO def handleBuddyBlockToggled(self, user, buddy, blocked): pass @@ -108,9 +112,6 @@ class WhatsAppBackend(SpectrumBackend): def handleLeaveRoomRequest(self, user, room): pass - def handleVCardRequest(self, user, buddy, ID): - pass - def handleVCardUpdatedRequest(self, user, photo, nickname): pass diff --git a/yowsupwrapper.py b/yowsupwrapper.py index 0e62125..ad374a5 100644 --- a/yowsupwrapper.py +++ b/yowsupwrapper.py @@ -206,13 +206,24 @@ class YowsupApp(object): - failure: (func) called when request has failed """ iq = LastseenIqProtocolEntity(phoneNumber + '@s.whatsapp.net') - self.sendIq(iq, self._lastSeenSuccess(success), failure) + self.sendIq(iq, onSuccess = self._lastSeenSuccess(success), onError = failure) def _lastSeenSuccess(self, success): def func(response, request): success(response._from.split('@')[0], response.seconds) return func + def requestProfilePicture(self, phoneNumber, onSuccess = None, onFailure = None): + """ + Requests profile picture of whatsapp user + Args: + - phoneNumber: (str) the phone number of the user + - success: (func) called when request is successfully processed. + - failure: (func) called when request has failed + """ + iq = GetPictureIqProtocolEntity(phoneNumber + '@s.whatsapp.net') + self.sendIq(iq, onSuccess = onSuccess, onError = onFailure) + def onAuthSuccess(self, status, kind, creation, expiration, props, nonce, t): """ Called when login is successful.