__author__ = "Steffen Vogel" __copyright__ = "Copyright 2013, Steffen Vogel" __license__ = "GPLv3" __maintainer__ = "Steffen Vogel" __email__ = "post@steffenvogel.de" __status__ = "Prototype" """ This file is part of transWhat transWhat is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or any later version. transwhat is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with transWhat. If not, see . """ import utils import logging import urllib import time from yowsup.stacks import YowStack from yowsup.layers import YowLayerEvent, YowParallelLayer from yowsup.layers.interface import YowInterfaceLayer, ProtocolEntityCallback from yowsup.layers.axolotl import YowAxolotlLayer from yowsup.layers.auth import (YowCryptLayer, YowAuthenticationProtocolLayer, AuthError) from yowsup.layers.protocol_iq import YowIqProtocolLayer from yowsup.layers.protocol_groups import YowGroupsProtocolLayer from yowsup.layers.coder import YowCoderLayer from yowsup.layers.network import YowNetworkLayer from yowsup.layers.protocol_messages import YowMessagesProtocolLayer from yowsup.layers.protocol_media import YowMediaProtocolLayer from yowsup.layers.stanzaregulator import YowStanzaRegulator from yowsup.layers.protocol_receipts import YowReceiptProtocolLayer from yowsup.layers.protocol_acks import YowAckProtocolLayer from yowsup.layers.logger import YowLoggerLayer from yowsup.common import YowConstants from yowsup.layers.protocol_receipts.protocolentities import * from yowsup import env from yowsup.layers.protocol_presence import * from yowsup.layers.protocol_presence.protocolentities import * from yowsup.layers.protocol_messages.protocolentities import TextMessageProtocolEntity from yowsup.layers.protocol_chatstate.protocolentities import * from yowsup.layers.protocol_acks.protocolentities import * from yowsup.layers import YowLayer from yowsup.layers.auth import YowCryptLayer, YowAuthenticationProtocolLayer from yowsup.layers.coder import YowCoderLayer from yowsup.layers.logger import YowLoggerLayer from yowsup.layers.network import YowNetworkLayer from yowsup.layers.protocol_messages import YowMessagesProtocolLayer from yowsup.layers.stanzaregulator import YowStanzaRegulator from yowsup.layers.protocol_media import YowMediaProtocolLayer from yowsup.layers.protocol_acks import YowAckProtocolLayer from yowsup.layers.protocol_receipts import YowReceiptProtocolLayer from yowsup.layers.protocol_groups import YowGroupsProtocolLayer from yowsup.layers.protocol_presence import YowPresenceProtocolLayer from yowsup.layers.protocol_ib import YowIbProtocolLayer from yowsup.layers.protocol_notifications import YowNotificationsProtocolLayer from yowsup.layers.protocol_iq import YowIqProtocolLayer from yowsup.layers.protocol_contacts import YowContactsIqProtocolLayer from yowsup.layers.protocol_chatstate import YowChatstateProtocolLayer from yowsup.layers.protocol_privacy import YowPrivacyProtocolLayer from yowsup.layers.protocol_profiles import YowProfilesProtocolLayer from yowsup.layers.protocol_calls import YowCallsProtocolLayer from Spectrum2 import protocol_pb2 from buddy import BuddyList from threading import Timer from group import Group from bot import Bot from constants import * from yowsupwrapper import YowsupApp class Session(YowsupApp): def __init__(self, backend, user, legacyName, extra, db): super(Session, self).__init__() self.logger = logging.getLogger(self.__class__.__name__) self.logger.info("Created: %s", legacyName) self.db = db self.backend = backend self.user = user self.legacyName = legacyName self.buddies = BuddyList(self.legacyName, self.db) self.bot = Bot(self) self.status = protocol_pb2.STATUS_NONE self.statusMessage = '' self.groups = {} self.presenceRequested = [] self.offlineQueue = [] self.groupOfflineQueue = { } self.timer = None self.password = None self.initialized = False self.loggedin = False self.bot = Bot(self) def __del__(self): # handleLogoutRequest self.logout() def call(self, method, **kwargs): self.logger.debug("%s(%s)", method, ", ".join(str(k) + ': ' + str(v) for k, v in kwargs.items())) ##self.stack.broadcastEvent(YowLayerEvent(method, **kwargs)) def logout(self): self.loggedin = False super(Session, self).logout() def login(self, password): self.loggedin = True self.password = password super(Session, self).login(self.legacyName, self.password) def updateRoomList(self): rooms = [] for room, group in self.groups.iteritems(): rooms.append([room, group.subject]) self.backend.handleRoomList(rooms) def updateRoster(self): self.logger.debug("Update roster") old = self.buddies.keys() self.buddies.load() new = self.buddies.keys() add = set(new) - set(old) remove = set(old) - set(new) self.logger.debug("Roster remove: %s", str(list(remove))) self.logger.debug("Roster add: %s", str(list(add))) for number in remove: self.backend.handleBuddyChanged(self.user, number, "", [], protocol_pb2.STATUS_NONE) self.backend.handleBuddyRemoved(self.user, number) self.unsubscribePresence(number) for number in add: buddy = self.buddies[number] self.subscribePresence(number) self.requestLastSeen(number, self._lastSeen) def _lastSeen(self, number, seconds): self.logger.debug("Last seen %s at %s seconds" % (number, str(seconds))) if seconds < 60: self.onPresenceAvailable(number) else: self.onPresenceUnavailable(number) # 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"], protocol_pb2.STATUS_ONLINE) self.initialized = True self.sendPresence(True) self.updateRoster() # 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 # Called by superclass def onDisconnect(self): self.logger.debug('Disconnected') self.backend.handleDisconnected(self.user, 0, 'Disconnected for unknown reasons') self.loggedin = False # Called by superclass def onReceipt(self, _id, _from, timestamp, type, participant, offline, items): self.logger.debug("received receipt, sending ack: " + ' '.join(map(str, [_id, _from, timestamp, type, participant, offline, items])) ) buddy = self.buddies[_from.split('@')[0]] self.backend.handleBuddyChanged(self.user, buddy.number.number, buddy.nick, buddy.groups, protocol_pb2.STATUS_ONLINE) # Called by superclass def onAck(self, _id, _class, _from, timestamp): self.logger.debug('received ack ' + ' '.join(map(str, [_id, _class, _from,timestamp,])) ) # Called by superclass def onMessage(self, messageEntity): self.logger.debug(str(messageEntity)) buddy = messageEntity.getFrom().split('@')[0] messageContent = utils.softToUni(messageEntity.getBody()) timestamp = messageEntity.getTimestamp() self.sendReceipt(messageEntity.getId(), messageEntity.getFrom(), None, messageEntity.getParticipant()) if messageEntity.isBroadcast(): self.logger.info("Broadcast received from %s to %s: %s (at ts=%s)",\ buddy, self.legacyName, messageContent, timestamp) messageContent = "[Broadcast] " + messageContent else: self.logger.info("Message received from %s to %s: %s (at ts=%s)", buddy, self.legacyName, messageContent, timestamp) self.sendMessageToXMPP(buddy, messageContent, timestamp) # if receiptRequested: self.call("message_ack", (jid, messageId)) # Called by superclass def onContactTyping(self, buddy): self.logger.info("Started typing: %s", buddy) 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) self.backend.handleBuddyTyped(self.user, buddy) self.timer = Timer(3, self.backend.handleBuddyStoppedTyping, (self.user, buddy)).start() def onPresenceReceived(self, _type, name, jid, lastseen): self.logger.info("Presence received: %s %s %s %s", _type, name, jid, lastseen) buddy = jid.split("@")[0] # seems to be causing an error # self.logger.info("Lastseen: %s %s", buddy, utils.ago(lastseen)) if buddy in self.presenceRequested: timestamp = time.localtime(time.time() - lastseen) timestring = time.strftime("%a, %d %b %Y %H:%M:%S", timestamp) self.sendMessageToXMPP(buddy, "%s (%s)" % (timestring, utils.ago(lastseen))) self.presenceRequested.remove(buddy) if lastseen < 60: self.onPresenceAvailable(buddy) else: self.onPresenceUnavailable(buddy) 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) except KeyError: self.logger.error("Buddy not found: %s", buddy) def onPresenceUnavailable(self, buddy): try: buddy = self.buddies[buddy] self.logger.info("Is unavailable: %s", buddy) self.backend.handleBuddyChanged(self.user, buddy.number.number, buddy.nick, buddy.groups, protocol_pb2.STATUS_XA) except KeyError: self.logger.error("Buddy not found: %s", 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) # 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) def sendMessageToWA(self, sender, message): self.logger.info("Message sent from %s to %s: %s", self.legacyName, sender, message) message = message.encode("utf-8") # if sender == "bot": # self.bot.parse(message) # elif "-" in sender: # group msg # if "/" in sender: # room, buddy = sender.split("/") # self.sendTextMessage(buddy + '@s.whatsapp.net', message) # else: # room = sender # group = self.groups[room] # # self.backend.handleMessage(self.user, room, message, group.nick) # self.sendTextMessage(room + '@g.us', message) # # else: # private msg # buddy = sender # if message == "\\lastseen": # self.presenceRequested.append(buddy) # self.call("presence_request", buddy = (buddy + "@s.whatsapp.net",)) # else: self.sendTextMessage(sender + '@s.whatsapp.net', message) def sendMessageToXMPP(self, buddy, messageContent, timestamp = ""): 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, buddy, messageContent, timestamp = ""): 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", buddy, room, messageContent) if room not in self.groupOfflineQueue: self.groupOfflineQueue[room] = [ ] self.groupOfflineQueue[room].append((buddy, messageContent, timestamp)) else: self.logger.debug("Group message sent from %s to %s: %s", buddy, room, messageContent) self.backend.handleMessage(self.user, room, messageContent, buddy, "", timestamp) def changeStatus(self, status): if status != self.status: self.logger.info("Status changed: %s", status) self.status = status if status == protocol_pb2.STATUS_ONLINE or status == 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.encode('utf-8')) 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]) # also for adding a new buddy def updateBuddy(self, buddy, nick, groups): if buddy != "bot": self.buddies.update(buddy, nick, groups) self.updateRoster() def removeBuddy(self, buddy): if buddy != "bot": self.logger.info("Buddy removed: %s", buddy) self.buddies.remove(buddy) self.updateRoster() def joinRoom(self, room, nick): if room in self.groups: group = self.groups[room] self.logger.info("Joining room: %s room=%s, nick=%s", self.legacyName, room, nick) group.nick = nick self.call("group_getParticipants", (room + "@g.us",)) self.backend.handleSubject(self.user, room, group.subject, group.subjectOwner) else: self.logger.warn("Room doesn't exist: %s", room) def onMediaReceived(self, messageId, jid, preview, url, size, receiptRequested, isBroadcast): buddy = jid.split("@")[0] self.logger.info("Media received from %s: %s", buddy, url) self.sendMessageToXMPP(buddy, utils.shorten(url)) if receiptRequested: self.call("message_ack", (jid, messageId)) 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 onVcardReceived(self, messageId, jid, name, data, receiptRequested, isBroadcast): # TODO buddy = jid.split("@")[0] self.logger.info("VCard received from %s", buddy) self.sendMessageToXMPP(buddy, "Received VCard (not implemented yet)") if receiptRequested: self.call("message_ack", (jid, messageId)) def onGroupGotInfo(self, gjid, owner, subject, subjectOwner, subjectTimestamp, creationTimestamp): room = gjid.split("@")[0] owner = owner.split("@")[0] subjectOwner = subjectOwner.split("@")[0] if room in self.groups: room = self.groups[room] room.owner = owner room.subjectOwner = subjectOwner room.subject = subject else: self.groups[room] = Group(room, owner, subject, subjectOwner) self.updateRoomList() def onGroupGotParticipants(self, gjid, jids): room = gjid.split("@")[0] group = self.groups[room] for jid in jids: buddy = jid.split("@")[0] self.logger.info("Added %s to room %s", buddy, room) if buddy == group.owner: flags = protocol_pb2.PARTICIPANT_FLAG_MODERATOR else: flags = protocol_pb2.PARTICIPANT_FLAG_NONE self.backend.handleParticipantChanged(self.user, buddy, room, flags, protocol_pb2.STATUS_ONLINE) # TODO check status if 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]) def onGroupMessageReceived(self, messageId, gjid, jid, messageContent, timestamp, receiptRequested, pushName): buddy = jid.split("@")[0] room = gjid.split("@")[0] self.logger.info("Group message received in %s from %s: %s", room, buddy, messageContent) self.sendGroupMessageToXMPP(room, buddy, utils.softToUni(messageContent), timestamp) if receiptRequested: self.call("message_ack", (gjid, 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 onGroupParticipantAdded(self, gjid, jid, author, timestamp, messageId, receiptRequested): room = gjid.split("@")[0] buddy = jid.split("@")[0] logger.info("Added % to room %s", buddy, room) self.backend.handleParticipantChanged(self.user, buddy, room, protocol_pb2.PARTICIPANT_FLAG_NONE, protocol_pb2.STATUS_ONLINE) if receiptRequested: self.call("notification_ack", (gjid, messageId)) 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, protocol_pb2.PARTICIPANT_FLAG_NONE, 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))