diff options
author | Youness Alaoui <youness.alaoui@collabora.co.uk> | 2009-03-12 17:04:15 -0400 |
---|---|---|
committer | Youness Alaoui <youness.alaoui@collabora.co.uk> | 2009-03-12 17:04:15 -0400 |
commit | ede8699d1e7ebe17afe8fd9435ad1df21a5102e1 (patch) | |
tree | a32e6e3c93102ea2adfd17a89cd7535bcce66945 | |
parent | 0cef59fdd769b467b8a26ac22b8e7bd52598f9c0 (diff) | |
parent | 09d58b2dc1bb53c48dfc9f630ce23e819d21a534 (diff) |
Merge commit 'origin/master'
Conflicts:
pymsn/msnp2p/session_manager.py
-rw-r--r-- | pymsn/client.py | 28 | ||||
-rw-r--r-- | pymsn/conversation.py | 2 | ||||
-rw-r--r-- | pymsn/event/__init__.py | 1 | ||||
-rw-r--r-- | pymsn/event/mailbox.py | 43 | ||||
-rw-r--r-- | pymsn/msnp/__init__.py | 1 | ||||
-rw-r--r-- | pymsn/msnp/base.py | 5 | ||||
-rw-r--r-- | pymsn/msnp/mailbox.py | 117 | ||||
-rw-r--r-- | pymsn/msnp/notification.py | 107 | ||||
-rw-r--r-- | pymsn/msnp2p/session_manager.py | 6 | ||||
-rw-r--r-- | pymsn/profile.py | 10 | ||||
-rw-r--r-- | pymsn/service/OfflineIM/offline_messages_box.py | 8 |
11 files changed, 311 insertions, 17 deletions
diff --git a/pymsn/client.py b/pymsn/client.py index 6e2782a..61af313 100644 --- a/pymsn/client.py +++ b/pymsn/client.py @@ -79,6 +79,7 @@ password was wrong, this will lead us to use the L{pymsn.event} interfaces: """ + import pymsn.profile as profile import pymsn.msnp as msnp @@ -148,11 +149,12 @@ class Client(EventsDispatcher): self._external_conversations = {} self._sso = None - + self._profile = None self._address_book = None self._oim_box = None - + self._mailbox = None + self.__die = False self.__connect_transport_signals() self.__connect_protocol_signals() @@ -188,6 +190,12 @@ class Client(EventsDispatcher): return self._oim_box @property + def mailbox(self): + """The mailbox of the current user + @type: L{<pymsn.msnp.mailbox.Mailbox>}""" + return self._mailbox + + @property def spaces(self): """The MSN Spaces of the current user @type: L{Spaces<pymsn.service.Spaces>}""" @@ -213,6 +221,8 @@ class Client(EventsDispatcher): self.__die = False self._profile = profile.Profile((account, password), self._protocol) self.__connect_profile_signals() + self._mailbox = msnp.Mailbox(self._protocol) + self.__connect_mailbox_signals() self._transport.establish_connection() self._state = ClientState.CONNECTING @@ -261,7 +271,19 @@ class Client(EventsDispatcher): self.profile.connect("notify::personal-message", property_changed) self.profile.connect("notify::current-media", property_changed) self.profile.connect("notify::msn-object", property_changed) - + + def __connect_mailbox_signals(self): + """Connect mailbox signals""" + def new_mail_received(mailbox, mail): + self._dispatch("on_mailbox_new_mail_received", mail) + + def unread_changed(mailbox, unread_count, initial): + method_name = "on_mailbox_unread_mail_count_changed" + self._dispatch(method_name, unread_count, initial) + + self.mailbox.connect("unread-mail-count-changed", unread_changed) + self.mailbox.connect("new-mail-received", new_mail_received) + def __connect_contact_signals(self, contact): """Connect contact signals""" def event(contact, *args): diff --git a/pymsn/conversation.py b/pymsn/conversation.py index b47b1d1..b79cfa6 100644 --- a/pymsn/conversation.py +++ b/pymsn/conversation.py @@ -343,6 +343,8 @@ class AbstractConversation(ConversationInterface, EventsDispatcher): message_encoding = message.content_type[1] try: message_formatting = message.get_header('X-MMS-IM-Format') + if not message_formatting: + message_formatting = '=' except KeyError: message_formatting = '=' diff --git a/pymsn/event/__init__.py b/pymsn/event/__init__.py index 0c9b604..72caa55 100644 --- a/pymsn/event/__init__.py +++ b/pymsn/event/__init__.py @@ -72,3 +72,4 @@ from contact import * from address_book import * from offline_messages import * from invite import * +from mailbox import * diff --git a/pymsn/event/mailbox.py b/pymsn/event/mailbox.py new file mode 100644 index 0000000..60b9d18 --- /dev/null +++ b/pymsn/event/mailbox.py @@ -0,0 +1,43 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2008 Alen Bou-Haidar <alencool@gmail.com> +# +# This program 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 2 of the License, or +# (at your option) any later version. +# +# This program 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 this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +# + +"""Mailbox event interfaces + +The interfaces defined in this module allow receiving notification events about +the mailbox.""" + +from pymsn.event import BaseEventInterface + +__all__ = ["MailboxEventInterface"] + +class MailboxEventInterface(BaseEventInterface): + """interfaces allowing the user to get notified about events from the Inbox. + """ + + def __init__(self, client): + BaseEventInterface.__init__(self, client) + + def on_mailbox_unread_mail_count_changed(self, unread_mail_count, + initial=False): + """The number of unread mail messages""" + pass + + def on_mailbox_new_mail_received(self, mail_message): + """New mail message notification""" + pass diff --git a/pymsn/msnp/__init__.py b/pymsn/msnp/__init__.py index c9b8ca1..439b397 100644 --- a/pymsn/msnp/__init__.py +++ b/pymsn/msnp/__init__.py @@ -26,4 +26,5 @@ from message import * from constants import * from notification import * from switchboard import * +from mailbox import * from base import ProtocolState diff --git a/pymsn/msnp/base.py b/pymsn/msnp/base.py index b43fd9f..a05057c 100644 --- a/pymsn/msnp/base.py +++ b/pymsn/msnp/base.py @@ -76,8 +76,9 @@ class BaseProtocol(object): def _send_command(self, command, arguments=(), payload=None, increment=True, callback=None, *cb_args): - self._transport.send_command_ex(command, arguments, payload, increment, - callback, *cb_args) + command = self._transport.send_command_ex(command, arguments, payload, + increment, callback, *cb_args) + return command.transaction_id # default handlers def _default_handler(self, command): diff --git a/pymsn/msnp/mailbox.py b/pymsn/msnp/mailbox.py new file mode 100644 index 0000000..6636857 --- /dev/null +++ b/pymsn/msnp/mailbox.py @@ -0,0 +1,117 @@ +# -*- coding: utf-8 -*- +# +# pymsn - a python client library for Msn +# +# Copyright (C) 2008 Alen Bou-Haidar <alencool@gmail.com> +# +# This program 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 2 of the License, or +# (at your option) any later version. +# +# This program 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 this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + +import gobject + +__all__ = ['Mailbox', 'MailMessage'] + +class MailMessage(object): + + def __init__(self, name, address, subject, post_url, form_data): + self._name = name + self._subject = subject + self._address = address + self._post_url = post_url + self._form_data = form_data + + @property + def name(self): + """The name of the person who sent the email""" + return self._name + + @property + def address(self): + """Email address of the person who sent the email""" + return self._address + + @property + def post_url(self): + """post url""" + return self._post_url + + @property + def form_data(self): + """form url""" + return self._form_data + +class Mailbox(gobject.GObject): + """Mailbox information of the User connecting to the service + + @undocumented: __gsignals__, __gproperties__, do_get_property""" + + __gsignals__ = { + "new-mail-received" : (gobject.SIGNAL_RUN_FIRST, + gobject.TYPE_NONE, + (object,)), + "unread-mail-count-changed" : (gobject.SIGNAL_RUN_FIRST, + gobject.TYPE_NONE, + (gobject.TYPE_UINT, bool)), + } + + __gproperties__ = { + "unread-mail-count": (gobject.TYPE_UINT, + "Inbox Unread", + "Number of unread mail in the users inbox", + 0, gobject.G_MAXUINT, 0, + gobject.PARAM_READABLE), + } + + def __init__(self, ns_client): + gobject.GObject.__init__(self) + self._ns_client = ns_client + self._unread_mail_count = 0 + + @property + def unread_mail_count(self): + """Number of unread mail in the users inbox + @type: integer""" + return self._unread_mail_count + + def request_compose_mail_url(self, contact, callback): + self._ns_client.send_url_request(('COMPOSE', contact.account), callback) + + def request_inbox_url(self, callback): + self._ns_client.send_url_request(('INBOX',), callback) + + def _unread_mail_increased(self, delta): + self._unread_mail_count += delta + self.emit("unread-mail-count-changed", self._unread_mail_count, False) + self.notify("unread-mail-count") + + def _unread_mail_decreased(self, delta): + self._unread_mail_count -= delta + self.emit("unread-mail-count-changed", self._unread_mail_count, False) + self.notify("unread-mail-count") + + def _initial_set(self, unread_number): + if unread_number > 0: + self._unread_mail_count = unread_number + self.emit("unread-mail-count-changed", unread_number, True) + self.notify("unread-mail-count") + + def _new_mail(self, name, address, subject, post_url, form_data): + mail = MailMessage(name, address, subject, post_url, form_data) + self.emit("new-mail-received", mail) + + def do_get_property(self, pspec): + name = pspec.name.lower().replace("-", "_") + return getattr(self, name) +gobject.type_register(Mailbox) diff --git a/pymsn/msnp/notification.py b/pymsn/msnp/notification.py index 615e92b..24d05a6 100644 --- a/pymsn/msnp/notification.py +++ b/pymsn/msnp/notification.py @@ -38,6 +38,8 @@ import pymsn.service.SingleSignOn as SSO import pymsn.service.AddressBook as AB import pymsn.service.OfflineIM as OIM +import md5 +import time import logging import urllib import gobject @@ -100,6 +102,7 @@ class NotificationProtocol(BaseProtocol, gobject.GObject): gobject.GObject.__init__(self) self.__state = ProtocolState.CLOSED self._protocol_version = 0 + self._url_callbacks = {} # tr_id=>callback # Properties ------------------------------------------------------------ def __get_state(self): @@ -245,6 +248,11 @@ class NotificationProtocol(BaseProtocol, gobject.GObject): (contact.account, contact.network_id, message_type), payload=message) + def send_url_request(self, url_command_args, callback): + tr_id = self._send_command('URL', url_command_args) + self._url_callbacks[tr_id] = callback + + # Handlers --------------------------------------------------------------- # --------- Connection --------------------------------------------------- def _handle_VER(self, command): @@ -468,10 +476,6 @@ class NotificationProtocol(BaseProtocol, gobject.GObject): self._state = ProtocolState.SYNCHRONIZING self._client.address_book.sync() elif content_type[0] in \ - ('text/x-msmsgsinitialemailnotification', \ - 'text/x-msmsgsemailnotification'): - self.emit("mail-received", message) - elif content_type[0] in \ ('text/x-msmsgsinitialmdatanotification', \ 'text/x-msmsgsoimnotification'): if self._client.oim_box is not None: @@ -483,8 +487,46 @@ class NotificationProtocol(BaseProtocol, gobject.GObject): if mail_data == 'too-large': mail_data = None self._client.oim_box.sync(mail_data) - elif content_type[0] == 'text/x-msmsgsactivemailnotification': + if mail_data and \ + content_type[0] == 'text/x-msmsgsinitialmdatanotification': + #Initial mail + start = mail_data.find('<IU>') + 4 + end = mail_data.find('</IU>') + if start < end: + mailbox_unread = int(mail_data[start:end]) + self._client.mailbox._initial_set(mailbox_unread) + + elif content_type[0] == 'text/x-msmsgsinitialemailnotification': + #Initial mail (obsolete by MSNP11) pass + elif content_type[0] == 'text/x-msmsgsemailnotification': + #New mail + m = HTTPMessage() + m.parse(message.body) + name = m.get_header('From') + address = m.get_header('From-Addr') + subject = m.get_header('Subject') + message_url = m.get_header('Message-URL') + post_url = m.get_header('Post-URL') + post_id = m.get_header('id') + dest = m.get_header('Dest-Folder') + if dest == 'ACTIVE': + self._client.mailbox._unread_mail_increased(1) + build = self._build_url_post_data + post_url, form_data = build(message_url, post_url, post_id) + self._client.mailbox._new_mail(name, address, subject, + post_url, form_data) + elif content_type[0] == 'text/x-msmsgsactivemailnotification': + #Movement of unread mail + m = HTTPMessage() + m.parse(message.body) + src = m.get_header('Src-Folder') + dest = m.get_header('Dest-Folder') + delta = int(m.get_header('Message-Delta')) + if src == 'ACTIVE': + self._client.mailbox._unread_mail_decreased(delta) + elif dest == 'ACTIVE': + self._client.mailbox._unread_mail_increased(delta) def _handle_UBM(self, command): network_id = int(command.arguments[1]) @@ -502,6 +544,54 @@ class NotificationProtocol(BaseProtocol, gobject.GObject): message = Message(contact, command.payload) self.emit("unmanaged-message-received", contact, message) + # --------- Urls --------------------------------------------------------- + + def _build_url_post_data(self, + message_url="/cgi-bin/HoTMaiL", + post_url='https://loginnet.passport.com/ppsecure/md5auth.srf?', + post_id='2'): + + profile = {} + lines = self._client.profile.profile.split("\r\n") + for line in lines: + line = line.strip() + if line: + name, value = line.split(":", 1) + profile[name] = value.strip() + + account = self._client.profile.account + password = str(self._client.profile.password) + sl = str(int(time.time()) - int(profile['LoginTime'])) + auth = profile['MSPAuth'] + sid = profile['sid'] + auth = profile['MSPAuth'] + creds = md5.new(auth + sl + password).hexdigest() + + post_data = dict([ + ('mode', 'ttl'), + ('login', account.split('@')[0]), + ('username', account), + ('sid', sid), + ('kv', ''), + ('id', post_id), + ('sl', sl), + ('rru', message_url), + ('auth', auth), + ('creds', creds), + ('svc', 'mail'), + ('js', 'yes')]) + return (post_url, post_data) + + def _handle_URL(self, command): + tr_id = command.transaction_id + if tr_id in self._url_callbacks: + message_url, post_url, post_id = command.arguments + post_url, form_dict = self._build_url_post_data(message_url, + post_url, post_id) + callback = self._url_callbacks[tr_id] + del self._url_callbacks[tr_id] + callback(post_url, form_dict) + # --------- Invitation --------------------------------------------------- def _handle_RNG(self,command): session_id = command.arguments[0] @@ -553,7 +643,7 @@ class NotificationProtocol(BaseProtocol, gobject.GObject): contacts = address_book.contacts\ .search_by_memberships(profile.Membership.FORWARD)\ .group_by_domain() - + payloads = ['<ml l="1">'] mask = ~(profile.Membership.REVERSE | profile.Membership.PENDING) for domain, contacts in contacts.iteritems(): @@ -570,8 +660,11 @@ class NotificationProtocol(BaseProtocol, gobject.GObject): payloads[-1] += node payloads[-1] += '</d>' payloads[-1] += '</ml>' - + + import re + pattern = re.compile ('<d n="[^"]+"></d>') for payload in payloads: + payload = pattern.sub('', payload) self._send_command("ADL", payload=payload) self._state = ProtocolState.SYNCHRONIZED diff --git a/pymsn/msnp2p/session_manager.py b/pymsn/msnp2p/session_manager.py index 87c4087..4c401d0 100644 --- a/pymsn/msnp2p/session_manager.py +++ b/pymsn/msnp2p/session_manager.py @@ -165,7 +165,11 @@ class P2PSessionManager(gobject.GObject): except SLPError: #TODO: answer with a 603 Decline ? logger.error("SLPError") - return + return None + else: + return None + #elif isinstance(message.body, SLPTransferRequestBody): + # pass else: logger.warning('Received initial blob with SessionID=0 and non INVITE SLP data') #FIXME - pick up properly on the transreq diff --git a/pymsn/profile.py b/pymsn/profile.py index 83f5fb7..522cc68 100644 --- a/pymsn/profile.py +++ b/pymsn/profile.py @@ -487,6 +487,9 @@ class Profile(gobject.GObject): return self._personal_message, self._current_media return locals() + def request_profile_url(self, callback): + self._ns_client.send_url_request(('PROFILE', '0x0409'), callback) + def _server_property_changed(self, name, value): attr_name = "_" + name.lower().replace("-", "_") if attr_name == "_msn_object" and value is not None: @@ -709,6 +712,13 @@ class Contact(gobject.GObject): return result[1] else: return "" + + @property + def profile_url(self): + """Contact profile url + @type: string""" + account = self._account + return "http://members.msn.com/default.msnw?mem=%s&pgmarket=" % account ### membership management def is_member(self, memberships): diff --git a/pymsn/service/OfflineIM/offline_messages_box.py b/pymsn/service/OfflineIM/offline_messages_box.py index b497b45..d0379a1 100644 --- a/pymsn/service/OfflineIM/offline_messages_box.py +++ b/pymsn/service/OfflineIM/offline_messages_box.py @@ -250,12 +250,12 @@ class OfflineMessagesBox(gobject.GObject): metadata = Metadata(xml_data) for m in metadata.findall('./M'): id = m.findtext('./I') - network = (m.findtext('T','int'), m.findtext('S','int')) - if network == (11,6): + network = m.findtext('T','int') + if network == 11: network_id = NetworkID.MSN - elif network == (13,7): + elif network == 13: network_id = NetworkID.EXTERNAL - + account = m.findtext('./E') try: |