From 98b9487c7f0ff932af7c04131643f1d321f481d2 Mon Sep 17 00:00:00 2001 From: root Date: Mon, 9 Nov 2015 23:09:12 +0100 Subject: [PATCH] send imgaes, locations, ... fix presence --- Spectrum2/backend.py | 65 ++++++++-- bot.py | 84 ++++++++++++- buddy.py | 22 +++- session.py | 285 +++++++++++++++++++++++++++++++++++++------ whatsappbackend.py | 7 +- yowsupwrapper.py | 61 +++++++++ 6 files changed, 470 insertions(+), 54 deletions(-) diff --git a/Spectrum2/backend.py b/Spectrum2/backend.py index 342f0e4..ec04767 100644 --- a/Spectrum2/backend.py +++ b/Spectrum2/backend.py @@ -3,7 +3,6 @@ import socket import struct import sys import os - import logging import google.protobuf @@ -20,13 +19,13 @@ class SpectrumBackend: @param host: Host where Spectrum2 NetworkPluginServer runs. @param port: Port. """ - def __init__(self): self.m_pingReceived = False self.m_data = "" self.m_init_res = 0 self.logger = logging.getLogger(self.__class__.__name__) + def handleMessage(self, user, legacyName, msg, nickname = "", xhtml = "", timestamp = ""): m = protocol_pb2.ConversationMessage() m.userName = user @@ -39,6 +38,17 @@ class SpectrumBackend: message = WRAP(m.SerializeToString(), protocol_pb2.WrapperMessage.TYPE_CONV_MESSAGE) self.send(message) + def handleMessageAck(self, user, legacyName, ID): + m = protocol_pb2.ConversationMessage() + m.userName = user + m.buddyName = legacyName + m.message = "" + m.id = ID + + message = WRAP(m.SerializeToString(), protocol_pb2.WrapperMessage.TYPE_CONV_MESSAGE_ACK) + self.send(message) + + def handleAttention(self, user, buddyName, msg): m = protocol_pb2.ConversationMessage() m.userName = user @@ -206,7 +216,7 @@ class SpectrumBackend: def handleFTData(self, ftID, data): d = protocol_pb2.FileTransferData() - d.ftID = ftID + d.ftid = ftID d.data = data message = WRAP(d.SerializeToString(), protocol_pb2.WrapperMessage.TYPE_FT_DATA); @@ -242,10 +252,20 @@ class SpectrumBackend: def handleConvMessagePayload(self, data): payload = protocol_pb2.ConversationMessage() + self.logger.error("handleConvMessagePayload") if (payload.ParseFromString(data) == False): #TODO: ERROR return - self.handleMessageSendRequest(payload.userName, payload.buddyName, payload.message, payload.xhtml) + self.handleMessageSendRequest(payload.userName, payload.buddyName, payload.message, payload.xhtml, payload.id) + + def handleConvMessageAckPayload(self, data): + payload = protocol_pb2.ConversationMessage() + if (payload.ParseFromString(data) == False): + #TODO: ERROR + return + self.handleMessageAckRequest(payload.userName, payload.buddyName, payload.id) + + def handleAttentionPayload(self, data): payload = protocol_pb2.ConversationMessage() @@ -345,24 +365,30 @@ class SpectrumBackend: if (len(self.m_data) >= 4): expected_size = struct.unpack('!I', self.m_data[0:4])[0] if (len(self.m_data) - 4 < expected_size): + self.logger.error("Expected Data Size Error") return else: + self.logger.error("Data too small") return wrapper = protocol_pb2.WrapperMessage() - try: - parseFromString = wrapper.ParseFromString(self.m_data[4:]) - except: - parseFromString = True - self.logger.error("Parse from String exception") + try: + parseFromString = wrapper.ParseFromString(self.m_data[4:]) + except: + parseFromString = True + self.logger.error("Parse from String exception") - if parseFromString == False: + + if (parseFromString == False): self.m_data = self.m_data[expected_size+4:] - self.logger.error("Parse from String exception") + self.logger.error("Parse from String error") return + self.m_data = self.m_data[4+expected_size:] + #self.logger.error("Data Type: %s",wrapper.type) + if wrapper.type == protocol_pb2.WrapperMessage.TYPE_LOGIN: self.handleLoginPayload(wrapper.payload) @@ -402,6 +428,8 @@ class SpectrumBackend: self.handleFTContinuePayload(wrapper.payload) elif wrapper.type == protocol_pb2.WrapperMessage.TYPE_EXIT: self.handleExitRequest() + elif wrapper.type == protocol_pb2.WrapperMessage.TYPE_CONV_MESSAGE_ACK: + self.handleConvMessageAckPayload(wrapper.payload) elif wrapper.type == protocol_pb2.WrapperMessage.TYPE_RAW_XML: self.handleRawXmlRequest(wrapper.payload) @@ -474,17 +502,30 @@ class SpectrumBackend: raise NotImplementedError, "Implement me" - def handleMessageSendRequest(self, user, legacyName, message, xhtml = ""): + def handleMessageSendRequest(self, user, legacyName, message, xhtml = "", ID = 0): """ Called when XMPP user sends message to legacy network. @param user: XMPP JID of user for which this event occurs. @param legacyName: Legacy network name of buddy or room. @param message: Plain text message. @param xhtml: XHTML message. + @param ID: message ID """ raise NotImplementedError, "Implement me" + def handleMessageAckRequest(self, user, legacyName, ID = 0): + """ + Called when XMPP user sends message to legacy network. + @param user: XMPP JID of user for which this event occurs. + @param legacyName: Legacy network name of buddy or room. + @param ID: message ID + """ + + # raise NotImplementedError, "Implement me" + pass + + def handleVCardRequest(self, user, legacyName, ID): """ Called when XMPP user requests VCard of buddy. @param user: XMPP JID of user for which this event occurs. diff --git a/bot.py b/bot.py index aa9bd35..469dd78 100644 --- a/bot.py +++ b/bot.py @@ -31,20 +31,26 @@ import os import utils from constants import * +#from googleclient import GoogleClient -from Yowsup.Contacts.contacts import WAContactsSyncRequest +#from Yowsup.Contacts.contacts import WAContactsSyncRequest class Bot(): def __init__(self, session, name = "Bot"): self.session = session self.name = name + #self.google = GoogleClient() + self.commands = { + "import": self._import, "help": self._help, "prune": self._prune, "welcome": self._welcome, "fortune": self._fortune, - "sync": self._sync + "sync": self._sync, + "groups": self._groups, + "getgroups": self._getgroups } def parse(self, message): @@ -75,6 +81,41 @@ class Bot(): def send(self, message): self.session.backend.handleMessage(self.session.user, self.name, message) + def __do_import(self, token): + # Google + google = self.google.getContacts(token) + self.send("%d buddies imported from google" % len(google)) + + result = { } + for number, name in google.iteritems(): + number = re.sub("[^0-9]", "", number) + number = number if number[0] == "0" else "+" + number + + result[number] = { 'nick': name, 'state': 0 } + + # WhatsApp + user = self.session.legacyName + password = self.session.password + sync = WAContactsSyncRequest(user, password, result.keys()) + whatsapp = sync.send()['c'] + + for w in whatsapp: + result[w['p']]['state'] = w['w'] + result[w['p']]['number'] = w['n'] + + self.send("%d buddies are using whatsapp" % len(filter(lambda w: w['w'], whatsapp))) + + for r in result.values(): + if r['nick']: + self.session.buddies.add( + number = r['number'], + nick = r['nick'], + groups = [u'Google'], + state = r['state'] + ) + + self.send("%d buddies imported" % len(whatsapp)) + def __get_token(self, filename, timeout = 30): file = open(filename, 'r') file.seek(-1, 2) # look at the end @@ -96,6 +137,25 @@ class Bot(): file.close() # commands + def _import(self, token = None): + if not token: + token_url = self.google.getTokenUrl("http://whatsapp.0l.de/auth.py") + auth_url = "http://whatsapp.0l.de/auth.py?number=%s&auth_url=%s" % (self.session.legacyName, urllib.quote(token_url)) + short_url = utils.shorten(auth_url) + self.send("please visit this url to auth: %s" % short_url) + + self.send("waiting for authorization...") + token = self.__get_token(TOKEN_FILE) + if token: + self.send("got token: %s" % token) + self.__do_import(token) + self.session.updateRoster() + else: + self.send("timeout! please use \"\\import [token]\"") + else: + self.__do_import(token) + self.session.updateRoster() + def _sync(self): user = self.session.legacyName password = self.session.password @@ -112,15 +172,18 @@ class Bot(): self.send("""following bot commands are available: \\help show this message \\prune clear your buddylist +\\import [token] import buddies from Google \\sync sync your imported contacts with WhatsApp \\fortune [database] give me a quote +\\groups print all attended groups +\\getgroups get current groups from WA following user commands are available: \\lastseen request last online timestamp from buddy""") def _fortune(self, database = '', prefix=''): - if os.path.exists("/usr/share/games/fortunes/%s" % database): - fortune = os.popen('/usr/games/fortune %s' % database).read() + if os.path.exists("/usr/share/fortune/%s" % database): + fortune = os.popen('/usr/bin/fortune %s' % database).read() self.send(prefix + fortune[:-1]) else: self.send("invalid database") @@ -134,3 +197,16 @@ following user commands are available: self.session.buddies.prune() self.session.updateRoster() self.send("buddy list cleared") + 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 + "@transwhat.xmpp.ewg.2y.net " + self.session.groups[group].subject + " Owner: " + nick ) + def _getgroups(self): + #self.session.call("group_getGroups", ("participating",)) + self.session.requestGroupsList(self.session._updateGroups) + diff --git a/buddy.py b/buddy.py index e07ec46..8bb09e3 100644 --- a/buddy.py +++ b/buddy.py @@ -25,6 +25,8 @@ __status__ = "Prototype" from Spectrum2 import protocol_pb2 import logging +import threading + class Number(): @@ -56,6 +58,8 @@ class Buddy(): self.number = number self.groups = groups self.image_hash = image_hash + self.statusMsg = "" + def update(self, nick, groups, image_hash): self.nick = nick @@ -65,7 +69,7 @@ class Buddy(): groups = u",".join(groups).encode("latin-1") cur = self.db.cursor() - 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)) + cur.execute("UPDATE buddies SET nick = %s, groups = %s, image_hash = %s WHERE owner_id = %s AND buddy_id = %s", (self.nick, groups, self.image_hash, self.owner.id, self.number.id)) self.db.commit() def delete(self): @@ -91,9 +95,12 @@ class BuddyList(dict): def __init__(self, owner, db): self.db = db self.owner = Number(owner, 1, db) + self.lock = threading.Lock() + def load(self): self.clear() + self.lock.acquire() cur = self.db.cursor() cur.execute("""SELECT @@ -114,13 +121,17 @@ class BuddyList(dict): for i in range(cur.rowcount): 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) + self.lock.release() + def update(self, number, nick, groups, image_hash): + self.lock.acquire() if number in self: buddy = self[number] buddy.update(nick, groups, image_hash) else: buddy = self.add(number, nick, groups, 1, image_hash) + self.lock.release() return buddy @@ -130,17 +141,24 @@ class BuddyList(dict): def remove(self, number): try: buddy = self[number] + self.lock.acquire() buddy.delete() + self.lock.release() return buddy except KeyError: return None def prune(self): + self.lock.acquire() + cur = self.db.cursor() cur.execute("DELETE FROM buddies WHERE owner_id = %s", self.owner.id) self.db.commit() + self.lock.release() + def sync(self, user, password): + self.lock.acquire() cur = self.db.cursor() cur.execute("""SELECT n.number AS number, @@ -165,5 +183,5 @@ class BuddyList(dict): cur.execute("UPDATE numbers SET state = %s WHERE number = %s", (number['w'], number['n'])) self.db.commit() using += number['w'] - + self.lock.release() return using diff --git a/session.py b/session.py index bdd9144..d5022ae 100644 --- a/session.py +++ b/session.py @@ -27,6 +27,15 @@ import logging import urllib import time +from PIL import Image +import MySQLdb +import sys +import os + +from yowsup.common.tools import TimeTools +from yowsup.layers.protocol_media.mediauploader import MediaUploader +from yowsup.layers.protocol_media.mediadownloader import MediaDownloader + from Spectrum2 import protocol_pb2 from buddy import BuddyList @@ -36,6 +45,16 @@ from bot import Bot from constants import * from yowsupwrapper import YowsupApp + +class MsgIDs: + def __init__(self, xmppId, waId): + self.xmppId = xmppId + self.waId = waId + self.cnt = 0 + + + + class Session(YowsupApp): def __init__(self, backend, user, legacyName, extra, db): @@ -43,7 +62,9 @@ class Session(YowsupApp): self.logger = logging.getLogger(self.__class__.__name__) self.logger.info("Created: %s", legacyName) - self.db = db + #self.db = db + self.db = MySQLdb.connect(DB_HOST, DB_USER, DB_PASS, DB_TABLE) + self.backend = backend self.user = user self.legacyName = legacyName @@ -61,11 +82,18 @@ class Session(YowsupApp): self.timer = None self.password = None self.initialized = False + self.lastMsgId = None self.synced = False self.buddies = BuddyList(self.legacyName, self.db) self.bot = Bot(self) + self.imgMsgId = None + self.imgPath = "" + self.imgBuddy = None + self.imgType = "" + + def __del__(self): # handleLogoutRequest self.logout() @@ -81,11 +109,13 @@ class Session(YowsupApp): def _shortenGroupId(self, gid): # FIXME: will have problems if number begins with 0 - return '-'.join(hex(int(s))[2:] for s in gid.split('-')) - + #return '-'.join(hex(int(s))[2:] for s in gid.split('-')) + return gid + def _lengthenGroupId(self, gid): # FIXME: will have problems if number begins with 0 - return '-'.join(str(int(s, 16)) for s in gid.split('-')) + #return '-'.join(str(int(s, 16)) for s in gid.split('-')) + return gid def updateRoomList(self): rooms = [] @@ -144,7 +174,7 @@ class Session(YowsupApp): oroom.subject = subject else: self.groups[room] = Group(room, owner, subject, subjectOwner) - self.joinRoom(self._shortenGroupId(room), self.user.split("@")[0]) + #self.joinRoom(self._shortenGroupId(room), self.user.split("@")[0]) self._addParticipantsToRoom(room, group.getParticipants()) @@ -159,6 +189,10 @@ class Session(YowsupApp): def joinRoom(self, room, nick): room = self._lengthenGroupId(room) + if room not in self.groups: + time.sleep(5) + + if room in self.groups: self.logger.info("Joining room: %s room=%s, nick=%s", self.legacyName, room, nick) @@ -206,6 +240,7 @@ class Session(YowsupApp): def _addParticipantsToRoom(self, room, participants): group = self.groups[room] group.participants = participants + group.nick = self.user.split("@")[0] for jid, _type in participants.iteritems(): buddy = jid.split("@")[0] @@ -220,10 +255,10 @@ class Session(YowsupApp): flags = protocol_pb2.PARTICIPANT_FLAG_MODERATOR else: flags = protocol_pb2.PARTICIPANT_FLAG_NONE - if buddy == self.legacyName: - nick = group.nick - flags = protocol_pb2.PARTICIPANT_FLAG_ME - buddyFull = self.user + if buddy == self.legacyName: + nick = group.nick + flags = flags | protocol_pb2.PARTICIPANT_FLAG_ME + buddyFull = self.user self.backend.handleParticipantChanged(self.user, buddyFull, self._shortenGroupId(room), flags, protocol_pb2.STATUS_ONLINE, buddy, nick) @@ -246,7 +281,7 @@ class Session(YowsupApp): ["Admin"], protocol_pb2.STATUS_ONLINE) if self.initialized == False: self.sendOfflineMessages() - self.bot.call("welcome") + #self.bot.call("welcome") self.initialized = True self.sendPresence(True) self.updateRoster() @@ -274,8 +309,13 @@ class Session(YowsupApp): ) try: buddy = self.buddies[_from.split('@')[0]] - self.backend.handleBuddyChanged(self.user, buddy.number.number, - buddy.nick, buddy.groups, protocol_pb2.STATUS_ONLINE) + #self.backend.handleBuddyChanged(self.user, buddy.number.number, + # buddy.nick, buddy.groups, protocol_pb2.STATUS_ONLINE) + self.backend.handleMessageAck(self.user, buddy.number.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: pass @@ -316,24 +356,43 @@ class Session(YowsupApp): def onImage(self, image): self.logger.debug('Received image message %s', str(image)) buddy = image._from.split('@')[0] + participant = image.participant message = image.url + ' ' + image.caption - self.sendMessageToXMPP(buddy, message, image.timestamp) + if participant is not None: # Group message + partname = participant.split('@')[0] + self.sendGroupMessageToXMPP(buddy, partname, message, image.timestamp) + else: + + self.sendMessageToXMPP(buddy, message, image.timestamp) self.sendReceipt(image._id, image._from, None, image.participant) # Called by superclass def onAudio(self, audio): self.logger.debug('Received audio message %s', str(audio)) buddy = audio._from.split('@')[0] + participant = audio.participant message = audio.url - self.sendMessageToXMPP(buddy, message, audio.timestamp) + if participant is not None: # Group message + partname = participant.split('@')[0] + self.sendGroupMessageToXMPP(buddy, partname, message, audio.timestamp) + else: + + self.sendMessageToXMPP(buddy, message, audio.timestamp) self.sendReceipt(audio._id, audio._from, None, audio.participant) # Called by superclass def onVideo(self, video): self.logger.debug('Received video message %s', str(video)) buddy = video._from.split('@')[0] + participant = video.participant + message = video.url - self.sendMessageToXMPP(buddy, message, video.timestamp) + if participant is not None: # Group message + partname = participant.split('@')[0] + self.sendGroupMessageToXMPP(buddy, partname, message, video.timestamp) + else: + + self.sendMessageToXMPP(buddy, message, video.timestamp) self.sendReceipt(video._id, video._from, None, video.participant) def onLocation(self, location): @@ -341,13 +400,22 @@ class Session(YowsupApp): latitude = location.getLatitude() longitude = location.getLongitude() url = location.getLocationUrl() + participant = location.participant self.logger.debug("Location received from %s: %s, %s", buddy, latitude, longitude) - self.sendMessageToXMPP(buddy, url, location.timestamp) - self.sendMessageToXMPP(buddy, 'geo:' + latitude + ',' + longitude, + if participant is not None: # Group message + partname = participant.split('@')[0] + self.sendGroupMessageToXMPP(buddy, partname, url, location.timestamp) + self.sendGroupMessageToXMPP(buddy, partname, 'geo:' + latitude + ',' + longitude, + location.timestamp) + else: + + self.sendMessageToXMPP(buddy, url, location.timestamp) + self.sendMessageToXMPP(buddy, 'geo:' + latitude + ',' + longitude, location.timestamp) + self.sendReceipt(location._id, location._from, None, location.participant) # Called by superclass @@ -358,9 +426,14 @@ class Session(YowsupApp): ])) ) buddy = _from.split("@")[0] - self.sendMessageToXMPP(buddy, "Received VCard (not implemented yet)") + if participant is not None: # Group message + partname = participant.split('@')[0] + self.sendGroupMessageToXMPP(buddy, partname, "Received VCard (not implemented yet)", timestamp) + else: + + self.sendMessageToXMPP(buddy, "Received VCard (not implemented yet)") # self.sendMessageToXMPP(buddy, card_data) - self.transferFile(buddy, str(name), card_data) + #self.transferFile(buddy, str(name), card_data) self.sendReceipt(_id, _from, None, participant) def transferFile(self, buddy, name, data): @@ -393,6 +466,15 @@ class Session(YowsupApp): buddy = jid.split("@")[0] # seems to be causing an error # self.logger.info("Lastseen: %s %s", buddy, utils.ago(lastseen)) + if lastseen != None and lastseen != "deny": + #lastseen = int(TimeTools.utcTimestamp()) - int(lastseen) + try: + + lastseen=time.time() - int(lastseen) + except ValueError: + lastseen = -1 + else: + lastseen = -1 if buddy in self.presenceRequested: timestamp = time.localtime(time.time() - lastseen) @@ -400,26 +482,27 @@ class Session(YowsupApp): self.sendMessageToXMPP(buddy, "%s (%s)" % (timestring, utils.ago(lastseen))) self.presenceRequested.remove(buddy) - if lastseen < 60: + if (lastseen < 60) and (lastseen >= 0): self.onPresenceAvailable(buddy) else: - self.onPresenceUnavailable(buddy) + self.onPresenceUnavailable(buddy, lastseen) def onPresenceAvailable(self, buddy): try: buddy = self.buddies[buddy] self.logger.info("Is available: %s", buddy) self.backend.handleBuddyChanged(self.user, buddy.number.number, - buddy.nick, buddy.groups, protocol_pb2.STATUS_ONLINE) + buddy.nick, buddy.groups, protocol_pb2.STATUS_ONLINE, buddy.statusMsg, buddy.image_hash) except KeyError: self.logger.error("Buddy not found: %s", buddy) - def onPresenceUnavailable(self, buddy): + def onPresenceUnavailable(self, buddy, lastseen): try: buddy = self.buddies[buddy] self.logger.info("Is unavailable: %s", buddy) + statusmsg = buddy.statusMsg + "\n Last seen: " + utils.ago(lastseen) self.backend.handleBuddyChanged(self.user, buddy.number.number, - buddy.nick, buddy.groups, protocol_pb2.STATUS_XA) + buddy.nick, buddy.groups, protocol_pb2.STATUS_AWAY, statusmsg, buddy.image_hash) except KeyError: self.logger.error("Buddy not found: %s", buddy) @@ -438,7 +521,7 @@ class Session(YowsupApp): self.logger.info("Stopped typing: %s to %s", self.legacyName, buddy) self.sendTyping(buddy, False) - def sendMessageToWA(self, sender, message): + def sendMessageToWA(self, sender, message, ID): self.logger.info("Message sent from %s to %s: %s", self.legacyName, sender, message) @@ -466,14 +549,83 @@ class Session(YowsupApp): ) except KeyError: self.logger.error('Group not found: %s', room) - self.sendTextMessage(self._lengthenGroupId(room) + '@g.us', message) + + if (".jpg" in message) or (".webp" in message): + if (".jpg" in message): + self.imgType = "jpg" + if (".webp" in message): + self.imgType = "webp" + self.imgMsgId = ID + self.imgBuddy = room + "@g.us" + downloader = MediaDownloader(self.onDlsuccess, self.onDlerror) + downloader.download(message) + #self.imgMsgId = ID + #self.imgBuddy = room + "@g.us" + elif "geo:" in message: + self._sendLocation(room + "@g.us", message, ID) + + else: + + self.sendTextMessage(self._lengthenGroupId(room) + '@g.us', message) else: # private msg buddy = sender # if message == "\\lastseen": # self.call("presence_request", buddy = (buddy + "@s.whatsapp.net",)) # else: - self.sendTextMessage(sender + '@s.whatsapp.net', message) + if message == "\\lastseen": + self.presenceRequested.append(buddy) + #self.call("presence_request", (buddy + "@s.whatsapp.net",)) + self._requestLastSeen(buddy) + elif message == "\\gpp": + self.logger.info("Get Profile Picture! ") + self.sendMessageToXMPP(buddy, "Fetching Profile Picture") + #self.call("contact_getProfilePicture", (buddy + "@s.whatsapp.net",)) + self.requestVCard(buddy) + else: + if (".jpg" in message) or (".webp" in message): + #waId = self.call("message_imageSend", (buddy + "@s.whatsapp.net", message, None, 0, None)) + #waId = self.call("message_send", (buddy + "@s.whatsapp.net", message)) + if (".jpg" in message): + self.imgType = "jpg" + if (".webp" in message): + self.imgType = "webp" + self.imgMsgId = ID + self.imgBuddy = buddy + "@s.whatsapp.net" + + downloader = MediaDownloader(self.onDlsuccess, self.onDlerror) + downloader.download(message) + #self.imgMsgId = ID + #self.imgBuddy = buddy + "@s.whatsapp.net" + elif "geo:" in message: + 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) + #self.sendTextMessage(sender + '@s.whatsapp.net', message) + + def _requestLastSeen(self, buddy): + + def onSuccess(buddy, lastseen): + timestamp = time.localtime(time.time()-(lastseen/60)) + timestring = time.strftime("%a, %d %b %Y %H:%M:%S", timestamp) + self.sendMessageToXMPP(buddy, "%s (%s) %s" % (timestring, utils.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): + #with open('/opt/transwhat/map.jpg', 'rb') as imageFile: + # raw = base64.b64encode(imageFile.read()) + 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: @@ -534,10 +686,10 @@ class Session(YowsupApp): self.setStatus(statusMessage.encode('utf-8')) self.logger.info("Status message changed: %s", statusMessage) - if self.initialized == False: - self.sendOfflineMessages() - self.bot.call("welcome") - self.initialized = True + #if self.initialized == False: + # self.sendOfflineMessages() + # self.bot.call("welcome") + # self.initialized = True def sendOfflineMessages(self): # Flush Queues @@ -549,7 +701,8 @@ class Session(YowsupApp): def updateBuddy(self, buddy, nick, groups, image_hash = None): if buddy != "bot": self.buddies.update(buddy, nick, groups, image_hash) - self.updateRoster() + if self.initialized == True: + self.updateRoster() def removeBuddy(self, buddy): if buddy != "bot": @@ -558,13 +711,14 @@ class Session(YowsupApp): self.updateRoster() - def requestVCard(self, buddy, ID): + def requestVCard(self, buddy, ID=None): 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) + if ID != None: + self.backend.handleVCard(self.user, ID, buddy, "", "", response.pictureData) obuddy = self.buddies[buddy] self.updateBuddy(buddy, obuddy.nick, obuddy.groups, image_hash) @@ -605,3 +759,66 @@ class Session(YowsupApp): def onGroupPictureUpdated(self, jid, author, timestamp, messageId, pictureId, receiptRequested): # TODO if receiptRequested: self.call("notification_ack", (jid, messageId)) + + + + def onDlsuccess(self, path): + self.logger.info("Success: Image downloaded to %s", path) + os.rename(path, path+"."+self.imgType) + if self.imgType != "jpg": + im = Image.open(path+"."+self.imgType) + im.save(path+".jpg") + self.imgPath = path+".jpg" + statinfo = os.stat(self.imgPath) + name=os.path.basename(self.imgPath) + self.logger.info("Buddy %s",self.imgBuddy) + self.image_send(self.imgBuddy, self.imgPath) + + #self.logger.info("Sending picture %s of size %s with name %s",self.imgPath, statinfo.st_size, name) + #mtype = "image" + + #sha1 = hashlib.sha256() + #fp = open(self.imgPath, 'rb') + #try: + # sha1.update(fp.read()) + # hsh = base64.b64encode(sha1.digest()) + # self.call("media_requestUpload", (hsh, mtype, os.path.getsize(self.imgPath))) + #finally: + # fp.close() + + + def onDlerror(self): + self.logger.info("Download Error") + + + def _doSendImage(self, filePath, url, to, ip = None, caption = None): + waId = self.doSendImage(filePath, url, to, ip, caption) + self.msgIDs[waId] = MsgIDs(self.imgMsgId, waId) + + def _doSendAudio(self, filePath, url, to, ip = None, caption = None): + waId = self.doSendAudio(filePath, url, to, ip, caption) + self.msgIDs[waId] = MsgIDs(self.imgMsgId, waId) + + + + + 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 + diff --git a/whatsappbackend.py b/whatsappbackend.py index b973f76..ae3a6b9 100644 --- a/whatsappbackend.py +++ b/whatsappbackend.py @@ -58,7 +58,7 @@ class WhatsAppBackend(SpectrumBackend): self.sessions[user].logout() del self.sessions[user] - def handleMessageSendRequest(self, user, buddy, message, xhtml = ""): + def handleMessageSendRequest(self, user, buddy, message, xhtml = "", ID = 0): self.logger.debug("handleMessageSendRequest(user=%s, buddy=%s, message=%s, xhtml = %s)", user, buddy, message, xhtml) # For some reason spectrum occasionally sends to identical messages to # a buddy, one to the bare jid and one to /bot. This causes duplicate @@ -70,7 +70,7 @@ class WhatsAppBackend(SpectrumBackend): # IDEA there is an ID field in ConvMessage. If it is extracted it will work usersMessage = self.lastMessage[user] if buddy not in usersMessage or usersMessage[buddy] != message: - self.sessions[user].sendMessageToWA(buddy, message) + self.sessions[user].sendMessageToWA(buddy, message, ID) usersMessage[buddy] = message def handleJoinRoomRequest(self, user, room, nickname, pasword): @@ -136,6 +136,9 @@ class WhatsAppBackend(SpectrumBackend): def handleRawXmlRequest(self, xml): pass + def handleMessageAckRequest(self, user, legacyName, ID = 0): + self.logger.info("Meassage ACK request for %s !!",leagcyName) + def sendData(self, data): self.io.sendData(data) diff --git a/yowsupwrapper.py b/yowsupwrapper.py index 2a8349b..6369279 100644 --- a/yowsupwrapper.py +++ b/yowsupwrapper.py @@ -37,9 +37,12 @@ from yowsup.layers.protocol_messages.protocolentities import * from yowsup.layers.protocol_presence.protocolentities import * from yowsup.layers.protocol_profiles.protocolentities import * from yowsup.layers.protocol_receipts.protocolentities import * +from yowsup.layers.protocol_media.mediauploader import MediaUploader from functools import partial +#from session import MsgIDs + class YowsupApp(object): def __init__(self): env.CURRENT_ENV = env.S40YowsupEnv() @@ -132,6 +135,64 @@ class YowsupApp(object): """ messageEntity = TextMessageProtocolEntity(message, to = to) self.sendEntity(messageEntity) + return messageEntity.getId() + + def sendLocation(self, to, latitude, longitude): + messageEntity = LocationMediaMessageProtocolEntity(latitude,longitude, None, None, "raw", to = to) + self.sendEntity(messageEntity) + return messageEntity.getId() + + def image_send(self, jid, path, caption = None): + entity = RequestUploadIqProtocolEntity(RequestUploadIqProtocolEntity.MEDIA_TYPE_IMAGE, filePath=path) + successFn = lambda successEntity, originalEntity: self.onRequestUploadResult(jid, path, successEntity, originalEntity, caption) + errorFn = lambda errorEntity, originalEntity: self.onRequestUploadError(jid, path, errorEntity, originalEntity) + + self.sendIq(entity, successFn, errorFn) + + def onRequestUploadResult(self, jid, filePath, resultRequestUploadIqProtocolEntity, requestUploadIqProtocolEntity, caption = None): + + if requestUploadIqProtocolEntity.mediaType == RequestUploadIqProtocolEntity.MEDIA_TYPE_AUDIO: + doSendFn = self._doSendAudio + else: + doSendFn = self._doSendImage + + if resultRequestUploadIqProtocolEntity.isDuplicate(): + doSendFn(filePath, resultRequestUploadIqProtocolEntity.getUrl(), jid, + resultRequestUploadIqProtocolEntity.getIp(), caption) + else: + successFn = lambda filePath, jid, url: doSendFn(filePath, url, jid, resultRequestUploadIqProtocolEntity.getIp(), caption) + mediaUploader = MediaUploader(jid, self.legacyName, filePath, + resultRequestUploadIqProtocolEntity.getUrl(), + resultRequestUploadIqProtocolEntity.getResumeOffset(), + successFn, self.onUploadError, self.onUploadProgress, async=False) + mediaUploader.start() + + def onRequestUploadError(self, jid, path, errorRequestUploadIqProtocolEntity, requestUploadIqProtocolEntity): + self.logger.error("Request upload for file %s for %s failed" % (path, jid)) + + def onUploadError(self, filePath, jid, url): + #logger.error("Upload file %s to %s for %s failed!" % (filePath, url, jid)) + self.logger.error("Upload Error!") + + def onUploadProgress(self, filePath, jid, url, progress): + #sys.stdout.write("%s => %s, %d%% \r" % (os.path.basename(filePath), jid, progress)) + #sys.stdout.flush() + pass + + def doSendImage(self, filePath, url, to, ip = None, caption = None): + entity = ImageDownloadableMediaMessageProtocolEntity.fromFilePath(filePath, url, ip, to, caption = caption) + self.sendEntity(entity) + #self.msgIDs[entity.getId()] = MsgIDs(self.imgMsgId, entity.getId()) + return entity.getId() + + + def doSendAudio(self, filePath, url, to, ip = None, caption = None): + entity = AudioDownloadableMediaMessageProtocolEntity.fromFilePath(filePath, url, ip, to) + self.sendEntity(entity) + #self.msgIDs[entity.getId()] = MsgIDs(self.imgMsgId, entity.getId()) + return entity.getId() + + def sendPresence(self, available): """