diff --git a/README.md b/README.md index 286f631..5b204eb 100644 --- a/README.md +++ b/README.md @@ -1,44 +1,33 @@ # 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 +Manual compile latest version from https://github.com/hanzz/libtransport. -#### e4u -is a simple emoji4unicode python wrapper library +## Contributors -Install with `pip install e4u` +Pull requests, bug reports etc. are welcome. Help us to provide a open implementation of the WhatsApp protocol. -#### Yowsup -is a Implementation of the WhatsApp protocol in python +The following persons have contributed major parts of this code: -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 - -## Contribute - -Pull requests, bug reports etc. are welcome. -Help us to provide a open implementation of the WhatsApp protocol. + - **Steffen Vogel** (@stv0g): Idea and initial implementation based on Yowsup 1 + - **Mohammed Yaseen Mowzer** (@moyamo): Port to Yowsup 2 ## 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/). diff --git a/Spectrum2/backend.py b/Spectrum2/backend.py index 3bef24f..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() @@ -204,7 +206,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); @@ -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/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/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) 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/buddy.py b/buddy.py index 8338c11..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 @@ -48,7 +47,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 +55,17 @@ 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 + if image_hash is not None: + 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,26 +112,28 @@ 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): - 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/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/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. 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/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/session.py b/session.py index 8e30faa..bdd9144 100644 --- a/session.py +++ b/session.py @@ -27,27 +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.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 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 Spectrum2 import protocol_pb2 from buddy import BuddyList @@ -55,10 +34,12 @@ 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): + super(Session, self).__init__() self.logger = logging.getLogger(self.__class__.__name__) self.logger.info("Created: %s", legacyName) @@ -73,121 +54,428 @@ class Session(): 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) - env.CURRENT_ENV = env.S40YowsupEnv() - layers = (SpectrumLayer, - YowParallelLayer((YowAuthenticationProtocolLayer, - YowMessagesProtocolLayer, - YowReceiptProtocolLayer, - YowAckProtocolLayer, - YowMediaProtocolLayer, - YowIqProtocolLayer, - YowGroupsProtocolLayer, - YowPresenceProtocolLayer)), - 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)) - def logout(self): - self.loggedin = False - self.stack.broadcastEvent(YowLayerEvent(YowNetworkLayer.EVENT_STATE_DISCONNECT)) + self.logger.info("%s logged out", self.user) + 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.logger.info("%s attempting login", self.user) 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) + 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) + def updateRoster(self): + self.logger.debug("Update roster") + + 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) + + self.logger.debug("Roster remove: %s", str(list(remove))) + self.logger.debug("Roster add: %s", str(list(add))) + + for number in remove: + self.backend.handleBuddyChanged(self.user, number, "", [], + protocol_pb2.STATUS_NONE) + self.backend.handleBuddyRemoved(self.user, number) + self.unsubscribePresence(number) + + for number in add: + buddy = self.buddies[number] + self.subscribePresence(number) + self.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 _updateGroups(self, response, request): + self.logger.debug('Received groups list %s', response) + groups = response.getGroups() + for group in groups: + room = group.getId() + owner = group.getOwner().split('@')[0] + subjectOwner = group.getSubjectOwner().split('@')[0] + subject = utils.softToUni(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) + self.joinRoom(self._shortenGroupId(room), self.user.split("@")[0]) + + 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): + room = self._lengthenGroupId(room) + 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) + 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] + 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, + self._shortenGroupId(room), flags, protocol_pb2.STATUS_ONLINE, buddy, nick) + + + def _lastSeen(self, number, seconds): + self.logger.debug("Last seen %s at %s seconds" % (number, str(seconds))) + if seconds < 60: + self.onPresenceAvailable(number) + else: + self.onPresenceUnavailable(number) + + # Called by superclass + def onAuthSuccess(self, status, kind, creation, + expiration, props, nonce, t): + self.logger.info("Auth success: %s", self.user) + + self.backend.handleConnected(self.user) + self.backend.handleBuddyChanged(self.user, "bot", self.bot.name, + ["Admin"], protocol_pb2.STATUS_ONLINE) + 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') + + # 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])) + ) + 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): + self.logger.debug('received ack ' + + ' '.join(map(str, [_id, _class, _from,timestamp,])) + ) + + # Called by superclass + 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) + if participant is not None: # Group message + partname = participant.split('@')[0] + 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 + # 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 + + # 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) + + # 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) + + # 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) + + 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' + + ' '.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): + self.logger.info("Started typing: %s", buddy) + if buddy != 'bot': + 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) + 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] +# 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) + 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.call("message_send", to = buddy + "@s.whatsapp.net", - message = 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] + try: + 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( + self.user, room, message.decode('utf-8'), group.nick + ) + except KeyError: + self.logger.error('Group not found: %s', room) + self.sendTextMessage(self._lengthenGroupId(room) + '@g.us', message) - self.backend.handleMessage(self.user, room, message, group.nick) - self.call("message_send", to = room + "@g.us", message = 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) +# if message == "\\lastseen": +# self.call("presence_request", buddy = (buddy + "@s.whatsapp.net",)) +# else: + 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)) @@ -202,34 +490,48 @@ class Session(): "", 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)) 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 (%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: self.logger.info("Status changed: %s", status) self.status = status - if status == protocol_pb2.STATUS_ONLINE or status == protocol_pb2.STATUS_FFC: - self.call("presence_sendAvailable") + if status == protocol_pb2.STATUS_ONLINE \ + or status == protocol_pb2.STATUS_FFC: + 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: @@ -244,9 +546,9 @@ class Session(): 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 = None): if buddy != "bot": - self.buddies.update(buddy, nick, groups) + self.buddies.update(buddy, nick, groups, image_hash) self.updateRoster() def removeBuddy(self, buddy): @@ -255,26 +557,21 @@ class Session(): 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) + 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) - group.nick = nick - - self.call("group_getParticipants", (room + "@g.us",)) - self.backend.handleSubject(self.user, room, group.subject, group.subjectOwner) - else: - self.logger.warn("Room doesn't exist: %s", room) - - def onMediaReceived(self, messageId, jid, preview, url, size, receiptRequested, isBroadcast): - buddy = jid.split("@")[0] - - self.logger.info("Media received from %s: %s", buddy, url) - self.sendMessageToXMPP(buddy, utils.shorten(url)) - if receiptRequested: self.call("message_ack", (jid, messageId)) + 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): buddy = jid.split("@")[0] self.logger.info("Location received from %s: %s, %s", buddy, latitude, longitude) @@ -283,70 +580,6 @@ class Session(): 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 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] - subjectOwner = subjectOwner.split("@")[0] - - if room in self.groups: - room = self.groups[room] - room.owner = owner - room.subjectOwner = subjectOwner - room.subject = subject - else: - self.groups[room] = Group(room, owner, subject, subjectOwner) - - self.updateRoomList() - - def onGroupGotParticipants(self, gjid, jids): - room = gjid.split("@")[0] - group = self.groups[room] - - for jid in jids: - buddy = jid.split("@")[0] - self.logger.info("Added %s to room %s", buddy, room) - - if buddy == group.owner: - flags = protocol_pb2.PARTICIPANT_FLAG_MODERATOR - else: - flags = protocol_pb2.PARTICIPANT_FLAG_NONE - - self.backend.handleParticipantChanged(self.user, buddy, room, flags, protocol_pb2.STATUS_ONLINE) # TODO check status - - if room in self.groupOfflineQueue: - while self.groupOfflineQueue[room]: - msg = self.groupOfflineQueue[room].pop(0) - self.backend.handleMessage(self.user, room, msg[1], msg[0], "", msg[2]) - self.logger.debug("Send queued group message to: %s %s %s", msg[0],msg[1], msg[2]) - - def onGroupMessageReceived(self, messageId, gjid, jid, messageContent, timestamp, receiptRequested, pushName): - buddy = jid.split("@")[0] - room = gjid.split("@")[0] - - self.logger.info("Group message received in %s from %s: %s", room, buddy, messageContent) - - self.sendGroupMessageToXMPP(room, buddy, utils.softToUni(messageContent), timestamp) - if receiptRequested: self.call("message_ack", (gjid, messageId)) def onGroupSubjectReceived(self, messageId, gjid, jid, subject, timestamp, receiptRequested): room = gjid.split("@")[0] @@ -356,15 +589,6 @@ class Session(): 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] - - loggin.info("Added % to room %s", buddy, room) - - self.backend.handleParticipantChanged(self.user, buddy, room, protocol_pb2.PARTICIPANT_FLAG_NONE, protocol_pb2.STATUS_ONLINE) - if receiptRequested: self.call("notification_ack", (gjid, messageId)) - def onGroupParticipantRemoved(self, gjid, jid, author, timestamp, messageId, receiptRequested): room = gjid.split("@")[0] buddy = jid.split("@")[0] @@ -381,163 +605,3 @@ class Session(): 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.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) - 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')) - 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_PAUSE, 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("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.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 - - 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) - - @ProtocolEntityCallback("message") - def onMessageReceived(self, messageEntity): - buddy = messageEntity.getFrom().split('@')[0] - messageContent = utils.softToUni(messageEntity.getBody()) - timestamp = messageEntity.getTimestamp() - - 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() - lastseen = presence.getLast() - buddy = jid.split("@")[0] - 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.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.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 90703a4..84abc31 100755 --- a/transwhat.py +++ b/transwhat.py @@ -25,12 +25,14 @@ __status__ = "Prototype" """ import argparse +import traceback import logging import asyncore import sys, os import MySQLdb import e4u import threading +import Queue sys.path.insert(0, os.getcwd()) @@ -39,6 +41,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() @@ -52,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 \ ) @@ -65,10 +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) -asyncore.loop(1) +while True: + 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/utils.py b/utils.py index 71d6f38..69a0c44 100644 --- a/utils.py +++ b/utils.py @@ -22,21 +22,9 @@ __status__ = "Prototype" along with transWhat. If not, see . """ -import urllib -import json import e4u import base64 - -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']) - +import hashlib def ago(secs): periods = ["second", "minute", "hour", "day", "week", "month", "year", "decade"] @@ -61,3 +49,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 4aa0b17..b973f76 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,19 @@ 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 + # + # 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) + usersMessage[buddy] = message def handleJoinRoomRequest(self, user, room, nickname, pasword): self.logger.debug("handleJoinRoomRequest(user=%s, room=%s, nickname=%s)", user, room, nickname) @@ -86,6 +102,11 @@ 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 @@ -93,9 +114,6 @@ class WhatsAppBackend(SpectrumBackend): def handleLeaveRoomRequest(self, user, room): pass - def handleVCardRequest(self, user, buddy, ID): - pass - def handleVCardUpdatedRequest(self, user, photo, nickname): pass @@ -103,7 +121,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 new file mode 100644 index 0000000..2a8349b --- /dev/null +++ b/yowsupwrapper.py @@ -0,0 +1,570 @@ +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 +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 + +# ProtocolEntities + +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 * +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): + env.CURRENT_ENV = env.S40YowsupEnv() + + layers = (YowsupAppLayer, + 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 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: 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) + self.sendEntity(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.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 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): + """ + Send status to whatsapp + + Args: + - statusTest: (str) Your whatsapp status + """ + iq = SetStatusIqProtocolEntity(statusText) + self.sendIq(iq) + + 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 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. + 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.sendIq(iq, onSuccess = partial(self._lastSeenSuccess, success), + onError = failure) + + def _lastSeenSuccess(self, success, response, request): + success(response._from.split('@')[0], response.seconds) + + def requestProfilePicture(self, phoneNumber, onSuccess = None, onFailure = None): + """ + Requests profile picture of whatsapp user + Args: + - phoneNumber: (str) the phone number of the user + - 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) + + def requestGroupsList(self, onSuccess = None, onFailure = None): + 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. + + 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 + + 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 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: (str) jid of user who sent the message in a groupchat + - 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 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): + """ + 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, + 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 + +class YowsupAppLayer(YowInterfaceLayer): + EVENT_START = 'transwhat.event.YowsupAppLayer.start' + 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 + # 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') + return True + 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 + 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): + # 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(entity.ack()) + + @ProtocolEntityCallback('message') + def onMessageReceived(self, 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 + ) + elif isinstance(entity, LocationMediaMessageProtocolEntity): + self.caller.onLocation(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)