summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorYouness Alaoui <youness.alaoui@collabora.co.uk>2009-03-12 17:04:15 -0400
committerYouness Alaoui <youness.alaoui@collabora.co.uk>2009-03-12 17:04:15 -0400
commitede8699d1e7ebe17afe8fd9435ad1df21a5102e1 (patch)
treea32e6e3c93102ea2adfd17a89cd7535bcce66945
parent0cef59fdd769b467b8a26ac22b8e7bd52598f9c0 (diff)
parent09d58b2dc1bb53c48dfc9f630ce23e819d21a534 (diff)
Merge commit 'origin/master'
Conflicts: pymsn/msnp2p/session_manager.py
-rw-r--r--pymsn/client.py28
-rw-r--r--pymsn/conversation.py2
-rw-r--r--pymsn/event/__init__.py1
-rw-r--r--pymsn/event/mailbox.py43
-rw-r--r--pymsn/msnp/__init__.py1
-rw-r--r--pymsn/msnp/base.py5
-rw-r--r--pymsn/msnp/mailbox.py117
-rw-r--r--pymsn/msnp/notification.py107
-rw-r--r--pymsn/msnp2p/session_manager.py6
-rw-r--r--pymsn/profile.py10
-rw-r--r--pymsn/service/OfflineIM/offline_messages_box.py8
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: