From 7912b2aa8daf492f3933d975fab97e59937981e1 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Tue, 29 Sep 2009 07:36:52 -0500 Subject: [PATCH] Starting to generalize the gv backend --- src/channel/call.py | 3 +- src/channel/text.py | 7 +- src/channel_manager.py | 14 +--- src/connection.py | 22 ++++-- src/connection_manager.py | 24 ++++-- src/constants.py | 2 +- src/gv_backend.py | 175 +++++++++++++++++++++++--------------------- src/handle.py | 30 +++----- support/theonering.manager | 3 +- 9 files changed, 145 insertions(+), 135 deletions(-) mode change 100644 => 100755 src/gv_backend.py diff --git a/src/channel/call.py b/src/channel/call.py index 33ef173..21613b7 100644 --- a/src/channel/call.py +++ b/src/channel/call.py @@ -12,9 +12,8 @@ class CallChannel( telepathy.server.ChannelInterfaceCallState, ): - def __init__(self, connection, conversation): + def __init__(self, connection): self._recv_id = 0 - self._conversation = conversation self._connRef = weakref.ref(connection) telepathy.server.ChannelTypeText.__init__(self, connection, None) diff --git a/src/channel/text.py b/src/channel/text.py index 8fe4a5f..e882bfd 100644 --- a/src/channel/text.py +++ b/src/channel/text.py @@ -9,9 +9,8 @@ class TextChannel(telepathy.server.ChannelTypeText): Look into implementing ChannelInterfaceMessages for rich text formatting """ - def __init__(self, connection, conversation): + def __init__(self, connection): self._recv_id = 0 - self._conversation = conversation self._connRef = weakref.ref(connection) telepathy.server.ChannelTypeText.__init__(self, connection, None) @@ -21,9 +20,7 @@ class TextChannel(telepathy.server.ChannelTypeText): def Send(self, messageType, text): if messageType == telepathy.CHANNEL_TEXT_MESSAGE_TYPE_NORMAL: - self._conversation.send_text_message(text) - elif messageType == telepathy.CHANNEL_TEXT_MESSAGE_TYPE_ACTION and text == u"nudge": - self._conversation.send_nudge() + pass else: raise telepathy.NotImplemented("Unhandled message type") self.Sent(int(time.time()), messageType, text) diff --git a/src/channel_manager.py b/src/channel_manager.py index 90fdd99..979481f 100644 --- a/src/channel_manager.py +++ b/src/channel_manager.py @@ -39,32 +39,26 @@ class ChannelManager(object): self._connRef().add_channel(chan, handle, suppress_handler) return chan - def channel_for_text(self, handle, conversation=None, suppress_handler=False): + def channel_for_text(self, handle, suppress_handler=False): if handle in self._textChannels: chan = self._textChannels[handle] else: _moduleLogger.debug("Requesting new text channel") contact = handle.contact - if conversation is None: - client = self._connRef().msn_client - conversation = None - chan = channel.text.TextChannel(self._connRef(), conversation) + chan = channel.text.TextChannel(self._connRef()) self._textChannels[handle] = chan self._connRef().add_channel(chan, handle, suppress_handler) return chan - def channel_forcall(self, handle, conversation=None, suppress_handler=False): + def channel_for_call(self, handle, suppress_handler=False): if handle in self._callChannels: chan = self._callChannels[handle] else: _moduleLogger.debug("Requesting new call channel") contact = handle.contact - if conversation is None: - client = self._connRef().msn_client - conversation = None - chan = channel.call.CallChannel(self._connRef(), conversation) + chan = channel.call.CallChannel(self._connRef()) self._callChannels[handle] = chan self._connRef().add_channel(chan, handle, suppress_handler) return chan diff --git a/src/connection.py b/src/connection.py index 304491d..567d399 100644 --- a/src/connection.py +++ b/src/connection.py @@ -17,7 +17,8 @@ class TheOneRingConnection(telepathy.server.Connection, simple_presence.SimplePr MANDATORY_PARAMETERS = { 'account' : 's', - 'password' : 's' + 'password' : 's', + 'forward' : 's', } OPTIONAL_PARAMETERS = { } @@ -41,6 +42,7 @@ class TheOneRingConnection(telepathy.server.Connection, simple_presence.SimplePr parameters['account'].encode('utf-8'), parameters['password'].encode('utf-8'), ) + self._callbackNumber = parameters['forward'].encode('utf-8') self._channelManager = channel_manager.ChannelManager(self) cookieFilePath = "%s/cookies.txt" % constants._data_path_ @@ -79,6 +81,7 @@ class TheOneRingConnection(telepathy.server.Connection, simple_presence.SimplePr ) try: self._backend.login(*self._credentials) + self._backend.set_callback_number(self._callbackNumber) except gv_backend.NetworkError: self.StatusChanged( telepathy.CONNECTION_STATUS_DISCONNECTED, @@ -129,7 +132,10 @@ class TheOneRingConnection(telepathy.server.Connection, simple_presence.SimplePr elif type == telepathy.CHANNEL_TYPE_TEXT: if handleType != telepathy.HANDLE_TYPE_CONTACT: raise telepathy.NotImplemented("Only Contacts are allowed") - contact = handle.contact + channel = channelManager.channel_for_text(handle, None, suppressHandler) + elif type == telepathy.CHANNEL_TYPE_STREAMED_MEDIA: + if handleType != telepathy.HANDLE_TYPE_CONTACT: + raise telepathy.NotImplemented("Only Contacts are allowed") channel = channelManager.channel_for_text(handle, None, suppressHandler) else: raise telepathy.NotImplemented("unknown channel type %s" % type) @@ -141,7 +147,7 @@ class TheOneRingConnection(telepathy.server.Connection, simple_presence.SimplePr For org.freedesktop.telepathy.Connection """ self.check_connected() - self.check_handleType(handleType) + self.check_handle_type(handleType) handles = [] for name in names: @@ -159,16 +165,18 @@ class TheOneRingConnection(telepathy.server.Connection, simple_presence.SimplePr return handles def _create_contact_handle(self, name): - requestedContactId, requestedContactName = handle.field_split(name) + requestedContactId = name contacts = self._backend.get_contacts() contactsFound = [ (contactId, contactName) for (contactId, contactName) in contacts - if contactName == name + if contactId == requestedContactId ] if 0 < len(contactsFound): contactId, contactName = contactsFound[0] - h = handle.create_handle(self, 'contact', contactId, contactName) + if len(contactsFound) != 1: + _moduleLogger.error("Contact ID was not unique: %s for %s" % (contactId, contactName)) else: - h = handle.create_handle(self, 'contact', requestedContactId, requestedContactName) + contactId, contactName = requestedContactId, "" + h = handle.create_handle(self, 'contact', contactId, contactName) diff --git a/src/connection_manager.py b/src/connection_manager.py index 9f9dd88..e10f520 100644 --- a/src/connection_manager.py +++ b/src/connection_manager.py @@ -35,9 +35,12 @@ class TheOneRingConnectionManager(telepathy.server.ConnectionManager): defaultParameters = ConnectionClass.PARAMETER_DEFAULTS for parameterName, parameterType in mandatoryParameters.iteritems(): + flags = telepathy.CONN_MGR_PARAM_FLAG_REQUIRED + if parameterName == "password": + flags |= telepathy.CONN_MGR_PARAM_FLAG_SECRET param = ( parameterName, - telepathy.CONN_MGR_PARAM_FLAG_REQUIRED, + flags, parameterType, '', ) @@ -45,14 +48,19 @@ class TheOneRingConnectionManager(telepathy.server.ConnectionManager): for parameterName, parameterType in optionalParameters.iteritems(): if parameterName in defaultParameters: - param = ( - parameterName, - telepathy.CONN_MGR_PARAM_FLAG_HAS_DEFAULT, - parameterName, - defaultParameters[parameterName], - ) + flags = telepathy.CONN_MGR_PARAM_FLAG_HAS_DEFAULT + if parameterName == "password": + flags |= telepathy.CONN_MGR_PARAM_FLAG_SECRET + default = defaultParameters[parameterName] else: - param = (parameterName, 0, parameterName, '') + flags = 0 + default = "" + param = ( + parameterName, + flags, + parameterName, + default, + ) result.append(param) return result diff --git a/src/constants.py b/src/constants.py index 8c24289..2b55ef8 100644 --- a/src/constants.py +++ b/src/constants.py @@ -7,5 +7,5 @@ __build__ = 0 __app_magic__ = 0xdeadbeef _data_path_ = os.path.join(os.path.expanduser("~"), ".telepathy-theonering") _user_settings_ = "%s/settings.ini" % _data_path_ -_telepathy_protocol_name_ = "gvoice" +_telepathy_protocol_name_ = "sip" _telepathy_implementation_name_ = "theonering" diff --git a/src/gv_backend.py b/src/gv_backend.py old mode 100644 new mode 100755 index 4d6e19c..94bf180 --- a/src/gv_backend.py +++ b/src/gv_backend.py @@ -241,35 +241,6 @@ class GVDialer(object): """ return self._accountNum - def set_sane_callback(self): - """ - Try to set a sane default callback number on these preferences - 1) 1747 numbers ( Gizmo ) - 2) anything with gizmo in the name - 3) anything with computer in the name - 4) the first value - """ - numbers = self.get_callback_numbers() - - for number, description in numbers.iteritems(): - if re.compile(r"""1747""").match(number) is not None: - self.set_callback_number(number) - return - - for number, description in numbers.iteritems(): - if re.compile(r"""gizmo""", re.I).search(description) is not None: - self.set_callback_number(number) - return - - for number, description in numbers.iteritems(): - if re.compile(r"""computer""", re.I).search(description) is not None: - self.set_callback_number(number) - return - - for number, description in numbers.iteritems(): - self.set_callback_number(number) - return - def get_callback_numbers(self): """ @returns a dictionary mapping call back numbers to descriptions @@ -307,23 +278,6 @@ class GVDialer(object): for exactDate, name, number, relativeDate, action in sortedRecent: yield name, number, relativeDate, action - def get_addressbooks(self): - """ - @returns Iterable of (Address Book Factory, Book Id, Book Name) - """ - yield self, "", "" - - def open_addressbook(self, bookId): - return self - - @staticmethod - def contact_source_short_name(contactId): - return "GV" - - @staticmethod - def factory_name(): - return "Google Voice" - _contactsRe = re.compile(r"""(.*?)""", re.S) _contactsNextRe = re.compile(r""".*Next.*?""", re.S) _contactsURL = "https://www.google.com/voice/mobile/contacts" @@ -398,10 +352,7 @@ class GVDialer(object): decoratedSms = self._decorate_sms(parsedSms) allMessages = itertools.chain(decoratedVoicemails, decoratedSms) - sortedMessages = list(allMessages) - sortedMessages.sort(reverse=True) - for exactDate, header, number, relativeDate, message in sortedMessages: - yield header, number, relativeDate, message + return allMessages def _grab_json(self, flatXml): xmlTree = ElementTree.fromstring(flatXml) @@ -493,6 +444,7 @@ class GVDialer(object): _voicemailNumberRegex = re.compile(r"""""", re.MULTILINE) _prettyVoicemailNumberRegex = re.compile(r"""(.*?)""", re.MULTILINE) _voicemailLocationRegex = re.compile(r""".*?(.*?)""", re.MULTILINE) + _messagesContactID = re.compile(r""".*?\s*?(.*?)""", re.MULTILINE) #_voicemailMessageRegex = re.compile(r"""(.*?)""", re.MULTILINE) #_voicemailMessageRegex = re.compile(r"""(.*?)""", re.MULTILINE) _voicemailMessageRegex = re.compile(r"""((.*?)|(.*?))""", re.MULTILINE) @@ -522,6 +474,8 @@ class GVDialer(object): number = numberGroup.group(1).strip() if numberGroup else "" prettyNumberGroup = self._prettyVoicemailNumberRegex.search(messageHtml) prettyNumber = prettyNumberGroup.group(1).strip() if prettyNumberGroup else "" + contactIdGroup = self._messagesContactID.search(messageHtml) + contactId = contactIdGroup.group(1).strip() if contactIdGroup else number messageGroups = self._voicemailMessageRegex.finditer(messageHtml) messageParts = ( @@ -531,6 +485,7 @@ class GVDialer(object): yield { "id": messageId.strip(), + "contactId": contactId, "name": name, "time": exactTime, "relTime": relativeTime, @@ -540,29 +495,23 @@ class GVDialer(object): "messageParts": messageParts, } - def _decorate_voicemail(self, parsedVoicemail): + def _decorate_voicemail(self, parsedVoicemails): messagePartFormat = { "med1": "%s", "med2": "%s", "high": "%s", } - for voicemailData in parsedVoicemail: - exactTime = voicemailData["time"] - if voicemailData["name"]: - header = voicemailData["name"] - elif voicemailData["prettyNumber"]: - header = voicemailData["prettyNumber"] - elif voicemailData["location"]: - header = voicemailData["location"] - else: - header = "Unknown" + for voicemailData in parsedVoicemails: message = " ".join(( messagePartFormat[quality] % part for (quality, part) in voicemailData["messageParts"] )).strip() if not message: message = "No Transcription" - yield exactTime, header, voicemailData["number"], voicemailData["relTime"], (message, ) + whoFrom = voicemailData["name"] + when = voicemailData["time"] + voicemailData["messageParts"] = ((whoFrom, message, when), ) + yield voicemailData _smsFromRegex = re.compile(r"""(.*?)""", re.MULTILINE | re.DOTALL) _smsTextRegex = re.compile(r"""(.*?)""", re.MULTILINE | re.DOTALL) @@ -583,6 +532,8 @@ class GVDialer(object): number = numberGroup.group(1).strip() if numberGroup else "" prettyNumberGroup = self._prettyVoicemailNumberRegex.search(messageHtml) prettyNumber = prettyNumberGroup.group(1).strip() if prettyNumberGroup else "" + contactIdGroup = self._messagesContactID.search(messageHtml) + contactId = contactIdGroup.group(1).strip() if contactIdGroup else number fromGroups = self._smsFromRegex.finditer(messageHtml) fromParts = (group.group(1).strip() for group in fromGroups) @@ -595,32 +546,84 @@ class GVDialer(object): yield { "id": messageId.strip(), + "contactId": contactId, "name": name, "time": exactTime, "relTime": relativeTime, "prettyNumber": prettyNumber, "number": number, + "location": "", "messageParts": messageParts, } - def _decorate_sms(self, parsedSms): - for messageData in parsedSms: - exactTime = messageData["time"] - if messageData["name"]: - header = messageData["name"] - elif messageData["prettyNumber"]: - header = messageData["prettyNumber"] - else: - header = "Unknown" - number = messageData["number"] - relativeTime = messageData["relTime"] - messages = [ - "%s: %s" % (messagePart[0], messagePart[-1]) - for messagePart in messageData["messageParts"] - ] - if not messages: - messages = ("No Transcription", ) - yield exactTime, header, number, relativeTime, messages + def _decorate_sms(self, parsedTexts): + return parsedTexts + + +def set_sane_callback(backend): + """ + Try to set a sane default callback number on these preferences + 1) 1747 numbers ( Gizmo ) + 2) anything with gizmo in the name + 3) anything with computer in the name + 4) the first value + """ + numbers = backend.get_callback_numbers() + + priorityOrderedCriteria = [ + ("1747", None), + (None, "gizmo"), + (None, "computer"), + (None, "sip"), + (None, None), + ] + + for numberCriteria, descriptionCriteria in priorityOrderedCriteria: + for number, description in numbers.iteritems(): + if numberCriteria is not None and re.compile(numberCriteria).match(number) is None: + continue + if descriptionCriteria is not None and re.compile(descriptionCriteria).match(description) is None: + continue + backend.set_callback_number(number) + return + + +def sort_messages(allMessages): + sortableAllMessages = [ + (message["time"], message) + for message in allMessages + ] + sortableAllMessages.sort(reverse=True) + return ( + message + for (exactTime, message) in sortableAllMessages + ) + + +def decorate_message(messageData): + exactTime = messageData["time"] + if messageData["name"]: + header = messageData["name"] + elif messageData["prettyNumber"]: + header = messageData["prettyNumber"] + else: + header = "Unknown" + number = messageData["number"] + relativeTime = messageData["relTime"] + + messageParts = list(messageData["messageParts"]) + if len(messageParts) == 0: + messages = ("No Transcription", ) + elif len(messageParts) == 1: + messages = (messageParts[0][1], ) + else: + messages = [ + "%s: %s" % (messagePart[0], messagePart[-1]) + for messagePart in messageParts + ] + + decoratedResults = header, number, relativeTime, messages + return decoratedResults def test_backend(username, password): @@ -640,7 +643,15 @@ def test_backend(username, password): # for contact in backend.get_contacts(): # print contact # pprint.pprint(list(backend.get_contact_details(contact[0]))) - for message in backend.get_messages(): - pprint.pprint(message) + #for message in backend.get_messages(): + # pprint.pprint(message) + for message in sort_messages(backend.get_messages()): + pprint.pprint(decorate_message(message)) return backend + + +if __name__ == "__main__": + import sys + logging.basicConfig(level=logging.DEBUG) + test_backend(sys.argv[1], sys.argv[2]) diff --git a/src/handle.py b/src/handle.py index e2c7ae3..702e35a 100644 --- a/src/handle.py +++ b/src/handle.py @@ -64,34 +64,26 @@ class ConnectionHandle(TheOneRingHandle): self.profile = connection.username -def field_join(fields): - """ - >>> field_join("1", "First Name") - '1#First Name' - """ - return "#".join(fields) - - -def field_split(fields): - """ - >>> field_split('1#First Name') - ['1', 'First Name'] - """ - return fields.split("#") - - class ContactHandle(TheOneRingHandle): def __init__(self, connection, id, contactId, contactAccount): handleType = telepathy.HANDLE_TYPE_CONTACT - handleName = field_join(contactId, contactAccount) + handleName = contactId TheOneRingHandle.__init__(self, connection, id, handleType, handleName) - self.account = contactAccount + self._account = contactAccount self._id = contactId @property - def contact(self): + def contactID(self): + return self._id + + @property + def contactName(self): + return self._account + + @property + def contactDetails(self): return self._conn.gvoice_client.get_contact_details(self._id) diff --git a/support/theonering.manager b/support/theonering.manager index 31b8dbd..5a9bbaf 100644 --- a/support/theonering.manager +++ b/support/theonering.manager @@ -5,4 +5,5 @@ ObjectPath = /org/freedesktop/Telepathy/ConnectionManager/theonering [Protocol GoogleVoice] param-account = s required -param-password = s required +param-password = s required secret +param-forward = s required -- 1.7.9.5