From 2f73ed1a1098d0d22f44b68df8a9abd859a4714d Mon Sep 17 00:00:00 2001 From: moyamo Date: Sat, 29 Aug 2015 20:50:07 +0200 Subject: [PATCH 01/22] Send Acks and Receipts on receiving a message --- session.py | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/session.py b/session.py index 8e30faa..0b445c9 100644 --- a/session.py +++ b/session.py @@ -43,11 +43,13 @@ 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 Spectrum2 import protocol_pb2 from buddy import BuddyList @@ -415,7 +417,8 @@ class SpectrumLayer(YowInterfaceLayer): self.toLower(entity) retval = True elif layerEvent.getName() == 'profile_setStatus': - entity = PresenceProtocolEntity(name = layerEvent.getArg('message')) + # entity = PresenceProtocolEntity(name = layerEvent.getArg('message')) + entity = PresenceProtocolEntity(name = 'This status is non-empty') self.toLower(entity) retval = True elif layerEvent.getName() == 'message_send': @@ -453,6 +456,7 @@ class SpectrumLayer(YowInterfaceLayer): self.backend.handleConnected(self.user) self.backend.handleBuddyChanged(self.user, "bot", self.bot.name, ["Admin"], protocol_pb2.STATUS_ONLINE) + self.session.initialized = True self.updateRoster() @@ -462,6 +466,20 @@ class SpectrumLayer(YowInterfaceLayer): self.backend.handleDisconnected(self.user, 0, entity.getReason()) self.session.password = None + @ProtocolEntityCallback("receipt") + def onReceipt(self, entity): + self.logger.debug("received receipt, sending ack: " + str(entity.ack())) + ack = OutgoingAckProtocolEntity(entity.getId(), "receipt", entity.getType(), entity.getFrom()) + self.toLower(ack) + + @ProtocolEntityCallback("ack") + def onAck(self, entity): + self.logger.debug('received ack ' + str(entity)) + + @ProtocolEntityCallback("notification") + def onNotification(self, notification): + self.toLower(notification.ack()) + def updateRoster(self): self.logger.debug("Update roster") @@ -492,6 +510,9 @@ class SpectrumLayer(YowInterfaceLayer): messageContent = utils.softToUni(messageEntity.getBody()) timestamp = messageEntity.getTimestamp() + receipt = OutgoingReceiptProtocolEntity(messageEntity.getId(),messageEntity.getFrom(), 'read', messageEntity.getParticipant()) + self.toLower(receipt) + if messageEntity.isBroadcast(): self.logger.info("Broadcast received from %s to %s: %s (at ts=%s)",\ buddy, self.legacyName, messageContent, timestamp) @@ -499,8 +520,9 @@ class SpectrumLayer(YowInterfaceLayer): else: self.logger.info("Message received from %s to %s: %s (at ts=%s)", buddy, self.legacyName, messageContent, timestamp) + self.session.sendMessageToXMPP(buddy, messageContent, timestamp) + - self.session.sendMessageToXMPP(buddy, messageContent, timestamp) # if receiptRequested: self.call("message_ack", (jid, messageId)) @ProtocolEntityCallback("presence") From 59754848a7a52099ce1f7ec27b0e66a8d34d5129 Mon Sep 17 00:00:00 2001 From: moyamo Date: Sat, 29 Aug 2015 22:01:44 +0200 Subject: [PATCH 02/22] Enable Axolotl encryption as it is mandatory. --- session.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/session.py b/session.py index 0b445c9..6758f67 100644 --- a/session.py +++ b/session.py @@ -30,6 +30,7 @@ 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 @@ -94,6 +95,7 @@ class Session(): YowIqProtocolLayer, YowGroupsProtocolLayer, YowPresenceProtocolLayer)), + YowAxolotlLayer, YowCoderLayer, YowCryptLayer, YowStanzaRegulator, @@ -530,7 +532,8 @@ class SpectrumLayer(YowInterfaceLayer): jid = presence.getFrom() lastseen = presence.getLast() buddy = jid.split("@")[0] - self.logger.info("Lastseen: %s %s", buddy, utils.ago(lastseen)) +# seems to be causing an error +# self.logger.info("Lastseen: %s %s", buddy, utils.ago(lastseen)) if buddy in self.session.presenceRequested: timestamp = time.localtime(time.time() - lastseen) From c7afff2cf995f3e10eafbf20545c543c680cb132 Mon Sep 17 00:00:00 2001 From: moyamo Date: Sun, 30 Aug 2015 19:16:53 +0200 Subject: [PATCH 03/22] Add more protocol layers to the yowstack --- session.py | 49 +++++++++++++++++++++++++++++++++++++------------ 1 file changed, 37 insertions(+), 12 deletions(-) diff --git a/session.py b/session.py index 6758f67..ccbc2ac 100644 --- a/session.py +++ b/session.py @@ -51,6 +51,26 @@ 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 @@ -92,7 +112,15 @@ class Session(): YowReceiptProtocolLayer, YowAckProtocolLayer, YowMediaProtocolLayer, + YowIbProtocolLayer, YowIqProtocolLayer, + YowNotificationsProtocolLayer, + YowContactsIqProtocolLayer, +# YowChatstateProtocolLayer, + YowCallsProtocolLayer, + YowMediaProtocolLayer, + YowPrivacyProtocolLayer, + YowProfilesProtocolLayer, YowGroupsProtocolLayer, YowPresenceProtocolLayer)), YowAxolotlLayer, @@ -400,16 +428,13 @@ class SpectrumLayer(YowInterfaceLayer): self.db = layerEvent.getArg("db") self.session = layerEvent.getArg("session") - self.buddies = BuddyList(self.legacyName, self.db) + self.session.buddies = BuddyList(self.legacyName, self.db) self.bot = Bot(self) retval = True elif layerEvent.getName() == YowNetworkLayer.EVENT_STATE_DISCONNECTED: reason = layerEvent.getArg("reason") self.logger.info("Disconnected: %s (%s)", self.user, reason) - if not self.session.loggedin or self.session.password is None: - self.backend.handleDisconnected(self.user, 0, reason) - else: - self.session.login(self.session.password) + self.backend.handleDisconnected(self.user, 0, reason) elif layerEvent.getName() == 'presence_sendAvailable': entity = AvailablePresenceProtocolEntity() self.toLower(entity) @@ -439,7 +464,7 @@ class SpectrumLayer(YowInterfaceLayer): elif layerEvent.getName() == 'typing_paused': buddy = layerEvent.getArg('buddy') state = OutgoingChatstateProtocolEntity( - ChatstateProtocolEntity.STATE_PAUSE, buddy + ChatstateProtocolEntity.STATE_PAUSED, buddy ) self.toLower(state) retval = True @@ -485,9 +510,9 @@ class SpectrumLayer(YowInterfaceLayer): def updateRoster(self): self.logger.debug("Update roster") - old = self.buddies.keys() - self.buddies.load() - new = self.buddies.keys() + old = self.session.buddies.keys() + self.session.buddies.load() + new = self.session.buddies.keys() add = set(new) - set(old) remove = set(old) - set(new) @@ -502,7 +527,7 @@ class SpectrumLayer(YowInterfaceLayer): self.toLower(entity) for number in add: - buddy = self.buddies[number] + buddy = self.session.buddies[number] entity = SubscribePresenceProtocolEntity(number + "@s.whatsapp.net") self.toLower(entity) @@ -550,7 +575,7 @@ class SpectrumLayer(YowInterfaceLayer): buddy = jid.split("@")[0] try: - buddy = self.buddies[buddy] + buddy = self.session.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: @@ -560,7 +585,7 @@ class SpectrumLayer(YowInterfaceLayer): buddy = jid.split("@")[0] try: - buddy = self.buddies[buddy] + buddy = self.session.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: From 8cc3c8514534bd38866f9eca62f8d5e7667fcf7f Mon Sep 17 00:00:00 2001 From: moyamo Date: Sun, 30 Aug 2015 20:08:41 +0200 Subject: [PATCH 04/22] Add a yowsup wrapper to simplify the session.py code --- yowsup.py | 178 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 178 insertions(+) create mode 100644 yowsup.py diff --git a/yowsup.py b/yowsup.py new file mode 100644 index 0000000..4022417 --- /dev/null +++ b/yowsup.py @@ -0,0 +1,178 @@ + +from yowsup import env +from yowsup.stacks import YowStack + +# Layers +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 + +class YowsupApp: + def __init__(self): + self.logged_in = False + + env.CURRENT_ENV = env.S40YowsupEnv() + + layers = (SpectrumLayer, + YowParallelLayer((YowAuthenticationProtocolLayer, + YowMessagesProtocolLayer, + YowReceiptProtocolLayer, + YowAckProtocolLayer, + YowMediaProtocolLayer, + YowIbProtocolLayer, + YowIqProtocolLayer, + YowNotificationsProtocolLayer, + YowContactsIqProtocolLayer, +# YowChatstateProtocolLayer, + YowCallsProtocolLayer, + YowMediaProtocolLayer, + YowPrivacyProtocolLayer, + YowProfilesProtocolLayer, + YowGroupsProtocolLayer, + YowPresenceProtocolLayer)), + YowAxolotlLayer, + YowCoderLayer, + YowCryptLayer, + YowStanzaRegulator, + YowNetworkLayer + ) + self.stack = YowStack(layers) + self.stack.broadcastEvent( + YowLayerEvent(YowsupAppLayer.EVENT_START, caller = self) + ) + + 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 + +from yowsup.layers.interface import YowInterfaceLayer, ProtocolEntityCallback + +class YowsupAppLayer(YowInterfaceLayer): + EVENT_START = 'transwhat.event.SpectrumLayer.start' + + 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') + + @ProtocolEntityCallback('success') + def onAuthSuccess(self, entity): + # entity is SuccessProtocolEntity + status = entity.status + 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('receipt') + def onReceipt(self, entity): + """Sends ack automatically""" + # entity is IncomingReceiptProtocolEntity + ack = OutgoingAckProtocolEntity(entity.getId(), + 'receipt', entity.getType(), entity.getFrom()) + self.toLower(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('notification') + def onNotification(self, entity): + """ + Sends ack automatically + """ + self.toLower(notification.ack()) + + @ProtocolEntityCallback("message") + def onMessageReceived(self, entity): + self.caller.onMessage(entity) From 254465301b366c0210b7a93c36aa58d33bdb5907 Mon Sep 17 00:00:00 2001 From: moyamo Date: Sun, 30 Aug 2015 20:51:44 +0200 Subject: [PATCH 05/22] Make Session use YowsupApp instead of SpectrumLayer --- session.py | 279 ++++++++++++++-------------------- yowsup.py => yowsupwrapper.py | 99 ++++++++++-- 2 files changed, 203 insertions(+), 175 deletions(-) rename yowsup.py => yowsupwrapper.py (64%) diff --git a/session.py b/session.py index ccbc2ac..d877078 100644 --- a/session.py +++ b/session.py @@ -44,32 +44,32 @@ 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.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.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.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 @@ -78,8 +78,9 @@ from threading import Timer from group import Group from bot import Bot from constants import * +from yowsupwrapper import YowsupApp -class Session(): +class Session(YowsupApp): def __init__(self, backend, user, legacyName, extra, db): self.logger = logging.getLogger(self.__class__.__name__) @@ -105,76 +106,22 @@ class Session(): self.bot = Bot(self) - env.CURRENT_ENV = env.S40YowsupEnv() - layers = (SpectrumLayer, - YowParallelLayer((YowAuthenticationProtocolLayer, - YowMessagesProtocolLayer, - YowReceiptProtocolLayer, - YowAckProtocolLayer, - YowMediaProtocolLayer, - YowIbProtocolLayer, - YowIqProtocolLayer, - YowNotificationsProtocolLayer, - YowContactsIqProtocolLayer, -# YowChatstateProtocolLayer, - YowCallsProtocolLayer, - YowMediaProtocolLayer, - YowPrivacyProtocolLayer, - YowProfilesProtocolLayer, - YowGroupsProtocolLayer, - YowPresenceProtocolLayer)), - YowAxolotlLayer, - YowCoderLayer, - YowCryptLayer, - YowStanzaRegulator, - YowNetworkLayer - ) - self.stack = YowStack(layers) - self.stack.broadcastEvent( - YowLayerEvent(SpectrumLayer.EVENT_START, - backend = self.backend, - user = self.user, - db = self.db, - legacyName = self.legacyName, - session = 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)) + ##self.stack.broadcastEvent(YowLayerEvent(method, **kwargs)) def logout(self): self.loggedin = False - self.stack.broadcastEvent(YowLayerEvent(YowNetworkLayer.EVENT_STATE_DISCONNECT)) + super(Session, self).logout() def login(self, password): - self.stack.setProp(YowAuthenticationProtocolLayer.PROP_CREDENTIALS, - (self.legacyName, password)) - self.stack.setProp(YowNetworkLayer.PROP_ENDPOINT, - YowConstants.ENDPOINTS[0]) - self.stack.setProp(YowCoderLayer.PROP_DOMAIN, - YowConstants.DOMAIN) - self.stack.setProp(YowCoderLayer.PROP_RESOURCE, - env.CURRENT_ENV.getResource()) - self.stack.setProp(YowIqProtocolLayer.PROP_PING_INTERVAL, 5) self.loggedin = True self.password = password - try: - self.stack.broadcastEvent( - YowLayerEvent(YowNetworkLayer.EVENT_STATE_CONNECT)) - except TypeError as e: # Occurs when password is not correctly formated - self.logger.debug("Auth error -> user: %s; details: %s;", - self.user, e) - try: - self.stack.loop(timeout=0.5, discrete=0.5) - except AuthError as e: # For some reason Yowsup throws an exception - self.logger.debug("Auth error -> user: %s; details: %s;", - self.user, e) + super(Session, self).login(self.legacyName, self.password) def updateRoomList(self): rooms = [] @@ -183,6 +130,86 @@ class Session(): 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) + entity = UnsubscribePresenceProtocolEntity(number + "@s.whatsapp.net") + self.toLower(entity) + + for number in add: + buddy = self.buddies[number] + entity = SubscribePresenceProtocolEntity(number + "@s.whatsapp.net") + self.toLower(entity) + + # 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.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.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])) + ) + + # 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 onMessageReceived(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(), 'read', + 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.session.sendMessageToXMPP(buddy, messageContent, timestamp) + + # if receiptRequested: self.call("message_ack", (jid, messageId)) + # spectrum RequestMethods def sendTypingStarted(self, buddy): if buddy != "bot": @@ -203,21 +230,21 @@ class Session(): elif "-" in sender: # group msg if "/" in sender: room, buddy = sender.split("/") - self.call("message_send", to = buddy + "@s.whatsapp.net", - message = message) + self.sendTextMessage(buddy + '@s.whatsapp.net', message) else: room = sender group = self.groups[room] self.backend.handleMessage(self.user, room, message, group.nick) - self.call("message_send", to = room + "@g.us", message = message) + 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.call("message_send", to=buddy + "@s.whatsapp.net", message=message) + self.sendTextMessage(buddy + '@s.whatsapp.net', message) def sendMessageToXMPP(self, buddy, messageContent, timestamp = ""): if timestamp: @@ -448,12 +475,12 @@ class SpectrumLayer(YowInterfaceLayer): entity = PresenceProtocolEntity(name = 'This status is non-empty') self.toLower(entity) retval = True - elif layerEvent.getName() == 'message_send': - to = layerEvent.getArg('to') - message = layerEvent.getArg('message') - messageEntity = TextMessageProtocolEntity(message, to = to) - self.toLower(messageEntity) - retval = True +# elif layerEvent.getName() == 'message_send': +# to = layerEvent.getArg('to') +# message = layerEvent.getArg('message') +# messageEntity = TextMessageProtocolEntity(message, to = to) +# self.toLower(messageEntity) +# retval = True elif layerEvent.getName() == 'typing_send': buddy = layerEvent.getArg('buddy') state = OutgoingChatstateProtocolEntity( @@ -476,82 +503,6 @@ class SpectrumLayer(YowInterfaceLayer): self.logger.debug("EVENT %s", layerEvent.getName()) return retval - - @ProtocolEntityCallback("success") - def onAuthSuccess(self, entity): - 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.session.initialized = True - - self.updateRoster() - - @ProtocolEntityCallback("failure") - def onAuthFailed(self, entity): - self.logger.info("Auth failed: %s (%s)", self.user, entity.getReason()) - self.backend.handleDisconnected(self.user, 0, entity.getReason()) - self.session.password = None - - @ProtocolEntityCallback("receipt") - def onReceipt(self, entity): - self.logger.debug("received receipt, sending ack: " + str(entity.ack())) - ack = OutgoingAckProtocolEntity(entity.getId(), "receipt", entity.getType(), entity.getFrom()) - self.toLower(ack) - - @ProtocolEntityCallback("ack") - def onAck(self, entity): - self.logger.debug('received ack ' + str(entity)) - - @ProtocolEntityCallback("notification") - def onNotification(self, notification): - self.toLower(notification.ack()) - - def updateRoster(self): - self.logger.debug("Update roster") - - old = self.session.buddies.keys() - self.session.buddies.load() - new = self.session.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) - entity = UnsubscribePresenceProtocolEntity(number + "@s.whatsapp.net") - self.toLower(entity) - - for number in add: - buddy = self.session.buddies[number] - entity = SubscribePresenceProtocolEntity(number + "@s.whatsapp.net") - self.toLower(entity) - - @ProtocolEntityCallback("message") - def onMessageReceived(self, messageEntity): - buddy = messageEntity.getFrom().split('@')[0] - messageContent = utils.softToUni(messageEntity.getBody()) - timestamp = messageEntity.getTimestamp() - - receipt = OutgoingReceiptProtocolEntity(messageEntity.getId(),messageEntity.getFrom(), 'read', messageEntity.getParticipant()) - self.toLower(receipt) - - 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.session.sendMessageToXMPP(buddy, messageContent, timestamp) - - - # if receiptRequested: self.call("message_ack", (jid, messageId)) - @ProtocolEntityCallback("presence") def onPrecenceUpdated(self, presence): jid = presence.getFrom() diff --git a/yowsup.py b/yowsupwrapper.py similarity index 64% rename from yowsup.py rename to yowsupwrapper.py index 4022417..70aa049 100644 --- a/yowsup.py +++ b/yowsupwrapper.py @@ -1,6 +1,8 @@ from yowsup import env from yowsup.stacks import YowStack +from yowsup.common import YowConstants +from yowsup.layers import YowLayerEvent, YowParallelLayer # Layers from yowsup.layers.auth import YowCryptLayer, YowAuthenticationProtocolLayer @@ -23,10 +25,15 @@ from yowsup.layers.protocol_privacy import YowPrivacyProtocolLayer from yowsup.layers.protocol_profiles import YowProfilesProtocolLayer from yowsup.layers.protocol_calls import YowCallsProtocolLayer +# ProtocolEntities + +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 * + class YowsupApp: def __init__(self): - self.logged_in = False - env.CURRENT_ENV = env.S40YowsupEnv() layers = (SpectrumLayer, @@ -57,6 +64,67 @@ class YowsupApp: 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(YowNetworkLayer.PROP_ENDPOINT, + YowConstants.ENDPOINTS[0]) + self.stack.setProp(YowCoderLayer.PROP_DOMAIN, + YowConstants.DOMAIN) + self.stack.setProp(YowCoderLayer.PROP_RESOURCE, + env.CURRENT_ENV.getResource()) +# 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 + - read: ('read' or something else) + - participant + """ + receipt = OutgoingReceiptProtocolEntity(_id, _from, read, participant) + self.stack.toLower(receipt) + + 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, to = to) + self.stack.toLower(messageEntity) + def onAuthSuccess(self, status, kind, creation, expiration, props, nonce, t): """ Called when login is successful. @@ -109,6 +177,11 @@ class YowsupApp: """ pass + def onDisconnect(self): + """ + Called when disconnected from whatsapp + """ + from yowsup.layers.interface import YowInterfaceLayer, ProtocolEntityCallback class YowsupAppLayer(YowInterfaceLayer): @@ -121,17 +194,21 @@ class YowsupAppLayer(YowInterfaceLayer): # return True otherwise if layerEvent.getName() == YowsupAppLayer.EVENT_START: self.caller = layerEvent.getArg('caller') + return True + elif layerEvent.getName() == YowNetworkLayer.EVENT_STATE_DISCONNECTED: + self.caller.onDisconnect() + return True @ProtocolEntityCallback('success') def onAuthSuccess(self, entity): # entity is SuccessProtocolEntity - status = entity.status - 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 + status = entity.status + 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') @@ -150,8 +227,8 @@ class YowsupAppLayer(YowInterfaceLayer): _id = entity._id _from = entity._from timestamp = entity.timestamp - type = entity.type - participant = entity.participant + type = entity.type + participant = entity.participant offline = entity.offline items = entity.items self.caller.onReceipt(_id, _from, timestamp, type, participant, offline, items) From 47d52ae22cec253e3cba20d3fcb0720f94aa455e Mon Sep 17 00:00:00 2001 From: moyamo Date: Sun, 30 Aug 2015 21:03:11 +0200 Subject: [PATCH 06/22] Use new-style classes and Session should call super.__init__ --- session.py | 1 + yowsupwrapper.py | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/session.py b/session.py index d877078..e7cd9b6 100644 --- a/session.py +++ b/session.py @@ -83,6 +83,7 @@ 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) diff --git a/yowsupwrapper.py b/yowsupwrapper.py index 70aa049..bc7e592 100644 --- a/yowsupwrapper.py +++ b/yowsupwrapper.py @@ -32,7 +32,7 @@ from yowsup.layers.protocol_messages.protocolentities import TextMessageProtoco from yowsup.layers.protocol_chatstate.protocolentities import * from yowsup.layers.protocol_acks.protocolentities import * -class YowsupApp: +class YowsupApp(object): def __init__(self): env.CURRENT_ENV = env.S40YowsupEnv() @@ -234,7 +234,7 @@ class YowsupAppLayer(YowInterfaceLayer): self.caller.onReceipt(_id, _from, timestamp, type, participant, offline, items) @ProtocolEntityCallback('ack') - def onAck(self, entity) + def onAck(self, entity): # entity is IncomingAckProtocolEntity self.caller.onAck( entity._id, From 48313c54755a147e58f014fcb1bf69305166d5f0 Mon Sep 17 00:00:00 2001 From: moyamo Date: Sun, 30 Aug 2015 22:18:03 +0200 Subject: [PATCH 07/22] Minor fixes --- session.py | 44 +++++++++++++++++++++++--------------------- yowsupwrapper.py | 18 +++++++++++++++--- 2 files changed, 38 insertions(+), 24 deletions(-) diff --git a/session.py b/session.py index e7cd9b6..8eec4a9 100644 --- a/session.py +++ b/session.py @@ -91,6 +91,8 @@ class Session(YowsupApp): 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 = '' @@ -191,7 +193,7 @@ class Session(YowsupApp): ) # Called by superclass - def onMessageReceived(self, messageEntity): + def onMessage(self, messageEntity): self.logger.debug(str(messageEntity)) buddy = messageEntity.getFrom().split('@')[0] messageContent = utils.softToUni(messageEntity.getBody()) @@ -226,26 +228,26 @@ class Session(YowsupApp): 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(buddy + '@s.whatsapp.net', message) +# 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: diff --git a/yowsupwrapper.py b/yowsupwrapper.py index bc7e592..576fa31 100644 --- a/yowsupwrapper.py +++ b/yowsupwrapper.py @@ -3,8 +3,10 @@ from yowsup import env from yowsup.stacks import YowStack from yowsup.common import YowConstants from yowsup.layers import YowLayerEvent, YowParallelLayer +from yowsup.layers.auth import AuthError # Layers +from yowsup.layers.axolotl import YowAxolotlLayer from yowsup.layers.auth import YowCryptLayer, YowAuthenticationProtocolLayer from yowsup.layers.coder import YowCoderLayer from yowsup.layers.logger import YowLoggerLayer @@ -36,7 +38,7 @@ class YowsupApp(object): def __init__(self): env.CURRENT_ENV = env.S40YowsupEnv() - layers = (SpectrumLayer, + layers = (YowsupAppLayer, YowParallelLayer((YowAuthenticationProtocolLayer, YowMessagesProtocolLayer, YowReceiptProtocolLayer, @@ -123,7 +125,7 @@ class YowsupApp(object): - message: (str) the body of the message """ messageEntity = TextMessageProtocolEntity(message, to = to) - self.stack.toLower(messageEntity) + self.sendEntity(messageEntity) def onAuthSuccess(self, status, kind, creation, expiration, props, nonce, t): """ @@ -181,11 +183,18 @@ class YowsupApp(object): """ Called when disconnected from whatsapp """ + + 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 + )) from yowsup.layers.interface import YowInterfaceLayer, ProtocolEntityCallback class YowsupAppLayer(YowInterfaceLayer): - EVENT_START = 'transwhat.event.SpectrumLayer.start' + EVENT_START = 'transwhat.event.YowsupAppLayer.start' + TO_LOWER_EVENT = 'transwhat.event.YowsupAppLayer.to_lower' def onEvent(self, layerEvent): # We cannot pass instance varaibles in through init, so we use an event @@ -198,6 +207,9 @@ class YowsupAppLayer(YowInterfaceLayer): 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 @ProtocolEntityCallback('success') def onAuthSuccess(self, entity): From 67c5a7c9515e2c1a463be710ca4ddafc48a99c22 Mon Sep 17 00:00:00 2001 From: moyamo Date: Wed, 2 Sep 2015 17:04:49 +0200 Subject: [PATCH 08/22] Add presence updates and looping asyncore.loop() Add functions to update presence and status Make asyncore.loop() timeout after some time and call the functions in the YowStack.__detachedQueue. --- session.py | 45 +++++++++++++++++++++++---------------------- transwhat.py | 10 +++++++++- yowsupwrapper.py | 35 +++++++++++++++++++++++++++++------ 3 files changed, 61 insertions(+), 29 deletions(-) diff --git a/session.py b/session.py index 8eec4a9..fda0edb 100644 --- a/session.py +++ b/session.py @@ -149,13 +149,13 @@ class Session(YowsupApp): for number in remove: self.backend.handleBuddyChanged(self.user, number, "", [], protocol_pb2.STATUS_NONE) self.backend.handleBuddyRemoved(self.user, number) - entity = UnsubscribePresenceProtocolEntity(number + "@s.whatsapp.net") - self.toLower(entity) +# entity = UnsubscribePresenceProtocolEntity(number + "@s.whatsapp.net") +# self.toLower(entity) for number in add: buddy = self.buddies[number] - entity = SubscribePresenceProtocolEntity(number + "@s.whatsapp.net") - self.toLower(entity) +# entity = SubscribePresenceProtocolEntity(number + "@s.whatsapp.net") +# self.toLower(entity) # Called by superclass def onAuthSuccess(self, status, kind, creation, @@ -176,6 +176,7 @@ class Session(YowsupApp): # Called by superclass def onDisconnect(self): + self.logger.debug('Disconnected') self.backend.handleDisconnected(self.user, 0, 'Disconnected for unknown reasons') self.loggedin = False @@ -199,7 +200,7 @@ class Session(YowsupApp): messageContent = utils.softToUni(messageEntity.getBody()) timestamp = messageEntity.getTimestamp() - self.sendReceipt(messageEntity.getId(), messageEntity.getFrom(), 'read', + self.sendReceipt(messageEntity.getId(), messageEntity.getFrom(), None, messageEntity.getParticipant()) if messageEntity.isBroadcast(): @@ -209,7 +210,7 @@ class Session(YowsupApp): else: self.logger.info("Message received from %s to %s: %s (at ts=%s)", buddy, self.legacyName, messageContent, timestamp) - self.session.sendMessageToXMPP(buddy, messageContent, timestamp) + self.sendMessageToXMPP(buddy, messageContent, timestamp) # if receiptRequested: self.call("message_ack", (jid, messageId)) @@ -284,14 +285,14 @@ class Session(YowsupApp): self.status = status if status == protocol_pb2.STATUS_ONLINE or status == protocol_pb2.STATUS_FFC: - self.call("presence_sendAvailable") + self.sendPresence(True) else: - self.call("presence_sendUnavailable") + self.sendPresence(False) def changeStatusMessage(self, statusMessage): if (statusMessage != self.statusMessage) or (self.initialized == False): self.statusMessage = statusMessage - self.call("profile_setStatus", message = statusMessage.encode("utf-8")) + self.setStatus(statusMessage.encode('utf-8')) self.logger.info("Status message changed: %s", statusMessage) if self.initialized == False: @@ -465,19 +466,19 @@ class SpectrumLayer(YowInterfaceLayer): reason = layerEvent.getArg("reason") self.logger.info("Disconnected: %s (%s)", self.user, reason) self.backend.handleDisconnected(self.user, 0, reason) - elif layerEvent.getName() == 'presence_sendAvailable': - entity = AvailablePresenceProtocolEntity() - self.toLower(entity) - retval = True - elif layerEvent.getName() == 'presence_sendUnavailable': - entity = UnavailablePresenceProtocolEntity() - self.toLower(entity) - retval = True - elif layerEvent.getName() == 'profile_setStatus': - # entity = PresenceProtocolEntity(name = layerEvent.getArg('message')) - entity = PresenceProtocolEntity(name = 'This status is non-empty') - self.toLower(entity) - retval = True +# elif layerEvent.getName() == 'presence_sendAvailable': +# entity = AvailablePresenceProtocolEntity() +# self.toLower(entity) +# retval = True +# elif layerEvent.getName() == 'presence_sendUnavailable': +# entity = UnavailablePresenceProtocolEntity() +# self.toLower(entity) +# retval = True +# elif layerEvent.getName() == 'profile_setStatus': +# # entity = PresenceProtocolEntity(name = layerEvent.getArg('message')) +# entity = PresenceProtocolEntity(name = 'This status is non-empty') +# self.toLower(entity) +# retval = True # elif layerEvent.getName() == 'message_send': # to = layerEvent.getArg('to') # message = layerEvent.getArg('message') diff --git a/transwhat.py b/transwhat.py index 90703a4..d378b9b 100755 --- a/transwhat.py +++ b/transwhat.py @@ -31,6 +31,7 @@ import sys, os import MySQLdb import e4u import threading +import Queue sys.path.insert(0, os.getcwd()) @@ -39,6 +40,7 @@ from Spectrum2.iochannel import IOChannel from whatsappbackend import WhatsAppBackend from constants import * from yowsup.common import YowConstants +from yowsup.stacks import YowStack # Arguments parser = argparse.ArgumentParser() @@ -71,4 +73,10 @@ io = IOChannel(args.host, args.port, handleTransportData) plugin = WhatsAppBackend(io, db) -asyncore.loop(1) +while True: + asyncore.loop(timeout=1.0, count=10, use_poll = True) + try: + callback = YowStack._YowStack__detachedQueue.get(False) #doesn't block + callback() + except Queue.Empty: + pass diff --git a/yowsupwrapper.py b/yowsupwrapper.py index 576fa31..2dbc7b4 100644 --- a/yowsupwrapper.py +++ b/yowsupwrapper.py @@ -33,6 +33,7 @@ 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.protocol_receipts.protocolentities import * class YowsupApp(object): def __init__(self): @@ -92,10 +93,10 @@ class YowsupApp(object): 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) +# 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): """ @@ -114,7 +115,7 @@ class YowsupApp(object): - participant """ receipt = OutgoingReceiptProtocolEntity(_id, _from, read, participant) - self.stack.toLower(receipt) + self.sendEntity(receipt) def sendTextMessage(self, to, message): """ @@ -127,6 +128,28 @@ class YowsupApp(object): messageEntity = TextMessageProtocolEntity(message, to = to) self.sendEntity(messageEntity) + 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 setStatus(self, statusText): + """ + Send status to whatsapp + + Args: + - statusTest: (str) Your whatsapp status + """ + entity = PresenceProtocolEntity(name = statusText if len(statusText) == 0 else 'this') + self.sendEntity(entity) + def onAuthSuccess(self, status, kind, creation, expiration, props, nonce, t): """ Called when login is successful. @@ -165,7 +188,7 @@ class YowsupApp(object): - offline: (True, False or None) - items """ - pass + pass def onAck(self, _id,_class, _from, timestamp): """ From e43aeedd9d204279997a6335e343f684c3d6b103 Mon Sep 17 00:00:00 2001 From: moyamo Date: Thu, 3 Sep 2015 20:04:29 +0200 Subject: [PATCH 09/22] Add presence and chatstate * Chatstate typing and typing stop is sent and received * Presence is received and presence is sent when user types --- Spectrum2/iochannel.py | 10 ++- buddy.py | 10 ++- session.py | 194 +++++++++++++++-------------------------- transwhat.py | 33 +++++-- yowsupwrapper.py | 129 ++++++++++++++++++++++++++- 5 files changed, 235 insertions(+), 141 deletions(-) diff --git a/Spectrum2/iochannel.py b/Spectrum2/iochannel.py index 2d142c2..858fc2a 100644 --- a/Spectrum2/iochannel.py +++ b/Spectrum2/iochannel.py @@ -1,14 +1,17 @@ import asyncore, socket import logging +import sys class IOChannel(asyncore.dispatcher): - def __init__(self, host, port, callback): + def __init__(self, host, port, callback, closeCallback): asyncore.dispatcher.__init__(self) self.create_socket(socket.AF_INET, socket.SOCK_STREAM) self.connect((host, port)) + self.logger = logging.getLogger(self.__class__.__name__) self.callback = callback + self.closeCallback = closeCallback self.buffer = "" def sendData(self, data): @@ -28,6 +31,11 @@ class IOChannel(asyncore.dispatcher): sent = self.send(self.buffer) self.buffer = self.buffer[sent:] + def handle_close(self): + self.logger.info('Connection to backend closed, terminating.') + self.close() + self.closeCallback() + def writable(self): return (len(self.buffer) > 0) diff --git a/buddy.py b/buddy.py index 8338c11..923327f 100644 --- a/buddy.py +++ b/buddy.py @@ -125,10 +125,12 @@ class BuddyList(dict): return Buddy.create(self.owner, Number(number, state, self.db), nick, groups, self.db) def remove(self, number): - buddy = self[number] - buddy.delete() - - return buddy + try: + buddy = self[number] + buddy.delete() + return buddy + except KeyError: + return None def prune(self): cur = self.db.cursor() diff --git a/session.py b/session.py index fda0edb..b480806 100644 --- a/session.py +++ b/session.py @@ -149,13 +149,19 @@ class Session(YowsupApp): for number in remove: self.backend.handleBuddyChanged(self.user, number, "", [], protocol_pb2.STATUS_NONE) self.backend.handleBuddyRemoved(self.user, number) -# entity = UnsubscribePresenceProtocolEntity(number + "@s.whatsapp.net") -# self.toLower(entity) + self.unsubscribePresence(number) for number in add: buddy = self.buddies[number] -# entity = SubscribePresenceProtocolEntity(number + "@s.whatsapp.net") -# self.toLower(entity) + 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, @@ -165,6 +171,7 @@ class Session(YowsupApp): 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() @@ -186,9 +193,12 @@ class Session(YowsupApp): ' '.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): + def onAck(self, _id, _class, _from, timestamp): self.logger.debug('received ack ' + ' '.join(map(str, [_id, _class, _from,timestamp,])) ) @@ -214,16 +224,68 @@ class Session(YowsupApp): # 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.call("typing_send", buddy = (buddy + "@s.whatsapp.net",)) + 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.call("typing_paused", buddy = (buddy + "@s.whatsapp.net",)) + self.sendTyping(buddy, False) def sendMessageToWA(self, sender, message): self.logger.info("Message sent from %s to %s: %s", self.legacyName, sender, message) @@ -352,20 +414,6 @@ class Session(YowsupApp): self.sendMessageToXMPP(buddy, "Received VCard (not implemented yet)") if receiptRequested: self.call("message_ack", (jid, messageId)) - def onContactTyping(self, jid): - buddy = jid.split("@")[0] - self.logger.info("Started typing: %s", buddy) - self.backend.handleBuddyTyping(self.user, buddy) - - if self.timer != None: - self.timer.cancel() - - def onContactPaused(self, jid): - buddy = jid.split("@")[0] - self.logger.info("Paused typing: %s", buddy) - self.backend.handleBuddyTyped(self.user, jid.split("@")[0]) - self.timer = Timer(3, self.backend.handleBuddyStoppedTyping, (self.user, buddy)).start() - def onGroupGotInfo(self, gjid, owner, subject, subjectOwner, subjectTimestamp, creationTimestamp): room = gjid.split("@")[0] owner = owner.split("@")[0] @@ -423,7 +471,7 @@ class Session(YowsupApp): room = gjid.split("@")[0] buddy = jid.split("@")[0] - loggin.info("Added % to room %s", buddy, room) + 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)) @@ -444,105 +492,3 @@ class Session(YowsupApp): def onGroupPictureUpdated(self, jid, author, timestamp, messageId, pictureId, receiptRequested): # TODO if receiptRequested: self.call("notification_ack", (jid, messageId)) - -class SpectrumLayer(YowInterfaceLayer): - EVENT_START = "transwhat.event.SpectrumLayer.start" - - def onEvent(self, layerEvent): - # We cannot use __init__, since it can take no arguments - retval = False - if layerEvent.getName() == SpectrumLayer.EVENT_START: - self.logger = logging.getLogger(self.__class__.__name__) - self.backend = layerEvent.getArg("backend") - self.user = layerEvent.getArg("user") - self.legacyName = layerEvent.getArg("legacyName") - self.db = layerEvent.getArg("db") - self.session = layerEvent.getArg("session") - - self.session.buddies = BuddyList(self.legacyName, self.db) - self.bot = Bot(self) - retval = True - elif layerEvent.getName() == YowNetworkLayer.EVENT_STATE_DISCONNECTED: - reason = layerEvent.getArg("reason") - self.logger.info("Disconnected: %s (%s)", self.user, reason) - self.backend.handleDisconnected(self.user, 0, reason) -# elif layerEvent.getName() == 'presence_sendAvailable': -# entity = AvailablePresenceProtocolEntity() -# self.toLower(entity) -# retval = True -# elif layerEvent.getName() == 'presence_sendUnavailable': -# entity = UnavailablePresenceProtocolEntity() -# self.toLower(entity) -# retval = True -# elif layerEvent.getName() == 'profile_setStatus': -# # entity = PresenceProtocolEntity(name = layerEvent.getArg('message')) -# entity = PresenceProtocolEntity(name = 'This status is non-empty') -# self.toLower(entity) -# retval = True -# elif layerEvent.getName() == 'message_send': -# to = layerEvent.getArg('to') -# message = layerEvent.getArg('message') -# messageEntity = TextMessageProtocolEntity(message, to = to) -# self.toLower(messageEntity) -# retval = True - elif layerEvent.getName() == 'typing_send': - buddy = layerEvent.getArg('buddy') - state = OutgoingChatstateProtocolEntity( - ChatstateProtocolEntity.STATE_TYPING, buddy - ) - self.toLower(state) - retval = True - elif layerEvent.getName() == 'typing_paused': - buddy = layerEvent.getArg('buddy') - state = OutgoingChatstateProtocolEntity( - ChatstateProtocolEntity.STATE_PAUSED, buddy - ) - self.toLower(state) - retval = True - elif layerEvent.getName() == 'presence_request': - buddy = layerEvent.getArg('buddy') - sub = SubscribePresenceProtocolEntity(buddy) - self.toLower(sub) - - self.logger.debug("EVENT %s", layerEvent.getName()) - return retval - - @ProtocolEntityCallback("presence") - def onPrecenceUpdated(self, presence): - jid = presence.getFrom() - lastseen = presence.getLast() - buddy = jid.split("@")[0] -# seems to be causing an error -# self.logger.info("Lastseen: %s %s", buddy, utils.ago(lastseen)) - - if buddy in self.session.presenceRequested: - timestamp = time.localtime(time.time() - lastseen) - timestring = time.strftime("%a, %d %b %Y %H:%M:%S", timestamp) - self.session.sendMessageToXMPP(buddy, "%s (%s)" % (timestring, utils.ago(lastseen))) - self.session.presenceRequested.remove(buddy) - - if lastseen < 60: - self.onPrecenceAvailable(jid) - else: - self.onPrecenceUnavailable(jid) - - def onPrecenceAvailable(self, jid): - buddy = jid.split("@")[0] - - try: - buddy = self.session.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 onPrecenceUnavailable(self, jid): - buddy = jid.split("@")[0] - - try: - buddy = self.session.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) - diff --git a/transwhat.py b/transwhat.py index d378b9b..84abc31 100755 --- a/transwhat.py +++ b/transwhat.py @@ -25,6 +25,7 @@ __status__ = "Prototype" """ import argparse +import traceback import logging import asyncore import sys, os @@ -54,9 +55,10 @@ parser.add_argument('-j', type=str, required=True) args, unknown = parser.parse_known_args() YowConstants.PATH_STORAGE='/var/lib/spectrum2/' + args.j +loggingfile = '/var/log/spectrum2/' + args.j + '/backends/backend.log' # Logging logging.basicConfig( \ - filename='/var/log/spectrum2/' + args.j + '/backends/backend.log',\ + filename=loggingfile,\ format = "%(asctime)-15s %(levelname)s %(name)s: %(message)s", \ level = logging.DEBUG if args.debug else logging.INFO \ ) @@ -67,16 +69,31 @@ def handleTransportData(data): e4u.load() +closed = False +def connectionClosed(): + global closed + closed = True + # Main db = MySQLdb.connect(DB_HOST, DB_USER, DB_PASS, DB_TABLE) -io = IOChannel(args.host, args.port, handleTransportData) +io = IOChannel(args.host, args.port, handleTransportData, connectionClosed) plugin = WhatsAppBackend(io, db) while True: - asyncore.loop(timeout=1.0, count=10, use_poll = True) - try: - callback = YowStack._YowStack__detachedQueue.get(False) #doesn't block - callback() - except Queue.Empty: - pass + try: + asyncore.loop(timeout=1.0, count=10, use_poll = True) + try: + callback = YowStack._YowStack__detachedQueue.get(False) #doesn't block + callback() + except Queue.Empty: + pass + else: + break + if closed: + break + except SystemExit: + break + except: + logger = logging.getLogger('transwhat') + logger.error(traceback.format_exc()) diff --git a/yowsupwrapper.py b/yowsupwrapper.py index 2dbc7b4..c4e497b 100644 --- a/yowsupwrapper.py +++ b/yowsupwrapper.py @@ -49,7 +49,7 @@ class YowsupApp(object): YowIqProtocolLayer, YowNotificationsProtocolLayer, YowContactsIqProtocolLayer, -# YowChatstateProtocolLayer, + YowChatstateProtocolLayer, YowCallsProtocolLayer, YowMediaProtocolLayer, YowPrivacyProtocolLayer, @@ -139,6 +139,30 @@ class YowsupApp(object): 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 + """ + 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 setStatus(self, statusText): """ @@ -149,6 +173,48 @@ class YowsupApp(object): """ entity = PresenceProtocolEntity(name = statusText if len(statusText) == 0 else 'this') self.sendEntity(entity) + + 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 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.stack.broadcastEvent( + YowLayerEvent(YowsupAppLayer.SEND_IQ, + iq = iq, + success = self._lastSeenSuccess(success), + failure = failure, + ) + ) + def _lastSeenSuccess(self, success): + def func(response, request): + success(response._from.split('@')[0], response.seconds) + return func def onAuthSuccess(self, status, kind, creation, expiration, props, nonce, t): """ @@ -201,12 +267,43 @@ class YowsupApp(object): - 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 sendEntity(self, entity): """Sends an entity down the stack (as if YowsupAppLayer called toLower)""" self.stack.broadcastEvent(YowLayerEvent(YowsupAppLayer.TO_LOWER_EVENT, @@ -217,7 +314,8 @@ from yowsup.layers.interface import YowInterfaceLayer, ProtocolEntityCallback class YowsupAppLayer(YowInterfaceLayer): EVENT_START = 'transwhat.event.YowsupAppLayer.start' - TO_LOWER_EVENT = 'transwhat.event.YowsupAppLayer.to_lower' + 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 @@ -233,6 +331,13 @@ class YowsupAppLayer(YowInterfaceLayer): 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): @@ -283,8 +388,24 @@ class YowsupAppLayer(YowInterfaceLayer): """ Sends ack automatically """ - self.toLower(notification.ack()) + self.toLower(entity.ack()) - @ProtocolEntityCallback("message") + @ProtocolEntityCallback('message') def onMessageReceived(self, entity): self.caller.onMessage(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('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) From a6971ad8895472c62c66ec98c3fccc45b26bc904 Mon Sep 17 00:00:00 2001 From: moyamo Date: Sat, 5 Sep 2015 11:39:34 +0200 Subject: [PATCH 10/22] Add workaround to prevent double messages For some reason spectrum occasionally sends to identical messages to a buddy, one to the bare jid and one to /bot. This causes duplicate messages. Since it is unlikely a user wants to send the same message twice, we should just ignore the second message. This is only a work around, a proper fix should be implemented. --- whatsappbackend.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/whatsappbackend.py b/whatsappbackend.py index 4aa0b17..4c07fb7 100644 --- a/whatsappbackend.py +++ b/whatsappbackend.py @@ -36,6 +36,8 @@ class WhatsAppBackend(SpectrumBackend): self.io = io self.db = db self.sessions = { } + # Used to prevent duplicate messages + self.lastMessage = {} self.logger.debug("Backend started") @@ -45,6 +47,9 @@ class WhatsAppBackend(SpectrumBackend): if user not in self.sessions: self.sessions[user] = Session(self, user, legacyName, extra, self.db) + if user not in self.lastMessage: + self.lastMessage[user] = {} + self.sessions[user].login(password) def handleLogoutRequest(self, user, legacyName): @@ -54,8 +59,15 @@ class WhatsAppBackend(SpectrumBackend): del self.sessions[user] def handleMessageSendRequest(self, user, buddy, message, xhtml = ""): - self.logger.debug("handleMessageSendRequest(user=%s, buddy=%s, message=%s)", user, buddy, message) - self.sessions[user].sendMessageToWA(buddy, message) + 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 + # messages. Since it is unlikely a user wants to send the same message + # twice, we should just ignore the second message + usersMessage = self.lastMessage[user] + if buddy not in usersMessage or usersMessage[buddy] != message: + self.sessions[user].sendMessageToWA(buddy, message) + usersMessage[buddy] = message def handleJoinRoomRequest(self, user, room, nickname, pasword): self.logger.debug("handleJoinRoomRequest(user=%s, room=%s, nickname=%s)", user, room, nickname) From 4c365e3f810fe61d79035aaaceeba312c1d7818d Mon Sep 17 00:00:00 2001 From: moyamo Date: Sat, 5 Sep 2015 22:05:34 +0200 Subject: [PATCH 11/22] Add receiveing of images, videos and sound files When a whatsapp buddy sends an image, video or sound to the spectrum user, the spectrum user will receive a URL that links to the media. FileTranfer in spectrum is not working, apparently. Thus media cannot be sent yet. --- Spectrum2/backend.py | 2 +- session.py | 82 +++++++++++++++++++++++++---------- whatsappbackend.py | 6 ++- yowsupwrapper.py | 101 +++++++++++++++++++++++++++++++++++++++++-- 4 files changed, 163 insertions(+), 28 deletions(-) diff --git a/Spectrum2/backend.py b/Spectrum2/backend.py index 3bef24f..a027adc 100644 --- a/Spectrum2/backend.py +++ b/Spectrum2/backend.py @@ -204,7 +204,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); diff --git a/session.py b/session.py index b480806..ddd14f8 100644 --- a/session.py +++ b/session.py @@ -204,26 +204,69 @@ class Session(YowsupApp): ) # 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() + def onTextMessage(self, _id, _from, to, notify, timestamp, participant, offline, retry, body): + self.logger.debug('received TextMessage' + + ' '.join(map(str, [ + _id, _from, to, notify, timestamp, + participant, offline, retry, body + ])) + ) + buddy = _from.split('@')[0] + messageContent = utils.softToUni(body) + self.sendReceipt(_id, _from, None, participant) + self.logger.info("Message received from %s to %s: %s (at ts=%s)", + buddy, self.legacyName, messageContent, timestamp) + self.sendMessageToXMPP(buddy, messageContent, timestamp) + # isBroadcast always returns false, I'm not sure how to get a broadcast + # message. + #if messageEntity.isBroadcast(): + # self.logger.info("Broadcast received from %s to %s: %s (at ts=%s)",\ + # buddy, self.legacyName, messageContent, timestamp) + # messageContent = "[Broadcast] " + messageContent - self.sendReceipt(messageEntity.getId(), messageEntity.getFrom(), None, - messageEntity.getParticipant()) + # Called by superclass + def onImage(self, image): + self.logger.debug('Received image message %s', str(image)) + buddy = image._from.split('@')[0] + message = image.url + ' ' + image.caption + self.sendMessageToXMPP(buddy, message, image.timestamp) + self.sendReceipt(image._id, image._from, None, image.participant) - 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) + # Called by superclass + def onAudio(self, audio): + self.logger.debug('Received audio message %s', str(audio)) + buddy = audio._from.split('@')[0] + message = audio.url + self.sendMessageToXMPP(buddy, message, audio.timestamp) + self.sendReceipt(audio._id, audio._from, None, audio.participant) - # if receiptRequested: self.call("message_ack", (jid, messageId)) + # Called by superclass + def onVideo(self, video): + self.logger.debug('Received video message %s', str(video)) + buddy = video._from.split('@')[0] + message = video.url + self.sendMessageToXMPP(buddy, message, video.timestamp) + self.sendReceipt(video._id, video._from, None, video.participant) + # Called by superclass + def onVCard(self, _id, _from, name, card_data, to, notify, timestamp, participant): + self.logger.debug('received VCard' + + ' '.join(map(str, [ + _id, _from, name, card_data, to, notify, timestamp, participant + ])) + ) + buddy = _from.split("@")[0] + self.sendMessageToXMPP(buddy, "Received VCard (not implemented yet)") + self.sendMessageToXMPP(buddy, card_data) + self.transferFile(buddy, str(name), card_data) + self.sendReceipt(_id, _from, None, participant) + + 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): @@ -408,11 +451,6 @@ class Session(YowsupApp): 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] @@ -471,7 +509,7 @@ class Session(YowsupApp): room = gjid.split("@")[0] buddy = jid.split("@")[0] - logger.info("Added % to room %s", buddy, room) + self.logger.info("Added %s 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)) diff --git a/whatsappbackend.py b/whatsappbackend.py index 4c07fb7..95a0ae1 100644 --- a/whatsappbackend.py +++ b/whatsappbackend.py @@ -64,6 +64,9 @@ class WhatsAppBackend(SpectrumBackend): # a buddy, one to the bare jid and one to /bot. This causes duplicate # messages. Since it is unlikely a user wants to send the same message # twice, we should just ignore the second message + # + # TODO Proper fix, this work around drops all duplicate messages even + # intentional ones. usersMessage = self.lastMessage[user] if buddy not in usersMessage or usersMessage[buddy] != message: self.sessions[user].sendMessageToWA(buddy, message) @@ -115,7 +118,8 @@ class WhatsAppBackend(SpectrumBackend): pass def handleFTStartRequest(self, user, buddy, fileName, size, ftID): - pass + 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 diff --git a/yowsupwrapper.py b/yowsupwrapper.py index c4e497b..78b0a69 100644 --- a/yowsupwrapper.py +++ b/yowsupwrapper.py @@ -30,7 +30,8 @@ from yowsup.layers.protocol_calls import YowCallsProtocolLayer # ProtocolEntities from yowsup.layers.protocol_presence.protocolentities import * -from yowsup.layers.protocol_messages.protocolentities import TextMessageProtocolEntity +from yowsup.layers.protocol_messages.protocolentities import * +from yowsup.layers.protocol_media.protocolentities import * from yowsup.layers.protocol_chatstate.protocolentities import * from yowsup.layers.protocol_acks.protocolentities import * from yowsup.layers.protocol_receipts.protocolentities import * @@ -110,8 +111,8 @@ class YowsupApp(object): Args: - _id: id of message received - - _from - - read: ('read' or something else) + - _from: jid of person who sent the message + - read: ('read' or None) None is just delivered, 'read' is read - participant """ receipt = OutgoingReceiptProtocolEntity(_id, _from, read, participant) @@ -304,6 +305,67 @@ class YowsupApp(object): """ 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: + - 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 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 sendEntity(self, entity): """Sends an entity down the stack (as if YowsupAppLayer called toLower)""" self.stack.broadcastEvent(YowLayerEvent(YowsupAppLayer.TO_LOWER_EVENT, @@ -392,7 +454,38 @@ class YowsupAppLayer(YowInterfaceLayer): @ProtocolEntityCallback('message') def onMessageReceived(self, entity): - self.caller.onMessage(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 + ) @ProtocolEntityCallback('presence') def onPresenceReceived(self, presence): From d2efa0dd7bc7858b62176f8f835e08a9f47afe17 Mon Sep 17 00:00:00 2001 From: moyamo Date: Sat, 5 Sep 2015 22:22:30 +0200 Subject: [PATCH 12/22] Fix status message --- session.py | 52 +----------------------------------------------- yowsupwrapper.py | 24 +++++++++++++--------- 2 files changed, 16 insertions(+), 60 deletions(-) diff --git a/session.py b/session.py index ddd14f8..63baa5b 100644 --- a/session.py +++ b/session.py @@ -27,50 +27,6 @@ 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 @@ -436,13 +392,7 @@ class Session(YowsupApp): 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)) - + # 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) diff --git a/yowsupwrapper.py b/yowsupwrapper.py index 78b0a69..0e62125 100644 --- a/yowsupwrapper.py +++ b/yowsupwrapper.py @@ -35,6 +35,7 @@ from yowsup.layers.protocol_media.protocolentities import * from yowsup.layers.protocol_chatstate.protocolentities import * from yowsup.layers.protocol_acks.protocolentities import * from yowsup.layers.protocol_receipts.protocolentities import * +from yowsup.layers.protocol_profiles.protocolentities import * class YowsupApp(object): def __init__(self): @@ -172,8 +173,8 @@ class YowsupApp(object): Args: - statusTest: (str) Your whatsapp status """ - entity = PresenceProtocolEntity(name = statusText if len(statusText) == 0 else 'this') - self.sendEntity(entity) + iq = SetStatusIqProtocolEntity(statusText) + self.sendIq(iq) def sendTyping(self, phoneNumber, typing): """ @@ -205,13 +206,8 @@ class YowsupApp(object): - failure: (func) called when request has failed """ iq = LastseenIqProtocolEntity(phoneNumber + '@s.whatsapp.net') - self.stack.broadcastEvent( - YowLayerEvent(YowsupAppLayer.SEND_IQ, - iq = iq, - success = self._lastSeenSuccess(success), - failure = failure, - ) - ) + self.sendIq(iq, self._lastSeenSuccess(success), failure) + def _lastSeenSuccess(self, success): def func(response, request): success(response._from.split('@')[0], response.seconds) @@ -371,6 +367,16 @@ class YowsupApp(object): 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 From 4fd313e516c31bb66116a3ca88ad0643eef4617c Mon Sep 17 00:00:00 2001 From: moyamo Date: Sun, 6 Sep 2015 00:14:12 +0200 Subject: [PATCH 13/22] 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. From 646f45434b07081eed722de0182c91ac01c4f9dd Mon Sep 17 00:00:00 2001 From: moyamo Date: Sun, 6 Sep 2015 14:06:16 +0200 Subject: [PATCH 14/22] Crude implementation of groupchat --- session.py | 103 +++++++++++++++++++++++++++-------------------- yowsupwrapper.py | 29 +++++++------ 2 files changed, 77 insertions(+), 55 deletions(-) diff --git a/session.py b/session.py index 6281a08..b86fa97 100644 --- a/session.py +++ b/session.py @@ -86,7 +86,7 @@ class Session(YowsupApp): rooms = [] for room, group in self.groups.iteritems(): rooms.append([room, group.subject]) - + self.logger.debug("Got rooms: %s", rooms) self.backend.handleRoomList(rooms) def updateRoster(self): @@ -111,7 +111,44 @@ class Session(YowsupApp): 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) + #self.requestLastSeen(number, self._lastSeen) + self.logger.debug('Requesting groups list') + self.requestGroupsList(self._updateGroups) + + def _updateGroups(self, response, request): + self.logger.debug('Received groups list %s', response) + # This XMPP client is not receiving this for some reason. + groups = response.getGroups() + for group in groups: + room = group.getId() + owner = group.getOwner() + subjectOwner = group.getSubjectOwner() + subject = group.getSubject() + + if room in self.groups: + oroom = self.groups[room] + oroom.owner = owner + oroom.subjectOwner = subjectOwner + oroom.subject = subject + else: + self.groups[room] = Group(room, owner, subject, subjectOwner) + # A crude implemtation of groups that act like buddies + + self.backend.handleBuddyChanged(self.user, room, subject, [], protocol_pb2.STATUS_NONE) + # This XMPP client is not receiving this for some reason. +# self.updateRoomList() +# for group in groups: +# room = group.getId() +# subjectOwner = group.getSubjectOwner() +# subject = group.getSubject() +# self.backend.handleSubject(self.user, room, subject, subjectOwner) +# for participant in group.getParticipants(): +# buddy = participant.split('@')[0] +# self.logger.debug("Added %s to room %s", buddy, room) +# self.backend.handleParticipantChanged(self.user, buddy, room, +# protocol_pb2.PARTICIPANT_FLAG_NONE, protocol_pb2.STATUS_ONLINE) + + def _lastSeen(self, number, seconds): self.logger.debug("Last seen %s at %s seconds" % (number, str(seconds))) @@ -173,7 +210,12 @@ class Session(YowsupApp): self.sendReceipt(_id, _from, None, participant) self.logger.info("Message received from %s to %s: %s (at ts=%s)", buddy, self.legacyName, messageContent, timestamp) - self.sendMessageToXMPP(buddy, messageContent, timestamp) + if participant is not None: + partname = participant.split('@')[0] + message = partname + ': ' + messageContent + self.sendMessageToXMPP(buddy, message, timestamp) + else: + self.sendMessageToXMPP(buddy, messageContent, timestamp) # isBroadcast always returns false, I'm not sure how to get a broadcast # message. #if messageEntity.isBroadcast(): @@ -291,28 +333,27 @@ class Session(YowsupApp): 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 + 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 + 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) + self.sendTextMessage(sender + '@s.whatsapp.net', message) - def sendMessageToXMPP(self, buddy, messageContent, timestamp = ""): + def sendMessageToXMPP(self, buddy, messageContent, timestamp = "", nickname = ""): if timestamp: timestamp = time.strftime("%Y%m%dT%H%M%S", time.gmtime(timestamp)) @@ -417,21 +458,6 @@ class Session(YowsupApp): 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] @@ -470,15 +496,6 @@ class Session(YowsupApp): 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] - - self.logger.info("Added %s 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] diff --git a/yowsupwrapper.py b/yowsupwrapper.py index ad374a5..8058b10 100644 --- a/yowsupwrapper.py +++ b/yowsupwrapper.py @@ -1,4 +1,3 @@ - from yowsup import env from yowsup.stacks import YowStack from yowsup.common import YowConstants @@ -29,13 +28,16 @@ from yowsup.layers.protocol_calls import YowCallsProtocolLayer # ProtocolEntities -from yowsup.layers.protocol_presence.protocolentities import * -from yowsup.layers.protocol_messages.protocolentities import * -from yowsup.layers.protocol_media.protocolentities import * +from yowsup.layers.protocol_acks.protocolentities import * from yowsup.layers.protocol_chatstate.protocolentities import * -from yowsup.layers.protocol_acks.protocolentities import * -from yowsup.layers.protocol_receipts.protocolentities import * +from yowsup.layers.protocol_groups.protocolentities import * +from yowsup.layers.protocol_media.protocolentities import * +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 functools import partial class YowsupApp(object): def __init__(self): @@ -206,12 +208,11 @@ class YowsupApp(object): - failure: (func) called when request has failed """ iq = LastseenIqProtocolEntity(phoneNumber + '@s.whatsapp.net') - self.sendIq(iq, onSuccess = self._lastSeenSuccess(success), onError = failure) + self.sendIq(iq, onSuccess = partial(self._lastSeenSuccess, success), + onError = failure) - def _lastSeenSuccess(self, success): - def func(response, request): - success(response._from.split('@')[0], response.seconds) - return func + def _lastSeenSuccess(self, success, response, request): + success(response._from.split('@')[0], response.seconds) def requestProfilePicture(self, phoneNumber, onSuccess = None, onFailure = None): """ @@ -223,6 +224,10 @@ class YowsupApp(object): """ 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 onAuthSuccess(self, status, kind, creation, expiration, props, nonce, t): """ @@ -322,7 +327,7 @@ class YowsupApp(object): - to: - notify: (str) human readable name of _from (e.g. John Smith) - timestamp: - - participant: + - participant: (str) jid of user who sent the message in a groupchat - offline: - retry: - body: The content of the message From 1b505f68574cb3ef62757806bd9d0ca0050eeb45 Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Mon, 7 Sep 2015 16:32:59 +0200 Subject: [PATCH 15/22] removed Google contacts import feature --- bot.py | 59 ----------- cgi/auth.py | 73 -------------- cgi/index.html | 11 -- cgi/sipgate.py | 237 -------------------------------------------- cgi/sniff.py | 99 ------------------ conf/apache.cfg | 63 ------------ constants.py.sample | 8 +- googleclient.py | 69 ------------- utils.py | 13 --- 9 files changed, 1 insertion(+), 631 deletions(-) delete mode 100755 cgi/auth.py delete mode 100644 cgi/index.html delete mode 100644 cgi/sipgate.py delete mode 100755 cgi/sniff.py delete mode 100644 conf/apache.cfg delete mode 100644 googleclient.py diff --git a/bot.py b/bot.py index 8712d91..aa9bd35 100644 --- a/bot.py +++ b/bot.py @@ -31,7 +31,6 @@ import os import utils from constants import * -#from googleclient import GoogleClient from Yowsup.Contacts.contacts import WAContactsSyncRequest @@ -40,10 +39,7 @@ class Bot(): self.session = session self.name = name - # self.google = GoogleClient() - self.commands = { -# "import": self._import, "help": self._help, "prune": self._prune, "welcome": self._welcome, @@ -79,41 +75,6 @@ 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 @@ -135,25 +96,6 @@ 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 @@ -170,7 +112,6 @@ 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 diff --git a/cgi/auth.py b/cgi/auth.py deleted file mode 100755 index 5eeb3c0..0000000 --- a/cgi/auth.py +++ /dev/null @@ -1,73 +0,0 @@ -#!/usr/bin/python - -__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 os -import sys -import cgi -import cgitb -import time - -sys.path.insert(1, os.path.join(sys.path[0], '..')) - -from constants import * - -def cookies(str): - return dict(c.split('=') for c in str.split(";")) - -def save_token(timestamp, number, token, filename="tokens"): - file = open(filename, 'a') - file.write("%s\t%s\t%s\n" % (str(timestamp), number, token)) - file.close() - -def main(): - form = cgi.FieldStorage() - number = form.getfirst("number") - auth_url = form.getfirst("auth_url") - token = form.getfirst("code") - - if auth_url: - print "Status: 301 Moved" - print "Location: %s" % auth_url - print "Content-type: text/html" - print "Set-Cookie: number=%s" % number - print "\n\n"; - - elif token and os.environ.has_key('HTTP_COOKIE'): - print "Status: 301 Moved" - print "Content-type: text/html" - print "Location: http://whatsapp.0l.de" - print - - c = cookies(os.environ['HTTP_COOKIE']) - save_token(time.time(), c['number'], token, TOKEN_FILE) - - else: - print "Content-type: text/html" - print "\n" - print "something strange happened :(" - -if __name__ == "__main__": - main() diff --git a/cgi/index.html b/cgi/index.html deleted file mode 100644 index 99a9632..0000000 --- a/cgi/index.html +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - diff --git a/cgi/sipgate.py b/cgi/sipgate.py deleted file mode 100644 index a3ece4b..0000000 --- a/cgi/sipgate.py +++ /dev/null @@ -1,237 +0,0 @@ -#!/usr/bin/env python -# -*- encoding: UTF8 -*- - -# author: Philipp Klaus, philipp.klaus →AT→ gmail.com - -# This file is part of python-sipgate-xmlrpc. -# -# python-sipgate-xmlrpc 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 -# (at your option) any later version. -# -# python-sipgate-xmlrpc 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 python-sipgate-xmlrpc. If not, see . - - -##################################################################### -###### This the most important file of the project: ####### -###### It contains the classe api, which ####### -###### implements the XML-RPC communication with the ####### -###### Sipgate API. ####### - -#from time import time -from sys import stderr -from xmlrpclib import ServerProxy, Fault, ProtocolError, ResponseError -from exceptions import TypeError -from socket import error as socket_error -import re - -VERSION = "0.9.2" -NAME = "%s - python-sipgate-xmlrpc/sipgate.py" -VENDOR = "https://github.com/pklaus/python-sipgate-xmlrpc" - -### ------- Here comes the most important piece of code: the api class with magic methods ----- - -class api (ServerProxy): - def __init__ (self, username=False, password=False, prog_name=False, verbose=False): - if not (username and password and prog_name): - raise SipgateAPIException('To use the class sipgate.api you must provide, username, password and a program name.') - address = SIPGATE_API_URL % {'username':username, 'password':password} - ### The super() call would be more modern but it doesn't work with the current Python version yet. - #super(api, self).__init__(address, verbose=debug) - ServerProxy.__init__(self, address,verbose=verbose) - ### It is considered good practice to Identify the client talking to the server: - self.ClientIdentify({ "ClientName" : NAME % prog_name, "ClientVersion" : VERSION, "ClientVendor" : VENDOR }) - - def __getattr__(self,name): - return _Method(self.__request, name) - - def __request (self, methodname, params): - if methodname.replace(API_PREFIX,'') not in VALID_METHODS: - stderr.write( UNKNOWN_METHOD_MESSAGE % { - 'method': methodname.replace(API_PREFIX,''), 'api_prefix': API_PREFIX, - 'api_version': SIPGATE_API_DOC_V, 'api_date': SIPGATE_API_DOC_D } ) - if len(params)>0 and not type(params[0]) is dict: - raise TypeError(DICT_AS_PARAM_MESSAGE % methodname.replace(API_PREFIX,'')) - method_function = ServerProxy.__getattr__(self,methodname) - try: - result = method_function(params[0] if len(params)>0 and type(params[0]) is dict else dict()) - # cast the result dictionary to a SipgateResponse (custom dictionary): - result = SipgateResponse(result) - except Fault, e: - raise SipgateAPIFault(e.faultCode, e.faultString) - except ProtocolError, e: - raise SipgateAPIProtocolError(e.url, e.errcode, e.errmsg, e.headers) - except socket_error, (value,message): - raise SipgateAPISocketError(value, message) - return result - -## -class SipgateResponse(dict): - def __init__(self, response_dict): - try: - self.StatusCode, self.StatusString = int(response_dict['StatusCode']), response_dict['StatusString'] - self.success = self.StatusCode == 200 - except: - raise TypeError(RESPONSE_NOT_A_DICTIONARY % response_dict) - dict.__init__(self, response_dict) - -class _Method: - # With the help of this class the api class does not - # need to state explicitly the possible XML-RPC calls. - def __init__(self, send, name): - self.__send = send - self.__name = API_PREFIX+name - def __call__(self, *args): - return self.__send(self.__name, args) - -### ------ now we define the exceptions that could occur ------ - -class SipgateAPIException(Exception): - pass - -class SipgateAPIFault(Fault, SipgateAPIException): - # As this inherits from xmlrpclib.Fault it also has the - # attributes faultCode and faultString. - pass - -class SipgateAPIProtocolError(ProtocolError, SipgateAPIException): - # As this inherits from xmlrpclib.ProtocolError it also has the - # attributes errcode and errmsg. - pass - -class SipgateAPISocketError(socket_error, SipgateAPIException): - # As this inherits from socket.error it also has the - # attributes . - pass - -### ------ This section contains message strings ------- - -UNKNOWN_METHOD_MESSAGE = "The method '%(method)s' for the API prefix '%(api_prefix)s' " + \ - "was called. This method, however, is currently not documented for the Sipgate API " + \ - "v%(api_version)s (%(api_date)s). Let's try but I've warned you.\n" -DICT_AS_PARAM_MESSAGE = 'Please specify a dictionary as function call parameter for api.%s().' -RESPONSE_NOT_A_DICTIONARY = 'The response "%s" does not seem to be a response from the ' + \ - 'Sipgate XML-RPC API.' - -### ------ This section contains constants of the Sipgate XML-RPC API ------- - -# This constant represents the version of the currently implemented Sipgate API -# ans is taken from the API description PDF: -SIPGATE_API_DOC_V = '1.06' -SIPGATE_API_DOC_D = 'August 21, 2007' - -# Sipgate basic and plus accounts must use this API URL: -SIPGATE_API_URL = "https://%(username)s:%(password)s@samurai.sipgate.net/RPC2" -# Sipgate one and team have a different URL: api.sipgate.net. -# see -API_PREFIX = 'samurai.' - -VALID_METHODS = [ - 'AccountStatementGet', - 'BalanceGet', - 'ClientIdentify', - 'HistoryGetByDate', - 'ItemizedEntriesGet', - 'OwnUriListGet', - 'PhonebookEntryGet', - 'PhonebookListGet', - 'RecommendedIntervalGet', - 'ServerdataGet', - 'SessionClose', - 'SessionInitiate', - 'SessionInitiateMulti', - 'SessionStatusGet', - 'TosListGet', - 'TosListGet', - 'UmSummaryGet', - 'UserdataGreetingGet', - 'UserdataSipGet', -] - -SERVER_STATUS_CODES = { - ### From Table A.1 and A.2 of the API docu: general server status codes - 200: 'Method success', - 400: 'Method not supported', - 401: 'Request denied (no reason specified)', - 402: 'Internal error', - 403: 'Invalid arguments', - 404: 'Resources exceeded (this MUST not be used to indicate parameters in error)', - 405: 'Invalid parameter name', - 406: 'Invalid parameter type', - 407: 'Invalid parameter value', - 408: 'Attempt to set a non-writable parameter', - 409: 'Notification request rejected.', - 410: 'Parameter exceeds maximum size.', - 411: 'Missing parameter.', - 412: 'Too many requests.', - 500: 'Date out of range.', - 501: 'Uri does not belong to user.', - 502: 'Unknown type of service.', - 503: 'Selected payment method failed.', - 504: 'Selected currency not supported.', - 505: 'Amount exceeds limit.', - 506: 'Malformed SIP URI.', - 507: 'URI not in list.', - 508: 'Format is not valid E.164.', - 509: 'Unknown status.', - 510: 'Unknown ID.', - 511: 'Invalid timevalue.', - 512: 'Referenced session not found.', - 513: 'Only single default per TOS allowed.', - 514: 'Malformed VCARD format.', - 515: 'Malformed PID format.', - 516: 'Presence information not available.', - 517: 'Invalid label name.', - 518: 'Label not assigned.', - 519: 'Label doesn’t exist.', - 520: 'Parameter includes invalid characters.', - 521: 'Bad password. (Rejected due to security concerns.)', - 522: 'Malformed timezone format.', - 523: 'Delay exceeds limit.', - 524: 'Requested VPN type not available.', - 525: 'Requested TOS not available.', - 526: 'Unified messaging not available.', - 527: 'URI not available for registration.', -} - -TYPE_OF_SERVICE = { - 'fax': 'pages', # fax transmission - 'text': 'characters', # text message (e.g. "SMS") - 'video': 'seconds', # video communication - 'voice': 'seconds', # voice communication -} - - -class helpers (object): - @staticmethod - def FQTN(phone_number, default_country_code): - """ - Assures phone numbers are in the form of a E164 Fully Qualified Telephone Number - without the leading + sign. - The alternative would be the Python port of Google's libphonenumber: - https://github.com/daviddrysdale/python-phonenumbers - """ - phone_number = phone_number.replace(' ','').replace('-','').replace('+','').replace('/','') - - ## number starting with 00 (so it's an international format) - if re.compile("^00[1-9][0-9]*$").match(phone_number): - return phone_number[2:] - - ## number starting with your country code (so it was already a FQTN): - if re.compile("^"+default_country_code+"[1-9][0-9]*$").match(phone_number): - return phone_number - - if re.compile("^0[1-9]*$").match(phone_number): - return default_country_code+phone_number[1:] - - if re.compile("^[1-9]*$").match(phone_number): - return phone_number - - raise TypeError("Couldn't parse this phone number: "+phone_number) diff --git a/cgi/sniff.py b/cgi/sniff.py deleted file mode 100755 index 1fdac92..0000000 --- a/cgi/sniff.py +++ /dev/null @@ -1,99 +0,0 @@ -#!/usr/bin/python - -__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 os -import sys -import cgi -import cgitb -import time -import pycurl -import StringIO -import json -import sipgate - -sys.path.insert(1, os.path.join(sys.path[0], '..')) - -from constants import * - -def send_sms(recipient, content): - sg = sipgate.api(SIPGATE_USERNAME, SIPGATE_PASSWORD, 'transwhat') - - default_uri = 'sip:NULL@sipgate.net' - for own_uri in sg.OwnUriListGet()['OwnUriList']: - if own_uri['DefaultUri']: - default_uri = own_uri['SipUri'] - - # SessionInitiate may return the following server status codes in case of errors: 501, 502, 506, 520, 525 - return sg.SessionInitiate({'LocalUri': default_uri, 'RemoteUri': 'sip:%s@sipgate.de' % recipient, 'TOS': 'text', 'Content': content }) - -def main(): - url = os.environ['SCRIPT_URI'] + '?' + os.environ['QUERY_STRING'] - - writer = StringIO.StringIO() - ch = pycurl.Curl() - - ch.setopt(pycurl.URL, url) - ch.setopt(pycurl.USERAGENT, os.environ['HTTP_USER_AGENT']) - - ch.setopt(pycurl.WRITEFUNCTION, writer.write) - ch.setopt(pycurl.SSL_VERIFYPEER, False) - ch.setopt(pycurl.HEADER, True) - - ch.perform() - - response = writer.getvalue() - headers, body = response.split("\r\n\r\n", 1) - headers = headers.split("\n") - preamble = headers.pop(0) - - code = preamble.split(" ", 2)[1] - status = preamble.split(" ", 2)[2] - - print "Status: %s %s" % (code, status) - for header in headers: - print header - - print - print body - - file = open(REQUESTS_FILE, "a") - file.write("\n--- Time: %s\n>>> Request: %s\n<<< Reponse Headers:\n%s\nResponse Body:\n%s\n" % (time.strftime("%a, %d %b %Y %H:%M:%S"), url, "\n".join(headers), body)) - file.close() - - # send password via sms to requester - if code == "200": - parsed = json.loads(body) - form = cgi.FieldStorage() - cc = form.getfirst("cc") - number = form.getfirst("in") - - if parsed.has_key('pw') and parsed.has_key('login'): - send_sms(parsed['login'], parsed['pw']) - - ch.close() - -if __name__ == "__main__": - main() diff --git a/conf/apache.cfg b/conf/apache.cfg deleted file mode 100644 index f5f1124..0000000 --- a/conf/apache.cfg +++ /dev/null @@ -1,63 +0,0 @@ - - ServerAdmin webmaster@0l.de - ServerName whatsapp.0l.de - - DocumentRoot /home/stv0g/files/whatsapp/transwhat/cgi - - Options FollowSymLinks - AllowOverride None - - - Options Indexes FollowSymLinks MultiViews +ExecCGI - AllowOverride All - Order allow,deny - allow from all - AddHandler cgi-script .py - - - ErrorLog /home/stv0g/files/whatsapp/htdocs/error.log - CustomLog /home/stv0g/files/whatsapp/htdocs/access.log combined - - - - - ServerAdmin webmaster@0l.de - ServerName whatsapp.0l.de - ServerAlias v.whatsapp.net - - DocumentRoot /home/stv0g/files/whatsapp/transwhat/cgi - - Options FollowSymLinks - AllowOverride None - - - Options Indexes FollowSymLinks MultiViews +ExecCGI - AllowOverride None - Order allow,deny - allow from all - AddHandler cgi-script .py - - - ErrorLog /home/stv0g/files/whatsapp/htdocs/error.log - CustomLog /home/stv0g/files/whatsapp/htdocs/access.log combined - - LogLevel info - # debug, info, notice, warn, error, crit, alert, emerg. - - # Rewrite - RewriteEngine on - - RewriteCond %{REQUEST_FILENAME} !-f - RewriteCond %{REQUEST_FILENAME} !-d - RewriteRule (.*) /sniff.py/$1 - - # SSL - SSLEngine on - SSLCertificateFile /home/stv0g/files/whatsapp/htdocs/whatsapp.crt - SSLCertificateKeyFile /home/stv0g/files/whatsapp/htdocs/whatsapp.key - - - SSLOptions +StdEnvVars - - - diff --git a/constants.py.sample b/constants.py.sample index 9856612..958c2e9 100644 --- a/constants.py.sample +++ b/constants.py.sample @@ -31,10 +31,4 @@ BASE_PATH = "/opt/transwhat" TOKEN_FILE = BASE_PATH + "/logs/tokens" MOTD_FILE = BASE_PATH + "/conf/motd" -REQUESTS_FILE = BASE_PATH + "/logs/requests" - -GOOGLE_CLIENT_ID = "" -GOOGLE_CLIENT_SECRET = "" - -SIPGATE_USERNAME="" -SIPGATE_PASSWORD="" +REQUESTS_FILE = BASE_PATH + "/logs/requests" \ No newline at end of file diff --git a/googleclient.py b/googleclient.py deleted file mode 100644 index 899bbd0..0000000 --- a/googleclient.py +++ /dev/null @@ -1,69 +0,0 @@ -__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 sys - -import gdata.gauth -import gdata.contacts.client -import gdata.contacts.data -import atom.data - -from constants import * - -gdata.contacts.REL_MOBILE='http://schemas.google.com/g/2005#mobile' - -class GoogleClient(): - - def __init__(self): - self.client = gdata.contacts.client.ContactsClient() - # self.token = gdata.gauth.OAuth2Token( - # client_id = GOOGLE_CLIENT_ID, - # client_secret = GOOGLE_CLIENT_SECRET, - # scope = 'https://www.google.com/m8/feeds/contacts', - # user_agent = 'whatTrans' - # ) - - def getTokenUrl(self, uri = 'urn:ietf:wg:oauth:2.0:oob'): - return self.token.generate_authorize_url(redirect_uri=uri) - - def getContacts(self, request_token): - access_token = self.token.get_access_token(request_token) - - self.token.authorize(self.client) - - numbers = { } - - feed = self.client.GetContacts() - while feed: - for i, entry in enumerate(feed.entry): - for number in entry.phone_number: - numbers[number.text] = entry.title.text - - next = feed.GetNextLink() - if next: - feed = self.client.GetContacts(next.href) - else: - break - - return numbers diff --git a/utils.py b/utils.py index c2e1ba6..69a0c44 100644 --- a/utils.py +++ b/utils.py @@ -22,23 +22,10 @@ __status__ = "Prototype" along with transWhat. If not, see . """ -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)) - response = url.read() - response = json.loads(response) - - for entry in response: - if entry['type'] == 'success': - host = entry['data'][0]['host'] - return "http://s.%s/%s" % (host['zone']['name'], host['punycode']) - - def ago(secs): periods = ["second", "minute", "hour", "day", "week", "month", "year", "decade"] lengths = [60, 60, 24, 7,4.35, 12, 10] From 54c61f4fcc558eef6591f7a3ac6fbf45d14e3e6a Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Mon, 7 Sep 2015 16:32:08 +0200 Subject: [PATCH 16/22] updated dependency section of README file --- README.md | 37 +++++++++++-------------------------- 1 file changed, 11 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index 286f631..4eaffbc 100644 --- a/README.md +++ b/README.md @@ -1,35 +1,21 @@ # transWhat -transWhat is a WhatsApp XMPP Gateway based on Spectrum2 +transWhat is a WhatsApp XMPP Gateway based on [Spectrum 2](http://www.spectrum.im) and [Yowsup 2](https://github.com/tgalal/yowsup). ## Dependencies +#### Python packages + + pip install e4u protobuf mysql dateutil + + - **e4u**: is a simple emoji4unicode python bindings + - **yowsup**: + - **mysqldb**: MySQL client python bindings + #### Spectrum 2 is a XMPP transport -Manual compile latest version from https://github.com/hanzz/libtransport - -#### e4u -is a simple emoji4unicode python wrapper library - -Install with `pip install e4u` - -#### Yowsup -is a Implementation of the WhatsApp protocol in python - -Use my patched version at https://github.com/stv0g/yowsup - -#### Google Atom and GData Python wrappers -required for Google contacts import - -#### MySQLdb -required - -#### Google protobuf -required - -#### date.util -required +Manual compile latest version from https://github.com/hanzz/libtransport. ## Contribute @@ -39,6 +25,5 @@ Help us to provide a open implementation of the WhatsApp protocol. ## Documentation A project wiki is available [here](http://dev.0l.de/projects/transwhat/start). -A mailinglist for discussion is available [here](http://lists.0l.de/listinfo/whatsapp). -A writeup of this project is also availabe at my [blog](http://www.steffenvogel.de/2013/06/29/transwhat/). +An *outdated* writeup of this project is also availabe at my [blog](http://www.steffenvogel.de/2013/06/29/transwhat/). From 291a7647c06f84af633a539100e25df8b461f2d9 Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Mon, 7 Sep 2015 16:37:59 +0200 Subject: [PATCH 17/22] updated contributors --- README.md | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 4eaffbc..5b204eb 100644 --- a/README.md +++ b/README.md @@ -17,10 +17,14 @@ is a XMPP transport Manual compile latest version from https://github.com/hanzz/libtransport. -## Contribute +## Contributors -Pull requests, bug reports etc. are welcome. -Help us to provide a open implementation of the WhatsApp protocol. +Pull requests, bug reports etc. are welcome. Help us to provide a open implementation of the WhatsApp protocol. + +The following persons have contributed major parts of this code: + + - **Steffen Vogel** (@stv0g): Idea and initial implementation based on Yowsup 1 + - **Mohammed Yaseen Mowzer** (@moyamo): Port to Yowsup 2 ## Documentation From f64665b45fd010072124267595b3fd5eb7189fe4 Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Mon, 7 Sep 2015 18:08:48 +0200 Subject: [PATCH 18/22] updated message of the day --- conf/motd | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/conf/motd b/conf/motd index 3cc61bc..e274226 100644 --- a/conf/motd +++ b/conf/motd @@ -1,9 +1,11 @@ Welcome to transWhat! + ===== NEWS ==== - 03.06.13 transWhat service is born -- 10.06.13 added bot user to import contacts and adjust settings, see http://2p.0l.de -- 14.06.13 finally enable password sniffing, see http://2o.0l.de - 18.06.13 major deployment of development version +- 07.09.15 transWhat is alive again. Now running with new Yowsup 2 library Type "\help" for a list of available commands. -Visit http://whatsapp.0l.de for the full documentation. + +Visit http://github.com/stv0g/transwhat/ for more details. +Join xmpp://transwhat@conference.jabber.ccc.de to hangout and discuss. From e92d36a2405e9bb7d016f340bd48703b6f94bfc9 Mon Sep 17 00:00:00 2001 From: moyamo Date: Mon, 14 Sep 2015 19:55:53 +0200 Subject: [PATCH 19/22] Regenerate protocol buffers --- Spectrum2/protocol_pb2.py | 612 +++++++++++++++++++++----------------- 1 file changed, 347 insertions(+), 265 deletions(-) diff --git a/Spectrum2/protocol_pb2.py b/Spectrum2/protocol_pb2.py index e01bd27..638435c 100644 --- a/Spectrum2/protocol_pb2.py +++ b/Spectrum2/protocol_pb2.py @@ -1,88 +1,99 @@ # Generated by the protocol buffer compiler. DO NOT EDIT! +# source: protocol.proto -from google.protobuf import descriptor -from google.protobuf import message -from google.protobuf import reflection +import sys +_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1')) +from google.protobuf.internal import enum_type_wrapper +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from google.protobuf import reflection as _reflection +from google.protobuf import symbol_database as _symbol_database from google.protobuf import descriptor_pb2 # @@protoc_insertion_point(imports) +_sym_db = _symbol_database.Default() -DESCRIPTOR = descriptor.FileDescriptor( + + + +DESCRIPTOR = _descriptor.FileDescriptor( name='protocol.proto', package='pbnetwork', - serialized_pb='\n\x0eprotocol.proto\x12\tpbnetwork\"\x19\n\tConnected\x12\x0c\n\x04user\x18\x01 \x02(\t\"<\n\x0c\x44isconnected\x12\x0c\n\x04user\x18\x01 \x02(\t\x12\r\n\x05\x65rror\x18\x02 \x02(\x05\x12\x0f\n\x07message\x18\x03 \x01(\t\"P\n\x05Login\x12\x0c\n\x04user\x18\x01 \x02(\t\x12\x12\n\nlegacyName\x18\x02 \x02(\t\x12\x10\n\x08password\x18\x03 \x02(\t\x12\x13\n\x0b\x65xtraFields\x18\x04 \x03(\t\"*\n\x06Logout\x12\x0c\n\x04user\x18\x01 \x02(\t\x12\x12\n\nlegacyName\x18\x02 \x02(\t\"\xab\x01\n\x05\x42uddy\x12\x10\n\x08userName\x18\x01 \x02(\t\x12\x11\n\tbuddyName\x18\x02 \x02(\t\x12\r\n\x05\x61lias\x18\x03 \x01(\t\x12\r\n\x05group\x18\x04 \x03(\t\x12%\n\x06status\x18\x05 \x01(\x0e\x32\x15.pbnetwork.StatusType\x12\x15\n\rstatusMessage\x18\x06 \x01(\t\x12\x10\n\x08iconHash\x18\x07 \x01(\t\x12\x0f\n\x07\x62locked\x18\x08 \x01(\x08\"\xa9\x01\n\x13\x43onversationMessage\x12\x10\n\x08userName\x18\x01 \x02(\t\x12\x11\n\tbuddyName\x18\x02 \x02(\t\x12\x0f\n\x07message\x18\x03 \x02(\t\x12\x10\n\x08nickname\x18\x04 \x01(\t\x12\r\n\x05xhtml\x18\x05 \x01(\t\x12\x11\n\ttimestamp\x18\x06 \x01(\t\x12\x10\n\x08headline\x18\x07 \x01(\x08\x12\n\n\x02id\x18\x08 \x01(\t\x12\n\n\x02pm\x18\t \x01(\x08\"J\n\x04Room\x12\x10\n\x08userName\x18\x01 \x02(\t\x12\x10\n\x08nickname\x18\x02 \x02(\t\x12\x0c\n\x04room\x18\x03 \x02(\t\x12\x10\n\x08password\x18\x04 \x01(\t\"&\n\x08RoomList\x12\x0c\n\x04room\x18\x01 \x03(\t\x12\x0c\n\x04name\x18\x02 \x03(\t\"\x9c\x01\n\x0bParticipant\x12\x10\n\x08userName\x18\x01 \x02(\t\x12\x0c\n\x04room\x18\x02 \x02(\t\x12\x10\n\x08nickname\x18\x03 \x02(\t\x12\x0c\n\x04\x66lag\x18\x04 \x02(\x05\x12%\n\x06status\x18\x05 \x02(\x0e\x32\x15.pbnetwork.StatusType\x12\x15\n\rstatusMessage\x18\x06 \x01(\t\x12\x0f\n\x07newname\x18\x07 \x01(\t\"k\n\x05VCard\x12\x10\n\x08userName\x18\x01 \x02(\t\x12\x11\n\tbuddyName\x18\x02 \x02(\t\x12\n\n\x02id\x18\x03 \x02(\x05\x12\x10\n\x08\x66ullname\x18\x04 \x01(\t\x12\x10\n\x08nickname\x18\x05 \x01(\t\x12\r\n\x05photo\x18\x06 \x01(\x0c\"X\n\x06Status\x12\x10\n\x08userName\x18\x01 \x02(\t\x12%\n\x06status\x18\x03 \x02(\x0e\x32\x15.pbnetwork.StatusType\x12\x15\n\rstatusMessage\x18\x04 \x01(\t\"B\n\x05Stats\x12\x0b\n\x03res\x18\x01 \x02(\x05\x12\x10\n\x08init_res\x18\x02 \x02(\x05\x12\x0e\n\x06shared\x18\x03 \x02(\x05\x12\n\n\x02id\x18\x04 \x02(\t\"Y\n\x04\x46ile\x12\x10\n\x08userName\x18\x01 \x02(\t\x12\x11\n\tbuddyName\x18\x02 \x02(\t\x12\x10\n\x08\x66ileName\x18\x03 \x02(\t\x12\x0c\n\x04size\x18\x04 \x02(\x05\x12\x0c\n\x04\x66tID\x18\x05 \x01(\x05\".\n\x10\x46ileTransferData\x12\x0c\n\x04\x66tID\x18\x01 \x02(\x05\x12\x0c\n\x04\x64\x61ta\x18\x02 \x02(\x0c\"\x1f\n\rBackendConfig\x12\x0e\n\x06\x63onfig\x18\x01 \x02(\t\"\x9a\x06\n\x0eWrapperMessage\x12,\n\x04type\x18\x01 \x02(\x0e\x32\x1e.pbnetwork.WrapperMessage.Type\x12\x0f\n\x07payload\x18\x02 \x01(\x0c\"\xc8\x05\n\x04Type\x12\x12\n\x0eTYPE_CONNECTED\x10\x01\x12\x15\n\x11TYPE_DISCONNECTED\x10\x02\x12\x0e\n\nTYPE_LOGIN\x10\x03\x12\x0f\n\x0bTYPE_LOGOUT\x10\x04\x12\x16\n\x12TYPE_BUDDY_CHANGED\x10\x06\x12\x16\n\x12TYPE_BUDDY_REMOVED\x10\x07\x12\x15\n\x11TYPE_CONV_MESSAGE\x10\x08\x12\r\n\tTYPE_PING\x10\t\x12\r\n\tTYPE_PONG\x10\n\x12\x12\n\x0eTYPE_JOIN_ROOM\x10\x0b\x12\x13\n\x0fTYPE_LEAVE_ROOM\x10\x0c\x12\x1c\n\x18TYPE_PARTICIPANT_CHANGED\x10\r\x12\x1e\n\x1aTYPE_ROOM_NICKNAME_CHANGED\x10\x0e\x12\x1d\n\x19TYPE_ROOM_SUBJECT_CHANGED\x10\x0f\x12\x0e\n\nTYPE_VCARD\x10\x10\x12\x17\n\x13TYPE_STATUS_CHANGED\x10\x11\x12\x15\n\x11TYPE_BUDDY_TYPING\x10\x12\x12\x1d\n\x19TYPE_BUDDY_STOPPED_TYPING\x10\x13\x12\x14\n\x10TYPE_BUDDY_TYPED\x10\x14\x12\x15\n\x11TYPE_AUTH_REQUEST\x10\x15\x12\x12\n\x0eTYPE_ATTENTION\x10\x16\x12\x0e\n\nTYPE_STATS\x10\x17\x12\x11\n\rTYPE_FT_START\x10\x18\x12\x12\n\x0eTYPE_FT_FINISH\x10\x19\x12\x10\n\x0cTYPE_FT_DATA\x10\x1a\x12\x11\n\rTYPE_FT_PAUSE\x10\x1b\x12\x14\n\x10TYPE_FT_CONTINUE\x10\x1c\x12\r\n\tTYPE_EXIT\x10\x1d\x12\x17\n\x13TYPE_BACKEND_CONFIG\x10\x1e\x12\x0e\n\nTYPE_QUERY\x10\x1f\x12\x12\n\x0eTYPE_ROOM_LIST\x10 \x12\x19\n\x15TYPE_CONV_MESSAGE_ACK\x10!\x12\x10\n\x0cTYPE_RAW_XML\x10\"*\xb3\x05\n\x0f\x43onnectionError\x12\"\n\x1e\x43ONNECTION_ERROR_NETWORK_ERROR\x10\x00\x12%\n!CONNECTION_ERROR_INVALID_USERNAME\x10\x01\x12*\n&CONNECTION_ERROR_AUTHENTICATION_FAILED\x10\x02\x12.\n*CONNECTION_ERROR_AUTHENTICATION_IMPOSSIBLE\x10\x03\x12#\n\x1f\x43ONNECTION_ERROR_NO_SSL_SUPPORT\x10\x04\x12%\n!CONNECTION_ERROR_ENCRYPTION_ERROR\x10\x05\x12 \n\x1c\x43ONNECTION_ERROR_NAME_IN_USE\x10\x06\x12%\n!CONNECTION_ERROR_INVALID_SETTINGS\x10\x07\x12&\n\"CONNECTION_ERROR_CERT_NOT_PROVIDED\x10\x08\x12#\n\x1f\x43ONNECTION_ERROR_CERT_UNTRUSTED\x10\t\x12!\n\x1d\x43ONNECTION_ERROR_CERT_EXPIRED\x10\n\x12\'\n#CONNECTION_ERROR_CERT_NOT_ACTIVATED\x10\x0b\x12+\n\'CONNECTION_ERROR_CERT_HOSTNAME_MISMATCH\x10\x0c\x12.\n*CONNECTION_ERROR_CERT_FINGERPRINT_MISMATCH\x10\r\x12%\n!CONNECTION_ERROR_CERT_SELF_SIGNED\x10\x0e\x12%\n!CONNECTION_ERROR_CERT_OTHER_ERROR\x10\x0f\x12 \n\x1c\x43ONNECTION_ERROR_OTHER_ERROR\x10\x10*\x86\x01\n\nStatusType\x12\x11\n\rSTATUS_ONLINE\x10\x00\x12\x0f\n\x0bSTATUS_AWAY\x10\x01\x12\x0e\n\nSTATUS_FFC\x10\x02\x12\r\n\tSTATUS_XA\x10\x03\x12\x0e\n\nSTATUS_DND\x10\x04\x12\x0f\n\x0bSTATUS_NONE\x10\x05\x12\x14\n\x10STATUS_INVISIBLE\x10\x06*\x88\x02\n\x0fParticipantFlag\x12\x19\n\x15PARTICIPANT_FLAG_NONE\x10\x00\x12\x1e\n\x1aPARTICIPANT_FLAG_MODERATOR\x10\x01\x12\x1d\n\x19PARTICIPANT_FLAG_CONFLICT\x10\x02\x12\x1b\n\x17PARTICIPANT_FLAG_BANNED\x10\x04\x12#\n\x1fPARTICIPANT_FLAG_NOT_AUTHORIZED\x10\x08\x12\x17\n\x13PARTICIPANT_FLAG_ME\x10\x10\x12\x1b\n\x17PARTICIPANT_FLAG_KICKED\x10 \x12#\n\x1fPARTICIPANT_FLAG_ROOM_NOT_FOUND\x10@') + serialized_pb=_b('\n\x0eprotocol.proto\x12\tpbnetwork\"\x19\n\tConnected\x12\x0c\n\x04user\x18\x01 \x02(\t\"<\n\x0c\x44isconnected\x12\x0c\n\x04user\x18\x01 \x02(\t\x12\r\n\x05\x65rror\x18\x02 \x02(\x05\x12\x0f\n\x07message\x18\x03 \x01(\t\"P\n\x05Login\x12\x0c\n\x04user\x18\x01 \x02(\t\x12\x12\n\nlegacyName\x18\x02 \x02(\t\x12\x10\n\x08password\x18\x03 \x02(\t\x12\x13\n\x0b\x65xtraFields\x18\x04 \x03(\t\"*\n\x06Logout\x12\x0c\n\x04user\x18\x01 \x02(\t\x12\x12\n\nlegacyName\x18\x02 \x02(\t\"\xab\x01\n\x05\x42uddy\x12\x10\n\x08userName\x18\x01 \x02(\t\x12\x11\n\tbuddyName\x18\x02 \x02(\t\x12\r\n\x05\x61lias\x18\x03 \x01(\t\x12\r\n\x05group\x18\x04 \x03(\t\x12%\n\x06status\x18\x05 \x01(\x0e\x32\x15.pbnetwork.StatusType\x12\x15\n\rstatusMessage\x18\x06 \x01(\t\x12\x10\n\x08iconHash\x18\x07 \x01(\t\x12\x0f\n\x07\x62locked\x18\x08 \x01(\x08\"\xa9\x01\n\x13\x43onversationMessage\x12\x10\n\x08userName\x18\x01 \x02(\t\x12\x11\n\tbuddyName\x18\x02 \x02(\t\x12\x0f\n\x07message\x18\x03 \x02(\t\x12\x10\n\x08nickname\x18\x04 \x01(\t\x12\r\n\x05xhtml\x18\x05 \x01(\t\x12\x11\n\ttimestamp\x18\x06 \x01(\t\x12\x10\n\x08headline\x18\x07 \x01(\x08\x12\n\n\x02id\x18\x08 \x01(\t\x12\n\n\x02pm\x18\t \x01(\x08\"J\n\x04Room\x12\x10\n\x08userName\x18\x01 \x02(\t\x12\x10\n\x08nickname\x18\x02 \x02(\t\x12\x0c\n\x04room\x18\x03 \x02(\t\x12\x10\n\x08password\x18\x04 \x01(\t\"&\n\x08RoomList\x12\x0c\n\x04room\x18\x01 \x03(\t\x12\x0c\n\x04name\x18\x02 \x03(\t\"\x9c\x01\n\x0bParticipant\x12\x10\n\x08userName\x18\x01 \x02(\t\x12\x0c\n\x04room\x18\x02 \x02(\t\x12\x10\n\x08nickname\x18\x03 \x02(\t\x12\x0c\n\x04\x66lag\x18\x04 \x02(\x05\x12%\n\x06status\x18\x05 \x02(\x0e\x32\x15.pbnetwork.StatusType\x12\x15\n\rstatusMessage\x18\x06 \x01(\t\x12\x0f\n\x07newname\x18\x07 \x01(\t\"k\n\x05VCard\x12\x10\n\x08userName\x18\x01 \x02(\t\x12\x11\n\tbuddyName\x18\x02 \x02(\t\x12\n\n\x02id\x18\x03 \x02(\x05\x12\x10\n\x08\x66ullname\x18\x04 \x01(\t\x12\x10\n\x08nickname\x18\x05 \x01(\t\x12\r\n\x05photo\x18\x06 \x01(\x0c\"X\n\x06Status\x12\x10\n\x08userName\x18\x01 \x02(\t\x12%\n\x06status\x18\x03 \x02(\x0e\x32\x15.pbnetwork.StatusType\x12\x15\n\rstatusMessage\x18\x04 \x01(\t\"B\n\x05Stats\x12\x0b\n\x03res\x18\x01 \x02(\x05\x12\x10\n\x08init_res\x18\x02 \x02(\x05\x12\x0e\n\x06shared\x18\x03 \x02(\x05\x12\n\n\x02id\x18\x04 \x02(\t\"Y\n\x04\x46ile\x12\x10\n\x08userName\x18\x01 \x02(\t\x12\x11\n\tbuddyName\x18\x02 \x02(\t\x12\x10\n\x08\x66ileName\x18\x03 \x02(\t\x12\x0c\n\x04size\x18\x04 \x02(\x05\x12\x0c\n\x04\x66tID\x18\x05 \x01(\x05\".\n\x10\x46ileTransferData\x12\x0c\n\x04\x66tID\x18\x01 \x02(\x05\x12\x0c\n\x04\x64\x61ta\x18\x02 \x02(\x0c\"\x1f\n\rBackendConfig\x12\x0e\n\x06\x63onfig\x18\x01 \x02(\t\"\x9a\x06\n\x0eWrapperMessage\x12,\n\x04type\x18\x01 \x02(\x0e\x32\x1e.pbnetwork.WrapperMessage.Type\x12\x0f\n\x07payload\x18\x02 \x01(\x0c\"\xc8\x05\n\x04Type\x12\x12\n\x0eTYPE_CONNECTED\x10\x01\x12\x15\n\x11TYPE_DISCONNECTED\x10\x02\x12\x0e\n\nTYPE_LOGIN\x10\x03\x12\x0f\n\x0bTYPE_LOGOUT\x10\x04\x12\x16\n\x12TYPE_BUDDY_CHANGED\x10\x06\x12\x16\n\x12TYPE_BUDDY_REMOVED\x10\x07\x12\x15\n\x11TYPE_CONV_MESSAGE\x10\x08\x12\r\n\tTYPE_PING\x10\t\x12\r\n\tTYPE_PONG\x10\n\x12\x12\n\x0eTYPE_JOIN_ROOM\x10\x0b\x12\x13\n\x0fTYPE_LEAVE_ROOM\x10\x0c\x12\x1c\n\x18TYPE_PARTICIPANT_CHANGED\x10\r\x12\x1e\n\x1aTYPE_ROOM_NICKNAME_CHANGED\x10\x0e\x12\x1d\n\x19TYPE_ROOM_SUBJECT_CHANGED\x10\x0f\x12\x0e\n\nTYPE_VCARD\x10\x10\x12\x17\n\x13TYPE_STATUS_CHANGED\x10\x11\x12\x15\n\x11TYPE_BUDDY_TYPING\x10\x12\x12\x1d\n\x19TYPE_BUDDY_STOPPED_TYPING\x10\x13\x12\x14\n\x10TYPE_BUDDY_TYPED\x10\x14\x12\x15\n\x11TYPE_AUTH_REQUEST\x10\x15\x12\x12\n\x0eTYPE_ATTENTION\x10\x16\x12\x0e\n\nTYPE_STATS\x10\x17\x12\x11\n\rTYPE_FT_START\x10\x18\x12\x12\n\x0eTYPE_FT_FINISH\x10\x19\x12\x10\n\x0cTYPE_FT_DATA\x10\x1a\x12\x11\n\rTYPE_FT_PAUSE\x10\x1b\x12\x14\n\x10TYPE_FT_CONTINUE\x10\x1c\x12\r\n\tTYPE_EXIT\x10\x1d\x12\x17\n\x13TYPE_BACKEND_CONFIG\x10\x1e\x12\x0e\n\nTYPE_QUERY\x10\x1f\x12\x12\n\x0eTYPE_ROOM_LIST\x10 \x12\x19\n\x15TYPE_CONV_MESSAGE_ACK\x10!\x12\x10\n\x0cTYPE_RAW_XML\x10\"*\xb3\x05\n\x0f\x43onnectionError\x12\"\n\x1e\x43ONNECTION_ERROR_NETWORK_ERROR\x10\x00\x12%\n!CONNECTION_ERROR_INVALID_USERNAME\x10\x01\x12*\n&CONNECTION_ERROR_AUTHENTICATION_FAILED\x10\x02\x12.\n*CONNECTION_ERROR_AUTHENTICATION_IMPOSSIBLE\x10\x03\x12#\n\x1f\x43ONNECTION_ERROR_NO_SSL_SUPPORT\x10\x04\x12%\n!CONNECTION_ERROR_ENCRYPTION_ERROR\x10\x05\x12 \n\x1c\x43ONNECTION_ERROR_NAME_IN_USE\x10\x06\x12%\n!CONNECTION_ERROR_INVALID_SETTINGS\x10\x07\x12&\n\"CONNECTION_ERROR_CERT_NOT_PROVIDED\x10\x08\x12#\n\x1f\x43ONNECTION_ERROR_CERT_UNTRUSTED\x10\t\x12!\n\x1d\x43ONNECTION_ERROR_CERT_EXPIRED\x10\n\x12\'\n#CONNECTION_ERROR_CERT_NOT_ACTIVATED\x10\x0b\x12+\n\'CONNECTION_ERROR_CERT_HOSTNAME_MISMATCH\x10\x0c\x12.\n*CONNECTION_ERROR_CERT_FINGERPRINT_MISMATCH\x10\r\x12%\n!CONNECTION_ERROR_CERT_SELF_SIGNED\x10\x0e\x12%\n!CONNECTION_ERROR_CERT_OTHER_ERROR\x10\x0f\x12 \n\x1c\x43ONNECTION_ERROR_OTHER_ERROR\x10\x10*\x86\x01\n\nStatusType\x12\x11\n\rSTATUS_ONLINE\x10\x00\x12\x0f\n\x0bSTATUS_AWAY\x10\x01\x12\x0e\n\nSTATUS_FFC\x10\x02\x12\r\n\tSTATUS_XA\x10\x03\x12\x0e\n\nSTATUS_DND\x10\x04\x12\x0f\n\x0bSTATUS_NONE\x10\x05\x12\x14\n\x10STATUS_INVISIBLE\x10\x06*\x88\x02\n\x0fParticipantFlag\x12\x19\n\x15PARTICIPANT_FLAG_NONE\x10\x00\x12\x1e\n\x1aPARTICIPANT_FLAG_MODERATOR\x10\x01\x12\x1d\n\x19PARTICIPANT_FLAG_CONFLICT\x10\x02\x12\x1b\n\x17PARTICIPANT_FLAG_BANNED\x10\x04\x12#\n\x1fPARTICIPANT_FLAG_NOT_AUTHORIZED\x10\x08\x12\x17\n\x13PARTICIPANT_FLAG_ME\x10\x10\x12\x1b\n\x17PARTICIPANT_FLAG_KICKED\x10 \x12#\n\x1fPARTICIPANT_FLAG_ROOM_NOT_FOUND\x10@') +) +_sym_db.RegisterFileDescriptor(DESCRIPTOR) -_CONNECTIONERROR = descriptor.EnumDescriptor( +_CONNECTIONERROR = _descriptor.EnumDescriptor( name='ConnectionError', full_name='pbnetwork.ConnectionError', filename=None, file=DESCRIPTOR, values=[ - descriptor.EnumValueDescriptor( + _descriptor.EnumValueDescriptor( name='CONNECTION_ERROR_NETWORK_ERROR', index=0, number=0, options=None, type=None), - descriptor.EnumValueDescriptor( + _descriptor.EnumValueDescriptor( name='CONNECTION_ERROR_INVALID_USERNAME', index=1, number=1, options=None, type=None), - descriptor.EnumValueDescriptor( + _descriptor.EnumValueDescriptor( name='CONNECTION_ERROR_AUTHENTICATION_FAILED', index=2, number=2, options=None, type=None), - descriptor.EnumValueDescriptor( + _descriptor.EnumValueDescriptor( name='CONNECTION_ERROR_AUTHENTICATION_IMPOSSIBLE', index=3, number=3, options=None, type=None), - descriptor.EnumValueDescriptor( + _descriptor.EnumValueDescriptor( name='CONNECTION_ERROR_NO_SSL_SUPPORT', index=4, number=4, options=None, type=None), - descriptor.EnumValueDescriptor( + _descriptor.EnumValueDescriptor( name='CONNECTION_ERROR_ENCRYPTION_ERROR', index=5, number=5, options=None, type=None), - descriptor.EnumValueDescriptor( + _descriptor.EnumValueDescriptor( name='CONNECTION_ERROR_NAME_IN_USE', index=6, number=6, options=None, type=None), - descriptor.EnumValueDescriptor( + _descriptor.EnumValueDescriptor( name='CONNECTION_ERROR_INVALID_SETTINGS', index=7, number=7, options=None, type=None), - descriptor.EnumValueDescriptor( + _descriptor.EnumValueDescriptor( name='CONNECTION_ERROR_CERT_NOT_PROVIDED', index=8, number=8, options=None, type=None), - descriptor.EnumValueDescriptor( + _descriptor.EnumValueDescriptor( name='CONNECTION_ERROR_CERT_UNTRUSTED', index=9, number=9, options=None, type=None), - descriptor.EnumValueDescriptor( + _descriptor.EnumValueDescriptor( name='CONNECTION_ERROR_CERT_EXPIRED', index=10, number=10, options=None, type=None), - descriptor.EnumValueDescriptor( + _descriptor.EnumValueDescriptor( name='CONNECTION_ERROR_CERT_NOT_ACTIVATED', index=11, number=11, options=None, type=None), - descriptor.EnumValueDescriptor( + _descriptor.EnumValueDescriptor( name='CONNECTION_ERROR_CERT_HOSTNAME_MISMATCH', index=12, number=12, options=None, type=None), - descriptor.EnumValueDescriptor( + _descriptor.EnumValueDescriptor( name='CONNECTION_ERROR_CERT_FINGERPRINT_MISMATCH', index=13, number=13, options=None, type=None), - descriptor.EnumValueDescriptor( + _descriptor.EnumValueDescriptor( name='CONNECTION_ERROR_CERT_SELF_SIGNED', index=14, number=14, options=None, type=None), - descriptor.EnumValueDescriptor( + _descriptor.EnumValueDescriptor( name='CONNECTION_ERROR_CERT_OTHER_ERROR', index=15, number=15, options=None, type=None), - descriptor.EnumValueDescriptor( + _descriptor.EnumValueDescriptor( name='CONNECTION_ERROR_OTHER_ERROR', index=16, number=16, options=None, type=None), @@ -92,39 +103,40 @@ _CONNECTIONERROR = descriptor.EnumDescriptor( serialized_start=2102, serialized_end=2793, ) +_sym_db.RegisterEnumDescriptor(_CONNECTIONERROR) - -_STATUSTYPE = descriptor.EnumDescriptor( +ConnectionError = enum_type_wrapper.EnumTypeWrapper(_CONNECTIONERROR) +_STATUSTYPE = _descriptor.EnumDescriptor( name='StatusType', full_name='pbnetwork.StatusType', filename=None, file=DESCRIPTOR, values=[ - descriptor.EnumValueDescriptor( + _descriptor.EnumValueDescriptor( name='STATUS_ONLINE', index=0, number=0, options=None, type=None), - descriptor.EnumValueDescriptor( + _descriptor.EnumValueDescriptor( name='STATUS_AWAY', index=1, number=1, options=None, type=None), - descriptor.EnumValueDescriptor( + _descriptor.EnumValueDescriptor( name='STATUS_FFC', index=2, number=2, options=None, type=None), - descriptor.EnumValueDescriptor( + _descriptor.EnumValueDescriptor( name='STATUS_XA', index=3, number=3, options=None, type=None), - descriptor.EnumValueDescriptor( + _descriptor.EnumValueDescriptor( name='STATUS_DND', index=4, number=4, options=None, type=None), - descriptor.EnumValueDescriptor( + _descriptor.EnumValueDescriptor( name='STATUS_NONE', index=5, number=5, options=None, type=None), - descriptor.EnumValueDescriptor( + _descriptor.EnumValueDescriptor( name='STATUS_INVISIBLE', index=6, number=6, options=None, type=None), @@ -134,43 +146,44 @@ _STATUSTYPE = descriptor.EnumDescriptor( serialized_start=2796, serialized_end=2930, ) +_sym_db.RegisterEnumDescriptor(_STATUSTYPE) - -_PARTICIPANTFLAG = descriptor.EnumDescriptor( +StatusType = enum_type_wrapper.EnumTypeWrapper(_STATUSTYPE) +_PARTICIPANTFLAG = _descriptor.EnumDescriptor( name='ParticipantFlag', full_name='pbnetwork.ParticipantFlag', filename=None, file=DESCRIPTOR, values=[ - descriptor.EnumValueDescriptor( + _descriptor.EnumValueDescriptor( name='PARTICIPANT_FLAG_NONE', index=0, number=0, options=None, type=None), - descriptor.EnumValueDescriptor( + _descriptor.EnumValueDescriptor( name='PARTICIPANT_FLAG_MODERATOR', index=1, number=1, options=None, type=None), - descriptor.EnumValueDescriptor( + _descriptor.EnumValueDescriptor( name='PARTICIPANT_FLAG_CONFLICT', index=2, number=2, options=None, type=None), - descriptor.EnumValueDescriptor( + _descriptor.EnumValueDescriptor( name='PARTICIPANT_FLAG_BANNED', index=3, number=4, options=None, type=None), - descriptor.EnumValueDescriptor( + _descriptor.EnumValueDescriptor( name='PARTICIPANT_FLAG_NOT_AUTHORIZED', index=4, number=8, options=None, type=None), - descriptor.EnumValueDescriptor( + _descriptor.EnumValueDescriptor( name='PARTICIPANT_FLAG_ME', index=5, number=16, options=None, type=None), - descriptor.EnumValueDescriptor( + _descriptor.EnumValueDescriptor( name='PARTICIPANT_FLAG_KICKED', index=6, number=32, options=None, type=None), - descriptor.EnumValueDescriptor( + _descriptor.EnumValueDescriptor( name='PARTICIPANT_FLAG_ROOM_NOT_FOUND', index=7, number=64, options=None, type=None), @@ -180,8 +193,9 @@ _PARTICIPANTFLAG = descriptor.EnumDescriptor( serialized_start=2933, serialized_end=3197, ) +_sym_db.RegisterEnumDescriptor(_PARTICIPANTFLAG) - +ParticipantFlag = enum_type_wrapper.EnumTypeWrapper(_PARTICIPANTFLAG) CONNECTION_ERROR_NETWORK_ERROR = 0 CONNECTION_ERROR_INVALID_USERNAME = 1 CONNECTION_ERROR_AUTHENTICATION_FAILED = 2 @@ -216,141 +230,141 @@ PARTICIPANT_FLAG_KICKED = 32 PARTICIPANT_FLAG_ROOM_NOT_FOUND = 64 -_WRAPPERMESSAGE_TYPE = descriptor.EnumDescriptor( +_WRAPPERMESSAGE_TYPE = _descriptor.EnumDescriptor( name='Type', full_name='pbnetwork.WrapperMessage.Type', filename=None, file=DESCRIPTOR, values=[ - descriptor.EnumValueDescriptor( + _descriptor.EnumValueDescriptor( name='TYPE_CONNECTED', index=0, number=1, options=None, type=None), - descriptor.EnumValueDescriptor( + _descriptor.EnumValueDescriptor( name='TYPE_DISCONNECTED', index=1, number=2, options=None, type=None), - descriptor.EnumValueDescriptor( + _descriptor.EnumValueDescriptor( name='TYPE_LOGIN', index=2, number=3, options=None, type=None), - descriptor.EnumValueDescriptor( + _descriptor.EnumValueDescriptor( name='TYPE_LOGOUT', index=3, number=4, options=None, type=None), - descriptor.EnumValueDescriptor( + _descriptor.EnumValueDescriptor( name='TYPE_BUDDY_CHANGED', index=4, number=6, options=None, type=None), - descriptor.EnumValueDescriptor( + _descriptor.EnumValueDescriptor( name='TYPE_BUDDY_REMOVED', index=5, number=7, options=None, type=None), - descriptor.EnumValueDescriptor( + _descriptor.EnumValueDescriptor( name='TYPE_CONV_MESSAGE', index=6, number=8, options=None, type=None), - descriptor.EnumValueDescriptor( + _descriptor.EnumValueDescriptor( name='TYPE_PING', index=7, number=9, options=None, type=None), - descriptor.EnumValueDescriptor( + _descriptor.EnumValueDescriptor( name='TYPE_PONG', index=8, number=10, options=None, type=None), - descriptor.EnumValueDescriptor( + _descriptor.EnumValueDescriptor( name='TYPE_JOIN_ROOM', index=9, number=11, options=None, type=None), - descriptor.EnumValueDescriptor( + _descriptor.EnumValueDescriptor( name='TYPE_LEAVE_ROOM', index=10, number=12, options=None, type=None), - descriptor.EnumValueDescriptor( + _descriptor.EnumValueDescriptor( name='TYPE_PARTICIPANT_CHANGED', index=11, number=13, options=None, type=None), - descriptor.EnumValueDescriptor( + _descriptor.EnumValueDescriptor( name='TYPE_ROOM_NICKNAME_CHANGED', index=12, number=14, options=None, type=None), - descriptor.EnumValueDescriptor( + _descriptor.EnumValueDescriptor( name='TYPE_ROOM_SUBJECT_CHANGED', index=13, number=15, options=None, type=None), - descriptor.EnumValueDescriptor( + _descriptor.EnumValueDescriptor( name='TYPE_VCARD', index=14, number=16, options=None, type=None), - descriptor.EnumValueDescriptor( + _descriptor.EnumValueDescriptor( name='TYPE_STATUS_CHANGED', index=15, number=17, options=None, type=None), - descriptor.EnumValueDescriptor( + _descriptor.EnumValueDescriptor( name='TYPE_BUDDY_TYPING', index=16, number=18, options=None, type=None), - descriptor.EnumValueDescriptor( + _descriptor.EnumValueDescriptor( name='TYPE_BUDDY_STOPPED_TYPING', index=17, number=19, options=None, type=None), - descriptor.EnumValueDescriptor( + _descriptor.EnumValueDescriptor( name='TYPE_BUDDY_TYPED', index=18, number=20, options=None, type=None), - descriptor.EnumValueDescriptor( + _descriptor.EnumValueDescriptor( name='TYPE_AUTH_REQUEST', index=19, number=21, options=None, type=None), - descriptor.EnumValueDescriptor( + _descriptor.EnumValueDescriptor( name='TYPE_ATTENTION', index=20, number=22, options=None, type=None), - descriptor.EnumValueDescriptor( + _descriptor.EnumValueDescriptor( name='TYPE_STATS', index=21, number=23, options=None, type=None), - descriptor.EnumValueDescriptor( + _descriptor.EnumValueDescriptor( name='TYPE_FT_START', index=22, number=24, options=None, type=None), - descriptor.EnumValueDescriptor( + _descriptor.EnumValueDescriptor( name='TYPE_FT_FINISH', index=23, number=25, options=None, type=None), - descriptor.EnumValueDescriptor( + _descriptor.EnumValueDescriptor( name='TYPE_FT_DATA', index=24, number=26, options=None, type=None), - descriptor.EnumValueDescriptor( + _descriptor.EnumValueDescriptor( name='TYPE_FT_PAUSE', index=25, number=27, options=None, type=None), - descriptor.EnumValueDescriptor( + _descriptor.EnumValueDescriptor( name='TYPE_FT_CONTINUE', index=26, number=28, options=None, type=None), - descriptor.EnumValueDescriptor( + _descriptor.EnumValueDescriptor( name='TYPE_EXIT', index=27, number=29, options=None, type=None), - descriptor.EnumValueDescriptor( + _descriptor.EnumValueDescriptor( name='TYPE_BACKEND_CONFIG', index=28, number=30, options=None, type=None), - descriptor.EnumValueDescriptor( + _descriptor.EnumValueDescriptor( name='TYPE_QUERY', index=29, number=31, options=None, type=None), - descriptor.EnumValueDescriptor( + _descriptor.EnumValueDescriptor( name='TYPE_ROOM_LIST', index=30, number=32, options=None, type=None), - descriptor.EnumValueDescriptor( + _descriptor.EnumValueDescriptor( name='TYPE_CONV_MESSAGE_ACK', index=31, number=33, options=None, type=None), - descriptor.EnumValueDescriptor( + _descriptor.EnumValueDescriptor( name='TYPE_RAW_XML', index=32, number=34, options=None, type=None), @@ -360,19 +374,20 @@ _WRAPPERMESSAGE_TYPE = descriptor.EnumDescriptor( serialized_start=1387, serialized_end=2099, ) +_sym_db.RegisterEnumDescriptor(_WRAPPERMESSAGE_TYPE) -_CONNECTED = descriptor.Descriptor( +_CONNECTED = _descriptor.Descriptor( name='Connected', full_name='pbnetwork.Connected', filename=None, file=DESCRIPTOR, containing_type=None, fields=[ - descriptor.FieldDescriptor( + _descriptor.FieldDescriptor( name='user', full_name='pbnetwork.Connected.user', index=0, number=1, type=9, cpp_type=9, label=2, - has_default_value=False, default_value=unicode("", "utf-8"), + has_default_value=False, default_value=_b("").decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, options=None), @@ -385,36 +400,38 @@ _CONNECTED = descriptor.Descriptor( options=None, is_extendable=False, extension_ranges=[], + oneofs=[ + ], serialized_start=29, serialized_end=54, ) -_DISCONNECTED = descriptor.Descriptor( +_DISCONNECTED = _descriptor.Descriptor( name='Disconnected', full_name='pbnetwork.Disconnected', filename=None, file=DESCRIPTOR, containing_type=None, fields=[ - descriptor.FieldDescriptor( + _descriptor.FieldDescriptor( name='user', full_name='pbnetwork.Disconnected.user', index=0, number=1, type=9, cpp_type=9, label=2, - has_default_value=False, default_value=unicode("", "utf-8"), + has_default_value=False, default_value=_b("").decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, options=None), - descriptor.FieldDescriptor( + _descriptor.FieldDescriptor( name='error', full_name='pbnetwork.Disconnected.error', index=1, number=2, type=5, cpp_type=1, label=2, has_default_value=False, default_value=0, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, options=None), - descriptor.FieldDescriptor( + _descriptor.FieldDescriptor( name='message', full_name='pbnetwork.Disconnected.message', index=2, number=3, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=unicode("", "utf-8"), + has_default_value=False, default_value=_b("").decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, options=None), @@ -427,40 +444,42 @@ _DISCONNECTED = descriptor.Descriptor( options=None, is_extendable=False, extension_ranges=[], + oneofs=[ + ], serialized_start=56, serialized_end=116, ) -_LOGIN = descriptor.Descriptor( +_LOGIN = _descriptor.Descriptor( name='Login', full_name='pbnetwork.Login', filename=None, file=DESCRIPTOR, containing_type=None, fields=[ - descriptor.FieldDescriptor( + _descriptor.FieldDescriptor( name='user', full_name='pbnetwork.Login.user', index=0, number=1, type=9, cpp_type=9, label=2, - has_default_value=False, default_value=unicode("", "utf-8"), + has_default_value=False, default_value=_b("").decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, options=None), - descriptor.FieldDescriptor( + _descriptor.FieldDescriptor( name='legacyName', full_name='pbnetwork.Login.legacyName', index=1, number=2, type=9, cpp_type=9, label=2, - has_default_value=False, default_value=unicode("", "utf-8"), + has_default_value=False, default_value=_b("").decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, options=None), - descriptor.FieldDescriptor( + _descriptor.FieldDescriptor( name='password', full_name='pbnetwork.Login.password', index=2, number=3, type=9, cpp_type=9, label=2, - has_default_value=False, default_value=unicode("", "utf-8"), + has_default_value=False, default_value=_b("").decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, options=None), - descriptor.FieldDescriptor( + _descriptor.FieldDescriptor( name='extraFields', full_name='pbnetwork.Login.extraFields', index=3, number=4, type=9, cpp_type=9, label=3, has_default_value=False, default_value=[], @@ -476,29 +495,31 @@ _LOGIN = descriptor.Descriptor( options=None, is_extendable=False, extension_ranges=[], + oneofs=[ + ], serialized_start=118, serialized_end=198, ) -_LOGOUT = descriptor.Descriptor( +_LOGOUT = _descriptor.Descriptor( name='Logout', full_name='pbnetwork.Logout', filename=None, file=DESCRIPTOR, containing_type=None, fields=[ - descriptor.FieldDescriptor( + _descriptor.FieldDescriptor( name='user', full_name='pbnetwork.Logout.user', index=0, number=1, type=9, cpp_type=9, label=2, - has_default_value=False, default_value=unicode("", "utf-8"), + has_default_value=False, default_value=_b("").decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, options=None), - descriptor.FieldDescriptor( + _descriptor.FieldDescriptor( name='legacyName', full_name='pbnetwork.Logout.legacyName', index=1, number=2, type=9, cpp_type=9, label=2, - has_default_value=False, default_value=unicode("", "utf-8"), + has_default_value=False, default_value=_b("").decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, options=None), @@ -511,68 +532,70 @@ _LOGOUT = descriptor.Descriptor( options=None, is_extendable=False, extension_ranges=[], + oneofs=[ + ], serialized_start=200, serialized_end=242, ) -_BUDDY = descriptor.Descriptor( +_BUDDY = _descriptor.Descriptor( name='Buddy', full_name='pbnetwork.Buddy', filename=None, file=DESCRIPTOR, containing_type=None, fields=[ - descriptor.FieldDescriptor( + _descriptor.FieldDescriptor( name='userName', full_name='pbnetwork.Buddy.userName', index=0, number=1, type=9, cpp_type=9, label=2, - has_default_value=False, default_value=unicode("", "utf-8"), + has_default_value=False, default_value=_b("").decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, options=None), - descriptor.FieldDescriptor( + _descriptor.FieldDescriptor( name='buddyName', full_name='pbnetwork.Buddy.buddyName', index=1, number=2, type=9, cpp_type=9, label=2, - has_default_value=False, default_value=unicode("", "utf-8"), + has_default_value=False, default_value=_b("").decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, options=None), - descriptor.FieldDescriptor( + _descriptor.FieldDescriptor( name='alias', full_name='pbnetwork.Buddy.alias', index=2, number=3, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=unicode("", "utf-8"), + has_default_value=False, default_value=_b("").decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, options=None), - descriptor.FieldDescriptor( + _descriptor.FieldDescriptor( name='group', full_name='pbnetwork.Buddy.group', index=3, number=4, type=9, cpp_type=9, label=3, has_default_value=False, default_value=[], message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, options=None), - descriptor.FieldDescriptor( + _descriptor.FieldDescriptor( name='status', full_name='pbnetwork.Buddy.status', index=4, number=5, type=14, cpp_type=8, label=1, has_default_value=False, default_value=0, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, options=None), - descriptor.FieldDescriptor( + _descriptor.FieldDescriptor( name='statusMessage', full_name='pbnetwork.Buddy.statusMessage', index=5, number=6, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=unicode("", "utf-8"), + has_default_value=False, default_value=_b("").decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, options=None), - descriptor.FieldDescriptor( + _descriptor.FieldDescriptor( name='iconHash', full_name='pbnetwork.Buddy.iconHash', index=6, number=7, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=unicode("", "utf-8"), + has_default_value=False, default_value=_b("").decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, options=None), - descriptor.FieldDescriptor( + _descriptor.FieldDescriptor( name='blocked', full_name='pbnetwork.Buddy.blocked', index=7, number=8, type=8, cpp_type=7, label=1, has_default_value=False, default_value=False, @@ -588,75 +611,77 @@ _BUDDY = descriptor.Descriptor( options=None, is_extendable=False, extension_ranges=[], + oneofs=[ + ], serialized_start=245, serialized_end=416, ) -_CONVERSATIONMESSAGE = descriptor.Descriptor( +_CONVERSATIONMESSAGE = _descriptor.Descriptor( name='ConversationMessage', full_name='pbnetwork.ConversationMessage', filename=None, file=DESCRIPTOR, containing_type=None, fields=[ - descriptor.FieldDescriptor( + _descriptor.FieldDescriptor( name='userName', full_name='pbnetwork.ConversationMessage.userName', index=0, number=1, type=9, cpp_type=9, label=2, - has_default_value=False, default_value=unicode("", "utf-8"), + has_default_value=False, default_value=_b("").decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, options=None), - descriptor.FieldDescriptor( + _descriptor.FieldDescriptor( name='buddyName', full_name='pbnetwork.ConversationMessage.buddyName', index=1, number=2, type=9, cpp_type=9, label=2, - has_default_value=False, default_value=unicode("", "utf-8"), + has_default_value=False, default_value=_b("").decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, options=None), - descriptor.FieldDescriptor( + _descriptor.FieldDescriptor( name='message', full_name='pbnetwork.ConversationMessage.message', index=2, number=3, type=9, cpp_type=9, label=2, - has_default_value=False, default_value=unicode("", "utf-8"), + has_default_value=False, default_value=_b("").decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, options=None), - descriptor.FieldDescriptor( + _descriptor.FieldDescriptor( name='nickname', full_name='pbnetwork.ConversationMessage.nickname', index=3, number=4, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=unicode("", "utf-8"), + has_default_value=False, default_value=_b("").decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, options=None), - descriptor.FieldDescriptor( + _descriptor.FieldDescriptor( name='xhtml', full_name='pbnetwork.ConversationMessage.xhtml', index=4, number=5, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=unicode("", "utf-8"), + has_default_value=False, default_value=_b("").decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, options=None), - descriptor.FieldDescriptor( + _descriptor.FieldDescriptor( name='timestamp', full_name='pbnetwork.ConversationMessage.timestamp', index=5, number=6, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=unicode("", "utf-8"), + has_default_value=False, default_value=_b("").decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, options=None), - descriptor.FieldDescriptor( + _descriptor.FieldDescriptor( name='headline', full_name='pbnetwork.ConversationMessage.headline', index=6, number=7, type=8, cpp_type=7, label=1, has_default_value=False, default_value=False, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, options=None), - descriptor.FieldDescriptor( + _descriptor.FieldDescriptor( name='id', full_name='pbnetwork.ConversationMessage.id', index=7, number=8, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=unicode("", "utf-8"), + has_default_value=False, default_value=_b("").decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, options=None), - descriptor.FieldDescriptor( + _descriptor.FieldDescriptor( name='pm', full_name='pbnetwork.ConversationMessage.pm', index=8, number=9, type=8, cpp_type=7, label=1, has_default_value=False, default_value=False, @@ -672,43 +697,45 @@ _CONVERSATIONMESSAGE = descriptor.Descriptor( options=None, is_extendable=False, extension_ranges=[], + oneofs=[ + ], serialized_start=419, serialized_end=588, ) -_ROOM = descriptor.Descriptor( +_ROOM = _descriptor.Descriptor( name='Room', full_name='pbnetwork.Room', filename=None, file=DESCRIPTOR, containing_type=None, fields=[ - descriptor.FieldDescriptor( + _descriptor.FieldDescriptor( name='userName', full_name='pbnetwork.Room.userName', index=0, number=1, type=9, cpp_type=9, label=2, - has_default_value=False, default_value=unicode("", "utf-8"), + has_default_value=False, default_value=_b("").decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, options=None), - descriptor.FieldDescriptor( + _descriptor.FieldDescriptor( name='nickname', full_name='pbnetwork.Room.nickname', index=1, number=2, type=9, cpp_type=9, label=2, - has_default_value=False, default_value=unicode("", "utf-8"), + has_default_value=False, default_value=_b("").decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, options=None), - descriptor.FieldDescriptor( + _descriptor.FieldDescriptor( name='room', full_name='pbnetwork.Room.room', index=2, number=3, type=9, cpp_type=9, label=2, - has_default_value=False, default_value=unicode("", "utf-8"), + has_default_value=False, default_value=_b("").decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, options=None), - descriptor.FieldDescriptor( + _descriptor.FieldDescriptor( name='password', full_name='pbnetwork.Room.password', index=3, number=4, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=unicode("", "utf-8"), + has_default_value=False, default_value=_b("").decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, options=None), @@ -721,26 +748,28 @@ _ROOM = descriptor.Descriptor( options=None, is_extendable=False, extension_ranges=[], + oneofs=[ + ], serialized_start=590, serialized_end=664, ) -_ROOMLIST = descriptor.Descriptor( +_ROOMLIST = _descriptor.Descriptor( name='RoomList', full_name='pbnetwork.RoomList', filename=None, file=DESCRIPTOR, containing_type=None, fields=[ - descriptor.FieldDescriptor( + _descriptor.FieldDescriptor( name='room', full_name='pbnetwork.RoomList.room', index=0, number=1, type=9, cpp_type=9, label=3, has_default_value=False, default_value=[], message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, options=None), - descriptor.FieldDescriptor( + _descriptor.FieldDescriptor( name='name', full_name='pbnetwork.RoomList.name', index=1, number=2, type=9, cpp_type=9, label=3, has_default_value=False, default_value=[], @@ -756,64 +785,66 @@ _ROOMLIST = descriptor.Descriptor( options=None, is_extendable=False, extension_ranges=[], + oneofs=[ + ], serialized_start=666, serialized_end=704, ) -_PARTICIPANT = descriptor.Descriptor( +_PARTICIPANT = _descriptor.Descriptor( name='Participant', full_name='pbnetwork.Participant', filename=None, file=DESCRIPTOR, containing_type=None, fields=[ - descriptor.FieldDescriptor( + _descriptor.FieldDescriptor( name='userName', full_name='pbnetwork.Participant.userName', index=0, number=1, type=9, cpp_type=9, label=2, - has_default_value=False, default_value=unicode("", "utf-8"), + has_default_value=False, default_value=_b("").decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, options=None), - descriptor.FieldDescriptor( + _descriptor.FieldDescriptor( name='room', full_name='pbnetwork.Participant.room', index=1, number=2, type=9, cpp_type=9, label=2, - has_default_value=False, default_value=unicode("", "utf-8"), + has_default_value=False, default_value=_b("").decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, options=None), - descriptor.FieldDescriptor( + _descriptor.FieldDescriptor( name='nickname', full_name='pbnetwork.Participant.nickname', index=2, number=3, type=9, cpp_type=9, label=2, - has_default_value=False, default_value=unicode("", "utf-8"), + has_default_value=False, default_value=_b("").decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, options=None), - descriptor.FieldDescriptor( + _descriptor.FieldDescriptor( name='flag', full_name='pbnetwork.Participant.flag', index=3, number=4, type=5, cpp_type=1, label=2, has_default_value=False, default_value=0, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, options=None), - descriptor.FieldDescriptor( + _descriptor.FieldDescriptor( name='status', full_name='pbnetwork.Participant.status', index=4, number=5, type=14, cpp_type=8, label=2, has_default_value=False, default_value=0, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, options=None), - descriptor.FieldDescriptor( + _descriptor.FieldDescriptor( name='statusMessage', full_name='pbnetwork.Participant.statusMessage', index=5, number=6, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=unicode("", "utf-8"), + has_default_value=False, default_value=_b("").decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, options=None), - descriptor.FieldDescriptor( + _descriptor.FieldDescriptor( name='newname', full_name='pbnetwork.Participant.newname', index=6, number=7, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=unicode("", "utf-8"), + has_default_value=False, default_value=_b("").decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, options=None), @@ -826,57 +857,59 @@ _PARTICIPANT = descriptor.Descriptor( options=None, is_extendable=False, extension_ranges=[], + oneofs=[ + ], serialized_start=707, serialized_end=863, ) -_VCARD = descriptor.Descriptor( +_VCARD = _descriptor.Descriptor( name='VCard', full_name='pbnetwork.VCard', filename=None, file=DESCRIPTOR, containing_type=None, fields=[ - descriptor.FieldDescriptor( + _descriptor.FieldDescriptor( name='userName', full_name='pbnetwork.VCard.userName', index=0, number=1, type=9, cpp_type=9, label=2, - has_default_value=False, default_value=unicode("", "utf-8"), + has_default_value=False, default_value=_b("").decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, options=None), - descriptor.FieldDescriptor( + _descriptor.FieldDescriptor( name='buddyName', full_name='pbnetwork.VCard.buddyName', index=1, number=2, type=9, cpp_type=9, label=2, - has_default_value=False, default_value=unicode("", "utf-8"), + has_default_value=False, default_value=_b("").decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, options=None), - descriptor.FieldDescriptor( + _descriptor.FieldDescriptor( name='id', full_name='pbnetwork.VCard.id', index=2, number=3, type=5, cpp_type=1, label=2, has_default_value=False, default_value=0, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, options=None), - descriptor.FieldDescriptor( + _descriptor.FieldDescriptor( name='fullname', full_name='pbnetwork.VCard.fullname', index=3, number=4, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=unicode("", "utf-8"), + has_default_value=False, default_value=_b("").decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, options=None), - descriptor.FieldDescriptor( + _descriptor.FieldDescriptor( name='nickname', full_name='pbnetwork.VCard.nickname', index=4, number=5, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=unicode("", "utf-8"), + has_default_value=False, default_value=_b("").decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, options=None), - descriptor.FieldDescriptor( + _descriptor.FieldDescriptor( name='photo', full_name='pbnetwork.VCard.photo', index=5, number=6, type=12, cpp_type=9, label=1, - has_default_value=False, default_value="", + has_default_value=False, default_value=_b(""), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, options=None), @@ -889,36 +922,38 @@ _VCARD = descriptor.Descriptor( options=None, is_extendable=False, extension_ranges=[], + oneofs=[ + ], serialized_start=865, serialized_end=972, ) -_STATUS = descriptor.Descriptor( +_STATUS = _descriptor.Descriptor( name='Status', full_name='pbnetwork.Status', filename=None, file=DESCRIPTOR, containing_type=None, fields=[ - descriptor.FieldDescriptor( + _descriptor.FieldDescriptor( name='userName', full_name='pbnetwork.Status.userName', index=0, number=1, type=9, cpp_type=9, label=2, - has_default_value=False, default_value=unicode("", "utf-8"), + has_default_value=False, default_value=_b("").decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, options=None), - descriptor.FieldDescriptor( + _descriptor.FieldDescriptor( name='status', full_name='pbnetwork.Status.status', index=1, number=3, type=14, cpp_type=8, label=2, has_default_value=False, default_value=0, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, options=None), - descriptor.FieldDescriptor( + _descriptor.FieldDescriptor( name='statusMessage', full_name='pbnetwork.Status.statusMessage', index=2, number=4, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=unicode("", "utf-8"), + has_default_value=False, default_value=_b("").decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, options=None), @@ -931,43 +966,45 @@ _STATUS = descriptor.Descriptor( options=None, is_extendable=False, extension_ranges=[], + oneofs=[ + ], serialized_start=974, serialized_end=1062, ) -_STATS = descriptor.Descriptor( +_STATS = _descriptor.Descriptor( name='Stats', full_name='pbnetwork.Stats', filename=None, file=DESCRIPTOR, containing_type=None, fields=[ - descriptor.FieldDescriptor( + _descriptor.FieldDescriptor( name='res', full_name='pbnetwork.Stats.res', index=0, number=1, type=5, cpp_type=1, label=2, has_default_value=False, default_value=0, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, options=None), - descriptor.FieldDescriptor( + _descriptor.FieldDescriptor( name='init_res', full_name='pbnetwork.Stats.init_res', index=1, number=2, type=5, cpp_type=1, label=2, has_default_value=False, default_value=0, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, options=None), - descriptor.FieldDescriptor( + _descriptor.FieldDescriptor( name='shared', full_name='pbnetwork.Stats.shared', index=2, number=3, type=5, cpp_type=1, label=2, has_default_value=False, default_value=0, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, options=None), - descriptor.FieldDescriptor( + _descriptor.FieldDescriptor( name='id', full_name='pbnetwork.Stats.id', index=3, number=4, type=9, cpp_type=9, label=2, - has_default_value=False, default_value=unicode("", "utf-8"), + has_default_value=False, default_value=_b("").decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, options=None), @@ -980,47 +1017,49 @@ _STATS = descriptor.Descriptor( options=None, is_extendable=False, extension_ranges=[], + oneofs=[ + ], serialized_start=1064, serialized_end=1130, ) -_FILE = descriptor.Descriptor( +_FILE = _descriptor.Descriptor( name='File', full_name='pbnetwork.File', filename=None, file=DESCRIPTOR, containing_type=None, fields=[ - descriptor.FieldDescriptor( + _descriptor.FieldDescriptor( name='userName', full_name='pbnetwork.File.userName', index=0, number=1, type=9, cpp_type=9, label=2, - has_default_value=False, default_value=unicode("", "utf-8"), + has_default_value=False, default_value=_b("").decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, options=None), - descriptor.FieldDescriptor( + _descriptor.FieldDescriptor( name='buddyName', full_name='pbnetwork.File.buddyName', index=1, number=2, type=9, cpp_type=9, label=2, - has_default_value=False, default_value=unicode("", "utf-8"), + has_default_value=False, default_value=_b("").decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, options=None), - descriptor.FieldDescriptor( + _descriptor.FieldDescriptor( name='fileName', full_name='pbnetwork.File.fileName', index=2, number=3, type=9, cpp_type=9, label=2, - has_default_value=False, default_value=unicode("", "utf-8"), + has_default_value=False, default_value=_b("").decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, options=None), - descriptor.FieldDescriptor( + _descriptor.FieldDescriptor( name='size', full_name='pbnetwork.File.size', index=3, number=4, type=5, cpp_type=1, label=2, has_default_value=False, default_value=0, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, options=None), - descriptor.FieldDescriptor( + _descriptor.FieldDescriptor( name='ftID', full_name='pbnetwork.File.ftID', index=4, number=5, type=5, cpp_type=1, label=1, has_default_value=False, default_value=0, @@ -1036,29 +1075,31 @@ _FILE = descriptor.Descriptor( options=None, is_extendable=False, extension_ranges=[], + oneofs=[ + ], serialized_start=1132, serialized_end=1221, ) -_FILETRANSFERDATA = descriptor.Descriptor( +_FILETRANSFERDATA = _descriptor.Descriptor( name='FileTransferData', full_name='pbnetwork.FileTransferData', filename=None, file=DESCRIPTOR, containing_type=None, fields=[ - descriptor.FieldDescriptor( + _descriptor.FieldDescriptor( name='ftID', full_name='pbnetwork.FileTransferData.ftID', index=0, number=1, type=5, cpp_type=1, label=2, has_default_value=False, default_value=0, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, options=None), - descriptor.FieldDescriptor( + _descriptor.FieldDescriptor( name='data', full_name='pbnetwork.FileTransferData.data', index=1, number=2, type=12, cpp_type=9, label=2, - has_default_value=False, default_value="", + has_default_value=False, default_value=_b(""), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, options=None), @@ -1071,22 +1112,24 @@ _FILETRANSFERDATA = descriptor.Descriptor( options=None, is_extendable=False, extension_ranges=[], + oneofs=[ + ], serialized_start=1223, serialized_end=1269, ) -_BACKENDCONFIG = descriptor.Descriptor( +_BACKENDCONFIG = _descriptor.Descriptor( name='BackendConfig', full_name='pbnetwork.BackendConfig', filename=None, file=DESCRIPTOR, containing_type=None, fields=[ - descriptor.FieldDescriptor( + _descriptor.FieldDescriptor( name='config', full_name='pbnetwork.BackendConfig.config', index=0, number=1, type=9, cpp_type=9, label=2, - has_default_value=False, default_value=unicode("", "utf-8"), + has_default_value=False, default_value=_b("").decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, options=None), @@ -1099,29 +1142,31 @@ _BACKENDCONFIG = descriptor.Descriptor( options=None, is_extendable=False, extension_ranges=[], + oneofs=[ + ], serialized_start=1271, serialized_end=1302, ) -_WRAPPERMESSAGE = descriptor.Descriptor( +_WRAPPERMESSAGE = _descriptor.Descriptor( name='WrapperMessage', full_name='pbnetwork.WrapperMessage', filename=None, file=DESCRIPTOR, containing_type=None, fields=[ - descriptor.FieldDescriptor( + _descriptor.FieldDescriptor( name='type', full_name='pbnetwork.WrapperMessage.type', index=0, number=1, type=14, cpp_type=8, label=2, has_default_value=False, default_value=1, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, options=None), - descriptor.FieldDescriptor( + _descriptor.FieldDescriptor( name='payload', full_name='pbnetwork.WrapperMessage.payload', index=1, number=2, type=12, cpp_type=9, label=1, - has_default_value=False, default_value="", + has_default_value=False, default_value=_b(""), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, options=None), @@ -1135,111 +1180,148 @@ _WRAPPERMESSAGE = descriptor.Descriptor( options=None, is_extendable=False, extension_ranges=[], + oneofs=[ + ], serialized_start=1305, serialized_end=2099, ) - _BUDDY.fields_by_name['status'].enum_type = _STATUSTYPE _PARTICIPANT.fields_by_name['status'].enum_type = _STATUSTYPE _STATUS.fields_by_name['status'].enum_type = _STATUSTYPE _WRAPPERMESSAGE.fields_by_name['type'].enum_type = _WRAPPERMESSAGE_TYPE -_WRAPPERMESSAGE_TYPE.containing_type = _WRAPPERMESSAGE; +_WRAPPERMESSAGE_TYPE.containing_type = _WRAPPERMESSAGE +DESCRIPTOR.message_types_by_name['Connected'] = _CONNECTED +DESCRIPTOR.message_types_by_name['Disconnected'] = _DISCONNECTED +DESCRIPTOR.message_types_by_name['Login'] = _LOGIN +DESCRIPTOR.message_types_by_name['Logout'] = _LOGOUT +DESCRIPTOR.message_types_by_name['Buddy'] = _BUDDY +DESCRIPTOR.message_types_by_name['ConversationMessage'] = _CONVERSATIONMESSAGE +DESCRIPTOR.message_types_by_name['Room'] = _ROOM +DESCRIPTOR.message_types_by_name['RoomList'] = _ROOMLIST +DESCRIPTOR.message_types_by_name['Participant'] = _PARTICIPANT +DESCRIPTOR.message_types_by_name['VCard'] = _VCARD +DESCRIPTOR.message_types_by_name['Status'] = _STATUS +DESCRIPTOR.message_types_by_name['Stats'] = _STATS +DESCRIPTOR.message_types_by_name['File'] = _FILE +DESCRIPTOR.message_types_by_name['FileTransferData'] = _FILETRANSFERDATA +DESCRIPTOR.message_types_by_name['BackendConfig'] = _BACKENDCONFIG +DESCRIPTOR.message_types_by_name['WrapperMessage'] = _WRAPPERMESSAGE +DESCRIPTOR.enum_types_by_name['ConnectionError'] = _CONNECTIONERROR +DESCRIPTOR.enum_types_by_name['StatusType'] = _STATUSTYPE +DESCRIPTOR.enum_types_by_name['ParticipantFlag'] = _PARTICIPANTFLAG -class Connected(message.Message): - __metaclass__ = reflection.GeneratedProtocolMessageType - DESCRIPTOR = _CONNECTED - +Connected = _reflection.GeneratedProtocolMessageType('Connected', (_message.Message,), dict( + DESCRIPTOR = _CONNECTED, + __module__ = 'protocol_pb2' # @@protoc_insertion_point(class_scope:pbnetwork.Connected) + )) +_sym_db.RegisterMessage(Connected) -class Disconnected(message.Message): - __metaclass__ = reflection.GeneratedProtocolMessageType - DESCRIPTOR = _DISCONNECTED - +Disconnected = _reflection.GeneratedProtocolMessageType('Disconnected', (_message.Message,), dict( + DESCRIPTOR = _DISCONNECTED, + __module__ = 'protocol_pb2' # @@protoc_insertion_point(class_scope:pbnetwork.Disconnected) + )) +_sym_db.RegisterMessage(Disconnected) -class Login(message.Message): - __metaclass__ = reflection.GeneratedProtocolMessageType - DESCRIPTOR = _LOGIN - +Login = _reflection.GeneratedProtocolMessageType('Login', (_message.Message,), dict( + DESCRIPTOR = _LOGIN, + __module__ = 'protocol_pb2' # @@protoc_insertion_point(class_scope:pbnetwork.Login) + )) +_sym_db.RegisterMessage(Login) -class Logout(message.Message): - __metaclass__ = reflection.GeneratedProtocolMessageType - DESCRIPTOR = _LOGOUT - +Logout = _reflection.GeneratedProtocolMessageType('Logout', (_message.Message,), dict( + DESCRIPTOR = _LOGOUT, + __module__ = 'protocol_pb2' # @@protoc_insertion_point(class_scope:pbnetwork.Logout) + )) +_sym_db.RegisterMessage(Logout) -class Buddy(message.Message): - __metaclass__ = reflection.GeneratedProtocolMessageType - DESCRIPTOR = _BUDDY - +Buddy = _reflection.GeneratedProtocolMessageType('Buddy', (_message.Message,), dict( + DESCRIPTOR = _BUDDY, + __module__ = 'protocol_pb2' # @@protoc_insertion_point(class_scope:pbnetwork.Buddy) + )) +_sym_db.RegisterMessage(Buddy) -class ConversationMessage(message.Message): - __metaclass__ = reflection.GeneratedProtocolMessageType - DESCRIPTOR = _CONVERSATIONMESSAGE - +ConversationMessage = _reflection.GeneratedProtocolMessageType('ConversationMessage', (_message.Message,), dict( + DESCRIPTOR = _CONVERSATIONMESSAGE, + __module__ = 'protocol_pb2' # @@protoc_insertion_point(class_scope:pbnetwork.ConversationMessage) + )) +_sym_db.RegisterMessage(ConversationMessage) -class Room(message.Message): - __metaclass__ = reflection.GeneratedProtocolMessageType - DESCRIPTOR = _ROOM - +Room = _reflection.GeneratedProtocolMessageType('Room', (_message.Message,), dict( + DESCRIPTOR = _ROOM, + __module__ = 'protocol_pb2' # @@protoc_insertion_point(class_scope:pbnetwork.Room) + )) +_sym_db.RegisterMessage(Room) -class RoomList(message.Message): - __metaclass__ = reflection.GeneratedProtocolMessageType - DESCRIPTOR = _ROOMLIST - +RoomList = _reflection.GeneratedProtocolMessageType('RoomList', (_message.Message,), dict( + DESCRIPTOR = _ROOMLIST, + __module__ = 'protocol_pb2' # @@protoc_insertion_point(class_scope:pbnetwork.RoomList) + )) +_sym_db.RegisterMessage(RoomList) -class Participant(message.Message): - __metaclass__ = reflection.GeneratedProtocolMessageType - DESCRIPTOR = _PARTICIPANT - +Participant = _reflection.GeneratedProtocolMessageType('Participant', (_message.Message,), dict( + DESCRIPTOR = _PARTICIPANT, + __module__ = 'protocol_pb2' # @@protoc_insertion_point(class_scope:pbnetwork.Participant) + )) +_sym_db.RegisterMessage(Participant) -class VCard(message.Message): - __metaclass__ = reflection.GeneratedProtocolMessageType - DESCRIPTOR = _VCARD - +VCard = _reflection.GeneratedProtocolMessageType('VCard', (_message.Message,), dict( + DESCRIPTOR = _VCARD, + __module__ = 'protocol_pb2' # @@protoc_insertion_point(class_scope:pbnetwork.VCard) + )) +_sym_db.RegisterMessage(VCard) -class Status(message.Message): - __metaclass__ = reflection.GeneratedProtocolMessageType - DESCRIPTOR = _STATUS - +Status = _reflection.GeneratedProtocolMessageType('Status', (_message.Message,), dict( + DESCRIPTOR = _STATUS, + __module__ = 'protocol_pb2' # @@protoc_insertion_point(class_scope:pbnetwork.Status) + )) +_sym_db.RegisterMessage(Status) -class Stats(message.Message): - __metaclass__ = reflection.GeneratedProtocolMessageType - DESCRIPTOR = _STATS - +Stats = _reflection.GeneratedProtocolMessageType('Stats', (_message.Message,), dict( + DESCRIPTOR = _STATS, + __module__ = 'protocol_pb2' # @@protoc_insertion_point(class_scope:pbnetwork.Stats) + )) +_sym_db.RegisterMessage(Stats) -class File(message.Message): - __metaclass__ = reflection.GeneratedProtocolMessageType - DESCRIPTOR = _FILE - +File = _reflection.GeneratedProtocolMessageType('File', (_message.Message,), dict( + DESCRIPTOR = _FILE, + __module__ = 'protocol_pb2' # @@protoc_insertion_point(class_scope:pbnetwork.File) + )) +_sym_db.RegisterMessage(File) -class FileTransferData(message.Message): - __metaclass__ = reflection.GeneratedProtocolMessageType - DESCRIPTOR = _FILETRANSFERDATA - +FileTransferData = _reflection.GeneratedProtocolMessageType('FileTransferData', (_message.Message,), dict( + DESCRIPTOR = _FILETRANSFERDATA, + __module__ = 'protocol_pb2' # @@protoc_insertion_point(class_scope:pbnetwork.FileTransferData) + )) +_sym_db.RegisterMessage(FileTransferData) -class BackendConfig(message.Message): - __metaclass__ = reflection.GeneratedProtocolMessageType - DESCRIPTOR = _BACKENDCONFIG - +BackendConfig = _reflection.GeneratedProtocolMessageType('BackendConfig', (_message.Message,), dict( + DESCRIPTOR = _BACKENDCONFIG, + __module__ = 'protocol_pb2' # @@protoc_insertion_point(class_scope:pbnetwork.BackendConfig) + )) +_sym_db.RegisterMessage(BackendConfig) -class WrapperMessage(message.Message): - __metaclass__ = reflection.GeneratedProtocolMessageType - DESCRIPTOR = _WRAPPERMESSAGE - +WrapperMessage = _reflection.GeneratedProtocolMessageType('WrapperMessage', (_message.Message,), dict( + DESCRIPTOR = _WRAPPERMESSAGE, + __module__ = 'protocol_pb2' # @@protoc_insertion_point(class_scope:pbnetwork.WrapperMessage) + )) +_sym_db.RegisterMessage(WrapperMessage) + # @@protoc_insertion_point(module_scope) From 6c12956dd663be4ad072594bf062f84abe56eefe Mon Sep 17 00:00:00 2001 From: moyamo Date: Tue, 15 Sep 2015 21:29:26 +0200 Subject: [PATCH 20/22] Attempt to fix groupchats --- Spectrum2/backend.py | 3 +- session.py | 266 ++++++++++++++++++++++++++----------------- yowsupwrapper.py | 48 +++++++- 3 files changed, 211 insertions(+), 106 deletions(-) diff --git a/Spectrum2/backend.py b/Spectrum2/backend.py index a027adc..afbdec8 100644 --- a/Spectrum2/backend.py +++ b/Spectrum2/backend.py @@ -25,7 +25,7 @@ class SpectrumBackend: self.m_data = "" self.m_init_res = 0 - def handleMessage(self, user, legacyName, msg, nickname = "", xhtml = "", timestamp = ""): + def handleMessage(self, user, legacyName, msg, nickname = "", xhtml = "", timestamp = "", pm = True): m = protocol_pb2.ConversationMessage() m.userName = user m.buddyName = legacyName @@ -33,6 +33,7 @@ class SpectrumBackend: m.nickname = nickname m.xhtml = xhtml m.timestamp = str(timestamp) + m.pm = pm message = WRAP(m.SerializeToString(), protocol_pb2.WrapperMessage.TYPE_CONV_MESSAGE) self.send(message) diff --git a/session.py b/session.py index b86fa97..b59544a 100644 --- a/session.py +++ b/session.py @@ -47,8 +47,6 @@ class Session(YowsupApp): 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 = '' @@ -56,36 +54,36 @@ class Session(YowsupApp): self.groups = {} self.presenceRequested = [] self.offlineQueue = [] + self.msgIDs = { } self.groupOfflineQueue = { } + self.shouldBeConnected = False self.timer = None self.password = None self.initialized = False - self.loggedin = False + self.synced = False + self.buddies = BuddyList(self.legacyName, self.db) 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 + self.logger.info("%s logged out", self.user) super(Session, self).logout() def login(self, password): - self.loggedin = True + self.logger.info("%s attempting login", self.user) self.password = password + self.shouldBeConncted = True 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.logger.debug("Got rooms: %s", rooms) self.backend.handleRoomList(rooms) @@ -95,6 +93,11 @@ class Session(YowsupApp): old = self.buddies.keys() self.buddies.load() new = self.buddies.keys() + contacts = new + + if self.synced == False: + self.sendSync(contacts, delta = False, interactive = True) + self.synced = True add = set(new) - set(old) remove = set(old) - set(new) @@ -103,27 +106,28 @@ class Session(YowsupApp): 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.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.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.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) - self.logger.debug('Requesting groups list') - self.requestGroupsList(self._updateGroups) - + def _updateGroups(self, response, request): self.logger.debug('Received groups list %s', response) - # This XMPP client is not receiving this for some reason. groups = response.getGroups() for group in groups: room = group.getId() - owner = group.getOwner() - subjectOwner = group.getSubjectOwner() - subject = group.getSubject() + owner = group.getOwner().split('@')[0] + subjectOwner = group.getSubjectOwner().split('@')[0] + subject = utils.softToUni(group.getSubject()) if room in self.groups: oroom = self.groups[room] @@ -132,22 +136,61 @@ class Session(YowsupApp): oroom.subject = subject else: self.groups[room] = Group(room, owner, subject, subjectOwner) - # A crude implemtation of groups that act like buddies + self.joinRoom(room, self.user.split("@")[0]) - self.backend.handleBuddyChanged(self.user, room, subject, [], protocol_pb2.STATUS_NONE) - # This XMPP client is not receiving this for some reason. -# self.updateRoomList() -# for group in groups: -# room = group.getId() -# subjectOwner = group.getSubjectOwner() -# subject = group.getSubject() -# self.backend.handleSubject(self.user, room, subject, subjectOwner) -# for participant in group.getParticipants(): -# buddy = participant.split('@')[0] -# self.logger.debug("Added %s to room %s", buddy, room) -# self.backend.handleParticipantChanged(self.user, buddy, room, -# protocol_pb2.PARTICIPANT_FLAG_NONE, protocol_pb2.STATUS_ONLINE) + self._addParticipantsToRoom(room, group.getParticipants()) + 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]) + self.updateRoomList() + + def joinRoom(self, room, nick): + if room in self.groups: + self.logger.info("Joining room: %s room=%s, nick=%s", + self.legacyName, room, nick) + + group = self.groups[room] + group.nick = nick + try: + ownerNick = self.buddies[group.subjectOwner].nick + except KeyError: + ownerNick = group.subjectOwner + + self.backend.handleSubject(self.user, room, group.subject, + ownerNick) + self.backend.handleRoomNicknameChanged(self.user, room, + group.subject) + else: + self.logger.warn("Room doesn't exist: %s", room) + + def _addParticipantsToRoom(self, room, participants): + group = self.groups[room] + + for jid, _type in participants.iteritems(): + buddy = jid.split("@")[0] + buddyFull = buddy + self.logger.info("Added %s to room %s", buddy, room) + try: + nick = self.buddies[buddy].nick + except KeyError: + nick = buddy + buddyFull = buddy + if _type == 'admin': + 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 + + self.backend.handleParticipantChanged(self.user, buddyFull, + room, flags, protocol_pb2.STATUS_ONLINE, buddy, nick) def _lastSeen(self, number, seconds): @@ -163,23 +206,29 @@ class Session(YowsupApp): 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.backend.handleBuddyChanged(self.user, "bot", self.bot.name, + ["Admin"], protocol_pb2.STATUS_ONLINE) + if self.initialized == False: + self.sendOfflineMessages() + self.bot.call("welcome") + self.initialized = True self.sendPresence(True) - self.updateRoster() + self.logger.debug('Requesting groups list') + self.requestGroupsList(self._updateGroups) + # 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.shouldBeConnected = False # 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): @@ -198,8 +247,9 @@ class Session(YowsupApp): ) # Called by superclass - def onTextMessage(self, _id, _from, to, notify, timestamp, participant, offline, retry, body): - self.logger.debug('received TextMessage' + + def onTextMessage(self, _id, _from, to, notify, timestamp, participant, + offline, retry, body): + self.logger.debug('received TextMessage' + ' '.join(map(str, [ _id, _from, to, notify, timestamp, participant, offline, retry, body @@ -210,10 +260,10 @@ class Session(YowsupApp): self.sendReceipt(_id, _from, None, participant) self.logger.info("Message received from %s to %s: %s (at ts=%s)", buddy, self.legacyName, messageContent, timestamp) - if participant is not None: + if participant is not None: # Group message partname = participant.split('@')[0] - message = partname + ': ' + messageContent - self.sendMessageToXMPP(buddy, message, timestamp) + self.sendGroupMessageToXMPP(buddy, partname, messageContent, + timestamp) else: self.sendMessageToXMPP(buddy, messageContent, timestamp) # isBroadcast always returns false, I'm not sure how to get a broadcast @@ -229,7 +279,7 @@ class Session(YowsupApp): buddy = image._from.split('@')[0] message = image.url + ' ' + image.caption self.sendMessageToXMPP(buddy, message, image.timestamp) - self.sendReceipt(image._id, image._from, None, image.participant) + self.sendReceipt(image._id, image._from, None, image.participant) # Called by superclass def onAudio(self, audio): @@ -237,7 +287,7 @@ class Session(YowsupApp): buddy = audio._from.split('@')[0] message = audio.url self.sendMessageToXMPP(buddy, message, audio.timestamp) - self.sendReceipt(audio._id, audio._from, None, audio.participant) + self.sendReceipt(audio._id, audio._from, None, audio.participant) # Called by superclass def onVideo(self, video): @@ -245,21 +295,35 @@ class Session(YowsupApp): buddy = video._from.split('@')[0] message = video.url self.sendMessageToXMPP(buddy, message, video.timestamp) - self.sendReceipt(video._id, video._from, None, video.participant) + self.sendReceipt(video._id, video._from, None, video.participant) + + def onLocation(self, location): + buddy = location._from.split('@')[0] + latitude = location.getLatitude() + longitude = location.getLongitude() + url = location.getLocationUrl() + + self.logger.debug("Location received from %s: %s, %s", + buddy, latitude, longitude) + + self.sendMessageToXMPP(buddy, url, location.timestamp) + self.sendMessageToXMPP(buddy, 'geo:' + latitude + ',' + longitude, + location.timestamp) + # Called by superclass def onVCard(self, _id, _from, name, card_data, to, notify, timestamp, participant): - self.logger.debug('received VCard' + + self.logger.debug('received VCard' + ' '.join(map(str, [ _id, _from, name, card_data, to, notify, timestamp, participant ])) ) buddy = _from.split("@")[0] self.sendMessageToXMPP(buddy, "Received VCard (not implemented yet)") - self.sendMessageToXMPP(buddy, card_data) +# self.sendMessageToXMPP(buddy, card_data) self.transferFile(buddy, str(name), card_data) self.sendReceipt(_id, _from, None, participant) - + def transferFile(self, buddy, name, data): # Not working self.logger.debug('transfering file %s', name) @@ -270,17 +334,21 @@ class Session(YowsupApp): # Called by superclass def onContactTyping(self, buddy): self.logger.info("Started typing: %s", buddy) - self.sendPresence(True) - self.backend.handleBuddyTyping(self.user, buddy) + if buddy != 'bot': + self.sendPresence(True) + self.backend.handleBuddyTyping(self.user, buddy) - if self.timer != None: - self.timer.cancel() + 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() + if buddy != 'bot': + 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] @@ -302,7 +370,8 @@ class Session(YowsupApp): 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) + 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) @@ -310,7 +379,8 @@ class Session(YowsupApp): 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) + 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) @@ -330,20 +400,33 @@ class Session(YowsupApp): self.sendTyping(buddy, False) def sendMessageToWA(self, sender, message): - self.logger.info("Message sent from %s to %s: %s", self.legacyName, 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) + if "/" in sender: # directed at single user + room, nick = sender.split("/") + for buddy, buddy3 in self.buddies.iteritems(): + self.logger.info("Group buddy=%s nick=%s", buddy, + buddy3.nick) + if buddy3.nick == nick: + nick = buddy + self.sendTextMessage(nick + '@s.whatsapp.net', message) else: room = sender -# group = self.groups[room] - -# self.backend.handleMessage(self.user, room, message, group.nick) + try: + group = self.groups[room] + self.logger.info("Group Message from %s to %s Groups: %s", + group.nick , group , self.groups) + self.backend.handleMessage( + self.user, room, message.decode('utf-8'), group.nick + ) + except KeyError: + self.logger.error('Group not found: %s', room) self.sendTextMessage(room + '@g.us', message) else: # private msg @@ -368,26 +451,37 @@ class Session(YowsupApp): "", timestamp) def sendGroupMessageToXMPP(self, room, buddy, messageContent, timestamp = ""): + try: + nick = self.buddies[buddy].nick + except KeyError: + nick = buddy + 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) + 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)) + 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) + self.logger.debug("Group message sent from %s to %s: %s", buddy, + room, messageContent) + self.backend.handleMessage(self.user, room, messageContent, nick, + "", timestamp, False) 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: + if status == protocol_pb2.STATUS_ONLINE \ + or status == protocol_pb2.STATUS_FFC: self.sendPresence(True) else: self.sendPresence(False) @@ -421,21 +515,8 @@ class Session(YowsupApp): 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 requestVCard(self, buddy, ID): - def onSuccess(response, request): self.logger.debug('Sending VCard (%s) with image id %s', ID, response.pictureId) @@ -458,32 +539,11 @@ class Session(YowsupApp): if receiptRequested: self.call("message_ack", (jid, messageId)) - 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.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)) diff --git a/yowsupwrapper.py b/yowsupwrapper.py index 8058b10..2a8349b 100644 --- a/yowsupwrapper.py +++ b/yowsupwrapper.py @@ -30,6 +30,7 @@ from yowsup.layers.protocol_calls import YowCallsProtocolLayer from yowsup.layers.protocol_acks.protocolentities import * from yowsup.layers.protocol_chatstate.protocolentities import * +from yowsup.layers.protocol_contacts.protocolentities import * from yowsup.layers.protocol_groups.protocolentities import * from yowsup.layers.protocol_media.protocolentities import * from yowsup.layers.protocol_messages.protocolentities import * @@ -197,6 +198,26 @@ class YowsupApp(object): ) self.sendEntity(state) + def sendSync(self, contacts, delta = False, interactive = True): + """ + 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 + """ + # TODO: Implement callbacks + mode = GetSyncIqProtocolEntity.MODE_DELTA if delta else GetSyncIqProtocolEntity.MODE_FULL + context = GetSyncIqProtocolEntity.CONTEXT_INTERACTIVE if interactive else GetSyncIqProtocolEntity.CONTEXT_REGISTRATION + iq = GetSyncIqProtocolEntity(contacts, mode, context) + self.sendIq(iq) + def requestLastSeen(self, phoneNumber, success = None, failure = None): """ Requests when user was last seen. @@ -219,8 +240,8 @@ class YowsupApp(object): 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 + - 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) @@ -229,6 +250,18 @@ class YowsupApp(object): 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 onAuthSuccess(self, status, kind, creation, expiration, props, nonce, t): """ Called when login is successful. @@ -361,6 +394,15 @@ class YowsupApp(object): - 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): """ @@ -508,6 +550,8 @@ class YowsupAppLayer(YowInterfaceLayer): entity.timestamp, entity.participant ) + elif isinstance(entity, LocationMediaMessageProtocolEntity): + self.caller.onLocation(entity) @ProtocolEntityCallback('presence') def onPresenceReceived(self, presence): From 7f1c9cd0dbc837773b51cb2b643161ecb5982f40 Mon Sep 17 00:00:00 2001 From: moyamo Date: Mon, 21 Sep 2015 20:30:11 +0200 Subject: [PATCH 21/22] Shorten the name of groupchats to overcome truncation bug The names of groupchats are truncated internally to 22 characters. This causes many problems with groupchats, since spectrum incorrectly identifies the chat by the long name. To overcome this the phoneNumber-unixTime is converted to hexadecimal, to shorten the groupchat below 22 characters --- Spectrum2/backend.py | 3 +- buddy.py | 4 +-- session.py | 77 +++++++++++++++++++++++++++++++------------- whatsappbackend.py | 2 ++ 4 files changed, 60 insertions(+), 26 deletions(-) diff --git a/Spectrum2/backend.py b/Spectrum2/backend.py index afbdec8..a027adc 100644 --- a/Spectrum2/backend.py +++ b/Spectrum2/backend.py @@ -25,7 +25,7 @@ class SpectrumBackend: self.m_data = "" self.m_init_res = 0 - def handleMessage(self, user, legacyName, msg, nickname = "", xhtml = "", timestamp = "", pm = True): + def handleMessage(self, user, legacyName, msg, nickname = "", xhtml = "", timestamp = ""): m = protocol_pb2.ConversationMessage() m.userName = user m.buddyName = legacyName @@ -33,7 +33,6 @@ class SpectrumBackend: m.nickname = nickname m.xhtml = xhtml m.timestamp = str(timestamp) - m.pm = pm message = WRAP(m.SerializeToString(), protocol_pb2.WrapperMessage.TYPE_CONV_MESSAGE) self.send(message) diff --git a/buddy.py b/buddy.py index 1ab9944..e07ec46 100644 --- a/buddy.py +++ b/buddy.py @@ -23,7 +23,6 @@ __status__ = "Prototype" """ from Spectrum2 import protocol_pb2 -from Yowsup.Contacts.contacts import WAContactsSyncRequest import logging @@ -61,7 +60,8 @@ class Buddy(): def update(self, nick, groups, image_hash): self.nick = nick self.groups = groups - self.image_hash = image_hash + if image_hash is not None: + self.image_hash = image_hash groups = u",".join(groups).encode("latin-1") cur = self.db.cursor() diff --git a/session.py b/session.py index b59544a..d65361f 100644 --- a/session.py +++ b/session.py @@ -79,10 +79,18 @@ class Session(YowsupApp): self.shouldBeConncted = True super(Session, self).login(self.legacyName, self.password) + def _shortenGroupId(self, gid): + # FIXME: will have problems if number begins with 0 + return '-'.join(hex(int(s))[2:] for s in gid.split('-')) + + def _lengthenGroupId(self, gid): + # FIXME: will have problems if number begins with 0 + return '-'.join(str(int(s, 16)) for s in gid.split('-')) + def updateRoomList(self): rooms = [] for room, group in self.groups.iteritems(): - rooms.append([room, group.subject]) + rooms.append([self._shortenGroupId(room), group.subject]) self.logger.debug("Got rooms: %s", rooms) self.backend.handleRoomList(rooms) @@ -136,7 +144,7 @@ class Session(YowsupApp): oroom.subject = subject else: self.groups[room] = Group(room, owner, subject, subjectOwner) - self.joinRoom(room, self.user.split("@")[0]) + self.joinRoom(self._shortenGroupId(room), self.user.split("@")[0]) self._addParticipantsToRoom(room, group.getParticipants()) @@ -150,6 +158,7 @@ class Session(YowsupApp): self.updateRoomList() def joinRoom(self, room, nick): + room = self._lengthenGroupId(room) if room in self.groups: self.logger.info("Joining room: %s room=%s, nick=%s", self.legacyName, room, nick) @@ -165,11 +174,38 @@ class Session(YowsupApp): ownerNick) self.backend.handleRoomNicknameChanged(self.user, room, group.subject) + self._refreshParticipants(room) else: self.logger.warn("Room doesn't exist: %s", room) + def _refreshParticipants(self, room): + group = self.groups[room] + for jid in group.participants: + buddy = jid.split("@")[0] + self.logger.info("Added %s to room %s", buddy, room) + try: + nick = self.buddies[buddy].nick + except KeyError: + nick = buddy + if nick == "": + nick = buddy + + buddyFull = buddy + if buddy == group.owner: + flags = protocol_pb2.PARTICIPANT_FLAG_MODERATOR + else: + flags = protocol_pb2.PARTICIPANT_FLAG_NONE + 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) + def _addParticipantsToRoom(self, room, participants): group = self.groups[room] + group.participants = participants for jid, _type in participants.iteritems(): buddy = jid.split("@")[0] @@ -190,7 +226,7 @@ class Session(YowsupApp): buddyFull = self.user self.backend.handleParticipantChanged(self.user, buddyFull, - room, flags, protocol_pb2.STATUS_ONLINE, buddy, nick) + self._shortenGroupId(room), flags, protocol_pb2.STATUS_ONLINE, buddy, nick) def _lastSeen(self, number, seconds): @@ -224,7 +260,7 @@ class Session(YowsupApp): self.backend.handleDisconnected(self.user, 0, reason) self.password = None self.shouldBeConnected = False - + # Called by superclass def onDisconnect(self): self.logger.debug('Disconnected') @@ -236,9 +272,12 @@ class Session(YowsupApp): ' '.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) + try: + buddy = self.buddies[_from.split('@')[0]] + self.backend.handleBuddyChanged(self.user, buddy.number.number, + buddy.nick, buddy.groups, protocol_pb2.STATUS_ONLINE) + except KeyError: + pass # Called by superclass def onAck(self, _id, _class, _from, timestamp): @@ -427,7 +466,7 @@ class Session(YowsupApp): ) except KeyError: self.logger.error('Group not found: %s', room) - self.sendTextMessage(room + '@g.us', message) + self.sendTextMessage(self._lengthenGroupId(room) + '@g.us', message) else: # private msg buddy = sender @@ -451,10 +490,13 @@ class Session(YowsupApp): "", timestamp) def sendGroupMessageToXMPP(self, room, buddy, messageContent, timestamp = ""): + self._refreshParticipants(room) try: nick = self.buddies[buddy].nick except KeyError: nick = buddy + if nick == "": + nick = buddy if timestamp: timestamp = time.strftime("%Y%m%dT%H%M%S", time.gmtime(timestamp)) @@ -470,10 +512,10 @@ class Session(YowsupApp): (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, nick, - "", timestamp, False) + self.logger.debug("Group message sent from %s (%s) to %s: %s", + buddy, nick, room, messageContent) + self.backend.handleMessage(self.user, self._shortenGroupId(room), + messageContent, nick, "", timestamp) def changeStatus(self, status): if status != self.status: @@ -504,7 +546,7 @@ 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, image_hash =""): + def updateBuddy(self, buddy, nick, groups, image_hash = None): if buddy != "bot": self.buddies.update(buddy, nick, groups, image_hash) self.updateRoster() @@ -539,15 +581,6 @@ class Session(YowsupApp): if receiptRequested: self.call("message_ack", (jid, messageId)) - 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] diff --git a/whatsappbackend.py b/whatsappbackend.py index b7b9827..b973f76 100644 --- a/whatsappbackend.py +++ b/whatsappbackend.py @@ -67,6 +67,7 @@ class WhatsAppBackend(SpectrumBackend): # # TODO Proper fix, this work around drops all duplicate messages even # intentional ones. + # 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) @@ -105,6 +106,7 @@ class WhatsAppBackend(SpectrumBackend): 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 From f4e85f7689ae95fc9881e6c84166b699ffad9ee9 Mon Sep 17 00:00:00 2001 From: moyamo Date: Mon, 21 Sep 2015 21:45:18 +0200 Subject: [PATCH 22/22] Try to prevent transwhat from crashing on malformed protobuf --- Spectrum2/backend.py | 11 ++++++++++- session.py | 2 +- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/Spectrum2/backend.py b/Spectrum2/backend.py index a027adc..342f0e4 100644 --- a/Spectrum2/backend.py +++ b/Spectrum2/backend.py @@ -4,6 +4,7 @@ import struct import sys import os +import logging import google.protobuf def WRAP(MESSAGE, TYPE): @@ -24,6 +25,7 @@ class SpectrumBackend: 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() @@ -349,8 +351,15 @@ class SpectrumBackend: wrapper = protocol_pb2.WrapperMessage() - if (wrapper.ParseFromString(self.m_data[4:]) == False): + try: + parseFromString = wrapper.ParseFromString(self.m_data[4:]) + except: + parseFromString = True + self.logger.error("Parse from String exception") + + if parseFromString == False: self.m_data = self.m_data[expected_size+4:] + self.logger.error("Parse from String exception") return self.m_data = self.m_data[4+expected_size:] diff --git a/session.py b/session.py index d65361f..bdd9144 100644 --- a/session.py +++ b/session.py @@ -458,7 +458,7 @@ class Session(YowsupApp): else: room = sender try: - group = self.groups[room] + group = self.groups[self._lengthenGroupId(room)] self.logger.info("Group Message from %s to %s Groups: %s", group.nick , group , self.groups) self.backend.handleMessage(