diff --git a/pymsn/AUTHORS b/pymsn/AUTHORS deleted file mode 100644 index 24ae6c67..00000000 --- a/pymsn/AUTHORS +++ /dev/null @@ -1,3 +0,0 @@ -Ali Sabil علي سبيل -Johann Prieur -Ole André Vadla Ravnås diff --git a/pymsn/COPYING b/pymsn/COPYING deleted file mode 100644 index 623b6258..00000000 --- a/pymsn/COPYING +++ /dev/null @@ -1,340 +0,0 @@ - GNU GENERAL PUBLIC LICENSE - Version 2, June 1991 - - Copyright (C) 1989, 1991 Free Software Foundation, Inc. - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The licenses for most software are designed to take away your -freedom to share and change it. By contrast, the GNU General Public -License is intended to guarantee your freedom to share and change free -software--to make sure the software is free for all its users. This -General Public License applies to most of the Free Software -Foundation's software and to any other program whose authors commit to -using it. (Some other Free Software Foundation software is covered by -the GNU Library General Public License instead.) You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -this service if you wish), that you receive source code or can get it -if you want it, that you can change the software or use pieces of it -in new free programs; and that you know you can do these things. - - To protect your rights, we need to make restrictions that forbid -anyone to deny you these rights or to ask you to surrender the rights. -These restrictions translate to certain responsibilities for you if you -distribute copies of the software, or if you modify it. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must give the recipients all the rights that -you have. You must make sure that they, too, receive or can get the -source code. And you must show them these terms so they know their -rights. - - We protect your rights with two steps: (1) copyright the software, and -(2) offer you this license which gives you legal permission to copy, -distribute and/or modify the software. - - Also, for each author's protection and ours, we want to make certain -that everyone understands that there is no warranty for this free -software. If the software is modified by someone else and passed on, we -want its recipients to know that what they have is not the original, so -that any problems introduced by others will not reflect on the original -authors' reputations. - - Finally, any free program is threatened constantly by software -patents. We wish to avoid the danger that redistributors of a free -program will individually obtain patent licenses, in effect making the -program proprietary. To prevent this, we have made it clear that any -patent must be licensed for everyone's free use or not licensed at all. - - The precise terms and conditions for copying, distribution and -modification follow. - - GNU GENERAL PUBLIC LICENSE - TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION - - 0. This License applies to any program or other work which contains -a notice placed by the copyright holder saying it may be distributed -under the terms of this General Public License. The "Program", below, -refers to any such program or work, and a "work based on the Program" -means either the Program or any derivative work under copyright law: -that is to say, a work containing the Program or a portion of it, -either verbatim or with modifications and/or translated into another -language. (Hereinafter, translation is included without limitation in -the term "modification".) Each licensee is addressed as "you". - -Activities other than copying, distribution and modification are not -covered by this License; they are outside its scope. The act of -running the Program is not restricted, and the output from the Program -is covered only if its contents constitute a work based on the -Program (independent of having been made by running the Program). -Whether that is true depends on what the Program does. - - 1. You may copy and distribute verbatim copies of the Program's -source code as you receive it, in any medium, provided that you -conspicuously and appropriately publish on each copy an appropriate -copyright notice and disclaimer of warranty; keep intact all the -notices that refer to this License and to the absence of any warranty; -and give any other recipients of the Program a copy of this License -along with the Program. - -You may charge a fee for the physical act of transferring a copy, and -you may at your option offer warranty protection in exchange for a fee. - - 2. You may modify your copy or copies of the Program or any portion -of it, thus forming a work based on the Program, and copy and -distribute such modifications or work under the terms of Section 1 -above, provided that you also meet all of these conditions: - - a) You must cause the modified files to carry prominent notices - stating that you changed the files and the date of any change. - - b) You must cause any work that you distribute or publish, that in - whole or in part contains or is derived from the Program or any - part thereof, to be licensed as a whole at no charge to all third - parties under the terms of this License. - - c) If the modified program normally reads commands interactively - when run, you must cause it, when started running for such - interactive use in the most ordinary way, to print or display an - announcement including an appropriate copyright notice and a - notice that there is no warranty (or else, saying that you provide - a warranty) and that users may redistribute the program under - these conditions, and telling the user how to view a copy of this - License. (Exception: if the Program itself is interactive but - does not normally print such an announcement, your work based on - the Program is not required to print an announcement.) - -These requirements apply to the modified work as a whole. If -identifiable sections of that work are not derived from the Program, -and can be reasonably considered independent and separate works in -themselves, then this License, and its terms, do not apply to those -sections when you distribute them as separate works. But when you -distribute the same sections as part of a whole which is a work based -on the Program, the distribution of the whole must be on the terms of -this License, whose permissions for other licensees extend to the -entire whole, and thus to each and every part regardless of who wrote it. - -Thus, it is not the intent of this section to claim rights or contest -your rights to work written entirely by you; rather, the intent is to -exercise the right to control the distribution of derivative or -collective works based on the Program. - -In addition, mere aggregation of another work not based on the Program -with the Program (or with a work based on the Program) on a volume of -a storage or distribution medium does not bring the other work under -the scope of this License. - - 3. You may copy and distribute the Program (or a work based on it, -under Section 2) in object code or executable form under the terms of -Sections 1 and 2 above provided that you also do one of the following: - - a) Accompany it with the complete corresponding machine-readable - source code, which must be distributed under the terms of Sections - 1 and 2 above on a medium customarily used for software interchange; or, - - b) Accompany it with a written offer, valid for at least three - years, to give any third party, for a charge no more than your - cost of physically performing source distribution, a complete - machine-readable copy of the corresponding source code, to be - distributed under the terms of Sections 1 and 2 above on a medium - customarily used for software interchange; or, - - c) Accompany it with the information you received as to the offer - to distribute corresponding source code. (This alternative is - allowed only for noncommercial distribution and only if you - received the program in object code or executable form with such - an offer, in accord with Subsection b above.) - -The source code for a work means the preferred form of the work for -making modifications to it. For an executable work, complete source -code means all the source code for all modules it contains, plus any -associated interface definition files, plus the scripts used to -control compilation and installation of the executable. However, as a -special exception, the source code distributed need not include -anything that is normally distributed (in either source or binary -form) with the major components (compiler, kernel, and so on) of the -operating system on which the executable runs, unless that component -itself accompanies the executable. - -If distribution of executable or object code is made by offering -access to copy from a designated place, then offering equivalent -access to copy the source code from the same place counts as -distribution of the source code, even though third parties are not -compelled to copy the source along with the object code. - - 4. You may not copy, modify, sublicense, or distribute the Program -except as expressly provided under this License. Any attempt -otherwise to copy, modify, sublicense or distribute the Program is -void, and will automatically terminate your rights under this License. -However, parties who have received copies, or rights, from you under -this License will not have their licenses terminated so long as such -parties remain in full compliance. - - 5. You are not required to accept this License, since you have not -signed it. However, nothing else grants you permission to modify or -distribute the Program or its derivative works. These actions are -prohibited by law if you do not accept this License. Therefore, by -modifying or distributing the Program (or any work based on the -Program), you indicate your acceptance of this License to do so, and -all its terms and conditions for copying, distributing or modifying -the Program or works based on it. - - 6. Each time you redistribute the Program (or any work based on the -Program), the recipient automatically receives a license from the -original licensor to copy, distribute or modify the Program subject to -these terms and conditions. You may not impose any further -restrictions on the recipients' exercise of the rights granted herein. -You are not responsible for enforcing compliance by third parties to -this License. - - 7. If, as a consequence of a court judgment or allegation of patent -infringement or for any other reason (not limited to patent issues), -conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot -distribute so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you -may not distribute the Program at all. For example, if a patent -license would not permit royalty-free redistribution of the Program by -all those who receive copies directly or indirectly through you, then -the only way you could satisfy both it and this License would be to -refrain entirely from distribution of the Program. - -If any portion of this section is held invalid or unenforceable under -any particular circumstance, the balance of the section is intended to -apply and the section as a whole is intended to apply in other -circumstances. - -It is not the purpose of this section to induce you to infringe any -patents or other property right claims or to contest validity of any -such claims; this section has the sole purpose of protecting the -integrity of the free software distribution system, which is -implemented by public license practices. Many people have made -generous contributions to the wide range of software distributed -through that system in reliance on consistent application of that -system; it is up to the author/donor to decide if he or she is willing -to distribute software through any other system and a licensee cannot -impose that choice. - -This section is intended to make thoroughly clear what is believed to -be a consequence of the rest of this License. - - 8. If the distribution and/or use of the Program is restricted in -certain countries either by patents or by copyrighted interfaces, the -original copyright holder who places the Program under this License -may add an explicit geographical distribution limitation excluding -those countries, so that distribution is permitted only in or among -countries not thus excluded. In such case, this License incorporates -the limitation as if written in the body of this License. - - 9. The Free Software Foundation may publish revised and/or new versions -of the General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - -Each version is given a distinguishing version number. If the Program -specifies a version number of this License which applies to it and "any -later version", you have the option of following the terms and conditions -either of that version or of any later version published by the Free -Software Foundation. If the Program does not specify a version number of -this License, you may choose any version ever published by the Free Software -Foundation. - - 10. If you wish to incorporate parts of the Program into other free -programs whose distribution conditions are different, write to the author -to ask for permission. For software which is copyrighted by the Free -Software Foundation, write to the Free Software Foundation; we sometimes -make exceptions for this. Our decision will be guided by the two goals -of preserving the free status of all derivatives of our free software and -of promoting the sharing and reuse of software generally. - - NO WARRANTY - - 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY -FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN -OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES -PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED -OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS -TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE -PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, -REPAIR OR CORRECTION. - - 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR -REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, -INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING -OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED -TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY -YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER -PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE -POSSIBILITY OF SUCH DAMAGES. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -convey the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - 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 Street, Fifth Floor, Boston, MA 02110-1301 USA - - -Also add information on how to contact you by electronic and paper mail. - -If the program is interactive, make it output a short notice like this -when it starts in an interactive mode: - - Gnomovision version 69, Copyright (C) year name of author - Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, the commands you use may -be called something other than `show w' and `show c'; they could even be -mouse-clicks or menu items--whatever suits your program. - -You should also get your employer (if you work as a programmer) or your -school, if any, to sign a "copyright disclaimer" for the program, if -necessary. Here is a sample; alter the names: - - Yoyodyne, Inc., hereby disclaims all copyright interest in the program - `Gnomovision' (which makes passes at compilers) written by James Hacker. - - , 1 April 1989 - Ty Coon, President of Vice - -This General Public License does not permit incorporating your program into -proprietary programs. If your program is a subroutine library, you may -consider it more useful to permit linking proprietary applications with the -library. If this is what you want to do, use the GNU Library General -Public License instead of this License. diff --git a/pymsn/MANIFEST.in b/pymsn/MANIFEST.in deleted file mode 100644 index 6e1fabbf..00000000 --- a/pymsn/MANIFEST.in +++ /dev/null @@ -1,5 +0,0 @@ -include test.py -include setup.py -include doc.py -include doc/user-api.conf -include README COPYING AUTHORS NEWS ChangeLog diff --git a/pymsn/NEWS b/pymsn/NEWS deleted file mode 100644 index d5372d4b..00000000 --- a/pymsn/NEWS +++ /dev/null @@ -1,46 +0,0 @@ -pymsn-0.3.2 (Green Moustache) -=============================== - - * List of changes between 0.3.2 and 0.3.3 - o Removed an assert statement causing pymsn to fail with the newest CVR response - o Removed the python-adns dependency - o Fixed a DNS resolution issue - o Fixed a small error in the decorator module - - * List of changes between 0.3.1 and 0.3.2 - o Fixed MSNObject handling in Profile - o Added Profile events support - o Fixed typo in the EmailContactAdd scenario - o Fixed Unicode problems in Contact.attributes - o Fixed a ContactMembershipsUpdate bug - o Removed the unused SLPTransferRequestBody check, this still not supported by our msnp2p stack - o Fixed the msnp2p test file - o Fixed iterating over the switchboard handlers and changing the handlers set (Stéphan Kochen) - o Fixed a missing AddressBookError import (Stéphan Kochen) - - * List of changes between 0.3.0 and 0.3.1 - o Fixed adding contacts already in Memberships list but not with isMessengerUser - - * List of changes between 0.2.2 and 0.3.0 - o Complete rewrite - o MSNP15 support - - -pymsn-0.2.2 (Yellow Ivy) -========================== - - * List of changes between 0.2.2 and 0.2.1 - o Added display pictures request in Client class - o Enhanced HTTPPollConnection, now supporting http proxy, and nasty - proxy not honoring the Keep-Alive - o Added ability to choose the transport in the Client - o Added Storage system to enable pymsn to cache object across sessions - o Added display picture publishing - - * List of bugs fixed between 0.2.2 and 0.2.1 - o Fixed https redirection while authentication - (encountered with @msn.com accounts) - o Fixed pymsn msnp2p bug causing second msnslp session to fail - o Fixed a bug showing wrong representation of Messages in debug messages - o Fixed display-picture-updated signal being emitted before data update - o Fixed transport in Conversation not being given the correct proxy diff --git a/pymsn/README b/pymsn/README deleted file mode 100644 index 6128dc99..00000000 --- a/pymsn/README +++ /dev/null @@ -1,13 +0,0 @@ -pymsn - Python msn client library -================================= - -pymsn is an MSN client library, that tries to abstract the MSN protocol -gory details. - -Dependencies -============ -python (>= 2.4) -python-gobject (>=2.10) -ElementTree (>=1.2.0) or cElementTree (>=1.0.5) or python (>= 2.5) -pyOpenSSL (>=0.6) -pyCrypto (>=2.0.0) diff --git a/pymsn/doc.py b/pymsn/doc.py deleted file mode 100644 index 63471558..00000000 --- a/pymsn/doc.py +++ /dev/null @@ -1,75 +0,0 @@ - - -"""this module was based on setup.py from pygame-ctype branch. -author: Alex Holkner """ - -import os -from os.path import join, abspath, dirname, splitext -import sys -import subprocess - -from distutils.cmd import Command - - -# A "do-everything" command class for building any type of documentation. -class BuildDocCommand(Command): - user_options = [('doc-dir=', None, 'directory to build documentation'), - ('epydoc=', None, 'epydoc executable')] - - def initialize_options(self): - self.doc_dir = join(abspath(dirname(sys.argv[0])), 'doc') - self.epydoc = 'epydoc' - - def finalize_options(self): - pass - - def run(self): - if 'pre' in self.doc: - subprocess.call(self.doc['pre'], shell=True) - - prev_dir = os.getcwd() - if 'chdir' in self.doc: - dir = abspath(join(self.doc_dir, self.doc['chdir'])) - try: - os.makedirs(dir) - except: - pass - os.chdir(dir) - - if 'config' in self.doc: - cmd = [self.epydoc, - '--no-private', - '--no-frames', - '--config "%s"' % self.doc['config']] - subprocess.call(' '.join(cmd), shell=True) - - os.chdir(prev_dir) - - if 'post' in self.doc: - subprocess.call(self.doc['post'], shell=True) - -# Fudge a command class given a dictionary description -def make_doc_command(**kwargs): - class c(BuildDocCommand): - doc = dict(**kwargs) - description = 'build %s' % doc['description'] - c.__name__ = 'build_doc_%s' % c.doc['name'].replace('-', '_') - return c - -# This command does nothing but run all the other doc commands. -# (sub_commands are set later) -class BuildAllDocCommand(Command): - description = 'build all documentation' - user_options = [] - sub_commands = [] - - def initialize_options(self): - pass - - def finalize_options(self): - pass - - def run(self): - for cmd_name in self.get_sub_commands(): - self.run_command(cmd_name) - diff --git a/pymsn/doc/user-api.conf b/pymsn/doc/user-api.conf deleted file mode 100644 index 4dbd1ac4..00000000 --- a/pymsn/doc/user-api.conf +++ /dev/null @@ -1,13 +0,0 @@ -[epydoc] - -# Information about the project. -name: pymsn -url: http://telepathy.freedesktop.org/wiki/Pymsn - -modules: pymsn.client pymsn.profile pymsn.conversation pymsn.event pymsn.p2p - -output: html -target: doc/user-api/ - -private: no - diff --git a/pymsn/pymsn/__init__.py b/pymsn/pymsn/__init__.py deleted file mode 100644 index 0eda876e..00000000 --- a/pymsn/pymsn/__init__.py +++ /dev/null @@ -1,41 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2005-2006 Ali Sabil -# -# 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 - -"""An implementation of the MSN Messenger Protocol - -pymsn is a library, written in Python, for accessing the MSN -instant messaging service. - - @group High Level Interface: client, profile, conversation, event - @group Low Level Interface: msnp, msnp2p, service - @group Network Layer: gnet -""" - -__version__ = "0.3.3" -__author__ = "Ali Sabil " -__url__ = "http://telepathy.freedesktop.org/wiki/Pymsn" -__license__ = "GNU GPL" - -from client import * -from conversation import * -from profile import NetworkID, Presence, Privacy, Membership, Contact, Group -import event - -import gnet.proxy -Proxy = gnet.proxy.ProxyFactory -ProxyInfos = gnet.proxy.ProxyInfos diff --git a/pymsn/pymsn/client.py b/pymsn/pymsn/client.py deleted file mode 100644 index a77d684e..00000000 --- a/pymsn/pymsn/client.py +++ /dev/null @@ -1,404 +0,0 @@ -# -*- coding: utf-8 -*- -# -# pymsn - a python client library for Msn -# -# Copyright (C) 2005-2007 Ali Sabil -# Copyright (C) 2006-2007 Ole André Vadla Ravnås -# Copyright (C) 2007 Johann Prieur -# -# 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 - -"""Client - -This module contains the main class used to login into the MSN Messenger -network. The following example demonstrates a simple client. - - >>> import pymsn - >>> - >>> server = ('messenger.hotmail.com', 1863) - >>> account = ('pymsn@hotmail.com', 'pymsn is great !') - >>> - >>> client = pymsn.Client(server) - >>> client.login(*account) - >>> - >>> if __name__ == "__main__": - ... import gobject - ... import logging - ... logging.basicConfig(level=logging.DEBUG) # allows us to see the protocol debug - ... - ... mainloop = gobject.MainLoop() - ... mainloop.run() - -This client will try to login, but will probably fail because of the wrong -password, so let's enhance this client so that it displays an error if the -password was wrong, this will lead us to use the L{pymsn.event} interfaces: - - >>> import pymsn - >>> import pymsn.event - >>> - >>> class ClientEventHandler(pymsn.event.ClientEventInterface): - ... def on_client_error(self, error_type, error): - ... if error_type == pymsn.event.ClientErrorType.AUTHENTICATION: - ... print "" - ... print "********************************************************" - ... print "* You bummer ! you did input a wrong username/password *" - ... print "********************************************************" - ... else: - ... print "ERROR :", error_type, " ->", error - >>> - >>> - >>> server = ('messenger.hotmail.com', 1863) - >>> account = ('pymsn@hotmail.com', 'pymsn is great !') - >>> - >>> client = pymsn.Client(server) - >>> client_events_handler = ClientEventHandler(client) - >>> - >>> client.login(*account) - >>> - >>> if __name__ == "__main__": - ... import gobject - ... import logging - ... - ... logging.basicConfig(level=logging.DEBUG) # allows us to see the protocol debug - ... - ... mainloop = gobject.MainLoop() - ... mainloop.run() - -""" - -import pymsn.profile as profile -import pymsn.msnp as msnp - -import pymsn.service.SingleSignOn as SSO -import pymsn.service.AddressBook as AB -import pymsn.service.OfflineIM as OIM -import pymsn.service.Spaces as Spaces - -from pymsn.util.decorator import rw_property -from pymsn.transport import * -from pymsn.switchboard_manager import SwitchboardManager -from pymsn.msnp2p import P2PSessionManager -from pymsn.p2p import MSNObjectStore -from pymsn.conversation import SwitchboardConversation, \ - ExternalNetworkConversation -from pymsn.event import ClientState, ClientErrorType, \ - AuthenticationError, EventsDispatcher - -import logging - -__all__ = ['Client'] - -logger = logging.getLogger('client') - -class Client(EventsDispatcher): - """This class provides way to connect to the notification server as well - as methods to manage the contact list, and the personnal settings. - @sort: __init__, login, logout, state, profile, address_book, - msn_object_store, oim_box, spaces""" - - def __init__(self, server, proxies={}, transport_class=DirectConnection): - """Initializer - - @param server: the Notification server to connect to. - @type server: tuple(host, port) - - @param proxies: proxies that we can use to connect - @type proxies: {type: string => L{gnet.proxy.ProxyInfos}} - - @param transport_class: the transport class to use for the network - connection - @type transport_class: L{pymsn.transport.AbstractTransport}""" - EventsDispatcher.__init__(self) - - self.__state = ClientState.CLOSED - - self._proxies = proxies - self._transport_class = transport_class - self._proxies = proxies - - self._transport = transport_class(server, ServerType.NOTIFICATION, - self._proxies) - self._protocol = msnp.NotificationProtocol(self, self._transport, - self._proxies) - - self._switchboard_manager = SwitchboardManager(self) - self._switchboard_manager.register_handler(SwitchboardConversation) - - self._p2p_session_manager = P2PSessionManager(self) - self._msn_object_store = MSNObjectStore(self) - - self._external_conversations = {} - - self._sso = None - - self._profile = None - self._address_book = None - self._oim_box = None - - self.__die = False - self.__connect_transport_signals() - self.__connect_protocol_signals() - self.__connect_switchboard_manager_signals() - - ### public: - @property - def msn_object_store(self): - """The MSNObjectStore instance associated with this client. - @type: L{MSNObjectStore}""" - return self._msn_object_store - - @property - def profile(self): - """The profile of the current user - @type: L{User}""" - return self._profile - - @property - def address_book(self): - """The address book of the current user - @type: L{AddressBook}""" - return self._address_book - - @property - def oim_box(self): - """The offline IM for the current user - @type: L{OfflineIM}""" - return self._oim_box - - @property - def spaces(self): - """The MSN Spaces of the current user - @type: L{Spaces}""" - return self._spaces - - @property - def state(self): - """The state of this Client - @type: L{pymsn.event.ClientState}""" - return self.__state - - def login(self, account, password): - """Login to the server. - - @param account: the account to use for authentication. - @type account: utf-8 encoded string - - @param password: the password needed to authenticate to the account - @type password: utf-8 encoded string - """ - if (self._state != ClientState.CLOSED): - logger.warning('login already in progress') - self.__die = False - self._profile = profile.Profile((account, password), self._protocol) - self.__connect_profile_signals() - self._transport.establish_connection() - self._state = ClientState.CONNECTING - - def logout(self): - """Logout from the server.""" - if self.__state != ClientState.OPEN: # FIXME: we need something better - return - self.__die = True - self._protocol.signoff() - self._switchboard_manager.close() - self.__state = ClientState.CLOSED - - ### protected: - @rw_property - def _state(): - def fget(self): - return self.__state - def fset(self, state): - self.__state = state - self._dispatch("on_client_state_changed", state) - return locals() - - def _register_external_conversation(self, conversation): - for contact in conversation.participants: - break - - if contact in self._external_conversations: - logger.warning("trying to register an external conversation twice") - return - self._external_conversations[contact] = conversation - - def _unregister_external_conversation(self, conversation): - for contact in conversation.participants: - break - del self._external_conversations[contact] - - ### private: - def __connect_profile_signals(self): - """Connect profile signals""" - def property_changed(profile, pspec): - method_name = "on_profile_%s_changed" % pspec.name.replace("-", "_") - self._dispatch(method_name) - - self.profile.connect("notify::presence", property_changed) - self.profile.connect("notify::display-name", property_changed) - 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_contact_signals(self, contact): - """Connect contact signals""" - def event(contact, *args): - event_name = args[-1] - event_args = args[:-1] - method_name = "on_contact_%s" % event_name.replace("-", "_") - self._dispatch(method_name, contact, *event_args) - - def property_changed(contact, pspec): - method_name = "on_contact_%s_changed" % pspec.name.replace("-", "_") - self._dispatch(method_name, contact) - - contact.connect("notify::memberships", property_changed) - contact.connect("notify::presence", property_changed) - contact.connect("notify::display-name", property_changed) - contact.connect("notify::personal-message", property_changed) - contact.connect("notify::current-media", property_changed) - contact.connect("notify::msn-object", property_changed) - contact.connect("notify::client-capabilities", property_changed) - - def connect_signal(name): - contact.connect(name, event, name) - connect_signal("infos-changed") - - def __connect_transport_signals(self): - """Connect transport signals""" - def connect_success(transp): - self._sso = SSO.SingleSignOn(self.profile.account, - self.profile.password, - self._proxies) - self._address_book = AB.AddressBook(self._sso, self._proxies) - self.__connect_addressbook_signals() - self._oim_box = OIM.OfflineMessagesBox(self._sso, self, self._proxies) - self.__connect_oim_box_signals() - self._spaces = Spaces.Spaces(self._sso, self._proxies) - - self._state = ClientState.CONNECTED - - def connect_failure(transp, reason): - self._dispatch("on_client_error", ClientErrorType.NETWORK, reason) - self._state = ClientState.CLOSED - - def disconnected(transp, reason): - if not self.__die: - self._dispatch("on_client_error", ClientErrorType.NETWORK, reason) - self.__die = False - self._state = ClientState.CLOSED - - self._transport.connect("connection-success", connect_success) - self._transport.connect("connection-failure", connect_failure) - self._transport.connect("connection-lost", disconnected) - - def __connect_protocol_signals(self): - """Connect protocol signals""" - def state_changed(proto, param): - state = proto.state - if state == msnp.ProtocolState.AUTHENTICATING: - self._state = ClientState.AUTHENTICATING - elif state == msnp.ProtocolState.AUTHENTICATED: - self._state = ClientState.AUTHENTICATED - elif state == msnp.ProtocolState.SYNCHRONIZING: - self._state = ClientState.SYNCHRONIZING - elif state == msnp.ProtocolState.SYNCHRONIZED: - self._state = ClientState.SYNCHRONIZED - elif state == msnp.ProtocolState.OPEN: - self._state = ClientState.OPEN - im_contacts = self.address_book.contacts - for contact in im_contacts: - self.__connect_contact_signals(contact) - - def authentication_failed(proto): - self._dispatch("on_client_error", ClientErrorType.AUTHENTICATION, - AuthenticationError.INVALID_USERNAME_OR_PASSWORD) - self.__die = True - self._transport.lose_connection() - - def unmanaged_message_received(proto, sender, message): - if sender in self._external_conversations: - conversation = self._external_conversations[sender] - conversation._on_message_received(message) - else: - conversation = ExternalNetworkConversation(self, [sender]) - self._register_external_conversation(conversation) - if self._dispatch("on_invite_conversation", conversation) == 0: - logger.warning("No event handler attached for conversations") - conversation._on_message_received(message) - - self._protocol.connect("notify::state", state_changed) - self._protocol.connect("authentication-failed", authentication_failed) - self._protocol.connect("unmanaged-message-received", unmanaged_message_received) - - def __connect_switchboard_manager_signals(self): - """Connect Switchboard Manager signals""" - def handler_created(switchboard_manager, handler_class, handler): - if handler_class is SwitchboardConversation: - if self._dispatch("on_invite_conversation", handler) == 0: - logger.warning("No event handler attached for conversations") - else: - logger.warning("Unknown Switchboard Handler class %s" % handler_class) - - self._switchboard_manager.connect("handler-created", handler_created) - - def __connect_addressbook_signals(self): - """Connect AddressBook signals""" - def event(address_book, *args): - event_name = args[-1] - event_args = args[:-1] - if event_name == "messenger-contact-added": - self.__connect_contact_signals(event_args[0]) - method_name = "on_addressbook_%s" % event_name.replace("-", "_") - self._dispatch(method_name, *event_args) - def error(address_book, error_code): - self._dispatch("on_client_error", ClientErrorType.ADDRESSBOOK, error_code) - self.__die = True - self._transport.lose_connection() - - self.address_book.connect('error', error) - - def connect_signal(name): - self.address_book.connect(name, event, name) - - connect_signal("messenger-contact-added") - connect_signal("contact-deleted") - connect_signal("contact-blocked") - connect_signal("contact-unblocked") - connect_signal("group-added") - connect_signal("group-deleted") - connect_signal("group-renamed") - connect_signal("group-contact-added") - connect_signal("group-contact-deleted") - - def __connect_oim_box_signals(self): - """Connect Offline IM signals""" - def event(oim_box, *args): - method_name = "on_oim_%s" % args[-1].replace("-", "_") - self._dispatch(method_name, *args[:-1]) - def state_changed(oim_box, pspec): - self._dispatch("on_oim_state_changed", oim_box.state) - def error(oim_box, error_code): - self._dispatch("on_client_error", ClientErrorType.OFFLINE_MESSAGES, error_code) - - self.oim_box.connect("notify::state", state_changed) - self.oim_box.connect('error', error) - - def connect_signal(name): - self.oim_box.connect(name, event, name) - connect_signal("messages-received") - connect_signal("messages-fetched") - connect_signal("message-sent") - connect_signal("messages-deleted") diff --git a/pymsn/pymsn/conversation.py b/pymsn/pymsn/conversation.py deleted file mode 100644 index b47b1d18..00000000 --- a/pymsn/pymsn/conversation.py +++ /dev/null @@ -1,447 +0,0 @@ -# -*- coding: utf-8 -*- -# -# pymsn - a python client library for Msn -# -# Copyright (C) 2005-2007 Ali Sabil -# Copyright (C) 2007 Johann Prieur -# -# 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 - -"""Conversation - -This module contains the classes needed to have a conversation with a -contact.""" - -import msnp -import p2p -from switchboard_manager import SwitchboardClient -from pymsn.event import EventsDispatcher -from pymsn.profile import NetworkID - -import logging -import gobject -from urllib import quote, unquote - -__all__ = ['Conversation', 'ConversationInterface', 'ConversationMessage', 'TextFormat'] - -logger = logging.getLogger('conversation') - - -def Conversation(client, contacts): - """Factory function used to create the appropriate conversation with the - given contacts. - - This is the method you need to use to start a conversation with both MSN - users and Yahoo! users. - @attention: you can only talk to one Yahoo! contact at a time, and you - cannot have multi-user conversations with both MSN and Yahoo! contacts. - - @param contacts: The list of contacts to invite into the conversation - @type contacts: [L{Contact}, ...] - - @returns: a Conversation object implementing L{ConversationInterface} - @rtype: L{ConversationInterface} - """ - msn_contacts = set([contact for contact in contacts \ - if contact.network_id == NetworkID.MSN]) - external_contacts = set(contacts) - msn_contacts - - if len(external_contacts) == 0: - return SwitchboardConversation(client, contacts) - elif len(msn_contacts) != 0: - raise NotImplementedError("The protocol doesn't allow mixing " \ - "contacts from different networks in a single conversation") - elif len(external_contacts) > 1: - raise NotImplementedError("The protocol doesn't allow having " \ - "more than one external contact in a conversation") - elif len(external_contacts) == 1: - return ExternalNetworkConversation(client, contacts) - - -class ConversationInterface(object): - """Interface implemented by all the Conversation objects, a Conversation - object allows the user to communicate with one or more peers""" - - def send_text_message(self, message): - """Send a message to all persons in this conversation. - - @param message: the message to send to the users on this conversation - @type message: L{Contact}""" - raise NotImplementedError - - def send_nudge(self): - """Sends a nudge to the contacts on this conversation.""" - raise NotImplementedError - - def send_typing_notification(self): - """Sends an user typing notification to the contacts on this - conversation.""" - raise NotImplementedError - - def invite_user(self, contact): - """Request a contact to join in the conversation. - - @param contact: the contact to invite. - @type contact: L{Contact}""" - raise NotImplementedError - - def leave(self): - """Leave the conversation.""" - raise NotImplementedError - - -class ConversationMessage(object): - """A Conversation message sent or received - - @ivar display_name: the display name to show for the sender of this message - @type display_name: utf-8 encoded string - - @ivar content: the content of the message - @type content: utf-8 encoded string - - @ivar formatting: the formatting for this message - @type formatting: L{TextFormat} - - @ivar msn_objects: a dictionary mapping smileys - to an L{MSNObject} - @type msn_objects: {smiley: string => L{MSNObject}} - """ - def __init__(self, content, formatting=None, msn_objects={}): - """Initializer - - @param content: the content of the message - @type content: utf-8 encoded string - - @param formatting: the formatting for this message - @type formatting: L{TextFormat} - - @param msn_objects: a dictionary mapping smileys - to an L{MSNObject} - @type msn_objects: {smiley: string => L{MSNObject}}""" - self.display_name = None - self.content = content - self.formatting = formatting - self.msn_objects = msn_objects - -class TextFormat(object): - - DEFAULT_FONT = 'MS Sans Serif' - - # effects - NO_EFFECT = 0 - BOLD = 1 - ITALIC = 2 - UNDERLINE = 4 - STRIKETHROUGH = 8 - - # charset - ANSI_CHARSET = '0' - DEFAULT_CHARSET = '1' - SYMBOL_CHARSET = '2' - MAC_CHARSETLT = '4d' - SHIFTJIS_CHARSET = '80' - HANGEUL_CHARSET = '81' - JOHAB_CHARSET = '82' - GB2312_CHARSET = '86' - CHINESEBIG5_CHARSET = '88' - GREEK_CHARSET = 'a1' - TURKISH_CHARSET = 'a2' - VIETNAMESE_CHARSET = 'a3' - HEBREW_CHARSET = 'b1' - ARABIC_CHARSET = 'b2' - BALTIC_CHARSET = 'ba' - RUSSIAN_CHARSET_DEFAULT = 'cc' - THAI_CHARSET = 'de' - EASTEUROPE_CHARSET = 'ee' - OEM_DEFAULT = 'ff' - - # family - FF_DONTCARE = 0 - FF_ROMAN = 1 - FF_SWISS = 2 - FF_MODERN = 3 - FF_SCRIPT = 4 - FF_DECORATIVE = 5 - - # pitch - DEFAULT_PITCH = 0 - FIXED_PITCH = 1 - VARIABLE_PITCH = 2 - - @staticmethod - def parse(format): - text_format = TextFormat() - text_format.__parse(format) - return text_format - - @property - def font(self): - return self._font - - @property - def style(self): - return self._style - - @property - def color(self): - return self._color - - @property - def right_alignment(self): - return self._right_alignment - - @property - def charset(self): - return self._charset - - @property - def pitch(self): - return self._pitch - - @property - def family(self): - return self._family - - def __init__(self, font=DEFAULT_FONT, style=NO_EFFECT, color='0', - charset=DEFAULT_CHARSET, family=FF_DONTCARE, - pitch=DEFAULT_PITCH, right_alignment=False): - self._font = font - self._style = style - self._color = color - self._charset = charset - self._pitch = pitch - self._family = family - self._right_alignment = right_alignment - - def __parse(self, format): - for property in format.split(';'): - key, value = [p.strip(' \t|').upper() \ - for p in property.split('=', 1)] - if key == 'FN': - # Font - self._font = unquote(value) - elif key == 'EF': - # Effects - if 'B' in value: self._style |= TextFormat.BOLD - if 'I' in value: self._style |= TextFormat.ITALIC - if 'U' in value: self._style |= TextFormat.UNDERLINE - if 'S' in value: self._style |= TextFormat.STRIKETHROUGH - elif key == 'CO': - # Color - value = value.zfill(6) - self._color = ''.join((value[4:6], value[2:4], value[0:2])) - elif key == 'CS': - # Charset - self._charset = value - elif key == 'PF': - # Family and pitch - value = value.zfill(2) - self._family = int(value[0]) - self._pitch = int(value[1]) - elif key == 'RL': - # Right alignment - if value == '1': self._right_alignement = True - - def __str__(self): - style = '' - if self._style & TextFormat.BOLD == TextFormat.BOLD: - style += 'B' - if self._style & TextFormat.ITALIC == TextFormat.ITALIC: - style += 'I' - if self._style & TextFormat.UNDERLINE == TextFormat.UNDERLINE: - style += 'U' - if self._style & TextFormat.STRIKETHROUGH == TextFormat.STRIKETHROUGH: - style += 'S' - - color = '%s%s%s' % (self._color[4:6], self._color[2:4], self._color[0:2]) - - format = 'FN=%s; EF=%s; CO=%s; CS=%s; PF=%d%d' % (quote(self._font), - style, color, - self._charset, - self._family, - self._pitch) - if self._right_alignment: format += '; RL=1' - - return format - - def __repr__(self): - return __str__(self) - - -class AbstractConversation(ConversationInterface, EventsDispatcher): - def __init__(self, client): - self._client = client - ConversationInterface.__init__(self) - EventsDispatcher.__init__(self) - - self.__last_received_msn_objects = {} - - def send_text_message(self, message): - if len(message.msn_objects) > 0: - body = [] - for alias, msn_object in message.msn_objects.iteritems(): - self._client._msn_object_store.publish(msn_object) - body.append(alias.encode("utf-8")) - body.append(str(msn_object)) - # FIXME : we need to distinguish animemoticon and emoticons - # and send the related msn objects in separated messages - self._send_message(("text/x-mms-animemoticon",), '\t'.join(body)) - - content_type = ("text/plain","utf-8") - body = message.content.encode("utf-8") - ack = msnp.MessageAcknowledgement.HALF - headers = {} - if message.formatting is not None: - headers["X-MMS-IM-Format"] = str(message.formatting) - - self._send_message(content_type, body, headers, ack) - - def send_nudge(self): - content_type = "text/x-msnmsgr-datacast" - body = "ID: 1\r\n\r\n".encode('UTF-8') #FIXME: we need to figure out the datacast objects :D - ack = msnp.MessageAcknowledgement.NONE - self._send_message(content_type, body, ack=ack) - - def send_typing_notification(self): - content_type = "text/x-msmsgscontrol" - body = "\r\n\r\n".encode('UTF-8') - headers = { "TypingUser" : self._client.profile.account.encode('UTF_8') } - ack = msnp.MessageAcknowledgement.NONE - self._send_message(content_type, body, headers, ack) - - def invite_user(self, contact): - raise NotImplementedError - - def leave(self): - raise NotImplementedError - - def _send_message(self, content_type, body, headers={}, - ack=msnp.MessageAcknowledgement.HALF): - raise NotImplementedError - - def _on_contact_joined(self, contact): - self._dispatch("on_conversation_user_joined", contact) - - def _on_contact_left(self, contact): - self._dispatch("on_conversation_user_left", contact) - - def _on_message_received(self, message): - sender = message.sender - message_type = message.content_type[0] - message_encoding = message.content_type[1] - try: - message_formatting = message.get_header('X-MMS-IM-Format') - except KeyError: - message_formatting = '=' - - if message_type == 'text/plain': - msg = ConversationMessage(unicode(message.body, message_encoding), - TextFormat.parse(message_formatting), - self.__last_received_msn_objects) - try: - display_name = message.get_header('P4-Context') - except KeyError: - display_name = sender.display_name - msg.display_name = display_name - self._dispatch("on_conversation_message_received", sender, msg) - self.__last_received_msn_objects = {} - elif message_type == 'text/x-msmsgscontrol': - self._dispatch("on_conversation_user_typing", sender) - elif message_type in ['text/x-mms-emoticon', - 'text/x-mms-animemoticon']: - msn_objects = {} - parts = message.body.split('\t') - logger.debug(parts) - for i in [i for i in range(len(parts)) if not i % 2]: - if parts[i] == '': break - msn_objects[parts[i]] = p2p.MSNObject.parse(self._client, - parts[i+1]) - self.__last_received_msn_objects = msn_objects - elif message_type == 'text/x-msnmsgr-datacast' and \ - message.body.strip() == "ID: 1": - self._dispatch("on_conversation_nudge_received", sender) - - def _on_message_sent(self, message): - pass - - def _on_error(self, error_type, error): - self._dispatch("on_conversation_error", error_type, error) - - -class ExternalNetworkConversation(AbstractConversation): - def __init__(self, client, contacts): - AbstractConversation.__init__(self, client) - self.participants = set(contacts) - client._register_external_conversation(self) - gobject.idle_add(self._open) - - def _open(self): - for contact in self.participants: - self._on_contact_joined(contact) - return False - - def invite_user(self, contact): - raise NotImplementedError("The protocol doesn't allow multiuser " \ - "conversations for external contacts") - - def leave(self): - self._client._unregister_external_conversation(self) - - def _send_message(self, content_type, body, headers={}, - ack=msnp.MessageAcknowledgement.HALF): - if content_type[0] in ['text/x-mms-emoticon', - 'text/x-mms-animemoticon']: - return - message = msnp.Message(self._client.profile) - for key, value in headers.iteritems(): - message.add_header(key, value) - message.content_type = content_type - message.body = body - for contact in self.participants: - self._client._protocol.\ - send_unmanaged_message(contact, message) - - -class SwitchboardConversation(AbstractConversation, SwitchboardClient): - def __init__(self, client, contacts): - SwitchboardClient.__init__(self, client, contacts, priority=0) - AbstractConversation.__init__(self, client) - - @staticmethod - def _can_handle_message(message, switchboard_client=None): - content_type = message.content_type[0] - if switchboard_client is None: - return content_type in ('text/plain', 'text/x-msnmsgr-datacast') - # FIXME : we need to not filter those 'text/x-mms-emoticon', 'text/x-mms-animemoticon' - return content_type in ('text/plain', 'text/x-msmsgscontrol', - 'text/x-msnmsgr-datacast', 'text/x-mms-emoticon', - 'text/x-mms-animemoticon') - - def invite_user(self, contact): - """Request a contact to join in the conversation. - - @param contact: the contact to invite. - @type contact: L{profile.Contact}""" - SwitchboardClient._invite_user(self, contact) - - def leave(self): - """Leave the conversation.""" - SwitchboardClient._leave(self) - - def _send_message(self, content_type, body, headers={}, - ack=msnp.MessageAcknowledgement.HALF): - SwitchboardClient._send_message(self, content_type, body, headers, ack) - - diff --git a/pymsn/pymsn/event/__init__.py b/pymsn/pymsn/event/__init__.py deleted file mode 100644 index 0c9b6049..00000000 --- a/pymsn/pymsn/event/__init__.py +++ /dev/null @@ -1,74 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2006 Ali Sabil -# -# 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 -# - -"""pymsn event interfaces. - -Defines the interfaces that the client can implement to benefit from the -client event notifications.""" - -from pymsn.util.weak import WeakSet - -class EventsDispatcher(object): - """Abstract object from which all the objects generating events inherit""" - - def __init__(self): - self._events_handlers = WeakSet() - - ### Callbacks - def register_events_handler(self, events_handler): - """Registers an event handler with this dispatcher - @param events_handler: an instance with methods as code of callbacks - @type events_handler: L{pymsn.event.BaseEventInterface} - """ - self._events_handlers.add(events_handler) - - def _dispatch(self, name, *args): - count = 0 - for event_handler in list(self._events_handlers): - if event_handler._dispatch_event(name, *args): - count += 1 - return count - -import weakref -class BaseEventInterface(object): - """Event handler interface, implemented by all the event handlers""" - - def __init__(self, client): - """Initializer - @param client: the client we want to be notified for its events - @type client: an object implementing L{EventsDispatcher}""" - self._client = weakref.proxy(client) - client.register_events_handler(self) - - def _dispatch_event(self, event_name, *params): - try: - handler = getattr(self, event_name) - except Exception, e: - return False - - handler(*params) - return True - -from client import * -from conversation import * -from profile import * -from contact import * -from address_book import * -from offline_messages import * -from invite import * diff --git a/pymsn/pymsn/event/address_book.py b/pymsn/pymsn/event/address_book.py deleted file mode 100644 index dac2454e..00000000 --- a/pymsn/pymsn/event/address_book.py +++ /dev/null @@ -1,54 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# - -from pymsn.event import BaseEventInterface - -__all__ = ["AddressBookEventInterface"] - -class AddressBookEventInterface(BaseEventInterface): - def __init__(self, client): - BaseEventInterface.__init__(self, client) - - def on_addressbook_messenger_contact_added(self, contact): - pass - - def on_addressbook_contact_deleted(self, contact): - pass - - def on_addressbook_contact_blocked(self, contact): - pass - - def on_addressbook_contact_unblocked(self, contact): - pass - - def on_addressbook_group_added(self, group): - pass - - def on_addressbook_group_deleted(self, group): - pass - - def on_addressbook_group_renamed(self, group): - pass - - def on_addressbook_group_contact_added(self, group, contact): - pass - - def on_addressbook_group_contact_deleted(self, group, contact): - pass - diff --git a/pymsn/pymsn/event/client.py b/pymsn/pymsn/event/client.py deleted file mode 100644 index f2378f22..00000000 --- a/pymsn/pymsn/event/client.py +++ /dev/null @@ -1,114 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Ali Sabil -# Copyright (C) 2007 Ole André Vadla Ravnås -# -# 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 -# - -"""Client event interfaces - -The interfaces defined in this module allow receiving core notification events -from the client. - - @sort: ClientEventInterface - @group Enums: ClientState, ClientErrorType - @group Error Enums: NetworkError, AuthenticationError, ProtocolError, - AddressBookError, OfflineMessagesBoxError""" - -from pymsn.event import BaseEventInterface - -import pymsn.gnet -import pymsn.service.AddressBook.constants -import pymsn.service.OfflineIM.constants -import pymsn.msnp - -__all__ = [ "ClientState", "ClientErrorType", - "NetworkError", "AuthenticationError", "ProtocolError", - "AddressBookError", "OfflineMessagesBoxError", - "ClientEventInterface" ] - -class ClientState(object): - "L{Client} states" - CLOSED = 0 - CONNECTING = 1 - CONNECTED = 2 - AUTHENTICATING = 3 - AUTHENTICATED = 4 - SYNCHRONIZING = 5 - SYNCHRONIZED = 6 - OPEN = 7 - -class ClientErrorType(object): - """L{Client} error types - @see: L{ClientEventInterface.on_client_error}""" - - NETWORK = 0 - "Network related errors" - AUTHENTICATION = 1 - "Authentication related errors" - PROTOCOL = 2 - "Protocol related errors" - ADDRESSBOOK = 3 - "Address book related errors" - OFFLINE_MESSAGES = 4 - "Offline IM related errors" - -NetworkError = pymsn.gnet.IoError -"Network related errors" - -class AuthenticationError(object): - "Authentication related errors" - UNKNOWN = 0 - INVALID_USERNAME = 1 - INVALID_PASSWORD = 2 - INVALID_USERNAME_OR_PASSWORD = 3 - -class ProtocolError(object): - "Protocol related errors" - UNKNOWN = 0 - -AddressBookError = pymsn.service.AddressBook.constants.AddressBookError -OfflineMessagesBoxError = pymsn.service.OfflineIM.constants.OfflineMessagesBoxError - - -class ClientEventInterface(BaseEventInterface): - """Interface allowing the user to get notified about the - L{Client} events""" - - def __init__(self, client): - """Initializer - @param client: the client we want to be notified for its events - @type client: L{Client}""" - BaseEventInterface.__init__(self, client) - - def on_client_state_changed(self, state): - """Called when the state of the L{Client} changes. - @param state: the new state of the client - @type state: L{ClientState}""" - pass - - def on_client_error(self, type, error): - """Called when an error occurs in the L{Client}. - - @param type: the error type - @type type: L{ClientErrorType} - - @param error: the error code - @type error: L{NetworkError} or L{AuthenticationError} or - L{ProtocolError} or L{AddressBookError} or - L{OfflineMessagesBoxError}""" - pass - diff --git a/pymsn/pymsn/event/contact.py b/pymsn/pymsn/event/contact.py deleted file mode 100644 index 893c5cb6..00000000 --- a/pymsn/pymsn/event/contact.py +++ /dev/null @@ -1,91 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Ali Sabil -# Copyright (C) 2007 Ole André Vadla Ravnås -# -# 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 -# - -"""Contact event interfaces - -The interfaces defined in this module allow receiving notification events -from the contacts.""" - -from pymsn.event import BaseEventInterface - -__all__ = ["ContactEventInterface"] - -class ContactEventInterface(BaseEventInterface): - """Interface allowing the user to get notified about the - L{Contact}s events""" - - def __init__(self, client): - """Initializer - @param client: the client we want to be notified for its events - @type client: L{Client}""" - BaseEventInterface.__init__(self, client) - - def on_contact_memberships_changed(self, contact): - """Called when the memberships of a contact changes. - @param contact: the contact whose presence changed - @type contact: L{Contact} - @see: L{Memberships}""" - pass - - def on_contact_presence_changed(self, contact): - """Called when the presence of a contact changes. - @param contact: the contact whose presence changed - @type contact: L{Contact}""" - pass - - def on_contact_display_name_changed(self, contact): - """Called when the display name of a contact changes. - @param contact: the contact whose presence changed - @type contact: L{Contact}""" - pass - - def on_contact_personal_message_changed(self, contact): - """Called when the personal message of a contact changes. - @param contact: the contact whose presence changed - @type contact: L{Contact}""" - pass - - def on_contact_current_media_changed(self, contact): - """Called when the current media of a contact changes. - @param contact: the contact whose presence changed - @type contact: L{Contact}""" - pass - - def on_contact_infos_changed(self, contact, infos): - """Called when the infos of a contact changes. - @param contact: the contact whose presence changed - @type contact: L{Contact}""" - pass - - def on_contact_client_capabilities_changed(self, contact): - """Called when the client capabilities of a contact changes. - @param contact: the contact whose presence changed - @type contact: L{Contact}""" - pass - - def on_contact_msn_object_changed(self, contact): - """Called when the MSNObject of a contact changes. - @param contact: the contact whose presence changed - @type contact: L{Contact} - - @see: L{MSNObjectStore}, - L{MSNObject}""" - pass - diff --git a/pymsn/pymsn/event/conversation.py b/pymsn/pymsn/event/conversation.py deleted file mode 100644 index 7cff5d37..00000000 --- a/pymsn/pymsn/event/conversation.py +++ /dev/null @@ -1,120 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Ali Sabil -# Copyright (C) 2007 Johann Prieur -# -# 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 -# - -"""Conversation event interfaces - -The interfaces defined in this module allow receiving notification events -from a L{Conversation} object.""" - -from pymsn.event import BaseEventInterface - -__all__ = ["ConversationEventInterface", "ConversationErrorType", - "ContactInviteError", "MessageError"] - - -class ConversationErrorType(object): - """L{Client} error types - @see: L{ClientEventInterface.on_client_error}""" - - NETWORK = 0 - "Network related errors" - AUTHENTICATION = 1 - "Authentication related errors" - PROTOCOL = 2 - "Protocol related errors" - CONTACT_INVITE = 3 - "Contact invitation related errors" - MESSAGE = 4 - "Message sending related errors" - - -class ContactInviteError(object): - "Contact invitation related errors" - UNKNOWN = 0 - - NOT_AVAILABLE = 1 - - -class MessageError(object): - "Message related errors" - UNKNOWN = 0 - - DELIVERY_FAILED = 1 - - -class ConversationEventInterface(BaseEventInterface): - """interfaces allowing the user to get notified about events - from a L{Conversation} object.""" - - def __init__(self, conversation): - """Initializer - @param conversation: the conversation we want to be notified for its events - @type conversation: L{Conversation}""" - BaseEventInterface.__init__(self, conversation) - - def on_conversation_state_changed(self, state): - """@attention: not implemented""" - pass - - def on_conversation_error(self, type, error): - """Called when an error occurs in the L{Client}. - - @param type: the error type - @type type: L{ClientErrorType} - - @param error: the error code - @type error: L{NetworkError} or L{AuthenticationError} or - L{ProtocolError} or L{ContactInviteError} or - L{MessageError}""" - pass - - def on_conversation_user_joined(self, contact): - """Called when a user joins the conversation. - @param contact: the contact whose presence changed - @type contact: L{Contact}""" - pass - - def on_conversation_user_left(self, contact): - """Called when a user leaved the conversation. - @param contact: the contact whose presence changed - @type contact: L{Contact}""" - pass - - def on_conversation_user_typing(self, contact): - """Called when a user is typing. - @param contact: the contact whose presence changed - @type contact: L{Contact}""" - pass - - def on_conversation_message_received(self, sender, message): - """Called when a user sends a message. - @param sender: the contact who sent the message - @type sender: L{Contact} - - @param message: the message - @type message: L{ConversationMessage}""" - pass - - def on_conversation_nudge_received(self, sender): - """Called when a user sends a nudge. - @param sender: the contact who sent the nudge - @type sender: L{Contact}""" - pass - diff --git a/pymsn/pymsn/event/invite.py b/pymsn/pymsn/event/invite.py deleted file mode 100644 index 37cdd247..00000000 --- a/pymsn/pymsn/event/invite.py +++ /dev/null @@ -1,41 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Ali Sabil -# -# 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 -# - -"""Invite event interfaces - -The interfaces defined in this module allow receiving notification events when -we get invited into an activity with other users.""" - -from pymsn.event import BaseEventInterface - -__all__ = ["InviteEventInterface"] - -class InviteEventInterface(BaseEventInterface): - def __init__(self, client): - """Initializer - @param client: the client we want to be notified for its events - @type client: L{Client}""" - BaseEventInterface.__init__(self, client) - - def on_invite_conversation(self, conversation): - """Called when we get invited into a conversation - @param conversation: the conversation - @type conversation: L{Conversation}""" - pass - diff --git a/pymsn/pymsn/event/offline_messages.py b/pymsn/pymsn/event/offline_messages.py deleted file mode 100644 index f7f5b5e3..00000000 --- a/pymsn/pymsn/event/offline_messages.py +++ /dev/null @@ -1,50 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# - -"""Offline IM event interfaces - -The interfaces defined in this module allow receiving notification events about -Offline messages.""" - -from pymsn.event import BaseEventInterface - -__all__ = ["OfflineMessagesEventInterface"] - -class OfflineMessagesEventInterface(BaseEventInterface): - """interfaces allowing the user to get notified about events from the - Offline IM box.""" - - def __init__(self, client): - BaseEventInterface.__init__(self, client) - - def on_oim_state_changed(self, state): - pass - - def on_oim_messages_received(self, messages): - pass - - def on_oim_messages_fetched(self, messages): - pass - - def on_oim_messages_deleted(self): - pass - - def on_oim_message_sent(self, recipient, message): - pass - diff --git a/pymsn/pymsn/event/profile.py b/pymsn/pymsn/event/profile.py deleted file mode 100644 index 89e844e1..00000000 --- a/pymsn/pymsn/event/profile.py +++ /dev/null @@ -1,57 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2008 Ali Sabil -# -# 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 -# - -"""Profile event interfaces - -The interfaces defined in this module allow receiving notification events when -the user's profile has been effectively changed on the server.""" - -from pymsn.event import BaseEventInterface - -__all__ = ["ProfileEventInterface"] - -class ProfileEventInterface(BaseEventInterface): - """Interface allowing the user to get notified about - L{Profile}s events""" - - def __init__(self, client): - """Initializer - @param client: the client we want to be notified for its events - @type client: L{Client}""" - BaseEventInterface.__init__(self, client) - - def on_profile_presence_changed(self): - """Called when the presence changes.""" - pass - - def on_profile_display_name_changed(self): - """Called when the display name changes.""" - pass - - def on_profile_personal_message_changed(self): - """Called when the personal message changes.""" - pass - - def on_profile_current_media_changed(self): - """Called when the current media changes.""" - pass - - def on_profile_msn_object_changed(self): - """Called when the MSNObject changes.""" - pass diff --git a/pymsn/pymsn/gnet/__init__.py b/pymsn/pymsn/gnet/__init__.py deleted file mode 100644 index e699431d..00000000 --- a/pymsn/pymsn/gnet/__init__.py +++ /dev/null @@ -1,27 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2005 Ole André Vadla Ravnås -# Copyright (C) 2006 Ali Sabil -# -# 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 -# - -"""GNet - glib based asynchronous networking library. - -The GNet library was designed as a replacement for the python asyncore -and asynchat modules that easily integrate with the glib main loop. -""" -from constants import IoStatus, IoError -import io diff --git a/pymsn/pymsn/gnet/constants.py b/pymsn/pymsn/gnet/constants.py deleted file mode 100644 index 80154efe..00000000 --- a/pymsn/pymsn/gnet/constants.py +++ /dev/null @@ -1,53 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2005 Ole André Vadla Ravnås -# Copyright (C) 2006 Ali Sabil -# -# 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 -# - -"""Constants used in GNet.""" -from socket import AF_INET, AF_INET6, \ - SOCK_STREAM, SOCK_DGRAM, SOCK_RAW, SOCK_RDM, SOCK_SEQPACKET -try: - from socket import AF_UNIX -except ImportError: - pass - -class GNet(object): - NAME = "gnet" - VERSION = "0.1" - -class IoStatus(object): - """Various networking status""" - CLOSING = 0 - CLOSED = 1 - OPENING = 2 - OPEN = 3 - -class IoError(object): - """I/O error codes""" - UNKNOWN = 0 - - CONNECTION_FAILED = 1 - CONNECTION_TIMED_OUT = 2 - - SSL_CONNECTION_FAILED = 10 - SSL_PROTOCOL_ERROR = 11 - - PROXY_CONNECTION_FAILED = 20 - PROXY_AUTHENTICATION_REQUIRED = 21 - PROXY_FORBIDDEN = 22 - diff --git a/pymsn/pymsn/gnet/io/__init__.py b/pymsn/pymsn/gnet/io/__init__.py deleted file mode 100644 index 8b9109d1..00000000 --- a/pymsn/pymsn/gnet/io/__init__.py +++ /dev/null @@ -1,27 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2006 Ali Sabil -# -# 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 -# - -"""GNet IO layer, provide most commonly used IO transports to be used -by higher level classes""" - -from abstract import * -from sock import * -from tcp import * -from ssl_socket import * -from ssl_tcp import * diff --git a/pymsn/pymsn/gnet/io/abstract.py b/pymsn/pymsn/gnet/io/abstract.py deleted file mode 100644 index d860dbbb..00000000 --- a/pymsn/pymsn/gnet/io/abstract.py +++ /dev/null @@ -1,189 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2005 Ole André Vadla Ravnås -# Copyright (C) 2006-2007 Ali Sabil -# Copyright (C) 2007 Johann Prieur -# -# 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 -# -from pymsn.gnet.constants import * - -import gobject - -__all__ = ['AbstractClient'] - -class AbstractClient(gobject.GObject): - """Abstract client base class. - All network client classes implements this interface. - - @sort: __init__, open, send, close - @undocumented: do_*, _configure, _pre_open, _post_open - - @since: 0.1""" - - __gproperties__ = { - "host": (gobject.TYPE_STRING, - "Remote Host", - "The remote host to connect to.", - "", - gobject.PARAM_READWRITE), - - "port": (gobject.TYPE_INT, - "Remote Port", - "The remote port to connect to.", - -1, 65535, -1, - gobject.PARAM_READWRITE), - - "status": (gobject.TYPE_INT, - "Connection Status", - "The status of this connection.", - 0, 3, IoStatus.CLOSED, - gobject.PARAM_READABLE), - } - - __gsignals__ = { - "error": (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - (gobject.TYPE_ULONG,)), - - "received": (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - (object, gobject.TYPE_ULONG)), - - "sent": (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - (object, gobject.TYPE_ULONG)), - } - - def __init__(self, host, port, domain=AF_INET, type=SOCK_STREAM): - """Initializer - - @param host: the hostname to connect to. - @type host: string - - @param port: a port number to connect to - @type port: integer > 0 and < 65536 - - @param domain: the communication domain. - @type domain: integer - @see socket module - - @param type: the communication semantics - @type type: integer - @see socket module - """ - gobject.GObject.__init__(self) - self._host = host - self._port = port - self._domain = domain - self._type = type - self._transport = None - self.__status = IoStatus.CLOSED - - def __del__(self): - self.close() - - # opening state methods - def _configure(self): - if len(self._host) == 0 or self._port < 0 or self._port > 65535: - raise ValueError("Wrong host or port number : (%s, %d)" % \ - (self._host, self._port) ) - if self.status in (IoStatus.OPENING, IoStatus.OPEN): - return False - assert(self.status == IoStatus.CLOSED) - return True - - def _pre_open(self, io_object=None): - self._status = IoStatus.OPENING - - def _post_open(self): - pass - - # public API - def open(self): - """Open the connection.""" - raise NotImplementedError - - - def close(self): - """Close the connection.""" - raise NotImplementedError - - def send(self, buffer, callback=None, *args): - """Send data to the server. - - @param buffer: data buffer. - @type buffer: string - - @param callback: a callback method that would be called when the - data is atually sent to the server. - @type callback: callback - - @param args: callback arguments to be passed to the callback. - """ - raise NotImplementedError - - # properties - def __get_host(self): - return self._host - def __set_host(self, host): - self.set_property("host", host) - host = property(__get_host, __set_host) - - def __get_port(self): - return self._port - def __set_port(self, port): - self.set_property("port", port) - port = property(__get_port, __set_port) - - @property - def domain(self): - return self._domain - - @property - def type(self): - return self._type - - def __get_status(self): - return self.__status - def __set_status(self, new_status): - if self.__status != new_status: - self.__status = new_status - self.notify("status") - _status = property(__get_status, __set_status) - status = property(__get_status) - - def do_get_property(self, pspec): - if pspec.name == "host": - return self._host - elif pspec.name == "port": - return self._port - elif pspec.name == "status": - return self.__status - else: - raise AttributeError, "unknown property %s" % pspec.name - - def do_set_property(self, pspec, value): - if pspec.name == "host": - if len(value) == 0: - raise ValueError("Wrong host %s" % self._host) - self._host = value - elif pspec.name == "port": - if self._port < 0 or self._port > 65535: - raise ValueError("Wrong port %d" % self._port) - self._port = value - else: - raise AttributeError, "unknown property %s" % pspec.name -gobject.type_register(AbstractClient) diff --git a/pymsn/pymsn/gnet/io/iochannel.py b/pymsn/pymsn/gnet/io/iochannel.py deleted file mode 100644 index 8139f5d2..00000000 --- a/pymsn/pymsn/gnet/io/iochannel.py +++ /dev/null @@ -1,161 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2005 Ole André Vadla Ravnås -# Copyright (C) 2006-2007 Ali Sabil -# Copyright (C) 2007 Johann Prieur -# -# 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 -# -from pymsn.gnet.constants import * -from pymsn.gnet.resolver import * -from abstract import AbstractClient - -import gobject -from errno import * - -__all__ = ['GIOChannelClient'] - -class OutgoingPacket(object): - """Represents a packet to be sent over the IO channel""" - def __init__(self, buffer, size, callback=None, *cb_args): - self.buffer = buffer - self.size = size - self._sent = 0 - self._callback = callback - self._callback_args = cb_args - - def read(self, size=2048): - if size is not None: - return self.buffer[self._sent:][0:size] - return self.buffer[self._sent:] - - def sent(self, size): - """update how many bytes have been sent""" - self._sent += size - - def is_complete(self): - """return whether this packet was completely transmitted or not""" - return self.size == self._sent - - def callback(self): - """Run the callback function if supplied""" - if self._callback is not None: - self._callback(*self._callback_args) - - -class GIOChannelClient(AbstractClient): - """Base class for clients using GIOChannel facilities - - @sort: __init__, open, send, close - @undocumented: do_*, _configure, _pre_open, _post_open - - @since: 0.1""" - - def __init__(self, host, port, domain=AF_INET, type=SOCK_STREAM): - AbstractClient.__init__(self, host, port, domain, type) - - def _pre_open(self, io_object): - io_object.setblocking(False) - channel = gobject.IOChannel(io_object.fileno()) - channel.set_flags(channel.get_flags() | gobject.IO_FLAG_NONBLOCK) - channel.set_encoding(None) - channel.set_buffered(False) - - self._transport = io_object - self._channel = channel - - self._source_id = None - self._source_condition = 0 - self._outgoing_queue = [] - AbstractClient._pre_open(self) - - def _post_open(self): - AbstractClient._post_open(self) - self._watch_remove() - - def _open(self, host, port): - resolver = HostnameResolver() - resolver.query(host, (self.__open, host, port)) - - def __open(self, resolve_response, host, port): - if resolve_response.status != 0: - self.emit("error", IoError.CONNECTION_FAILED) - self._transport.close() - return - else: - host = resolve_response.answer[0][1] - err = self._transport.connect_ex((host, port)) - self._watch_set_cond(gobject.IO_PRI | gobject.IO_IN | gobject.IO_OUT | - gobject.IO_HUP | gobject.IO_ERR | gobject.IO_NVAL, - lambda chan, cond: self._post_open()) - if err in (0, EINPROGRESS, EALREADY, EWOULDBLOCK, EISCONN): - return - elif err in (EHOSTUNREACH, EHOSTDOWN, ECONNREFUSED, ECONNABORTED, - ENETUNREACH, ENETDOWN): - self.emit("error", IoError.CONNECTION_FAILED) - self._transport.close() - - # convenience methods - def _watch_remove(self): - if self._source_id is not None: - gobject.source_remove(self._source_id) - self._source_id = None - self._source_condition = 0 - - def _watch_set_cond(self, cond, handler=None): - self._watch_remove() - self._source_condition = cond - if handler is None: - handler = self._io_channel_handler - self._source_id = self._channel.add_watch(cond, handler) - - def _watch_add_cond(self, cond): - if self._source_condition & cond == cond: - return - self._source_condition |= cond - self._watch_set_cond(self._source_condition) - - def _watch_remove_cond(self, cond): - if self._source_condition & cond == 0: - return - self._source_condition ^= cond - self._watch_set_cond(self._source_condition) - - # public API - def open(self): - if not self._configure(): - return - self._pre_open() - self._open(self._host, self._port) - - def close(self): - if self._status in (IoStatus.CLOSING, IoStatus.CLOSED): - return - self._status = IoStatus.CLOSING - self._watch_remove() - try: - self._channel.close() - self._transport.shutdown(socket.SHUT_RDWR) - except: - pass - self._transport.close() - self._status = IoStatus.CLOSED - - def send(self, buffer, callback=None, *args): - assert(self._status == IoStatus.OPEN), self._status - self._outgoing_queue.append(OutgoingPacket(buffer, len(buffer), - callback, *args)) - self._watch_add_cond(gobject.IO_OUT) -gobject.type_register(GIOChannelClient) diff --git a/pymsn/pymsn/gnet/io/sock.py b/pymsn/pymsn/gnet/io/sock.py deleted file mode 100644 index a588957f..00000000 --- a/pymsn/pymsn/gnet/io/sock.py +++ /dev/null @@ -1,98 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2005 Ole André Vadla Ravnås -# Copyright (C) 2006-2007 Ali Sabil -# Copyright (C) 2007 Johann Prieur -# -# 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 -# -from pymsn.gnet.constants import * -from iochannel import GIOChannelClient - -import gobject -import socket - - -__all__ = ['SocketClient'] - -class SocketClient(GIOChannelClient): - """Asynchronous Socket client class. - - @sort: __init__, open, send, close - @undocumented: do_*, _watch_*, __io_*, _connect_done_handler - - @since: 0.1""" - - def __init__(self, host, port, domain=AF_INET, type=SOCK_STREAM): - GIOChannelClient.__init__(self, host, port, domain, type) - - - def _pre_open(self, sock=None): - if sock is None: - sock = socket.socket(self._domain, self._type) - try: - sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1) - except AttributeError: - pass - GIOChannelClient._pre_open(self, sock) - - def _post_open(self): - GIOChannelClient._post_open(self) - opts = self._transport.getsockopt(socket.SOL_SOCKET, socket.SO_ERROR) - if opts == 0: - self._watch_set_cond(gobject.IO_IN | gobject.IO_PRI | - gobject.IO_ERR | gobject.IO_HUP) - self._status = IoStatus.OPEN - else: - self.emit("error", IoError.CONNECTION_FAILED) - self._status = IoStatus.CLOSED - return False - - def _io_channel_handler(self, chan, cond): - if self._status == IoStatus.CLOSED: - return False - - if cond & (gobject.IO_IN | gobject.IO_PRI): - buf = "" - try: - buf = self._channel.read(2048) - except gobject.GError: - pass - if buf == "": - self.close() - return False - self.emit("received", buf, len(buf)) - - # Check for error/EOF - if cond & (gobject.IO_ERR | gobject.IO_HUP): - self.close() - return False - - if cond & gobject.IO_OUT: - if len(self._outgoing_queue) > 0: # send next item - item = self._outgoing_queue[0] - item.sent(self._channel.write(item.read())) - if item.is_complete(): # sent item - self.emit("sent", item.buffer, item.size) - item.callback() - del self._outgoing_queue[0] - del item - if len(self._outgoing_queue) == 0: - self._watch_remove_cond(gobject.IO_OUT) - else: - self._watch_remove_cond(gobject.IO_OUT) - - return True -gobject.type_register(SocketClient) diff --git a/pymsn/pymsn/gnet/io/ssl_socket.py b/pymsn/pymsn/gnet/io/ssl_socket.py deleted file mode 100644 index bf8ebc07..00000000 --- a/pymsn/pymsn/gnet/io/ssl_socket.py +++ /dev/null @@ -1,117 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2005 Ole André Vadla Ravnås -# Copyright (C) 2006-2007 Ali Sabil -# Copyright (C) 2007 Johann Prieur -# -# 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 -# -from pymsn.gnet.constants import * -from iochannel import GIOChannelClient - -import gobject -import socket -import OpenSSL.SSL as OpenSSL - -__all__ = ['SSLSocketClient'] - -class SSLSocketClient(GIOChannelClient): - """Asynchronous Socket client class. - - @sort: __init__, open, send, close - @undocumented: do_*, _watch_*, __io_*, _connect_done_handler - - @since: 0.1""" - - def __init__(self, host, port, domain=AF_INET, type=SOCK_STREAM): - GIOChannelClient.__init__(self, host, port, domain, type) - - def _pre_open(self, sock=None): - if sock is None: - sock = socket.socket(self._domain, self._type) - try: - sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1) - except AttributeError: - pass - context = OpenSSL.Context(OpenSSL.SSLv23_METHOD) - ssl_sock = OpenSSL.Connection(context, sock) - GIOChannelClient._pre_open(self, ssl_sock) - - def _post_open(self): - GIOChannelClient._post_open(self) - if self._transport.getsockopt(socket.SOL_SOCKET, socket.SO_ERROR) == 0: - self._watch_set_cond(gobject.IO_IN | gobject.IO_PRI | gobject.IO_OUT | - gobject.IO_ERR | gobject.IO_HUP) - else: - self.emit("error", IoError.CONNECTION_FAILED) - self._status = IoStatus.CLOSED - return False - - def _io_channel_handler(self, chan, cond): - if self._status == IoStatus.CLOSED: - return False - if self._status == IoStatus.OPENING: - try: - self._transport.do_handshake() - except (OpenSSL.WantX509LookupError, - OpenSSL.WantReadError, OpenSSL.WantWriteError): - return True - except (OpenSSL.ZeroReturnError, OpenSSL.SysCallError): - self.emit("error", IoError.SSL_CONNECTION_FAILED) - self.close() - return False - else: - self._status = IoStatus.OPEN - elif self._status == IoStatus.OPEN: - if cond & (gobject.IO_IN | gobject.IO_PRI): - try: - buf = self._transport.recv(2048) - except (OpenSSL.WantX509LookupError, - OpenSSL.WantReadError, OpenSSL.WantWriteError): - return True - except (OpenSSL.ZeroReturnError, OpenSSL.SysCallError): - self.close() - return False - self.emit("received", buf, len(buf)) - - if cond & (gobject.IO_ERR | gobject.IO_HUP): - self.close() - return False - - if cond & gobject.IO_OUT: - if len(self._outgoing_queue) > 0: # send next item - item = self._outgoing_queue[0] - try: - ret = self._transport.send(item.read()) - except (OpenSSL.WantX509LookupError, - OpenSSL.WantReadError, OpenSSL.WantWriteError): - return True - except (OpenSSL.ZeroReturnError, OpenSSL.SysCallError): - self.close() - return False - item.sent(ret) - if item.is_complete(): # sent item - self.emit("sent", item.buffer, item.size) - item.callback() - del self._outgoing_queue[0] - del item - if len(self._outgoing_queue) == 0: - self._watch_remove_cond(gobject.IO_OUT) - else: - self._watch_remove_cond(gobject.IO_OUT) - - return True - -gobject.type_register(SSLSocketClient) diff --git a/pymsn/pymsn/gnet/io/ssl_tcp.py b/pymsn/pymsn/gnet/io/ssl_tcp.py deleted file mode 100644 index ed36c6e9..00000000 --- a/pymsn/pymsn/gnet/io/ssl_tcp.py +++ /dev/null @@ -1,47 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2005 Ole André Vadla Ravnås -# Copyright (C) 2006-2007 Ali Sabil -# Copyright (C) 2007 Johann Prieur -# -# 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 -# -from pymsn.gnet.constants import * -from pymsn.gnet.proxy.proxyfiable import ProxyfiableClient -from ssl_socket import SSLSocketClient - -import gobject - -__all__ = ['SSLTCPClient'] - -class SSLTCPClient(SSLSocketClient, ProxyfiableClient): - """Asynchronous TCP client class. - - @sort: __init__, open, send, close - @undocumented: do_*, _watch_*, __io_*, _connect_done_handler - - @since: 0.1""" - - def __init__(self, host, port): - """initializer - - @param host: the hostname to connect to. - @type host: string - - @param port: the port number to connect to. - @type port: integer > 0 and < 65536""" - SSLSocketClient.__init__(self, host, port, AF_INET, SOCK_STREAM) - ProxyfiableClient.__init__(self) -gobject.type_register(SSLTCPClient) diff --git a/pymsn/pymsn/gnet/io/tcp.py b/pymsn/pymsn/gnet/io/tcp.py deleted file mode 100644 index bd530ba4..00000000 --- a/pymsn/pymsn/gnet/io/tcp.py +++ /dev/null @@ -1,47 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2005 Ole André Vadla Ravnås -# Copyright (C) 2006-2007 Ali Sabil -# Copyright (C) 2007 Johann Prieur -# -# 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 -# -from pymsn.gnet.constants import * -from pymsn.gnet.proxy.proxyfiable import ProxyfiableClient -from sock import SocketClient - -import gobject - -__all__ = ['TCPClient'] - -class TCPClient(SocketClient, ProxyfiableClient): - """Asynchronous TCP client class. - - @sort: __init__, open, send, close - @undocumented: do_*, _watch_*, __io_*, _connect_done_handler - - @since: 0.1""" - - def __init__(self, host, port): - """initializer - - @param host: the hostname to connect to. - @type host: string - - @param port: the port number to connect to. - @type port: integer > 0 and < 65536""" - SocketClient.__init__(self, host, port, AF_INET, SOCK_STREAM) - ProxyfiableClient.__init__(self) -gobject.type_register(TCPClient) diff --git a/pymsn/pymsn/gnet/message/HTTP.py b/pymsn/pymsn/gnet/message/HTTP.py deleted file mode 100644 index a36d21e5..00000000 --- a/pymsn/pymsn/gnet/message/HTTP.py +++ /dev/null @@ -1,161 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2005 Ole André Vadla Ravnås -# Copyright (C) 2006 Ali Sabil -# -# 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 -# - -"""HTTP Messages structures.""" -from UserDict import DictMixin - -from pymsn.gnet.constants import * - -__all__ = ['HTTPMessage', 'HTTPResponse', 'HTTPRequest'] - -class odict(DictMixin): - def __init__(self, dict=None): - self._keys = [] - self.data = dict or {} - - def __getitem__(self, key): - return self.data[key] - - def __delitem__(self, key): - del self.data[key] - self._keys.remove(key) - - def __setitem__(self, key, item): - self.data[key] = item - if key not in self._keys: - self._keys.append(key) - - def keys(self): - return self._keys[:] - - -class HTTPMessage(object): - """HTTP style message abstraction - - @ivar headers: HTTP style headers of the message - @type headers: dict() - - @ivar body: HTTP Message Body - @type body: string - """ - def __init__(self): - self.clear() - - def add_header(self, name, value): - """Add the header with the given name to the set of headers of - this message - - @param name: name of the header - @param value: value of the header""" - value = str(value) - self.headers[name] = value - - def get_header(self, name): - """Returns the value of a given header""" - return self.headers[name] - - def clear(self): - """Empties the HTTP message""" - self.headers = odict() - self.body = "" - - def parse(self, chunk): - """Parses a given chunk of data and fill in the current object - - @param chunk: the chunk of data to parse - @type chunk: string""" - self.clear() - - lines = chunk.split("\r\n") - for i, line in enumerate(lines): - if line.strip() == "": - self.body = "\r\n".join(lines[i+1:]) - break - name, value = line.split(":", 1) - self.add_header(name.rstrip(), value.lstrip()) - - def __str__(self): - result = [] - body = str(self.body) - for name in self.headers: - result.append(": ".join((name, str(self.headers[name])))) - #if "Content-Length" not in self.headers: - # result.append("Content-Length: %d" % len(body)) - result.append("") - result.append(str(self.body)) - return "\r\n".join(result) - - -class HTTPResponse(HTTPMessage): - def __init__(self, headers=None, body="", status=200, reason="OK", version="1.0"): - if headers is None: - headers = {} - HTTPMessage.__init__(self) - for header, value in headers.iteritems(): - self.add_header(header, value) - self.body = body - self.status = status - self.reason = reason - self.version = version - - def parse(self, chunk): - start_line, message = chunk.split("\r\n", 1) - - version, status, reason = start_line.split(" ", 2) - self.status = int(status) - self.reason = reason - self.version = version.split("/",1)[1] - - HTTPMessage.parse(self, message) - - def __str__(self): - message = HTTPMessage.__str__(self) - start_line = "HTTP/%s %d %s" % (self.version, self.status, self.reason) - return start_line + "\r\n" + message - - -class HTTPRequest(HTTPMessage): - def __init__(self, headers=None, body="", method="GET", resource="/", version="1.0"): - if headers is None: - headers = {} - HTTPMessage.__init__(self) - for header, value in headers.iteritems(): - self.add_header(header, value) - self.body = body - self.method = method - self.resource = resource - self.version = version - - def parse(self, chunk): - start_line, message = chunk.split("\r\n", 1) - - method, resource, version = start_line.split(" ") - self.method = method - self.resource = resource - self.version = version.split("/",1)[1] - - HTTPMessage.parse(self, message) - - def __str__(self): - message = HTTPMessage.__str__(self) - start_line = "%s %s HTTP/%s" % (self.method, - self.resource, self.version) - return start_line + "\r\n" + message - diff --git a/pymsn/pymsn/gnet/message/SOAP.py b/pymsn/pymsn/gnet/message/SOAP.py deleted file mode 100644 index 53564dd9..00000000 --- a/pymsn/pymsn/gnet/message/SOAP.py +++ /dev/null @@ -1,154 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2006 Ali Sabil -# -# 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 -# - -"""SOAP Messages structures.""" - -import pymsn.util.element_tree as ElementTree -import pymsn.util.string_io as StringIO - -__all__=['SOAPRequest', 'SOAPResponse'] - -class NameSpace: - SOAP_ENVELOPE = "http://schemas.xmlsoap.org/soap/envelope/" - SOAP_ENCODING = "http://schemas.xmlsoap.org/soap/encoding/" - XML_SCHEMA = "http://www.w3.org/1999/XMLSchema" - XML_SCHEMA_INSTANCE = "http://www.w3.org/1999/XMLSchema-instance" - -class Encoding: - SOAP = "http://schemas.xmlsoap.org/soap/encoding/" - -class _SOAPSection: - ENVELOPE = "{" + NameSpace.SOAP_ENVELOPE + "}Envelope" - HEADER = "{" + NameSpace.SOAP_ENVELOPE + "}Header" - BODY = "{" + NameSpace.SOAP_ENVELOPE + "}Body" - - -class _SOAPElement(object): - def __init__(self, element): - self.element = element - - def append(self, tag, namespace=None, type=None, attrib={}, value=None, **kwargs): - if namespace is not None: - tag = "{" + namespace + "}" + tag - if type: - if isinstance(type, str): - type = ElementTree.QName(type, NameSpace.XML_SCHEMA) - else: - type = ElementTree.QName(type[1], type[0]) - attrib["{" + NameSpace.XML_SCHEMA_INSTANCE + "}type"] = type - - child = ElementTree.SubElement(self.element, tag, attrib, **kwargs) - child.text = value - return _SOAPElement(child) - - def __str__(self): - return ElementTree.tostring(self.element, "utf-8") - - -class SOAPRequest(object): - """Abstracts a SOAP Request to be sent to the server""" - - def __init__(self, method, namespace=None, encoding_style=Encoding.SOAP, **attr): - """Initializer - - @param method: the method to be called - @type method: string - - @param namespace: the namespace that the method belongs to - @type namespace: URI - - @param encoding_style: the encoding style for this method - @type encoding: URI - - @param **attr: attributes to be attached to the method""" - self.header = ElementTree.Element(_SOAPSection.HEADER) - if namespace is not None: - method = "{" + namespace + "}" + method - self.method = ElementTree.Element(method) - if encoding_style is not None: - self.method.set("{" + NameSpace.SOAP_ENVELOPE + "}encodingStyle", encoding_style) - - for attr_key, attr_value in attr.iteritems(): - self.method.set(attr_key, attr_value) - - def add_argument(self, name, namespace=None, type=None, attrib=None, value=None, **kwargs): - if namespace is not None: - name = "{" + namespace + "}" + name - return self._add_element(self.method, name, type, attrib, value, **kwargs) - - def add_header(self, name, namespace=None, attrib=None, value=None, **kwargs): - if namespace is not None: - name = "{" + namespace + "}" + name - return self._add_element(self.header, name, None, attrib, value, **kwargs) - - def _add_element(self, parent, name, type=None, attributes=None, value=None, **kwargs): - elem = ElementTree.SubElement(parent, name) - if attributes is None: - attributes = {} - attributes.update(kwargs) - if type: - type = self._qname(type, NameSpace.XML_SCHEMA) - elem.set("{" + NameSpace.XML_SCHEMA_INSTANCE + "}type", type) - for attr_key, attr_value in attributes.iteritems(): - elem.set(attr_key, attr_value) - elem.text = value - return _SOAPElement(elem) - - def _qname(self, name, default_ns): - if name[0] != "{": - return ElementTree.QName(default_ns, name) - return ElementTree.QName(name) - - def __str__(self): - envelope = ElementTree.Element(_SOAPSection.ENVELOPE) - if len(self.header) > 0: - envelope.append(self.header) - body = ElementTree.SubElement(envelope, _SOAPSection.BODY) - body.append(self.method) - return "" +\ - ElementTree.tostring(envelope, "utf-8") - - def __repr__(self): - return "" % self.method.tag - - -class SOAPResponse(object): - def __init__(self, data): - self.tree = self._parse(data) - self.header = self.tree.find(_SOAPSection.HEADER) - self.body = self.tree.find(_SOAPSection.BODY) - - def find(self, path): - return self.tree.find(path) - - def _parse(self, data): - events = ("start", "end", "start-ns", "end-ns") - ns = [] - data = StringIO.StringIO(data) - context = ElementTree.iterparse(data, events=events) - for event, elem in context: - if event == "start-ns": - ns.append(elem) - elif event == "end-ns": - ns.pop() - elif event == "start": - elem.set("(xmlns)", tuple(ns)) - data.close() - return context.root - diff --git a/pymsn/pymsn/gnet/message/__init__.py b/pymsn/pymsn/gnet/message/__init__.py deleted file mode 100644 index dd8ad840..00000000 --- a/pymsn/pymsn/gnet/message/__init__.py +++ /dev/null @@ -1,21 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2006 Ali Sabil -# -# 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 -# - -"""GNet messages types, those types are an abstract representation -of a known message type, like the HTTP messages or the SOAP messages""" diff --git a/pymsn/pymsn/gnet/parser.py b/pymsn/pymsn/gnet/parser.py deleted file mode 100644 index b59a03bb..00000000 --- a/pymsn/pymsn/gnet/parser.py +++ /dev/null @@ -1,190 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2005 Ole André Vadla Ravnås -# Copyright (C) 2006 Ali Sabil -# -# 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 -# - -"""Incomming data parsers.""" - -from constants import * -from message.HTTP import HTTPResponse - -import gobject - -__all__ = ['AbstractParser', 'DelimiterParser'] - -class AbstractParser(gobject.GObject): - """Base class for all stateful parsers. - - @since: 0.1""" - __gsignals__ = { - "received": (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - (object,)) - } - def __init__(self, transport, connect_signals=True): - """Initializer - - @param transport: the transport used to receive data - @type transport: an object derived from - L{io.AbstractClient}""" - gobject.GObject.__init__(self) - if connect_signals: - transport.connect("received", self._on_received) - transport.connect("notify::status", self._on_status_change) - self._transport = transport - self._reset_state() - - def _reset_state(self): - """Needs to be overriden in order to implement the default - parser state.""" - raise NotImplementedError - - def _on_status_change(self, transport, param): - status = transport.get_property("status") - if status == IoStatus.OPEN: - self._reset_state() - - def _on_received(self, transport, buf, length): - raise NotImplementedError - - -class DelimiterParser(AbstractParser): - """Receiver class that emit received signal when a chunk of data is - received. - - A chunk is defined by a delimiter which is either a string or an integer. - - @since: 0.1""" - - def __init__(self, transport): - """Initializer - - @param transport: the transport used to receive data - @type transport: L{io.AbstractClient}""" - AbstractParser.__init__(self, transport) - self._chunk_delimiter = "\n" - - def _reset_state(self): - self._recv_cache = "" - - def _on_received(self, transport, buf, length): - self._recv_cache += buf - self._process_recv_cache() - - def _process_recv_cache(self): - if len(self._recv_cache) == 0: - return - if self._chunk_delimiter is None or self._chunk_delimiter == "": - self.emit("received", self._recv_cache) - self._recv_cache = "" - return - - previous_length = len(self._recv_cache) - while len(self._recv_cache) != 0: - if isinstance(self._chunk_delimiter, int): - available = len(self._recv_cache) - required = self._chunk_delimiter - if required <= available: - self.emit ("received", self._recv_cache[:required]) - self._recv_cache = self._recv_cache[required:] - else: - s = self._recv_cache.split(self._chunk_delimiter, 1) - if len(s) > 1: - self.emit("received", s[0]) - self._recv_cache = s[1] - else: - self._recv_cache = s[0] - if len(self._recv_cache) == previous_length: # noting got consumed, exit - return - previous_length = len(self._recv_cache) - - def _set_chunk_delimiter(self, delimiter): - self._chunk_delimiter = delimiter - def _get_chunk_delimiter(self): - return self._chunk_delimiter - delimiter = property(_get_chunk_delimiter, - _set_chunk_delimiter, - doc="""The chunk delimiter, can be either a string or - an integer that specify the number of bytes for each chunk""") -gobject.type_register(DelimiterParser) - - -class HTTPParser(AbstractParser): - """Receiver class that emit received signal when an HTTP response is - received. - - @since: 0.1""" - - CHUNK_START_LINE = 0 - CHUNK_HEADERS = 1 - CHUNK_BODY = 2 - - def __init__(self, transport): - self._parser = DelimiterParser(transport) - self._parser.connect("received", self._on_chunk_received) - transport.connect("notify::status", self._on_status_change) - AbstractParser.__init__(self, transport, connect_signals=False) - - def _reset_state(self): - self._next_chunk = self.CHUNK_START_LINE - self._receive_buffer = "" - self._content_length = None - self._parser.delimiter = "\r\n" - - def _on_status_change(self, transport, param): - status = transport.get_property("status") - if status == IoStatus.OPEN: - self._reset_state() - elif status == IoStatus.CLOSING: - self._receive_buffer += self._parser._recv_cache - self.__emit_result() - - def _on_chunk_received(self, parser, chunk): - complete = False - if self._next_chunk == self.CHUNK_START_LINE: - self._receive_buffer += chunk + "\r\n" - self._next_chunk = self.CHUNK_HEADERS - elif self._next_chunk == self.CHUNK_HEADERS: - self._receive_buffer += chunk + "\r\n" - if chunk == "": - if self._content_length == 0: - complete = True - else: - self._parser.delimiter = self._content_length or 0 - self._next_chunk = self.CHUNK_BODY - else: - header, value = chunk.split(":", 1) - header, value = header.strip(), value.strip() - if header == "Content-Length": - self._content_length = int(value) - elif self._next_chunk == self.CHUNK_BODY: - self._receive_buffer += chunk - if self._content_length is not None: - complete = True - - if complete: - self.__emit_result() - - def __emit_result(self): - if self._receive_buffer == "": - return - response = HTTPResponse() - response.parse(self._receive_buffer) - self.emit("received", response) - self._reset_state() - diff --git a/pymsn/pymsn/gnet/protocol/HTTP.py b/pymsn/pymsn/gnet/protocol/HTTP.py deleted file mode 100644 index d857f9b7..00000000 --- a/pymsn/pymsn/gnet/protocol/HTTP.py +++ /dev/null @@ -1,154 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2006 Ali Sabil -# -# 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 -# - -from pymsn.gnet.constants import * -from pymsn.gnet.proxy import ProxyInfos -from pymsn.gnet.message.HTTP import HTTPRequest -from pymsn.gnet.io import TCPClient -from pymsn.gnet.parser import HTTPParser - -import gobject -import base64 -import platform - -__all__ = ['HTTP'] - - -class HTTP(gobject.GObject): - """HTTP protocol client class.""" - - __gsignals__ = { - "error" : (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - (gobject.TYPE_ULONG,)), - - "response-received": (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - (object,)), # HTTPResponse - - "request-sent": (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - (object,)), # HTTPRequest - } - - def __init__(self, host, port=80, proxy=None): - """Connection initialization - - @param host: the host to connect to. - @type host: string - - @param port: the port number to connect to - @type port: integer - - @param proxy: proxy that we can use to connect - @type proxy: L{gnet.proxy.ProxyInfos}""" - gobject.GObject.__init__(self) - assert(proxy is None or proxy.type == 'http') # TODO: add support for other proxies (socks4 and 5) - self._host = host - self._port = port - self.__proxy = proxy - self._transport = None - self._http_parser = None - self._outgoing_queue = [] - self._waiting_response = False - - def _setup_transport(self): - if self._transport is None: - if self.__proxy is not None: - self._transport = TCPClient(self.__proxy.host, self.__proxy.port) - else: - self._transport = TCPClient(self._host, self._port) - self._http_parser = HTTPParser(self._transport) - self._http_parser.connect("received", self._on_response_received) - self._transport.connect("notify::status", self._on_status_change) - self._transport.connect("error", self._on_error) - self._transport.connect("sent", self._on_request_sent) - - if self._transport.get_property("status") != IoStatus.OPEN: - self._transport.open() - - def _on_status_change(self, transport, param): - if transport.get_property("status") == IoStatus.OPEN: - self._process_queue() - elif transport.get_property("status") == IoStatus.CLOSED and\ - (self._waiting_response or len(self._outgoing_queue) > 0): - self._waiting_response = False - self._setup_transport() - - def _on_request_sent(self, transport, request, length): - assert(str(self._outgoing_queue[0]) == request) - self._waiting_response = True - self.emit("request-sent", self._outgoing_queue[0]) - - def _on_response_received(self, parser, response): - if response.status >= 100 and response.status < 200: - return - #if response.status in (301, 302): # UNTESTED: please test - # location = response.headers['Location'] - - # location = location.rsplit("://", 1) - # if len(location) == 2: - # scheme = location[0] - # location = location[1] - # if scheme == "http": - # location = location.rsplit(":", 1) - # self._host = location[0] - # if len(location) == 2: - # self._port = int(location[1]) - # self._outgoing_queue[0].headers['Host'] = response.headers['Location'] - # self._setup_transport() - # return - self._outgoing_queue.pop(0) # pop the request from the queue - self.emit("response-received", response) - self._waiting_response = False - self._process_queue() # next request ? - - def _on_error(self, transport, error): - self.emit("error", error) - - def _process_queue(self): - if len(self._outgoing_queue) == 0 or \ - self._waiting_response: # no pipelining - return - if self._transport is None or \ - self._transport.get_property("status") != IoStatus.OPEN: - self._setup_transport() - return - self._transport.send(str(self._outgoing_queue[0])) - - def request(self, resource='/', headers=None, data='', method='GET'): - if headers is None: - headers = {} - headers['Host'] = self._host + ':' + str(self._port) - headers['Content-Length'] = str(len(data)) - if 'User-Agent' not in headers: - user_agent = GNet.NAME, GNet.VERSION, platform.system(), platform.machine() - headers['User-Agent'] = "%s/%s (%s %s)" % user_agent - - if self.__proxy is not None: - url = 'http://%s:%d%s' % (self._host, self._port, resource) - if self.__proxy.user: - auth = self.__proxy.user + ':' + self.__proxy.password - credentials = base64.encodestring(auth).strip() - headers['Proxy-Authorization'] = 'Basic ' + credentials - else: - url = resource - request = HTTPRequest(headers, data, method, url) - self._outgoing_queue.append(request) - self._process_queue() diff --git a/pymsn/pymsn/gnet/protocol/HTTPS.py b/pymsn/pymsn/gnet/protocol/HTTPS.py deleted file mode 100644 index 2dff86bf..00000000 --- a/pymsn/pymsn/gnet/protocol/HTTPS.py +++ /dev/null @@ -1,60 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2006 Ali Sabil -# -# 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 -# - -from pymsn.gnet.constants import * -from pymsn.gnet.io import SSLTCPClient -from pymsn.gnet.proxy.HTTPConnect import HTTPConnectProxy -from pymsn.gnet.parser import HTTPParser -from HTTP import HTTP - -__all__ = ['HTTPS'] - -class HTTPS(HTTP): - """HTTP protocol client class.""" - def __init__(self, host, port=443, proxy=None): - """Connection initialization - - @param host: the host to connect to. - @type host: string - - @param port: the port number to connect to - @type port: integer - - @param proxy: proxy that we can use to connect - @type proxy: L{gnet.proxy.ProxyInfos}""" - HTTP.__init__(self, host, port) - assert(proxy is None or proxy.type == 'https') - self.__proxy = proxy - - def _setup_transport(self): - if self._transport is None: - transport = SSLTCPClient(self._host, self._port) - if self.__proxy is not None: - print 'Using proxy : ', repr(self.__proxy) - self._transport = HTTPConnectProxy(transport, self.__proxy) - else: - self._transport = transport - self._http_parser = HTTPParser(self._transport) - self._http_parser.connect("received", self._on_response_received) - self._transport.connect("notify::status", self._on_status_change) - self._transport.connect("error", self._on_error) - self._transport.connect("sent", self._on_request_sent) - - if self._transport.get_property("status") != IoStatus.OPEN: - self._transport.open() diff --git a/pymsn/pymsn/gnet/protocol/__init__.py b/pymsn/pymsn/gnet/protocol/__init__.py deleted file mode 100644 index 6db2cfcd..00000000 --- a/pymsn/pymsn/gnet/protocol/__init__.py +++ /dev/null @@ -1,35 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2006 Ali Sabil -# -# 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 -# - -"""GNect protocol support""" - -from HTTP import * -from HTTPS import * - -def ProtocolFactory(protocol, host, port=None, proxy=None): - if protocol == "http": - klass = HTTP - elif protocol == "https": - klass = HTTPS - - if port is None: - return klass(host, proxy=proxy) - else: - return klass(host, port, proxy=proxy) - diff --git a/pymsn/pymsn/gnet/proxy/HTTPConnect.py b/pymsn/pymsn/gnet/proxy/HTTPConnect.py deleted file mode 100644 index d5af8f95..00000000 --- a/pymsn/pymsn/gnet/proxy/HTTPConnect.py +++ /dev/null @@ -1,110 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2006 Ali Sabil -# -# 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 -# - -from abstract import AbstractProxy -from pymsn.gnet.io import TCPClient -from pymsn.gnet.constants import * -from pymsn.gnet.parser import HTTPParser - -import gobject -import base64 - -__all__ = ['HTTPConnectProxy'] - -class HTTPConnectProxy(AbstractProxy): - def __init__(self, client, proxy_infos): - assert(proxy_infos.type in ('http', 'https')), "HTTPConnectProxy expects an http(s) proxy description" - assert(client.domain == AF_INET), "HTTP CONNECT only handles INET address family" - assert(client.type == SOCK_STREAM), "HTTP CONNECT only handles SOCK_STREAM" - assert(client.status == IoStatus.CLOSED), "HTTPConnectProxy expects a closed client" - AbstractProxy.__init__(self, client, proxy_infos) - - self._transport = TCPClient(self._proxy.host, self._proxy.port) - self._transport.connect("notify::status", self._on_transport_status) - self._transport.connect("error", self._on_transport_error) - self._http_parser = HTTPParser(self._transport) - self._http_parser.connect("received", self._on_proxy_response) - - # opening state methods - def _pre_open(self, io_object=None): - AbstractProxy._pre_open(self) - - def _post_open(self): - AbstractProxy._post_open(self) - host = self._client.get_property("host") - port = self._client.get_property("port") - proxy_protocol = 'CONNECT %s:%s HTTP/1.1\r\n' % (host, port) - proxy_protocol += 'Proxy-Connection: Keep-Alive\r\n' - proxy_protocol += 'Pragma: no-cache\r\n' - proxy_protocol += 'User-Agent: %s/%s\r\n' % (GNet.NAME, GNet.VERSION) - if self._proxy.user: - auth = base64.encodestring(self._proxy.user + ':' + self._proxy.password).strip() - proxy_protocol += 'Proxy-authorization: Basic ' + auth + '\r\n' - proxy_protocol += '\r\n' - self._transport.send(proxy_protocol) - - # public API - def open(self): - """Open the connection.""" - if not self._configure(): - return - self._pre_open() - try: - self._transport.open() - except: - pass - - def close(self): - """Close the connection.""" - self._client._proxy_closed() - - def send(self, buffer, callback=None, *args): - self._client.send(buffer, callback, *args) - - # callbacks and signal handlers - def _on_transport_status(self, transport, param): - if transport.status == IoStatus.OPEN: - self._post_open() - elif transport.status == IoStatus.OPENING: - self._client._proxy_opening(self._transport._transport) - self._status = transport.status - else: - self._status = transport.status - - def _on_transport_error(self, transport, error_code): - if error_code == IoError.CONNECTION_FAILED: - error_code = IoError.PROXY_CONNECTION_FAILED - self.close() - self.emit("error", error_code) - - def _on_proxy_response(self, parser, response): - if self.status == IoStatus.OPENING: - if response.status == 200: - del self._http_parser - self._transport._watch_remove() # HACK: ok this is ugly ! - self._client._proxy_open() - elif response.status == 100: - return True - elif response.status == 407: - self.close() - self.emit("error", IoError.PROXY_AUTHENTICATION_REQUIRED) - else: - raise NotImplementedError("Unknown Proxy response code") - return False -gobject.type_register(HTTPConnectProxy) diff --git a/pymsn/pymsn/gnet/proxy/SOCKS4.py b/pymsn/pymsn/gnet/proxy/SOCKS4.py deleted file mode 100644 index ffdf7d42..00000000 --- a/pymsn/pymsn/gnet/proxy/SOCKS4.py +++ /dev/null @@ -1,132 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# - -from abstract import AbstractProxy -from pymsn.gnet.io import TCPClient -from pymsn.gnet.constants import * -from pymsn.gnet.parser import DelimiterParser - -import gobject -import struct - -__all__ = ['SOCKS4Proxy'] - -class SOCKS4Proxy(AbstractProxy): - - PROTOCOL_VERSION = 4 - CONNECT_COMMAND = 1 - - """Proxy class used to communicate with SOCKS4 proxies.""" - def __init__(self, client, proxy_infos): - assert(proxy_infos.type == 'socks4'), \ - "SOCKS4Proxy expects a socks4 proxy description" - # TODO : implement version 4a of the protocol to allow proxy-side name resolution - assert(client.domain == AF_INET), \ - "SOCKS4 CONNECT only handles INET address family" - assert(client.type == SOCK_STREAM), \ - "SOCKS4 CONNECT only handles SOCK_STREAM" - assert(client.status == IoStatus.CLOSED), \ - "SOCKS4Proxy expects a closed client" - AbstractProxy.__init__(self, client, proxy_infos) - - self._transport = TCPClient(self._proxy.host, self._proxy.port) - self._transport.connect("notify::status", self._on_transport_status) - self._transport.connect("error", self._on_transport_error) - - self._delimiter_parser = DelimiterParser(self._transport) - self._delimiter_parser.delimiter = 8 - self._delimiter_parser.connect("received", self._on_proxy_response) - - # Opening state methods - def _pre_open(self, io_object=None): - AbstractProxy._pre_open(self) - - def _post_open(self): - AbstractProxy._post_open(self) - host = self._client.get_property("host") - port = self._client.get_property("port") - user = self._proxy.user - - proxy_protocol = struct.pack('!BBH', SOCKS4Proxy.PROTOCOL_VERSION, - SOCKS4Proxy.CONNECT_COMMAND, port) - - for part in host.split('.'): - proxy_protocol += struct.pack('B', int(part)) - - proxy_protocol += user - proxy_protocol += struct.pack('B', 0) - - self._transport.send(proxy_protocol) - - # Public API - def open(self): - """Open the connection.""" - if not self._configure(): - return - self._pre_open() - try: - self._transport.open() - except: - pass - - def close(self): - """Close the connection.""" - self._client._proxy_closed() - - def send(self, buffer, callback=None, *args): - self._client.send(buffer, callback, *args) - - # Callbacks - def _on_transport_status(self, transport, param): - if transport.status == IoStatus.OPEN: - self._post_open() - elif transport.status == IoStatus.OPENING: - self._client._proxy_opening(self._transport._transport) - self._status = transport.status - else: - self._status = transport.status - - def _on_transport_error(self, transport, error_code): - if error_code == IoError.CONNECTION_FAILED: - error_code = IoError.PROXY_CONNECTION_FAILED - self.close() - self.emit("error", error_code) - - def _on_proxy_response(self, parser, response): - version, response_code = struct.unpack('BB', response[0:2]) - assert(version == 0) - if self.status == IoStatus.OPENING: - if response_code == 90: - del self._delimiter_parser - self._transport._watch_remove() # HACK: ok this is ugly ! - self._client._proxy_open() - elif response_code == 91: - self.close() - self.emit("error", IoError.PROXY_CONNECTION_FAILED) - elif response_code == 92: - self.close() - self.emit("error", IoError.PROXY_AUTHENTICATION_REQUIRED) - elif response_code == 93: - self.close() - self.emit("error", IoError.PROXY_AUTHENTICATION_REQUIRED) - else: - raise NotImplementedError("Unknow Proxy response code") - return False - -gobject.type_register(SOCKS4Proxy) diff --git a/pymsn/pymsn/gnet/proxy/__init__.py b/pymsn/pymsn/gnet/proxy/__init__.py deleted file mode 100644 index 11488c54..00000000 --- a/pymsn/pymsn/gnet/proxy/__init__.py +++ /dev/null @@ -1,23 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2006 Ali Sabil -# -# 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 -# - -"""GNet proxy support, provides proxy classes to be used with proxifiable -IO clients""" - -from proxy_infos import * diff --git a/pymsn/pymsn/gnet/proxy/abstract.py b/pymsn/pymsn/gnet/proxy/abstract.py deleted file mode 100644 index 9a1489dc..00000000 --- a/pymsn/pymsn/gnet/proxy/abstract.py +++ /dev/null @@ -1,48 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2006 Ali Sabil -# -# 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 -# - -from pymsn.gnet.io import AbstractClient -from pymsn.gnet.constants import IoStatus - -import gobject - -__all__ = ['AbstractProxy'] - -class AbstractProxy(AbstractClient): - def __init__(self, client, proxy_infos): - self._client = client - self._proxy = proxy_infos - self._client.connect("sent", self._on_client_sent) - self._client.connect("received", self._on_client_received) - self._client.connect("notify::status", self._on_client_status) - AbstractClient.__init__(self, self._proxy.host, self._proxy.port) - - def _on_client_status(self, client, param): - status = client.get_property("status") - if status == IoStatus.OPEN: - self._status = IoStatus.OPEN - elif status == IoStatus.CLOSED: - self._status = IoStatus.CLOSED - - def _on_client_sent(self, client, data, length): - self.emit("sent", data, length) - - def _on_client_received(self, client, data, length): - self.emit("received", data, length) -gobject.type_register(AbstractProxy) diff --git a/pymsn/pymsn/gnet/proxy/proxy_infos.py b/pymsn/pymsn/gnet/proxy/proxy_infos.py deleted file mode 100644 index 48a1a3d1..00000000 --- a/pymsn/pymsn/gnet/proxy/proxy_infos.py +++ /dev/null @@ -1,120 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2006 Ali Sabil -# -# 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 -# - -import base64 -import urlparse - -__all__ = ['ProxyInfos', 'ProxyFactory'] - -class ProxyInfos(object): - """Contain informations needed to make use of a proxy. - - @ivar host: hostname of the proxy server. - @ivar port: port used to connect to server. - @ivar type: proxy type - @ivar user: username to use for authentication. - @ivar password: password to use for authentication. - @undocumented __get_*, __set_* - - @since: 0.1""" - - def __init__(self, host='', port=0, type='http', user=None, password=None): - """Initializer - - @param host: the hostname of the proxy server. - @type host: string - - @param port: the port used to connect to server. - @type port: integer >= 0 and < 65536 - - @param type: proxy type - @type type: string in ('http', 'https', 'socks4', 'socks5') - - @param user: the username to use for authentication. - @type user: string - - @param password: the password to use for authentication. - @type password: string""" - self.host = host - self.port = port - self.type = type - self.user = user - self.password = password - - @staticmethod - def from_string(url, default_type='http'): - """Builds a new L{ProxyInfos} instance from a given proxy url string - @param url: the proxy url string - @type url: string - - @param default_type: the default proxy type - @type default_type: string in ('http', 'https', 'socks4', 'socks5') - - @return L{ProxyInfos} instance filled with the infos given in the - url""" - # scheme://netloc/path;parameters?query#fragment - # (scheme, netloc, path;parameters, query, fragment) - url = urlparse.urlsplit(url, default_type) - proxy_type = url[0] - location = url[1] - location = location.rsplit('@',1) - if len(location) == 1: - auth = ('','') - host = location[0] - else: - auth = location[0].split(':',1) - host = location[1] - host = host.split(':',1) - if len(host) == 1: - port = 8080 - else: - port = int(host[1]) - host = host[0] - return ProxyInfos(host, port, proxy_type, auth[0], auth[1]) - - def __get_port(self): - return self._port - def __set_port(self, port): - self._port = int(port) - assert(self._port >= 0 and self._port <= 65535) - port = property(__get_port, __set_port, doc="Port used to connect to server.") - - def __get_type(self): - return self._type - def __set_type(self, type): - assert(type in ('http', 'https', 'socks4', 'socks5')) - self._type = type - type = property(__get_type, __set_type, doc="Proxy type.") - - def __str__(self): - host = '%s:%u' % (self.host, self.port) - if self.user: - auth = '%s:%s' % (self.user, self.password) - host = auth + '@' + host - return self.type + '://' + host + '/' - - def __repr__(self): - host = '%s:%u' % (self.host, self.port) - if self.user: - auth = '%s:%s' % (self.user, "*" * len(self.password)) - host = auth + '@' + host - return self.type + '://' + host + '/' - -ProxyFactory = ProxyInfos.from_string - diff --git a/pymsn/pymsn/gnet/proxy/proxyfiable.py b/pymsn/pymsn/gnet/proxy/proxyfiable.py deleted file mode 100644 index ee851304..00000000 --- a/pymsn/pymsn/gnet/proxy/proxyfiable.py +++ /dev/null @@ -1,37 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2006 Ali Sabil -# -# 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 -# - -__all__ = ['ProxyfiableClient'] - -class ProxyfiableClient(object): - """All proxifiable clients must inherit from this class - to enable the Proxy object to manipulate them""" - - def __init__(self): - pass - - def _proxy_opening(self, sock): - if not self._configure(): return - self._pre_open(sock) - - def _proxy_open(self): - self._post_open() - - def _proxy_closed(self): - self.close() diff --git a/pymsn/pymsn/gnet/resolver.py b/pymsn/pymsn/gnet/resolver.py deleted file mode 100644 index 49e72a70..00000000 --- a/pymsn/pymsn/gnet/resolver.py +++ /dev/null @@ -1,98 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2006 Ali Sabil -# -# 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 -# - -"""GNet dns resolver""" - -import socket - -import gobject - -from pymsn.util.decorator import async - -__all__ = ['HostnameResolver'] - -class HostnameResponse(object): - def __init__(self, response): - self._response = response - - @property - def status(self): - return self._response[0] - - @property - def cname(self): - return self._response[1] - - @property - def expires(self): - return self._response[2] - - @property - def answer(self): - return self._response[3] - - def __repr__(self): - return repr(self._response) - -class HostnameResolver(object): - def __init__(self): - self._queries = {} - - def query(self, host, callback): - result = socket.getaddrinfo(host, None, socket.AF_INET, socket.SOCK_STREAM) - if len(result) == 0: - status = 1 - cname = '' - expires = 0 - addresses = () - else: - status = 0 - cname = result[0][3] - expires = 0 - addresses = ((socket.AF_INET, result[0][4][0]),) - self._emit_response(callback, (status, cname, expires, addresses)) - - @async - def _emit_response(self, callback, response): - callback[0](HostnameResponse(response), *callback[1:]) - return False - - -if __name__ == "__main__": - mainloop = gobject.MainLoop(is_running=True) - def print_throbber(): - print "*" - return True - - def hostname_resolved(result): - print result - mainloop.quit() - - def resolve_hostname(resolver, host): - print "Resolving" - resolver.query(host, (hostname_resolved,)) - return False - - resolver = HostnameResolver() - - gobject.timeout_add(10, print_throbber) - gobject.timeout_add(100, resolve_hostname, resolver, 'www.google.com') - #gobject.timeout_add(100, resolve_hostname, resolver, '209.85.129.104') - - mainloop.run() diff --git a/pymsn/pymsn/msnp/__init__.py b/pymsn/pymsn/msnp/__init__.py deleted file mode 100644 index c9b8ca13..00000000 --- a/pymsn/pymsn/msnp/__init__.py +++ /dev/null @@ -1,29 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2006 Ali Sabil -# -# 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 -# - -"""MSN Core protocol client implementation. -Contains a set of class abstracting the MSNP protocol used to communicate -with the Notification Server as well as the Switchboard Server""" - -from command import * -from message import * -from constants import * -from notification import * -from switchboard import * -from base import ProtocolState diff --git a/pymsn/pymsn/msnp/base.py b/pymsn/pymsn/msnp/base.py deleted file mode 100644 index b43fd9fd..00000000 --- a/pymsn/pymsn/msnp/base.py +++ /dev/null @@ -1,114 +0,0 @@ -# -*- coding: utf-8 -*- -# -# pymsn - a python client library for Msn -# -# Copyright (C) 2005-2006 Ali Sabil -# Copyright (C) 2005-2006 Ole André Vadla Ravnås -# -# 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 - -"""Base classes used by the specific classes of the Core Protocol""" - -import logging - -__all__ = ['BaseProtocol'] - -logger = logging.getLogger('protocol') - -class ProtocolState(object): - CLOSED = 0 - OPENING = 1 - AUTHENTICATING = 2 - AUTHENTICATED = 3 - SYNCHRONIZING = 4 - SYNCHRONIZED = 5 - OPEN = 6 - - -class BaseProtocol(object): - """Base class used to implement the Notification protocol as well - as the Switchboard protocol - @group Handlers: _handle_*, _default_handler, _error_handler - - @ivar _client: the parent instance of L{client.Client} - @type _client: L{client.Client} - - @ivar _transport: the transport instance - @type _transport: L{transport.BaseTransport} - - @ivar _proxies: a dictonary mapping the proxy type to a - L{gnet.proxy.ProxyInfos} instance - """ - - def __init__(self, client, transport, proxies={}): - """Initializer - - @param client: the parent instance of L{client.Client} - @type client: L{client.Client} - - @param transport: The transport to use to speak the protocol - @type transport: L{transport.BaseTransport} - - @param proxies: a dictonary mapping the proxy type to a - L{gnet.proxy.ProxyInfos} instance - @type proxies: {type: string, proxy:L{gnet.proxy.ProxyInfos}} - """ - transport.connect("command-received", self._dispatch_command) - transport.connect("connection-success", self._connect_cb) - transport.connect("connection-failure", self._disconnect_cb) - transport.connect("connection-lost", self._disconnect_cb) - - self._client = client - self._transport = transport - self._proxies = proxies - - 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) - - # default handlers - def _default_handler(self, command): - """ - Default handler used when no handler is defined - - @param command: the received command - @type command: L{command.Command} - """ - logger.warning('Notification unhandled command :' + repr(command)) - - def _error_handler(self, error): - """Handles errors - - @param error: an error command object - @type error: L{command.Command} - """ - logger.error('Notification got error :' + repr(error)) - - # callbacks - def _dispatch_command(self, connection, command): - if not command.is_error(): - handler = getattr(self, - '_handle_' + command.name, - self._default_handler) - handler(command) - else: - self._error_handler(command) - - def _connect_cb(self, transport): - pass - - def _disconnect_cb(self, transport, reason): - pass diff --git a/pymsn/pymsn/msnp/challenge.py b/pymsn/pymsn/msnp/challenge.py deleted file mode 100644 index 30a81ef4..00000000 --- a/pymsn/pymsn/msnp/challenge.py +++ /dev/null @@ -1,78 +0,0 @@ -# -*- coding: utf-8 -*- -# -# pymsn - a python client library for Msn -# -# Copyright (C) 2005-2007 Ali Sabil -# Copyright (C) 2005-2006 Ole André Vadla Ravnås -# Copyright (C) 2007 Johann Prieur -# -# 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 - -from constants import ProtocolConstant - -def _msn_challenge(data): - """ - Compute an answer for MSN Challenge from a given data - - @param data: the challenge string sent by the server - @type data: string - """ - import struct - import md5 - def little_endify(value, c_type="L"): - """Transform the given value into little endian""" - return struct.unpack(">" + c_type, struct.pack("<" + c_type, value))[0] - - md5_digest = md5.md5(data + ProtocolConstant.PRODUCT_KEY).digest() - # Make array of md5 string ints - md5_integers = struct.unpack("QQ", md5_digest)] - longs = [little_endify(x, "Q") for x in longs] - longs = [x ^ key for x in longs] - longs = [little_endify(abs(x), "Q") for x in longs] - out = "" - for value in longs: - value = hex(long(value)) - value = value[2:-1] - value = value.zfill(16) - out += value.lower() - return out - diff --git a/pymsn/pymsn/msnp/command.py b/pymsn/pymsn/msnp/command.py deleted file mode 100644 index 4f0b83e7..00000000 --- a/pymsn/pymsn/msnp/command.py +++ /dev/null @@ -1,221 +0,0 @@ -# -*- coding: utf-8 -*- -# -# pymsn - a python client library for Msn -# -# Copyright (C) 2005-2006 Ali Sabil -# -# 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 - -"""MSN protocol commands.""" - -from urllib import quote, unquote - -from pymsn.msnp.message import Message - -__all__ = ['Command'] - -class CommandPrinter(object): - def __init__(self, command): - self.command = command - - def __repr__(self): - printer = getattr(self, "_print_" + self.command.name, - self._print_default) - return printer() - - def _print_MSG(self): - command = self.command - - result = command.name - if command.transaction_id is not None: - result += ' ' + str(command.transaction_id) - - if command.arguments is not None and len(command.arguments) > 0: - result += ' ' + ' '.join(command.arguments) - - if command.payload is not None: - result += "\n" + repr(Message(None, str(command.payload))) - return result - - def _print_QRY(self): - command = self.command - - result = command.name - if command.transaction_id is not None: - result += ' ' + str(command.transaction_id) - - if command.arguments is not None and len(command.arguments) > 0: - arguments = [str(argument) for argument in command.arguments] - result += ' ' + ' '.join(arguments) - - if command.payload is not None: - payload = repr(command.payload) - length = len(payload) - if length > 0: - result += ' ' + str(length) + '\r\n' - result += payload - return result - - def _print_default(self): - command = self.command - - result = command.name - if command.transaction_id is not None: - result += ' ' + str(command.transaction_id) - - if command.arguments is not None and len(command.arguments) > 0: - arguments = [str(argument) for argument in command.arguments] - result += ' ' + ' '.join(arguments) - - if command.payload is not None: - payload = repr(command.payload) - length = len(payload) - if length > 0: - result += ' ' + str(length) + '\r\n' - if not command.is_error(): - result += '\t[payload]' - else: - result += payload - return result - - -class Command(object): - """Abstraction of MSN commands, this class enables parsing and construction - of commands. - - @ivar name: the 3 uppercase letters name of the command - @type name: string - - @ivar transaction_id: the transaction id of the command or None - @type transaction_id: integer - - @ivar arguments: the arguments of the command - @type arguments: tuple() - - @ivar payload: the payload of the command - @type payload: string or None""" - - OUTGOING_NO_TRID = ('OUT', 'PNG') - INCOMING_NO_TRID = ( - # NS commands - 'QNG', 'IPG', 'NOT', 'NLN', 'FLN', 'GCF', - 'QRY', 'SBS', 'UBN', 'UBM', 'UBX', - # SW commands - 'RNG', 'JOI', 'BYE', 'MSG') - - OUTGOING_PAYLOAD = ( - 'QRY', 'SDC', 'PGD', 'ADL', 'RML', 'UUN', - 'UUM', 'UUX', 'MSG', 'FQY') - - INCOMING_PAYLOAD = ( - 'GCF', 'MSG', 'UBN', 'UBM', 'UBX', 'IPG', - 'NOT', 'ADL', 'RML', 'FQY', - - '241', '509') - - def __init__(self): - self._reset() - - def _reset(self): - """Resets the object values""" - self.name = '' - self.transaction_id = None - self.arguments = None - self.payload = None - - ### public methods - def build(self, name, transaction_id, payload=None, *arguments): - """Updates the command with the given parameters - - @param name: the command name (3 letters) (e.g. MSG NLN ...) - @type name: string - - @param transaction_id: the transaction ID - @type transaction_id: integer - - @param *arguments: the command arguments - @type *arguments: string, ... - - @param payload: is the data to send with the command - @type payload: string - """ - self.name = name - self.transaction_id = transaction_id - self.arguments = arguments - self.payload = payload - - def parse(self, buf): - """Fills the Command object according parsing a string. - - @param buf: the data to parse - @type buf: string""" - self._reset() - lines = buf.split('\r\n', 1) - self.__parse_command(lines[0]) - if len(lines) > 1: # payload - self.payload = lines[1] - # remove the last argument as it is the data length - self.arguments = self.arguments[:-1] - - def is_error(self): - """Tells if the current command is an error code - - @rtype: bool""" - try: - int(self.name) - except ValueError: - return False - else: - return True - - def is_payload(self): - """Tells if the current comment is a payload command - - @rtype: bool""" - return self.payload is not None - - ### private and special methods - def __str__(self): - result = self.name[:] - if self.transaction_id is not None: - result += ' ' + str(self.transaction_id) - - if self.arguments is not None and len(self.arguments) > 0: - arguments = [str(arg) for arg in self.arguments] - result += ' ' + ' '.join(arguments) - - if self.payload is not None: - payload = str(self.payload) - length = len(payload) - if length > 0: - result += ' ' + str(length) + '\r\n' + payload - return result - - return result + '\r\n' - - def __repr__(self): - return repr(CommandPrinter(self)) - #return pymsn.util.debug.raw_cmd_to_debug(self.__str__()) - - def __parse_command(self, buf): - words = buf.split() - self.name, pos = words[0], 1 - if (words[0] not in self.INCOMING_NO_TRID) and\ - (words[0] not in self.OUTGOING_NO_TRID) and\ - len(words) > pos: - self.transaction_id = int(words[pos]) - pos += 1 - if len(words) > pos: - self.arguments = words[pos:] diff --git a/pymsn/pymsn/msnp/constants.py b/pymsn/pymsn/msnp/constants.py deleted file mode 100644 index f0e21d30..00000000 --- a/pymsn/pymsn/msnp/constants.py +++ /dev/null @@ -1,27 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# - -__all__ = ["ProtocolConstant"] - -class ProtocolConstant(object): - VER = ('MSNP15', 'MSNP14', 'MSNP13', 'CVR0') - CVR = ('0x0409', 'winnt', '5.1', 'i386', 'MSNMSGR', '8.1.0178', 'msmsgs') - PRODUCT_ID = "PROD0114ES4Z%Q5W" - PRODUCT_KEY = "PK}_A_0N_K%O?A9S" - CHL_MAGIC_NUM = 0x0E79A9C1 diff --git a/pymsn/pymsn/msnp/message.py b/pymsn/pymsn/msnp/message.py deleted file mode 100644 index cacdca4f..00000000 --- a/pymsn/pymsn/msnp/message.py +++ /dev/null @@ -1,111 +0,0 @@ -# -*- coding: utf-8 -*- -# -# pymsn - a python client library for Msn -# -# Copyright (C) 2005-2006 Ali Sabil -# -# 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 - -"""MSN protocol special command : MSG""" - -from pymsn.gnet.message.HTTP import HTTPMessage -import pymsn.util.debug as debug - -from urllib import quote, unquote -import struct - -__all__ = ['MessageAcknowledgement', 'Message'] - - -class MessageAcknowledgement(object): - """Message Acknowledgement""" - FULL = 'A' - """Acknowledgement required for both delivery success and failure""" - MSNC = 'D' - """Direct connection, no acknowledgment required from the server""" - HALF = 'N' - """Acknowledgment on delivery failures""" - NONE = 'U' - """No Acknowledgment""" - -class Message(HTTPMessage): - """Base Messages class. - - @ivar sender: sender - @type sender: profile.Contact - - @ivar body: message body - @type body: string - - @ivar headers: message headers - @type headers: {header_name: string => header_value:string} - - @ivar content_type: the message content type - @type content_type: tuple(mime_type, encoding)""" - - def __init__(self, sender=None, message=""): - """Initializer - - @param body: The body of the message, it is put after the headers - @type body: string""" - HTTPMessage.__init__(self) - self.sender = sender - if message: - self.parse(message) - - def __repr__(self): - """Represents the payload of the message""" - message = '' - for header_name, header_value in self.headers.iteritems(): - message += '\t%s: %s\\r\\n\n' % (header_name, header_value) - message += '\t\\r\\n\n' - if self.headers['Content-Type'] != "application/x-msnmsgrp2p": - message += '\t' + debug.escape_string(self.body).\ - replace("\r\n", "\\r\\n\n\t") - else: - tlp_header = self.body[:48] - tlp_footer = self.body[-4:] - tlp_flags = struct.unpack(" 0: - message += "\n\t" + "[%d bytes of data]" % len(body) - message += "\n\t" + debug.hexify_string(tlp_footer) - - return message.rstrip("\n\t") - - def __get_content_type(self): - if 'Content-Type' in self.headers: - content_type = self.headers['Content-Type'].split(';', 1) - if len(content_type) == 1: - return (content_type[0].strip(), 'UTF-8') - mime_type = content_type[0].strip() - encoding = content_type[1].split('=', 1)[1].strip() - return (mime_type, encoding) - return ('text/plain', 'UTF-8') - - def __set_content_type(self, content_type): - if not isinstance(content_type, str): - content_type = '; charset='.join(content_type) - self.headers['Content-Type'] = content_type - - content_type = property(__get_content_type, __set_content_type, - doc="a tuple specifying the content type") - diff --git a/pymsn/pymsn/msnp/notification.py b/pymsn/pymsn/msnp/notification.py deleted file mode 100644 index 615e92bf..00000000 --- a/pymsn/pymsn/msnp/notification.py +++ /dev/null @@ -1,627 +0,0 @@ -# -*- coding: utf-8 -*- -# -# pymsn - a python client library for Msn -# -# Copyright (C) 2005-2007 Ali Sabil -# Copyright (C) 2005-2006 Ole André Vadla Ravnås -# Copyright (C) 2007 Johann Prieur -# -# 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 - -"""Notification protocol Implementation -Implements the protocol used to communicate with the Notification Server.""" - -from base import BaseProtocol, ProtocolState -from message import Message -from constants import ProtocolConstant -from challenge import _msn_challenge - -import pymsn -from pymsn.gnet.message.HTTP import HTTPMessage -from pymsn.util.queue import PriorityQueue, LastElementQueue -from pymsn.util.decorator import throttled -import pymsn.util.element_tree as ElementTree -import pymsn.profile as profile -import pymsn.service.SingleSignOn as SSO -import pymsn.service.AddressBook as AB -import pymsn.service.OfflineIM as OIM - -import logging -import urllib -import gobject -import xml.sax.saxutils as xml_utils - -__all__ = ['NotificationProtocol'] - -logger = logging.getLogger('protocol:notification') - - -class NotificationProtocol(BaseProtocol, gobject.GObject): - """Protocol used to communicate with the Notification Server - - @undocumented: do_get_property, do_set_property - @group Handlers: _handle_*, _default_handler, _error_handler - - @ivar state: the current protocol state - @type state: integer - @see L{base.ProtocolState}""" - __gsignals__ = { - "authentication-failed" : (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - ()), - - "mail-received" : (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - (object,)), - - "switchboard-invitation-received" : (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - (object, object)), - - "unmanaged-message-received" : (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - (object, object)), - } - - __gproperties__ = { - "state": (gobject.TYPE_INT, - "State", - "The state of the communication with the server.", - 0, 6, ProtocolState.CLOSED, - gobject.PARAM_READABLE) - } - - def __init__(self, client, transport, proxies={}): - """Initializer - - @param client: the parent instance of L{client.Client} - @type client: L{client.Client} - - @param transport: The transport to use to speak the protocol - @type transport: L{transport.BaseTransport} - - @param proxies: a dictonary mapping the proxy type to a - L{gnet.proxy.ProxyInfos} instance - @type proxies: {type: string, proxy:L{gnet.proxy.ProxyInfos}} - """ - BaseProtocol.__init__(self, client, transport, proxies) - gobject.GObject.__init__(self) - self.__state = ProtocolState.CLOSED - self._protocol_version = 0 - - # Properties ------------------------------------------------------------ - def __get_state(self): - return self.__state - def __set_state(self, state): - self.__state = state - self.notify("state") - state = property(__get_state) - _state = property(__get_state, __set_state) - - def do_get_property(self, pspec): - if pspec.name == "state": - return self.__state - else: - raise AttributeError, "unknown property %s" % pspec.name - - def do_set_property(self, pspec, value): - raise AttributeError, "unknown property %s" % pspec.name - - # Public API ------------------------------------------------------------- - @throttled(2000, LastElementQueue()) - def set_presence(self, presence, client_id=0, msn_object=None): - """Publish the new user presence. - - @param presence: the new presence - @type presence: string L{profile.Presence}""" - if msn_object == None: - msn_object = "" - - if presence == profile.Presence.OFFLINE: - self.signoff() - else: - if msn_object: - self._client._msn_object_store.publish(msn_object) - self._send_command('CHG', - (presence, str(client_id), urllib.quote(str(msn_object)))) - - @throttled(2000, LastElementQueue()) - def set_display_name(self, display_name): - """Sets the new display name - - @param friendly_name: the new friendly name - @type friendly_name: string""" - self._send_command('PRP', - ('MFN', urllib.quote(display_name))) - - @throttled(2000, LastElementQueue()) - def set_personal_message(self, personal_message='', current_media=None): - """Sets the new personal message - - @param personal_message: the new personal message - @type personal_message: string""" - cm = '' - if current_media is not None: - cm ='\\0Music\\01\\0{0} - {1}\\0%s\\0%s\\0\\0' % \ - (xml_utils.escape(current_media[0]), - xml_utils.escape(current_media[1])) - - message = xml_utils.escape(personal_message) - pm = ''\ - '%s'\ - '%s'\ - '{CAFEBABE-DEAD-BEEF-BAAD-FEEDDEADC0DE}'\ - '' % (message, cm) - self._send_command('UUX', payload=pm) - self._client.profile._server_property_changed("personal-message", - personal_message) - if current_media is not None: - self._client.profile._server_property_changed("current-media", - current_media) - - def signoff(self): - """Logout from the server""" - self._send_command('OUT') - self._transport.lose_connection() - - @throttled(7600, list()) - def request_switchboard(self, priority, callback, *callback_args): - self.__switchboard_callbacks.add((callback, callback_args), priority) - self._send_command('XFR', ('SB',)) - - def add_contact_to_membership(self, account, - network_id=profile.NetworkID.MSN, - membership=profile.Membership.FORWARD): - """Add a contact to a given membership. - - @param account: the contact identifier - @type account: string - - @param network_id: the contact network - @type network_id: integer - @see L{pymsn.profile.NetworkID} - - @param membership: the list to be added to - @type membership: integer - @see L{pymsn.profile.Membership}""" - - if network_id == profile.NetworkID.MOBILE: - payload = '' % \ - (contact, membership) - self._send_command("ADL", payload=payload) - else: - contact, domain = account.split("@", 1) - payload = '' % \ - (domain, contact, membership, network_id) - self._send_command("ADL", payload=payload) - - def remove_contact_from_membership(self, account, - network_id=profile.NetworkID.MSN, - membership=profile.Membership.FORWARD): - """Remove a contact from a given membership. - - @param account: the contact identifier - @type account: string - - @param network_id: the contact network - @type network_id: integer - @see L{pymsn.profile.NetworkID} - - @param membership: the list to be added to - @type membership: integer - @see L{pymsn.profile.Membership}""" - - if network_id == profile.NetworkID.MOBILE: - payload = '' % \ - (contact, membership) - self._send_command("RML", payload=payload) - else: - contact, domain = account.split("@", 1) - payload = '' % \ - (domain, contact, membership, network_id) - self._send_command("RML", payload=payload) - - def send_unmanaged_message(self, contact, message): - content_type = message.content_type[0] - if content_type == 'text/x-msnmsgr-datacast': - message_type = 3 - elif content_type == 'text/x-msmsgscontrol': - message_type = 2 - else: - message_type = 1 - self._send_command('UUM', - (contact.account, contact.network_id, message_type), - payload=message) - - # Handlers --------------------------------------------------------------- - # --------- Connection --------------------------------------------------- - def _handle_VER(self, command): - self._protocol_version = int(command.arguments[0].lstrip('MSNP')) - self._send_command('CVR', - ProtocolConstant.CVR + (self._client.profile.account,)) - - def _handle_CVR(self, command): - if self._protocol_version >= 15: - method = 'SSO' - else: - method = 'TWN' - self._send_command('USR', - (method, 'I', self._client.profile.account)) - - def _handle_XFR(self, command): - if command.arguments[0] == 'NS': - try: - host, port = command.arguments[1].split(":", 1) - port = int(port) - except ValueError: - host = command.arguments[1] - port = self._transport.server[1] - logger.debug("<-> Redirecting to " + command.arguments[1]) - self._transport.reset_connection((host,port)) - else: # connect to a switchboard - try: - host, port = command.arguments[1].split(":", 1) - port = int(port) - except ValueError: - host = command.arguments[1] - port = self._transport.server[1] - session_id = command.arguments[3] - callback, callback_args = self.__switchboard_callbacks.pop(0) - callback(((host, port), session_id, None), *callback_args) - - def _handle_USR(self, command): - args_len = len(command.arguments) - - # MSNP15 have only 4 params for final USR - assert(args_len == 3 or args_len == 4), \ - "Received USR with invalid number of params : " + str(command) - - if command.arguments[0] == "OK": - self._state = ProtocolState.AUTHENTICATED - - # we need to authenticate with a passport server - elif command.arguments[1] == "S": - self._state = ProtocolState.AUTHENTICATING - if command.arguments[0] == "SSO": - self._client._sso.RequestMultipleSecurityTokens( - (self._sso_cb, command.arguments[3]), - (lambda *args: self.emit("authentication-failed"),), - SSO.LiveService.MESSENGER_CLEAR) - - self._client.address_book.connect("notify::state", - self._address_book_state_changed_cb) - - self._client.address_book.connect("messenger-contact-added", - self._address_book_contact_added_cb) - self._client.address_book.connect("contact-accepted", - self._address_book_contact_accepted_cb) - self._client.address_book.connect("contact-rejected", - self._address_book_contact_rejected_cb) - self._client.address_book.connect("contact-deleted", - self._address_book_contact_deleted_cb) - self._client.address_book.connect("contact-blocked", - self._address_book_contact_blocked_cb) - self._client.address_book.connect("contact-unblocked", - self._address_book_contact_unblocked_cb) - - elif command.arguments[0] == "TWN": - raise NotImplementedError, "Missing Implementation, please fix" - - def _handle_SBS(self, command): # unknown command - pass - - def _handle_OUT(self, command): - pass - - # --------- Presence & Privacy ------------------------------------------- - def _handle_BLP(self, command): - self._client.profile._server_property_changed("privacy", - command.arguments[0]) - - def _handle_CHG(self, command): - self._client.profile._server_property_changed("presence", - command.arguments[0]) - if len(command.arguments) > 2: - if command.arguments[2] != '0': - msn_object = pymsn.p2p.MSNObject.parse(self._client, - urllib.unquote(command.arguments[2])) - else: - msn_object = None - self._client.profile._server_property_changed("msn_object", msn_object) - else: - self._client.profile._server_property_changed("msn_object", None) - - def _handle_ILN(self,command): - self._handle_NLN(command) - - def _handle_FLN(self,command): - network_id = int(command.arguments[1]) - account = command.arguments[0] - - contacts = self._client.address_book.contacts.\ - search_by_network_id(network_id).\ - search_by_account(account) - - if len(contacts) == 0: - logger.warning("Contact (network_id=%d) %s not found" % \ - (network_id, account)) - - for contact in contacts: - contact._server_property_changed("presence", - profile.Presence.OFFLINE) - - def _handle_NLN(self,command): - network_id = int(command.arguments[2]) - account = command.arguments[1] - - contacts = self._client.address_book.contacts.\ - search_by_network_id(network_id).\ - search_by_account(account) - - if len(contacts) == 0: - logger.warning("Contact (network_id=%d) %s not found" % \ - (network_id, account)) - for contact in contacts: - presence = command.arguments[0] - display_name = urllib.unquote(command.arguments[3]) - capabilities = int(command.arguments[4]) - contact._server_property_changed("presence", presence) - contact._server_property_changed("display-name", display_name) - contact._server_property_changed("client-capabilities", capabilities) - if len(command.arguments) >= 6: - if command.arguments[5] != '0': - msn_object = pymsn.p2p.MSNObject.parse(self._client, - urllib.unquote(command.arguments[5])) - contact._server_property_changed("msn-object", msn_object) - elif command.arguments[5] == '0': - contact._server_property_changed("msn-object", None) - elif len(command.arguments) > 6: - icon_url = command.arguments[6] - contact._server_attribute_changed('icon_url', icon_url) - - # --------- Display name and co ------------------------------------------ - def _handle_PRP(self, command): - ctype = command.arguments[0] - if len(command.arguments) < 2: return - if ctype == 'MFN': - self._client.profile._server_property_changed('display-name', - urllib.unquote(command.arguments[1])) - # TODO: add support for other stuff - - def _handle_UUX(self, command): - pass - - def _handle_UBN(self,command): # contact infos - if not command.payload: - return - print "RECEIVED UBN : %s\n%s" % (command, command.payload) - - def _handle_UBX(self,command): # contact infos - if not command.payload: - return - - network_id = int(command.arguments[1]) - account = command.arguments[0] - - contacts = self._client.address_book.contacts.\ - search_by_network_id(network_id).\ - search_by_account(account) - - if len(contacts) == 0: - logger.warning("Contact (network_id=%d) %s not found" % \ - (network_id, account)) - for contact in contacts: - cm = ElementTree.fromstring(command.payload).find("./CurrentMedia") - if cm is not None and cm.text is not None: - parts = cm.text.split('\\0') - if parts[1] == 'Music' and parts[2] == '1': - cm = (parts[4].encode("utf-8"), parts[5].encode("utf-8")) - contact._server_property_changed("current-media", cm) - continue - elif parts[2] == '0': - contact._server_property_changed("current-media", None) - else: - contact._server_property_changed("current-media", None) - pm = ElementTree.fromstring(command.payload).find("./PSM") - if pm is not None and pm.text is not None: - pm = pm.text.encode("utf-8") - else: - pm = "" - contact._server_property_changed("personal-message", pm) - # --------- Contact List ------------------------------------------------- - def _handle_ADL(self, command): - if command.transaction_id == 0: # incoming ADL from the server - self._client.address_book.check_pending_invitations() - if len(command.arguments) > 0 and command.arguments[0] == "OK": - if self._state != ProtocolState.OPEN: # Initial ADL - self._state = ProtocolState.OPEN - self._transport.enable_ping() - else: # contact Added - pass - - # --------- Messages ----------------------------------------------------- - def _handle_MSG(self, command): - message = Message(None, command.payload) - content_type = message.content_type - if content_type[0] == 'text/x-msmsgsprofile': - self._client.profile._server_property_changed("profile", - command.payload) - - if self._protocol_version < 15: - #self._send_command('SYN', ('0', '0')) - raise NotImplementedError, "Missing Implementation, please fix" - else: - self._send_command("BLP", - (self._client.profile.privacy,)) - 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: - self._client.oim_box._state = \ - OIM.OfflineMessagesBoxState.NOT_SYNCHRONIZED - m = HTTPMessage() - m.parse(message.body) - mail_data = m.get_header('Mail-Data').strip() - if mail_data == 'too-large': - mail_data = None - self._client.oim_box.sync(mail_data) - elif content_type[0] == 'text/x-msmsgsactivemailnotification': - pass - - def _handle_UBM(self, command): - network_id = int(command.arguments[1]) - account = command.arguments[0] - - contacts = self._client.address_book.contacts.\ - search_by_network_id(network_id).\ - search_by_account(account) - - if len(contacts) == 0: - logger.warning("Contact (network_id=%d) %s not found" % \ - (network_id, account)) - else: - contact = contacts[0] - message = Message(contact, command.payload) - self.emit("unmanaged-message-received", contact, message) - - # --------- Invitation --------------------------------------------------- - def _handle_RNG(self,command): - session_id = command.arguments[0] - host, port = command.arguments[1].split(':',1) - port = int(port) - key = command.arguments[3] - account = command.arguments[4] - display_name = urllib.unquote(command.arguments[5]) - - session = ((host, port), session_id, key) - inviter = (account, display_name) - self.emit("switchboard-invitation-received", session, inviter) - - # --------- Challenge ---------------------------------------------------- - def _handle_QNG(self,command): - pass - - def _handle_QRY(self,command): - pass - - def _handle_CHL(self,command): - response = _msn_challenge(command.arguments[0]) - self._send_command('QRY', - (ProtocolConstant.PRODUCT_ID,), payload=response) - - # callbacks -------------------------------------------------------------- - def _connect_cb(self, transport): - self.__switchboard_callbacks = PriorityQueue() - self._state = ProtocolState.OPENING - self._send_command('VER', ProtocolConstant.VER) - - def _disconnect_cb(self, transport, reason): - self._state = ProtocolState.CLOSED - - def _sso_cb(self, tokens, nonce): - clear_token = tokens[SSO.LiveService.MESSENGER_CLEAR] - blob = clear_token.mbi_crypt(nonce) - - self._send_command("USR", - ("SSO", "S", clear_token.security_token, blob)) - - def _address_book_state_changed_cb(self, address_book, pspec): - MAX_PAYLOAD_SIZE = 7500 - if address_book.state != AB.AddressBookState.SYNCHRONIZED: - return - self._client.profile._server_property_changed("display-name", - address_book.profile.display_name) - - contacts = address_book.contacts\ - .search_by_memberships(profile.Membership.FORWARD)\ - .group_by_domain() - - payloads = [''] - mask = ~(profile.Membership.REVERSE | profile.Membership.PENDING) - for domain, contacts in contacts.iteritems(): - payloads[-1] += '' % domain - for contact in contacts: - user = contact.account.split("@", 1)[0] - lists = contact.memberships & mask - network_id = contact.network_id - node = '' % (user, lists, network_id) - size = len(payloads[-1]) + len(node) + len('') - if size >= MAX_PAYLOAD_SIZE: - payloads[-1] += '' - payloads.append('' % domain) - payloads[-1] += node - payloads[-1] += '' - payloads[-1] += '' - - for payload in payloads: - self._send_command("ADL", payload=payload) - self._state = ProtocolState.SYNCHRONIZED - - def _address_book_contact_added_cb(self, address_book, contact): - self.add_contact_to_membership(contact.account, contact.network_id, - profile.Membership.ALLOW) - - self.add_contact_to_membership(contact.account, contact.network_id, - profile.Membership.FORWARD) - - if contact.network_id != profile.NetworkID.MOBILE: - account, domain = contact.account.split('@', 1) - payload = ''% \ - (domain, account) - self._send_command("FQY", payload=payload) - - def _address_book_contact_deleted_cb(self, address_book, contact): - self.remove_contact_from_membership(contact.account, contact.network_id, - profile.Membership.ALLOW) - - self.add_contact_to_membership(contact.account, contact.network_id, - profile.Membership.BLOCK) - - self.remove_contact_from_membership(contact.account, contact.network_id, - profile.Membership.FORWARD) - - def _address_book_contact_accepted_cb(self, address_book, contact): - mask = ~(profile.Membership.REVERSE | profile.Membership.PENDING) - memberships = contact.memberships & mask - if memberships: - self.add_contact_to_membership(contact.account, contact.network_id, - memberships) - - def _address_book_contact_rejected_cb(self, address_book, contact): - mask = ~(profile.Membership.REVERSE | profile.Membership.PENDING) - memberships = contact.memberships & mask - if memberships: - self.add_contact_to_membership(contact.account, contact.network_id, - memberships) - - def _address_book_contact_blocked_cb(self, address_book, contact): - self.remove_contact_from_membership(contact.account, contact.network_id, - profile.Membership.ALLOW) - - self.add_contact_to_membership(contact.account, contact.network_id, - profile.Membership.BLOCK) - - def _address_book_contact_unblocked_cb(self, address_book, contact): - self.remove_contact_from_membership(contact.account, contact.network_id, - profile.Membership.BLOCK) - - self.add_contact_to_membership(contact.account, contact.network_id, - profile.Membership.ALLOW) diff --git a/pymsn/pymsn/msnp/switchboard.py b/pymsn/pymsn/msnp/switchboard.py deleted file mode 100644 index 0d339397..00000000 --- a/pymsn/pymsn/msnp/switchboard.py +++ /dev/null @@ -1,296 +0,0 @@ -# -*- coding: utf-8 -*- -# -# pymsn - a python client library for Msn -# -# Copyright (C) 2005-2007 Ali Sabil -# Copyright (C) 2005-2006 Ole André Vadla Ravnås -# Copyright (C) 2007 Johann Prieur -# -# 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 - -"""Switchboard protocol Implementation -Implements the protocol used to communicate with the Switchboard Server.""" - -from base import BaseProtocol, ProtocolState -from message import Message -import pymsn.profile - -import logging -import urllib -import gobject - -__all__ = ['SwitchboardProtocol'] - -logger = logging.getLogger('protocol:switchboard') - - -class SwitchboardProtocol(BaseProtocol, gobject.GObject): - """Protocol used to communicate with the Switchboard Server - - @undocumented: do_get_property, do_set_property - @group Handlers: _handle_*, _default_handler, _error_handler - - @ivar _state: the current protocol state - @type _state: integer - @see L{ProtocolState}""" - __gsignals__ = { - "message-received": (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - (object,)), - - "message-sent": (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - (object,)), - - "message-delivered": (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - (object,)), - - "message-undelivered": (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - (object,)), - - "user-joined": (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - (object,)), - - "user-left": (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - (object,)), - - "user-invitation-failed": (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - (object,))} - - __gproperties__ = { - "state": (gobject.TYPE_INT, - "State", - "The state of the communication with the server.", - 0, 6, ProtocolState.CLOSED, - gobject.PARAM_READABLE), - - "inviting": (gobject.TYPE_BOOLEAN, - "Inviting", - "True if an invite was sent, and the contact didn't join yet", - False, - gobject.PARAM_READABLE) - } - - def __init__(self, client, transport, session_id, key=None, proxies={}): - """Initializer - - @param client: the Client instance - - @param transport: The transport to use to speak the protocol - @type transport: L{transport.BaseTransport} - - @param session_id: the session to join if any - @type session_id: string - - @param key: the key used to authenticate to server when connecting - @type key: string - - @param proxies: a dictonary mapping the proxy type to a - L{gnet.proxy.ProxyInfos} instance - @type proxies: {type: string, proxy:L{gnet.proxy.ProxyInfos}} - """ - BaseProtocol.__init__(self, client, transport, proxies) - gobject.GObject.__init__(self) - self.participants = {} - self.__session_id = session_id - self.__key = key - self.__state = ProtocolState.CLOSED - self.__inviting = False - - self.__invitations = {} - - # Properties ------------------------------------------------------------ - def __get_state(self): - return self.__state - def __set_state(self, state): - self.__state = state - self.notify("state") - state = property(__get_state) - _state = property(__get_state, __set_state) - - def __get_inviting(self): - return self.__inviting - def __set_inviting(self, value): - if self.__inviting != value: - self.__inviting = value - self.notify("inviting") - inviting = property(__get_inviting) - _inviting = property(__get_inviting, __set_inviting) - - def do_get_property(self, pspec): - if pspec.name == "state": - return self.__state - elif pspec.name == "inviting": - return self.__inviting - else: - raise AttributeError, "unknown property %s" % pspec.name - - def do_set_property(self, pspec, value): - raise AttributeError, "unknown property %s" % pspec.name - - # Public API ------------------------------------------------------------- - def invite_user(self, contact): - """Invite user to join in the conversation - - @param contact: the contact to invite - @type contact: L{profile.Contact}""" - assert(self.state == ProtocolState.OPEN) - self.__invitations[self._transport.transaction_id] = contact - self._inviting = True - self._send_command('CAL', (contact.account,) ) - - def send_message(self, message, ack, callback=None, cb_args=()): - """Send a message to all contacts in this switchboard - - @param message: the message to send - @type message: L{message.Message}""" - assert(self.state == ProtocolState.OPEN) - self._send_command('MSG', - (ack,), - message, - True, - self.__on_message_sent, - message, callback, cb_args) - - def __on_message_sent(self, message, user_callback, user_cb_args): - self.emit("message-sent", message) - if user_callback: - user_callback(*user_cb_args) - - def leave(self): - """Leave the conversation""" - assert(self.state == ProtocolState.OPEN) - self._send_command('OUT') - # Handlers --------------------------------------------------------------- - # --------- Authentication ----------------------------------------------- - def _handle_ANS(self, command): - if command.arguments[0] == 'OK': - self._state = ProtocolState.SYNCHRONIZED - self._state = ProtocolState.OPEN - else: - self._state = ProtocolState.AUTHENTICATED - self._state = ProtocolState.SYNCHRONIZING - - def _handle_USR(self, command): - self._state = ProtocolState.AUTHENTICATED - self._state = ProtocolState.SYNCHRONIZING - self._state = ProtocolState.SYNCHRONIZED - self._state = ProtocolState.OPEN - - def _handle_OUT(self, command): - pass - # --------- Invitation --------------------------------------------------- - def __participant_join(self, account, display_name, client_id): - contacts = self._client.address_book.contacts.\ - search_by_account(account) - if len(contacts) == 0: - contact = pymsn.profile.Contact(id=0, - network_id=pymsn.profile.NetworkID.MSN, - account=account, - display_name=display_name) - else: - contact = contacts[0] - contact._server_property_changed("client-capabilities", client_id) - self.participants[account] = contact - self.emit("user-joined", contact) - - def _handle_IRO(self, command): - account = command.arguments[2] - display_name = urllib.unquote(command.arguments[3]) - client_id = int(command.arguments[4]) - self.__participant_join(account, display_name, client_id) - - def _handle_JOI(self, command): - account = command.arguments[0] - display_name = urllib.unquote(command.arguments[1]) - client_id = int(command.arguments[2]) - self.__participant_join(account, display_name, client_id) - if len(self.__invitations) == 0: - self._inviting = False - - def _handle_CAL(self, command): - # this should be followed by a JOI, so we only change - # the self._inviting state until we get the actual JOI - del self.__invitations[command.transaction_id] - - def _handle_BYE(self, command): - if len(command.arguments) == 1: - account = command.arguments[0] - self.emit("user-left", self.participants[account]) - del self.participants[account] - else: - self._state = ProtocolState.CLOSED - self.participants = {} - - # --------- Messenging --------------------------------------------------- - def _handle_MSG(self, command): - account = command.arguments[0] - display_name = urllib.unquote(command.arguments[1]) - contacts = self._client.address_book.contacts.\ - search_by_account(account) - if len(contacts) == 0: - contact = pymsn.profile.Contact(id=0, - network_id=pymsn.profile.NetworkID.MSN, - account=account, - display_name=display_name) - else: - contact = contacts[0] - message = Message(contact, command.payload) - self.emit("message-received", message) - - def _handle_ACK(self, command): - self.emit("message-delivered", command) - - def _handle_NAK(self, command): - self.emit("message-undelivered", command) - - def _error_handler(self, error): - """Handles errors - - @param error: an error command object - @type error: L{command.Command} - """ - if error.name in ('208', '215', '216', '217', '713'): - try: - contact = self.__invitations[error.transaction_id] - self.emit("user-invitation-failed", contact) - del self.__invitations[error.transaction_id] - if len(self.__invitations) == 0: - self._inviting = False - except: - pass - else: - logger.error('Notification got error :' + repr(error)) - # callbacks -------------------------------------------------------------- - def _connect_cb(self, transport): - self._state = ProtocolState.OPENING - account = self._client.profile.account - if self.__key is not None: - arguments = (account, self.__key, self.__session_id) - self._send_command('ANS', arguments) - else: - arguments = (account, self.__session_id) - self._send_command('USR', arguments) - self._state = ProtocolState.AUTHENTICATING - - def _disconnect_cb(self, transport, reason): - logger.info("Disconnected") - self._state = ProtocolState.CLOSED - diff --git a/pymsn/pymsn/msnp2p/SLP.py b/pymsn/pymsn/msnp2p/SLP.py deleted file mode 100644 index c285f83b..00000000 --- a/pymsn/pymsn/msnp2p/SLP.py +++ /dev/null @@ -1,306 +0,0 @@ -# -*- coding: utf-8 -*- -# -# pymsn - a python client library for Msn -# -# Copyright (C) 2007 Ali Sabil -# -# 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 - -from pymsn.gnet.message.HTTP import HTTPMessage -from pymsn.msnp2p.exceptions import ParseError -from pymsn.msnp2p.constants import SLPContentType - -import base64 - -__all__ = ['SLPMessage', 'SLPRequestMessage', 'SLPResponseMessage', - 'SLPMessageBody', 'SLPNullBody', 'SLPSessionRequestBody', - 'SLPSessionCloseBody', 'SLPSessionFailureResponseBody'] - - -class SLPMessage(HTTPMessage): - STD_HEADERS = ["To", "From", "Via", "CSeq", "Call-ID", "Max-Forwards"] - - def __init__(self, to="", frm="", branch="", cseq=0, call_id="", max_forwards=0): - HTTPMessage.__init__(self) - self.add_header("To", "" % to) - self.add_header("From", "" % frm) - if branch: - self.add_header("Via", "MSNSLP/1.0/TLP ;branch=%s" % branch) - self.add_header("CSeq", str(cseq)) - if call_id: - self.add_header("Call-ID", call_id) - self.add_header("Max-Forwards", str(max_forwards)) - - # Make the body a SLP Message wih "null" content type - self.body = SLPNullBody() - - @property - def to(self): - to = self.get_header("To") - return to.split(":", 1)[1][:-1] - - @property - def frm(self): - frm = self.get_header("From") - return frm.split(":", 1)[1][:-1] - - @property - def branch(self): - try: - via = self.get_header("Via") - params = via.split(";", 1)[1:] - - for param in params: - key, value = param.split('=') - if key.strip() == "branch": - return value.strip() - return "" - except KeyError: - return "" - - @property - def cseq(self): - return int(self.get_header("CSeq")) - - @property - def call_id(self): - try: - return self.get_header("Call-ID") - except KeyError: - return "" - - def parse(self, chunk): - HTTPMessage.parse(self, chunk) - - content_type = self.headers.get("Content-Type", "null") - - raw_body = self.body - self.body = SLPMessageBody.build(content_type, raw_body) - - def __str__(self): - if self.body is None: - self.add_header("Content-Type", "null") - self.add_header("Content-Length", 0) - else: - self.add_header("Content-Type", self.body.content_type) - self.add_header("Content-Length", len(str(self.body))) - - return HTTPMessage.__str__(self) - - @staticmethod - def build(raw_message): - if raw_message.find("MSNSLP/1.0") < 0: - raise ParseError("message doesn't seem to be an MSNSLP/1.0 message") - start_line, content = raw_message.split("\r\n", 1) - start_line = start_line.split(" ") - - if start_line[0].strip() == "MSNSLP/1.0": - status = int(start_line[1].strip()) - reason = " ".join(start_line[2:]).strip() - slp_message = SLPResponseMessage(status, reason) - else: - method = start_line[0].strip() - resource = start_line[1].strip() - slp_message = SLPRequestMessage(method, resource) - slp_message.parse(content) - - return slp_message - - -class SLPRequestMessage(SLPMessage): - def __init__(self, method, resource, *args, **kwargs): - SLPMessage.__init__(self, *args, **kwargs) - self.method = method - self.resource = resource - - self._to = resource.split(":", 1)[1] - - def __get_to(self): - return self._to - def __set_to(self, to): - self._to = to - self.resource = "MSNMSGR:" + to - self.add_header("To", "" % to) - to = property(__get_to, __set_to) - - def __str__(self): - message = SLPMessage.__str__(self) - start_line = "%s %s MSNSLP/1.0" % (self.method, self.resource) - return start_line + "\r\n" + message - - -class SLPResponseMessage(SLPMessage): - STATUS_MESSAGE = { - 200 : "OK", - 404 : "Not Found", - 500 : "Internal Error", - 603 : "Decline", - 606 : "Unacceptable"} - - def __init__(self, status, reason=None, *args, **kwargs): - SLPMessage.__init__(self, *args, **kwargs) - self.status = int(status) - self.reason = reason - - def __str__(self): - message = SLPMessage.__str__(self) - - if self.reason is None: - reason = SLPResponseMessage.STATUS_MESSAGE[self.status] - else: - reason = self.reason - - start_line = "MSNSLP/1.0 %d %s" % (self.status, reason) - return start_line + "\r\n" + message - - -class SLPMessageBody(HTTPMessage): - content_classes = {} - - def __init__(self, content_type, session_id=None, s_channel_state=0, capabilities_flags=1): - HTTPMessage.__init__(self) - self.content_type = content_type - - if session_id is not None: - self.add_header("SessionID", session_id) - if s_channel_state is not None: - self.add_header("SChannelState", s_channel_state) - if capabilities_flags is not None: - self.add_header("Capabilities-Flags", capabilities_flags) - - - @property - def session_id(self): - try: - return int(self.get_header("SessionID")) - except (KeyError, ValueError): - return 0 - - @property - def s_channel_state(self): - try: - return int(self.get_header("SChannelState")) - except (KeyError, ValueError): - return 0 - - @property - def capabilities_flags(self): - try: - return int(self.get_header("Capabilities-Flags")) - except (KeyError, ValueError): - return 0 - - def parse(self, data): - if len(data) == 0: - return - data.rstrip('\x00') - HTTPMessage.parse(self, data) - - def __str__(self): - return HTTPMessage.__str__(self) + "\x00" - - @staticmethod - def register_content(content_type, cls): - SLPMessageBody.content_classes[content_type] = cls - - @staticmethod - def build(content_type, content): - if content_type in SLPMessageBody.content_classes.keys(): - cls = SLPMessageBody.content_classes[content_type] - body = cls(); - else: - body = SLPMessageBody(content_type) - - body.parse(content) - return body - - -class SLPNullBody(SLPMessageBody): - def __init__(self): - SLPMessageBody.__init__(self, SLPContentType.NULL) -SLPMessageBody.register_content(SLPContentType.NULL, SLPNullBody) - - -class SLPSessionRequestBody(SLPMessageBody): - def __init__(self, euf_guid=None, app_id=None, context=None, - session_id=None, s_channel_state=0, capabilities_flags=1): - SLPMessageBody.__init__(self, SLPContentType.SESSION_REQUEST, - session_id, s_channel_state, capabilities_flags) - - if euf_guid is not None: - self.add_header("EUF-GUID", euf_guid) - if app_id is not None: - self.add_header("AppID", app_id) - if context is not None: - self.add_header("Context", base64.b64encode(context)) - - @property - def euf_guid(self): - try: - return self.get_header("EUF-GUID") - except (KeyError, ValueError): - return "" - - @property - def context(self): - try: - context = self.get_header("Context") - # Make the b64 string correct by append '=' to get a length as a - # multiple of 4. Kopete client seems to use incorrect b64 strings. - context += '=' * (len(context) % 4) - return base64.b64decode(context) - except KeyError: - return None - - @property - def application_id(self): - try: - return int(self.get_header("AppID")) - except (KeyError, ValueError): - return 0 - -SLPMessageBody.register_content(SLPContentType.SESSION_REQUEST, SLPSessionRequestBody) - - -class SLPSessionCloseBody(SLPMessageBody): - def __init__(self, context=None, session_id=None, s_channel_state=0, - capabilities_flags=1): - SLPMessageBody.__init__(self, SLPContentType.SESSION_CLOSE, - session_id, s_channel_state, capabilities_flags) - - if context is not None: - self.add_header("Context", base64.b64encode(context)); - - @property - def context(self): - try: - context = self.get_header("Context") - # Make the b64 string correct by append '=' to get a length as a - # multiple of 4. Kopete client seems to use incorrect b64 strings. - context += '=' * (len(context) % 4) - return base64.b64decode(context) - except KeyError: - return None - -SLPMessageBody.register_content(SLPContentType.SESSION_CLOSE, SLPSessionCloseBody) - - -class SLPSessionFailureResponseBody(SLPMessageBody): - def __init__(self, session_id=None, s_channel_state=0, capabilities_flags=1): - SLPMessageBody.__init__(self, SLPContentType.SESSION_FAILURE, - session_id, s_channel_state, capabilities_flags) - -SLPMessageBody.register_content(SLPContentType.SESSION_FAILURE, SLPSessionFailureResponseBody) - diff --git a/pymsn/pymsn/msnp2p/__init__.py b/pymsn/pymsn/msnp2p/__init__.py deleted file mode 100644 index 46db9b89..00000000 --- a/pymsn/pymsn/msnp2p/__init__.py +++ /dev/null @@ -1,27 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2006 Ali Sabil -# -# 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 -# - -"""MSNP2P protocol implementation. -Contains a set of class enabling the communication using the MSNP2P -protocol used to transfer data between peers, such as transfer of files -and display pictures.""" - -from session_manager import P2PSessionManager -from session import OutgoingP2PSession -from constants import EufGuid, ApplicationID diff --git a/pymsn/pymsn/msnp2p/constants.py b/pymsn/pymsn/msnp2p/constants.py deleted file mode 100644 index 9b423765..00000000 --- a/pymsn/pymsn/msnp2p/constants.py +++ /dev/null @@ -1,49 +0,0 @@ -# -*- coding: utf-8 -*- -# -# pymsn - a python client library for Msn -# -# Copyright (C) 2007 Ole André Vadla Ravnås -# -# 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 - -__all__ = ['EufGuid', 'ApplicationID', 'SLPContentType', 'SLPRequestMethod'] - -class EufGuid(object): - MSN_OBJECT = "{A4268EEC-FEC5-49E5-95C3-F126696BDBF6}" - FILE_TRANSFER = "{5D3E02AB-6190-11D3-BBBB-00C04F795683}" - MEDIA_RECEIVE_ONLY = "{1C9AA97E-9C05-4583-A3BD-908A196F1E92}" - MEDIA_SESSION = "{4BD96FC0-AB17-4425-A14A-439185962DC8}" - -class ApplicationID(object): - CUSTOM_EMOTICON_TRANSFER = 11 - DISPLAY_PICTURE_TRANSFER = 12 - -class SLPContentType(object): - """MSNSLP content types""" - SESSION_REQUEST = "application/x-msnmsgr-sessionreqbody" - SESSION_FAILURE = "application/x-msnmsgr-session-failure-respbody" - SESSION_CLOSE = "application/x-msnmsgr-sessionclosebody" - - TRANSFER_REQUEST = "application/x-msnmsgr-transreqbody" - TRANSFER_RESPONSE = "application/x-msnmsgr-transrespbody" - - TRANS_UDP_SWITCH = "application/x-msnmsgr-transudpswitch" - - NULL = "null" - -class SLPRequestMethod(object): - INVITE = 'INVITE' - BYE = 'BYE' - ACK = 'ACK' diff --git a/pymsn/pymsn/msnp2p/exceptions.py b/pymsn/pymsn/msnp2p/exceptions.py deleted file mode 100644 index 0a34255f..00000000 --- a/pymsn/pymsn/msnp2p/exceptions.py +++ /dev/null @@ -1,59 +0,0 @@ -# -*- coding: utf-8 -*- -# -# pymsn - a python client library for Msn -# -# Copyright (C) 2007 Ole André Vadla Ravnås -# -# 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 - -__all__ = ['ProtocolError', 'ParseError', 'SLPError', 'SLPSessionError'] - -class Error(Exception): - """Generic exception type""" - def __init__(self, code, message): - Exception.__init__(self) - self.code = code - self.message = message - - def __str__(self): - return str(self.code) + ':' + str(self.message) - -class ProtocolError(Error): - """Protocol Error, it should never be thrown""" - def __init__(self, message): - Error.__init__(self, 0, message) - - def __str__(self): - return self.message - -class ParseError(Error): - """Parsing Error""" - def __init__(self, message, infos=''): - Error.__init__(self, 1, message) - self.infos = infos - - def __str__(self): - return self.message - -class SLPError(Error): - """MSNSLP error, used by the msnp2p protocol""" - def __init__(self, message): - Error.__init__(self, 2, message) - -class SLPSessionError(Error): - """SLP Session error, used by the msnp2p protocol""" - def __init__(self, message): - Error.__init__(self, 2, message) - diff --git a/pymsn/pymsn/msnp2p/session.py b/pymsn/pymsn/msnp2p/session.py deleted file mode 100644 index 63325190..00000000 --- a/pymsn/pymsn/msnp2p/session.py +++ /dev/null @@ -1,211 +0,0 @@ -# -*- coding: utf-8 -*- -# -# pymsn - a python client library for Msn -# -# Copyright (C) 2007 Ali Sabil -# -# 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 - -from pymsn.msnp2p.constants import * -from pymsn.msnp2p.SLP import * -from pymsn.msnp2p.transport import * -from pymsn.msnp2p.exceptions import * - -import pymsn.util.guid as guid - -import gobject -import base64 -import random - -__all__ = ['OutgoingP2PSession'] - -MAX_INT32 = 0x7fffffff -MAX_INT16 = 0x7fff - -def _generate_id(max=MAX_INT32): - """ - Returns a random ID. - - @return: a random integer between 1000 and sys.maxint - @rtype: integer - """ - return random.randint(1000, max) - - -class P2PSession(gobject.GObject): - __gsignals__ = { - "transfer-completed" : (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - (object,)) - } - def __init__(self, session_manager, peer, euf_guid="", application_id=0): - gobject.GObject.__init__(self) - self._session_manager = session_manager - self._peer = peer - - self._id = _generate_id() - self._call_id = "{%s}" % guid.generate_guid() - - self._euf_guid = euf_guid - self._application_id = application_id - - self._cseq = 0 - self._branch = "{%s}" % guid.generate_guid() - self._session_manager._register_session(self) - - @property - def id(self): - return self._id - - @property - def call_id(self): - return self._call_id - - @property - def peer(self): - return self._peer - - def _close(self): - body = SLPSessionCloseBody() - - self._cseq = 0 - self._branch = "{%s}" % guid.generate_guid() - message = SLPRequestMessage(SLPRequestMethod.BYE, - "MSNMSGR:" + self._peer.account, - to=self._peer.account, - frm=self._session_manager._client.profile.account, - branch=self._branch, - cseq=self._cseq, - call_id=self._call_id) - message.body = body - self._send_p2p_data(message) - self._session_manager._unregister_session(self) - - def _send_p2p_data(self, data_or_file): - if isinstance(data_or_file, SLPMessage): - session_id = 0 - data = str(data_or_file) - total_size = len(data) - else: - session_id = self._id - data = data_or_file - total_size = None - - blob = MessageBlob(self._application_id, - data, total_size, session_id) - self._session_manager._transport_manager.send(self.peer, blob) - - def _on_blob_sent(self, blob): - if blob.session_id == 0: - # FIXME: handle the signaling correctly - return - - if blob.total_size == 4 and \ - blob.data.read() == ('\x00' * 4): - self._on_data_preparation_blob_sent(blob) - else: - self._on_data_blob_sent(blob) - - def _on_blob_received(self, blob): - if blob.session_id == 0: - # FIXME: handle the signaling correctly - return - - if blob.total_size == 4 and \ - blob.data.read() == ('\x00' * 4): - self._on_data_preparation_blob_received(blob) - else: - self._on_data_blob_received(blob) - self._close() - - def _on_data_preparation_blob_received(self, blob): - pass - - def _on_data_preparation_blob_sent(self, blob): - pass - - def _on_data_blob_sent(self, blob): - blob.data.seek(0, 0) - self.emit("transfer-completed", blob.data) - - def _on_data_blob_received(self, blob): - blob.data.seek(0, 0) - self.emit("transfer-completed", blob.data) - -gobject.type_register(P2PSession) - - -class IncomingP2PSession(P2PSession): - def __init__(self, session_manager, peer, id, message): - P2PSession.__init__(self, session_manager, peer, - message.body.euf_guid, message.body.application_id) - self._id = id - self._call_id = message.call_id - - self._cseq = message.cseq - self._branch = message.branch - try: - self._context = message.body.context.strip('\x00') - except AttributeError: - raise SLPError("Incoming INVITE without context") - - def accept(self, data_file): - gobject.idle_add(self._start_transfer, data_file) - - def reject(self): - self._respond(603) - - def _respond(self, status_code): - body = SLPSessionRequestBody(session_id=self._id) - - self._cseq += 1 - response = SLPResponseMessage(status_code, - to=self._peer.account, - frm=self._session_manager._client.profile.account, - cseq=self._cseq, - branch=self._branch, - call_id=self._call_id) - response.body = body - self._send_p2p_data(response) - - def _start_transfer(self, data_file): - self._respond(200) - self._send_p2p_data("\x00" * 4) - self._send_p2p_data(data_file) - return False - - -class OutgoingP2PSession(P2PSession): - def __init__(self, session_manager, peer, context, euf_guid, application_id): - P2PSession.__init__(self, session_manager, peer, euf_guid, application_id) - gobject.idle_add(self._invite, str(context)) - - def _invite(self, context): - self._session_manager._register_session(self) - body = SLPSessionRequestBody(self._euf_guid, self._application_id, - context, self._id) - - message = SLPRequestMessage(SLPRequestMethod.INVITE, - "MSNMSGR:" + self._peer.account, - to=self._peer.account, - frm=self._session_manager._client.profile.account, - branch=self._branch, - cseq=self._cseq, - call_id=self._call_id) - - message.body = body - self._send_p2p_data(message) - return False - diff --git a/pymsn/pymsn/msnp2p/session_manager.py b/pymsn/pymsn/msnp2p/session_manager.py deleted file mode 100644 index 6f9b0277..00000000 --- a/pymsn/pymsn/msnp2p/session_manager.py +++ /dev/null @@ -1,188 +0,0 @@ -# -*- coding: utf-8 -*- -# -# pymsn - a python client library for Msn -# -# Copyright (C) 2007 Ali Sabil -# -# 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 - -from pymsn.msnp2p.transport import * -from pymsn.msnp2p.exceptions import * -from pymsn.msnp2p.SLP import * -from pymsn.msnp2p.session import IncomingP2PSession -from pymsn.msnp2p.constants import SLPContentType, SLPRequestMethod - -import pymsn.profile - -import gobject -import weakref -import logging - -__all__ = ['P2PSessionManager'] - -logger = logging.getLogger('msnp2p:session-manager') - -class P2PSessionManager(gobject.GObject): - __gsignals__ = { - "incoming-session" : (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - (object,)) - } - - def __init__(self, client): - """Initializer""" - gobject.GObject.__init__(self) - - self._client = client - self._sessions = weakref.WeakValueDictionary() # session_id => session - self._transport_manager = P2PTransportManager(self._client) - self._transport_manager.connect("blob-received", - lambda tr, blob: self._on_blob_received(blob)) - self._transport_manager.connect("blob-sent", - lambda tr, blob: self._on_blob_sent(blob)) - - def _register_session(self, session): - self._sessions[session.id] = session - - def _unregister_session(self, session): - del self._sessions[session.id] - - def _blob_to_session(self, blob): - # Check to see if it's a signaling message - if blob.session_id == 0: - blob.data.seek(0, 0) - slp_data = blob.data.read() - blob.data.seek(0, 0) - try: - message = SLPMessage.build(slp_data) - except ParseError: - logger.warning('Received blob with SessionID=0 and non SLP data') - raise SLPError("Non SLP data for blob with null sessionID") - session_id = message.body.session_id - - # Backward compatible with older clients that use the call-id - # for responses - if session_id == 0: - call_id = message.call_id - for session in self._sessions.itervalues(): - if session.call_id == call_id: - return session - # Couldn't find a session for the call id we received - return None - if session_id in self._sessions: - return self._sessions[session_id] - # Session doesn't exist - return None - else: - session_id = blob.session_id - if session_id in self._sessions: - return self._sessions[blob.session_id] - else: - raise SLPSessionError("Unknown session") - - def _on_blob_received(self, blob): - try: - session = self._blob_to_session(blob) - except SLPError: - # If the blob has a null session id but a badly formed SLP - # Then we should do nothing. The official client doesn't answer. - # We can't send a '500 Internal Error' response since we can't - # parse the SLP, so we don't know who to send it to, or the call-id, etc... - return - except SLPSessionError: - # This means that we received a data packet for an unknown session - # We must RESET the session just like the official client does - # TODO send a TLP - return - - new_session = session is None - - # The session could not be found, create a new one if necessary - if session is None: - # Parse the SLP message. We know it's an SLP because if it was a data packet - # we would have received a ProtocolError exception - blob.data.seek(0, 0) - slp_data = blob.data.read() - blob.data.seek(0, 0) - - # No need to 'try', if it was invalid, we would have received an SLPError - message = SLPMessage.build(slp_data) - session_id = message.body.session_id - - logger.info("blob has SLP (%d):\n%s" % (session_id, message)) - - # Make sure the SLP has a session_id, otherwise, it means it's invite - # if it's a signaling SLP and the call-id could not be matched to - # an existing session - if session_id == 0: - # TODO send a 500 internal error - return - - # If there was no session then create one only if it's an INVITE - if isinstance(message, SLPRequestMessage) and \ - message.method == SLPRequestMethod.INVITE: - # Find the contact we received the message from - contacts = self._client.address_book.contacts.\ - search_by_network_id(pymsn.profile.NetworkID.MSN).\ - search_by_account(message.frm) - if len(contacts) == 0: - peer = pymsn.profile.Contact(id=0, - network_id=pymsn.profile.NetworkID.MSN, - account=message.frm, - display_name=message.frm) - else: - peer = contacts[0] - - # Create the session depending on the type of the message - if isinstance(message.body, SLPSessionRequestBody): - try: - session = IncomingP2PSession(self, peer, session_id, message) - except SLPError: - #TODO: answer with a 603 Decline ? - return - #elif isinstance(message.body, SLPTransferRequestBody): - # pass - else: - logger.warning('Received initial blob with SessionID=0 and non INVITE SLP data') - #TODO: answer with a 500 Internal Error - return None - - # The session should be notified of this blob - session._on_blob_received(blob) - - # emit the new session signal only after the session got notified of this blob - # if one of the functions connected to the signal ends the session it needs to - # first know its initial INVITE before knowing about it's BYE - if new_session: - logger.info("Creating new incomming session") - self.emit("incoming-session", session) - - def _on_blob_sent(self, blob): - session = None - try: - session = self._blob_to_session(blob) - except SLPError, e: - # Something is fishy.. we shouldn't have to send anything abnormal.. - logger.warning("Sent a bad message : %s" % (e)) - session = None - except SLPSessionError, e: - # May happen when we close the session - pass - - if session is None: - return - session._on_blob_sent(blob) - -gobject.type_register(P2PSessionManager) diff --git a/pymsn/pymsn/msnp2p/test.py b/pymsn/pymsn/msnp2p/test.py deleted file mode 100644 index 6c2ef7fa..00000000 --- a/pymsn/pymsn/msnp2p/test.py +++ /dev/null @@ -1,140 +0,0 @@ -#!/usr/bin/env python - -import pymsn -import pymsn.event -from pymsn.msnp2p.session_manager import * -from pymsn.msnp2p.session import * -from pymsn.msnp2p.constants import EufGuid - -import pymsn.util.string_io as StringIO - -import logging -import gobject - -logging.basicConfig(level=logging.DEBUG) - -finished = False - -def get_proxies(): - import urllib - proxies = urllib.getproxies() - result = {} - if 'https' not in proxies and \ - 'http' in proxies: - url = proxies['http'].replace("http://", "https://") - result['https'] = pymsn.Proxy(url) - for type, url in proxies.items(): - if type == 'no': continue - if type == 'https' and url.startswith('http://'): - url = url.replace('http://', 'https://', 1) - result[type] = pymsn.Proxy(url) - return result - - -class ClientEvents(pymsn.event.ClientEventInterface): - def on_client_state_changed(self, state): - if state == pymsn.event.ClientState.CLOSED: - self._client.quit() - elif state == pymsn.event.ClientState.OPEN: - self._client.profile.display_name = "Kimbix" - self._client.profile.personal_message = "Testing pymsn, and freeing the pandas!" - -# path = '/home/jprieur/projects/pymsn.rewrite/pymsn/service/ContentRoaming/test.jpeg' -# f = open(path, 'r') -# old_pos = f.tell() -# f.seek(0, 2) -# size = f.tell() -# f.seek(old_pos,0) - -# msn_object = \ -# pymsn.p2p.MSNObject(self._client.profile, -# size, pymsn.p2p.MSNObjectType.DISPLAY_PICTURE, -# 0, "lalala") -# msn_object._data = StringIO.StringIO(f.read()) - - self._client.profile.presence_msn_object = pymsn.Presence.ONLINE, None - self._client.profile.personal_message_current_media = "yo!", None - - gobject.timeout_add(5000, self._client.request_display_picture) - - def on_client_error(self, error_type, error): - print "ERROR :", error_type, " ->", error - -class Client(pymsn.Client): - def __init__(self, account, quit, http_mode=False): - server = ('messenger.hotmail.com', 1863) - self.quit = quit - self.account = account - if http_mode: - from pymsn.transport import HTTPPollConnection - pymsn.Client.__init__(self, server, get_proxies(), HTTPPollConnection) - else: - pymsn.Client.__init__(self, server, proxies = get_proxies()) - self.client_event_handler = ClientEvents(self) - self._p2p_session_manager = P2PSessionManager(self) - gobject.idle_add(self._connect) - - def _connect(self): - self.login(*self.account) - return False - - def request_display_picture(self): - contacts = self.address_book.contacts.\ - search_by_presence(pymsn.Presence.OFFLINE) - contacts = self.address_book.contacts - contacts - if len(contacts) == 0: - print "No online contacts" - return True - else: - contact = contacts[0] - print "CONTACT : ", contact.account, str(contact.msn_object) - if not contact.msn_object: - return True - self._msn_object_store.request(contact.msn_object, (self.__request_display_picture_callback,)) - return False - - def __request_display_picture_callback(self, msn_object): - print "Received %s" % str(msn_object) - - -def main(): - import sys - import getpass - import signal - - if "--http" in sys.argv: - http_mode = True - sys.argv.remove('--http') - else: - http_mode = False - - if len(sys.argv) < 2: - account = raw_input('Account: ') - else: - account = sys.argv[1] - - if len(sys.argv) < 3: - passwd = getpass.getpass('Password: ') - else: - passwd = sys.argv[2] - - mainloop = gobject.MainLoop(is_running=True) - - def quit(): - mainloop.quit() - - def sigterm_cb(): - gobject.idle_add(quit) - - signal.signal(signal.SIGTERM, sigterm_cb) - - n = Client((account, passwd), quit, http_mode) - - while mainloop.is_running(): - try: - mainloop.run() - except KeyboardInterrupt: - quit() - -if __name__ == '__main__': - main() diff --git a/pymsn/pymsn/msnp2p/transport/TLP.py b/pymsn/pymsn/msnp2p/transport/TLP.py deleted file mode 100644 index be4ac18d..00000000 --- a/pymsn/pymsn/msnp2p/transport/TLP.py +++ /dev/null @@ -1,248 +0,0 @@ -# -*- coding: utf-8 -*- -# -# pymsn - a python client library for Msn -# -# Copyright (C) 2007 Ali Sabil -# -# 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 pymsn.util.string_io as StringIO - -import struct -import random -import logging - -__all__ = ['MessageBlob'] - -MAX_INT32 = 2147483647 - -logger = logging.getLogger('msnp2p:transport') - -def _generate_id(max=MAX_INT32): - """ - Returns a random ID. - - @return: a random integer between 1000 and sys.maxint - @rtype: integer - """ - return random.randint(1000, max) - -_previous_chunk_id = _generate_id(MAX_INT32 - 1) -def _chunk_id(): - global _previous_chunk_id - _previous_chunk_id += 1 - if _previous_chunk_id == MAX_INT32: - _previous_chunk_id = 1 - return _previous_chunk_id - -class TLPHeader(object): - SIZE = 48 - - def __init__(self, *header): - header = list(header) - header[len(header):] = [0] * (9 - len(header)) - - self.session_id = header[0] - self.blob_id = header[1] - self.blob_offset = header[2] - self.blob_size = header[3] - self.chunk_size = header[4] - self.flags = header[5] - self.dw1 = header[6] - self.dw2 = header[7] - self.qw1 = header[8] - - def __str__(self): - return struct.pack(" 0: - total_size = len(data) - data = StringIO.StringIO(data) - else: - data = StringIO.StringIO() - - if total_size is None: - data.seek(0, 2) # relative to the end - total_size = data.tell() - data.seek(0, 0) - else: - total_size = 0 - - self.data = data - self.current_size = 0 - self.total_size = total_size - self.application_id = application_id - if session_id is None: - session_id = _generate_id() - self.session_id = session_id - self.id = blob_id or _generate_id() - - def __del__(self): - #if self.data is not None: - # self.data.close() - pass - - def __str__(self): - return repr(self) - - def __repr__(self): - return """""" % (self.id, self.id, - self.session_id, - self.current_size, - self.total_size, - self.application_id, - str(self.data)) - - @property - def transferred(self): - return self.current_size - - def is_complete(self): - return self.transferred == self.total_size - - def is_control_blob(self): - return False - - def get_chunk(self, max_size): - blob_offset = self.transferred - - if self.data is not None: - self.data.seek(blob_offset, 0) - data = self.data.read(max_size - TLPHeader.SIZE) - assert len(data) > 0, "Trying to read more data than available" - else: - data = "" - - header = TLPHeader() - header.session_id = self.session_id - header.blob_id = self.id - header.blob_offset = blob_offset - header.blob_size = self.total_size - header.chunk_size = len(data) - header.dw1 = _chunk_id() - if self.session_id != 0 and self.total_size != 4 and data != '\x00' * 4: - header.flags = TLPFlag.EACH - - chunk = MessageChunk(header, data) - chunk.application_id = self.application_id - self.current_size += header.chunk_size - return chunk - - def append_chunk(self, chunk): - assert self.data is not None, "Trying to write to a Read Only blob" - assert self.session_id == chunk.header.session_id, "Trying to append a chunk to the wrong blob" - assert self.id == chunk.header.blob_id, "Trying to append a chunk to the wrong blob" - self.data.seek(chunk.header.blob_offset, 0) - self.data.write(chunk.body) - self.data.seek(0, 2) - self.current_size = self.data.tell() - - -class ControlBlob(MessageBlob): - def __init__(self, session_id, flags, dw1=0, dw2=0, qw1=0): - MessageBlob.__init__(self, 0, None) - header = TLPHeader(session_id, self.id, 0, 0, 0, - flags, dw1, dw2, qw1) - self.chunk = MessageChunk(header, "") - - def __repr__(self): - return "" % (self.id, self.session_id) - - def get_chunk(self, max_size): - return self.chunk - - def is_control_blob(self): - return True - diff --git a/pymsn/pymsn/msnp2p/transport/__init__.py b/pymsn/pymsn/msnp2p/transport/__init__.py deleted file mode 100644 index b71ebb3d..00000000 --- a/pymsn/pymsn/msnp2p/transport/__init__.py +++ /dev/null @@ -1,23 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2006 Ali Sabil -# -# 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 -# - -"""MSNP2P transport layer""" - -from TLP import * -from transport_manager import * diff --git a/pymsn/pymsn/msnp2p/transport/base.py b/pymsn/pymsn/msnp2p/transport/base.py deleted file mode 100644 index 31ac3f70..00000000 --- a/pymsn/pymsn/msnp2p/transport/base.py +++ /dev/null @@ -1,154 +0,0 @@ -# -*- coding: utf-8 -*- -# -# pymsn - a python client library for Msn -# -# Copyright (C) 2007 Ali Sabil -# -# 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 - -from pymsn.msnp2p.transport.TLP import TLPFlag, MessageChunk, ControlBlob - -import gobject -import logging -import weakref - -__all__ = ['BaseP2PTransport'] - -logger = logging.getLogger('msnp2p:transport') - -class BaseP2PTransport(gobject.GObject): - __gsignals__ = { - "chunk-received": (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - (object,)), - - "chunk-sent": (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - (object,)), - } - - def __init__(self, transport_manager, name): - gobject.GObject.__init__(self) - self._transport_manager = weakref.proxy(transport_manager) - self._client = transport_manager._client - self._name = name - - self._transport_manager._register_transport(self) - self._reset() - - @property - def name(self): - return self._name - - @property - def peer(self): - raise NotImplementedError - - @property - def rating(self): - raise NotImplementedError - - @property - def max_chunk_size(self): - raise NotImplementedError - - def send(self, blob, callback=None, errback=None): - if blob.is_control_blob(): - self._control_blob_queue.append((blob, callback, errback)) - else: - self._data_blob_queue.append((blob, callback, errback)) - gobject.timeout_add(200, self._process_send_queues) - self._process_send_queues() - - def close(self): - self._transport_manager._unregister_transport(self) - - def _send_chunk(self, chunk): - raise NotImplementedError - - # Helper methods - def _reset(self): - self._control_blob_queue = [] - self._data_blob_queue = [] - self._pending_ack = {} # blob_id : [blob_offset1, blob_offset2 ...] - - def _add_pending_ack(self, blob_id, chunk_id=0): - if blob_id not in self._pending_ack: - self._pending_ack[blob_id] = set() - self._pending_ack[blob_id].add(chunk_id) - - def _del_pending_ack(self, blob_id, chunk_id=0): - if blob_id not in self._pending_ack: - return - self._pending_ack[blob_id].discard(chunk_id) - - if len(self._pending_ack[blob_id]) == 0: - del self._pending_ack[blob_id] - - def _on_chunk_received(self, chunk): - if chunk.require_ack(): - self._send_ack(chunk) - - if chunk.header.flags & TLPFlag.ACK: - self._del_pending_ack(chunk.header.dw1, chunk.header.dw2) - - #FIXME: handle all the other flags - - if not chunk.is_control_chunk(): - self.emit("chunk-received", chunk) - - self._process_send_queues() - - def _on_chunk_sent(self, chunk): - self.emit("chunk-sent", chunk) - self._process_send_queues() - - def _process_send_queues(self): - if len(self._control_blob_queue) > 0: - queue = self._control_blob_queue - elif len(self._data_blob_queue) > 0: - queue = self._data_blob_queue - else: - return False - - blob, callback, errback = queue[0] - chunk = blob.get_chunk(self.max_chunk_size) - if blob.is_complete(): - # FIXME: we should keep it in the queue until we receive the ACK - queue.pop(0) - if callback: - callback[0](*callback[1:]) - - if chunk.require_ack() : - self._add_pending_ack(chunk.header.blob_id, chunk.header.dw1) - self._send_chunk(chunk) - return True - - def _send_ack(self, received_chunk): - flags = received_chunk.header.flags - - flags = TLPFlag.ACK - if received_chunk.header.flags & TLPFlag.RAK: - flags |= TLPFlag.RAK - - ack_blob = ControlBlob(0, flags, - dw1 = received_chunk.header.blob_id, - dw2 = received_chunk.header.dw1, - qw1 = received_chunk.header.blob_size) - - self.send(ack_blob) - -gobject.type_register(BaseP2PTransport) - diff --git a/pymsn/pymsn/msnp2p/transport/switchboard.py b/pymsn/pymsn/msnp2p/transport/switchboard.py deleted file mode 100644 index 98ecfe88..00000000 --- a/pymsn/pymsn/msnp2p/transport/switchboard.py +++ /dev/null @@ -1,84 +0,0 @@ -# -*- coding: utf-8 -*- -# -# pymsn - a python client library for Msn -# -# Copyright (C) 2007 Ali Sabil -# -# 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 - -from pymsn.msnp.message import MessageAcknowledgement -from pymsn.msnp2p.transport.TLP import MessageChunk -from pymsn.msnp2p.transport.base import BaseP2PTransport -from pymsn.switchboard_manager import SwitchboardClient - -import gobject -import struct -import logging - -__all__ = ['SwitchboardP2PTransport'] - -logger = logging.getLogger('msnp2p:transport') - - -class SwitchboardP2PTransport(BaseP2PTransport, SwitchboardClient): - def __init__(self, client, contacts, transport_manager): - SwitchboardClient.__init__(self, client, contacts) - BaseP2PTransport.__init__(self, transport_manager, "switchboard") - - def close(self): - BaseP2PTransport.close(self) - self._leave() - - @staticmethod - def _can_handle_message(message, switchboard_client=None): - content_type = message.content_type[0] - return content_type == 'application/x-msnmsgrp2p' - - @property - def peer(self): - for peer in self.total_participants: - return peer - return None - - @property - def rating(self): - return 0 - - @property - def max_chunk_size(self): - return 1250 # length of the chunk including the header but not the footer - - def _send_chunk(self, chunk): - headers = {'P2P-Dest': self.peer.account} - content_type = 'application/x-msnmsgrp2p' - body = str(chunk) + struct.pack('>L', chunk.application_id) - self._send_message(content_type, body, headers, MessageAcknowledgement.MSNC) - - def _on_message_received(self, message): - chunk = MessageChunk.parse(message.body[:-4]) - chunk.application_id = struct.unpack('>L', message.body[-4:])[0] - self._on_chunk_received(chunk) - - def _on_message_sent(self, message): - chunk = MessageChunk.parse(message.body[:-4]) - chunk.application_id = struct.unpack('>L', message.body[-4:])[0] - self._on_chunk_sent(chunk) - - def _on_contact_joined(self, contact): - pass - - def _on_contact_left(self, contact): - self.close() - diff --git a/pymsn/pymsn/msnp2p/transport/transport_manager.py b/pymsn/pymsn/msnp2p/transport/transport_manager.py deleted file mode 100644 index d62d7f35..00000000 --- a/pymsn/pymsn/msnp2p/transport/transport_manager.py +++ /dev/null @@ -1,130 +0,0 @@ -# -*- coding: utf-8 -*- -# -# pymsn - a python client library for Msn -# -# Copyright (C) 2007 Ali Sabil -# -# 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 - -from pymsn.msnp2p.transport.switchboard import * -from pymsn.msnp2p.transport.TLP import MessageBlob - -import gobject -import struct -import logging - -__all__ = ['P2PTransportManager'] - -logger = logging.getLogger('msnp2p:transport') - - -class P2PTransportManager(gobject.GObject): - __gsignals__ = { - "blob-received" : (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - (object,)), - - "blob-sent" : (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - (object,)) - } - - def __init__(self, client): - gobject.GObject.__init__(self) - - self._client = client - switchboard_manager = self._client._switchboard_manager - switchboard_manager.register_handler(SwitchboardP2PTransport, self) - self._default_transport = \ - lambda transport_mgr, peer : \ - SwitchboardP2PTransport(client, (peer,), transport_mgr) - self._transports = set() - self._transport_signals = {} - self._signaling_blobs = {} # blob_id => blob - self._data_blobs = {} # session_id => blob - - def _register_transport(self, transport): - assert transport not in self._transports, "Trying to register transport twice" - self._transports.add(transport) - signals = [] - signals.append(transport.connect("chunk-received", self._on_chunk_received)) - signals.append(transport.connect("chunk-sent", self._on_chunk_sent)) - self._transport_signals[transport] = signals - - def _unregister_transport(self, transport): - self._transports.discard(transport) - signals = self._transport_signals[transport] - for signal in signals: - transport.disconnect(signal) - del self._transport_signals[transport] - - def _get_transport(self, peer): - for transport in self._transports: - if transport.peer == peer: - return transport - return self._default_transport(self, peer) - - def _on_chunk_received(self, transport, chunk): - session_id = chunk.header.session_id - blob_id = chunk.header.blob_id - - if session_id == 0: # signaling blob - if blob_id in self._signaling_blobs: - blob = self._signaling_blobs[blob_id] - else: - # create an in-memory blob - blob = MessageBlob(chunk.application_id, "", - chunk.header.blob_size, - session_id, chunk.header.blob_id) - self._signaling_blobs[blob_id] = blob - else: # data blob - if session_id in self._data_blobs: - blob = self._data_blobs[session_id] - if blob.transferred == 0: - blob.id = chunk.header.blob_id - else: - # create an in-memory blob - blob = MessageBlob(chunk.application_id, "", - chunk.header.blob_size, - session_id, chunk.header.blob_id) - self._data_blobs[session_id] = blob - - blob.append_chunk(chunk) - if blob.is_complete(): - blob.data.seek(0, 0) - self.emit("blob-received", blob) - if session_id == 0: - del self._signaling_blobs[blob_id] - else: - del self._data_blobs[session_id] - - def _on_chunk_sent(self, transport, chunk): - pass - - def _on_blob_sent(self, transport, blob): - self.emit("blob-sent", blob) - - def send(self, peer, blob): - transport = self._get_transport(peer) - transport.send(blob, (self._on_blob_sent, transport, blob)) - - def register_writable_blob(self, blob): - if blob.session_id in self._data_blobs: - logger.warning("registering already registered blob "\ - "with session_id=" + str(session_id)) - return - self._data_blobs[blob.session_id] = blob - -gobject.type_register(P2PTransportManager) diff --git a/pymsn/pymsn/p2p.py b/pymsn/pymsn/p2p.py deleted file mode 100644 index d6aa1da6..00000000 --- a/pymsn/pymsn/p2p.py +++ /dev/null @@ -1,264 +0,0 @@ -# -*- coding: utf-8 -*- -# -# pymsn - a python client library for Msn -# -# Copyright (C) 2007 Ali Sabil -# Copyright (C) 2007 Johann Prieur -# -# 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 - -"""P2P -This module contains the classes needed to engage in a peer to peer transfer -with a contact. - @group MSNObject: MSNObjectStore, MSNObject, MSNObjectType - @sort: MSNObjectStore, MSNObject, MSNObjectType""" - -from msnp2p import OutgoingP2PSession, EufGuid, ApplicationID -from msnp2p.exceptions import ParseError -from profile import NetworkID - -import pymsn.util.element_tree as ElementTree -import pymsn.util.string_io as StringIO - -import xml.sax.saxutils as xml -import urllib -import base64 -import sha -import logging - -__all__ = ['MSNObjectType', 'MSNObject', 'MSNObjectStore'] - -logger = logging.getLogger('p2p') - -class MSNObjectType(object): - """Represent the various MSNObject types""" - - CUSTOM_EMOTICON = 2 - "Custom smiley" - DISPLAY_PICTURE = 3 - "Display picture" - BACKGROUND_PICTURE = 5 - "Background picture" - DYNAMIC_DISPLAY_PICTURE = 7 - "Dynamic display picture" - WINK = 8 - "Wink" - VOICE_CLIP = 11 - "Void clip" - SAVED_STATE_PROPERTY = 12 - "Saved state property" - LOCATION = 14 - "Location" - -class MSNObject(object): - "Represents an MSNObject." - def __init__(self, creator, size, type, location, friendly, - shad=None, shac=None, data=None): - """Initializer - - @param creator: the creator of this MSNObject - @type creator: utf-8 encoded string representing the account - - @param size: the total size of the data represented by this MSNObject - @type size: int - - @param type: the type of the data - @type type: L{MSNObjectType} - - @param location: a filename for the MSNObject - @type location: utf-8 encoded string - - @param friendly: a friendly name for the MSNObject - @type friendly: utf-8 encoded string - - @param shad: sha1 digest of the data - - @param shac: sha1 digest of the MSNObject itself - - @param data: file object to the data represented by this MSNObject - @type data: File - """ - self._creator = creator - self._size = size - self._type = type - self._location = location - self._friendly = friendly - self._checksum_sha = shac - - if shad is None: - if data is None: - raise NotImplementedError - shad = self.__compute_data_hash(data) - self._data_sha = shad - self.__data = data - self._repr = None - - def __ne__(self, other): - return not (self == other) - - def __eq__(self, other): - if other == None: - return False - return other._type == self._type and \ - other._data_sha == self._data_sha - - def __hash__(self): - return hash(str(self._type) + self._data_sha) - - def __set_data(self, data): - if self._data_sha != self.__compute_data_hash(data): - logger.warning("Received data doesn't match the MSNObject data hash.") - return - - old_pos = data.tell() - data.seek(0, 2) - self._size = data.tell() - data.seek(old_pos, 0) - - self.__data = data - self._checksum_sha = self.__compute_checksum() - def __get_data(self): - return self.__data - _data = property(__get_data, __set_data) - - @staticmethod - def parse(client, xml_data): - data = StringIO.StringIO(xml_data) - try: - element = ElementTree.parse(data).getroot().attrib - except: - raise ParseError('Invalid MSNObject') - - try: - creator = client.address_book.contacts.\ - search_by_account(element["Creator"]).\ - search_by_network_id(NetworkID.MSN)[0] - except IndexError: - creator = None - - size = int(element["Size"]) - type = int(element["Type"]) - location = xml.unescape(element["Location"]) - friendly = base64.b64decode(xml.unescape(element["Friendly"])) - shad = element.get("SHA1D", None) - if shad is not None: - shad = base64.b64decode(shad) - shac = element.get("SHA1C", None) - if shac is not None: - shac = base64.b64decode(shac) - - result = MSNObject(creator, size, type, location, \ - friendly, shad, shac) - result._repr = xml_data - return result - - def __compute_data_hash(self, data): - digest = sha.new() - data.seek(0, 0) - read_data = data.read(1024) - while len(read_data) > 0: - digest.update(read_data) - read_data = data.read(1024) - data.seek(0, 0) - return digest.digest() - - def __compute_checksum(self): - input = "Creator%sSize%sType%sLocation%sFriendly%sSHA1D%s" % \ - (self._creator.account, str(self._size), str(self._type),\ - str(self._location), base64.b64encode(self._friendly), \ - base64.b64encode(self._data_sha)) - return sha.new(input).hexdigest() - - def __str__(self): - return self.__repr__() - - def __repr__(self): - if self._repr is not None: - return self._repr - dump = "" % \ - (xml.quoteattr(self._creator.account), - xml.quoteattr(str(self._type)), - xml.quoteattr(base64.b64encode(self._data_sha)), - xml.quoteattr(str(self._size)), - xml.quoteattr(str(self._location)), - xml.quoteattr(base64.b64encode(self._friendly))) - return dump - - -class MSNObjectStore(object): - - def __init__(self, client): - self._client = client - self._outgoing_sessions = {} # session => (handle_id, callback, errback) - self._incoming_sessions = {} - self._published_objects = set() - self._client._p2p_session_manager.connect("incoming-session", - self._incoming_session_received) - - def request(self, msn_object, callback, errback=None): - if msn_object._data is not None: - callback[0](msn_object, *callback[1:]) - - if msn_object._type == MSNObjectType.CUSTOM_EMOTICON: - application_id = ApplicationID.CUSTOM_EMOTICON_TRANSFER - elif msn_object._type == MSNObjectType.DISPLAY_PICTURE: - application_id = ApplicationID.DISPLAY_PICTURE_TRANSFER - else: - raise NotImplementedError - - session = OutgoingP2PSession(self._client._p2p_session_manager, - msn_object._creator, msn_object, - EufGuid.MSN_OBJECT, application_id) - handle_id = session.connect("transfer-completed", - self._outgoing_session_transfer_completed) - self._outgoing_sessions[session] = \ - (handle_id, callback, errback, msn_object) - - def publish(self, msn_object): - if msn_object._data is None: - logger.warning("Trying to publish an empty MSNObject") - else: - self._published_objects.add(msn_object) - - def _outgoing_session_transfer_completed(self, session, data): - handle_id, callback, errback, msn_object = self._outgoing_sessions[session] - session.disconnect(handle_id) - msn_object._data = data - - callback[0](msn_object, *callback[1:]) - del self._outgoing_sessions[session] - - def _incoming_session_received(self, session_manager, session): - if session._euf_guid != EufGuid.MSN_OBJECT: - return - handle_id = session.connect("transfer-completed", - self._incoming_session_transfer_completed) - self._incoming_sessions[session] = handle_id - try: - msn_object = MSNObject.parse(self._client, session._context) - except ParseError: - session.reject() - return - for obj in self._published_objects: - if obj._data_sha == msn_object._data_sha: - session.accept(obj._data) - return - session.reject() - - def _incoming_session_transfer_completed(self, session, data): - handle_id = self._incoming_sessions[session] - session.disconnect(handle_id) - del self._incoming_sessions[session] - diff --git a/pymsn/pymsn/profile.py b/pymsn/pymsn/profile.py deleted file mode 100644 index 83f5fb74..00000000 --- a/pymsn/pymsn/profile.py +++ /dev/null @@ -1,805 +0,0 @@ -# -*- coding: utf-8 -*- -# -# pymsn - a python client library for Msn -# -# Copyright (C) 2005-2006 Ali Sabil -# Copyright (C) 2007-2008 Johann Prieur -# -# 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 - -"""Profile of the User connecting to the service, as well as the profile of -contacts in his/her contact list. - - @sort: Profile, Contact, Group, ClientCapabilities - @group Enums: Presence, Membership, Privacy, NetworkID - @sort: Presence, Membership, Privacy, NetworkID""" - -from pymsn.util.decorator import rw_property - -import gobject - -__all__ = ['Profile', 'Contact', 'Group', - 'Presence', 'Membership', 'ContactType', 'Privacy', 'NetworkID', 'ClientCapabilities'] - - -class ClientCapabilities(object): - """Capabilities of the client. This allow adverstising what the User Agent - is capable of, for example being able to receive video stream, and being - able to receive nudges... - - @ivar is_bot: is the client a bot - @type is_bot: bool - - @ivar is_mobile_device: is the client running on a mobile device - @type is_mobile_device: bool - - @ivar is_msn_mobile: is the client an MSN Mobile device - @type is_msn_mobile: bool - - @ivar is_msn_direct_device: is the client an MSN Direct device - @type is_msn_direct_device: bool - - @ivar is_media_center_user: is the client running on a Media Center - @type is_media_center_user: bool - - @ivar is_msn8_user: is the client using WLM 8 - @type is_msn8_user: bool - - @ivar is_web_client: is the client web based - @type is_web_client: bool - - @ivar is_tgw_client: is the client a gateway - @type is_tgw_client: bool - - @ivar has_space: does the user has a space account - @type has_space: bool - - @ivar has_webcam: does the user has a webcam plugged in - @type has_webcam: bool - - @ivar has_onecare: does the user has the OneCare service - @type has_onecare: bool - - @ivar renders_gif: can the client render gif (for ink) - @type renders_gif: bool - - @ivar renders_isf: can the client render ISF (for ink) - @type renders_isf: bool - - @ivar supports_chunking: does the client supports chunking messages - @type supports_chunking: bool - - @ivar supports_direct_im: does the client supports direct IM - @type supports_direct_im: bool - - @ivar supports_winks: does the client supports Winks - @type supports_winks: bool - - @ivar supports_shared_search: does the client supports Shared Search - @type supports_shared_search: bool - - @ivar supports_voice_im: does the client supports voice clips - @type supports_voice_im: bool - - @ivar supports_secure_channel: does the client supports secure channels - @type supports_secure_channel: bool - - @ivar supports_sip_invite: does the client supports SIP - @type supports_sip_invite: bool - - @ivar supports_shared_drive: does the client supports File sharing - @type supports_shared_drive: bool - - @ivar p2p_supports_turn: does the client supports TURN for p2p transfer - @type p2p_supports_turn: bool - - @ivar p2p_bootstrap_via_uun: is the client able to use and understand UUN commands - @type p2p_bootstrap_via_uun: bool - - @undocumented: __getattr__, __setattr__, __str__ - """ - - _CAPABILITIES = { - 'is_bot': 0x00020000, - 'is_mobile_device': 0x00000001, - 'is_msn_mobile': 0x00000040, - 'is_msn_direct_device': 0x00000080, - - 'is_media_center_user': 0x00002000, - 'is_msn8_user': 0x00000002, - - 'is_web_client': 0x00000200, - 'is_tgw_client': 0x00000800, - - 'has_space': 0x00001000, - 'has_webcam': 0x00000010, - 'has_onecare': 0x01000000, - - 'renders_gif': 0x00000004, - 'renders_isf': 0x00000008, - - 'supports_chunking': 0x00000020, - 'supports_direct_im': 0x00004000, - 'supports_winks': 0x00008000, - 'supports_shared_search': 0x00010000, - 'supports_voice_im': 0x00040000, - 'supports_secure_channel': 0x00080000, - 'supports_sip_invite': 0x00100000, - 'supports_shared_drive': 0x00400000, - - 'p2p_supports_turn': 0x02000000, - 'p2p_bootstrap_via_uun': 0x04000000 - } - - def __init__(self, msnc=0, client_id=0): - """Initializer - - @param msnc: The MSNC version - @type msnc: integer < 8 and >= 0 - - @param client_id: the full client ID""" - MSNC = (0x0, # MSNC0 - 0x10000000, # MSNC1 - 0x20000000, # MSNC2 - 0x30000000, # MSNC3 - 0x40000000, # MSNC4 - 0x50000000, # MSNC5 - 0x60000000, # MSNC6 - 0x70000000) # MSNC7 - object.__setattr__(self, 'client_id', MSNC[msnc] | client_id) - - def __getattr__(self, name): - if name == "p2p_aware": - mask = 0xf0000000 - elif name in self._CAPABILITIES: - mask = self._CAPABILITIES[name] - else: - raise AttributeError("object 'ClientCapabilities' has no attribute '%s'" % name) - return (self.client_id & mask != 0) - - def __setattr__(self, name, value): - if name in self._CAPABILITIES: - mask = self._CAPABILITIES[name] - if value: - object.__setattr__(self, 'client_id', self.client_id | mask) - else: - object.__setattr__(self, 'client_id', self.client_id ^ mask) - else: - raise AttributeError("object 'ClientCapabilities' has no attribute '%s'" % name) - - def __str__(self): - return str(self.client_id) - - -class NetworkID(object): - """Refers to the contact Network ID""" - - MSN = 1 - """Microsoft Network""" - - LCS = 2 - """Microsoft Live Communication Server""" - - MOBILE = 4 - """Mobile phones""" - - EXTERNAL = 32 - """External IM etwork, currently Yahoo!""" - - -class Presence(object): - """Presence states. - - The members of this class are used to identify the Presence that a user - wants to advertise to the contacts on his/her contact list. - - @cvar ONLINE: online - @cvar BUSY: busy - @cvar IDLE: idle - @cvar AWAY: away - @cvar BE_RIGHT_BACK: be right back - @cvar ON_THE_PHONE: on the phone - @cvar OUT_TO_LUNCH: out to lunch - @cvar INVISIBLE: status hidden from contacts - @cvar OFFLINE: offline""" - ONLINE = 'NLN' - BUSY = 'BSY' - IDLE = 'IDL' - AWAY = 'AWY' - BE_RIGHT_BACK = 'BRB' - ON_THE_PHONE = 'PHN' - OUT_TO_LUNCH = 'LUN' - INVISIBLE = 'HDN' - OFFLINE = 'FLN' - - -class Privacy(object): - """User privacy, defines the default policy concerning contacts not - belonging to the ALLOW list nor to the BLOCK list. - - @cvar ALLOW: allow by default - @cvar BLOCK: block by default""" - ALLOW = 'AL' - BLOCK = 'BL' - - -class Membership(object): - """Contact Membership""" - - NONE = 0 - """Contact doesn't belong to the contact list, but belongs to the address book""" - - FORWARD = 1 - """Contact belongs to our contact list""" - - ALLOW = 2 - """Contact is explicitely allowed to see our presence regardless of the - currently set L{Privacy}""" - - BLOCK = 4 - """Contact is explicitely forbidden from seeing our presence regardless of - the currently set L{Privacy}""" - - REVERSE = 8 - """We belong to the FORWARD list of the contact""" - - PENDING = 16 - """Contact pending""" - - -class ContactType(object): - """Automatic update status flag""" - - ME = "Me" - """Contact is the user so there's no automatic update relationship""" - - EXTERNAL = "Messenger2" - """Contact is part of an external messenger service so there's no automatic - update relationship with the user""" - - REGULAR = "Regular" - """Contact has no automatic update relationship with the user""" - - LIVE = "Live" - """Contact has an automatic update relationship with the user and an - automatic update already occured""" - - LIVE_PENDING = "LivePending" - """Contact was requested automatic update from the user and didn't - give its authorization yet""" - - LIVE_REJECTED = "LiveRejected" - """Contact was requested automatic update from the user and rejected - the request""" - - LIVE_DROPPED = "LiveDropped" - """Contact had an automatic update relationship with the user but - the contact dropped it""" - - -class Profile(gobject.GObject): - """Profile of the User connecting to the service - - @undocumented: __gsignals__, __gproperties__, do_get_property""" - - __gproperties__ = { - "display-name": (gobject.TYPE_STRING, - "Friendly name", - "A nickname that the user chooses to display to others", - "", - gobject.PARAM_READABLE), - - "personal-message": (gobject.TYPE_STRING, - "Personal message", - "The personal message that the user wants to display", - "", - gobject.PARAM_READABLE), - - "current-media": (gobject.TYPE_PYOBJECT, - "Current media", - "The current media that the user wants to display", - gobject.PARAM_READABLE), - - "profile": (gobject.TYPE_STRING, - "Profile", - "the text/x-msmsgsprofile sent by the server", - "", - gobject.PARAM_READABLE), - - "presence": (gobject.TYPE_STRING, - "Presence", - "The presence to show to others", - Presence.OFFLINE, - gobject.PARAM_READABLE), - - "privacy": (gobject.TYPE_STRING, - "Privacy", - "The privacy policy to use", - Privacy.BLOCK, - gobject.PARAM_READABLE), - - "msn-object": (gobject.TYPE_STRING, - "MSN Object", - "MSN Object attached to the user, this generally represent " - "its display picture", - "", - gobject.PARAM_READABLE), - } - - def __init__(self, account, ns_client): - gobject.GObject.__init__(self) - self._ns_client = ns_client - self._account = account[0] - self._password = account[1] - - self._profile = "" - self._display_name = self._account.split("@", 1)[0] - self._presence = Presence.OFFLINE - self._privacy = Privacy.BLOCK - self._personal_message = "" - self._current_media = None - - self.client_id = ClientCapabilities(7) - #self.client_id.supports_sip_invite = True - #FIXME: this should only be advertised when a webcam is plugged - #self.client_id.has_webcam = True - - self._msn_object = None - - self.__pending_set_presence = [self._presence, self.client_id, self._msn_object] - self.__pending_set_personal_message = [self._personal_message, self._current_media] - - @property - def account(self): - """The user account - @type: utf-8 encoded string""" - return self._account - - @property - def password(self): - """The user password - @type: utf-8 encoded string""" - return self._password - - @property - def profile(self): - """The user profile retrieved from the MSN servers - @type: utf-8 encoded string""" - return self._profile - - @property - def id(self): - """The user identifier in a GUID form - @type: GUID string""" - return "00000000-0000-0000-0000-000000000000" - - @rw_property - def display_name(): - """The display name shown to you contacts - @type: utf-8 encoded string""" - def fset(self, display_name): - if not display_name: - return - self._ns_client.set_display_name(display_name) - def fget(self): - return self._display_name - return locals() - - @rw_property - def presence(): - """The presence displayed to you contacts - @type: L{Presence}""" - def fset(self, presence): - if presence == self._presence: - return - self.__pending_set_presence[0] = presence - self._ns_client.set_presence(*self.__pending_set_presence) - def fget(self): - return self._presence - return locals() - - @rw_property - def privacy(): - """The default privacy, can be either Privacy.ALLOW or Privacy.BLOCK - @type: L{Privacy}""" - def fset(self, privacy): - pass #FIXME: set the privacy setting - def fget(self): - return self._privacy - return locals() - - @rw_property - def personal_message(): - """The personal message displayed to you contacts - @type: utf-8 encoded string""" - def fset(self, personal_message): - if personal_message == self._personal_message: - return - self.__pending_set_personal_message[0] = personal_message - self._ns_client.set_personal_message(*self.__pending_set_personal_message) - def fget(self): - return self._personal_message - return locals() - - @rw_property - def current_media(): - """The current media displayed to you contacts - @type: (artist: string, track: string)""" - def fset(self, current_media): - if current_media == self._current_media: - return - self.__pending_set_personal_message[1] = current_media - self._ns_client.set_personal_message(*self.__pending_set_personal_message) - def fget(self): - return self._current_media - return locals() - - @rw_property - def msn_object(): - """The MSNObject attached to your contact, this MSNObject represents the - display picture to be shown to your peers - @type: L{MSNObject}""" - def fset(self, msn_object): - if msn_object == self._msn_object: - return - self.__pending_set_presence[2] = msn_object - self._ns_client.set_presence(*self.__pending_set_presence) - def fget(self): - return self._msn_object - return locals() - - @rw_property - def presence_msn_object(): - def fset(self, args): - presence, msn_object = args - if presence == self._presence and msn_object == self._msn_object: - return - self.__pending_set_presence[0] = presence - self.__pending_set_presence[2] = msn_object - self._ns_client.set_presence(*self.__pending_set_presence) - def fget(self): - return self._presence, self._msn_object - return locals() - - @rw_property - def personal_message_current_media(): - def fset(self, args): - personal_message, current_media = args - if personal_message == self._personal_message and \ - current_media == self._current_media: - return - self.__pending_set_personal_message[0] = personal_message - self.__pending_set_personal_message[1] = current_media - self._ns_client.set_personal_message(*self.__pending_set_personal_message) - def fget(self): - return self._personal_message, self._current_media - return locals() - - def _server_property_changed(self, name, value): - attr_name = "_" + name.lower().replace("-", "_") - if attr_name == "_msn_object" and value is not None: - value = self.__pending_set_presence[2] - old_value = getattr(self, attr_name) - if value != old_value: - setattr(self, attr_name, value) - self.notify(name) - - def do_get_property(self, pspec): - name = pspec.name.lower().replace("-", "_") - return getattr(self, name) -gobject.type_register(Profile) - - -class Contact(gobject.GObject): - """Contact related information - @undocumented: __gsignals__, __gproperties__, do_get_property""" - - __gsignals__ = { - "infos-changed": (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - (object,)), - } - - __gproperties__ = { - "memberships": (gobject.TYPE_UINT, - "Memberships", - "Membership relation with the contact.", - 0, 15, 0, gobject.PARAM_READABLE), - - "display-name": (gobject.TYPE_STRING, - "Friendly name", - "A nickname that the user chooses to display to others", - "", - gobject.PARAM_READWRITE), - - "personal-message": (gobject.TYPE_STRING, - "Personal message", - "The personal message that the user wants to display", - "", - gobject.PARAM_READABLE), - - "current-media": (gobject.TYPE_PYOBJECT, - "Current media", - "The current media that the user wants to display", - gobject.PARAM_READABLE), - - "presence": (gobject.TYPE_STRING, - "Presence", - "The presence to show to others", - Presence.OFFLINE, - gobject.PARAM_READABLE), - - "groups": (gobject.TYPE_PYOBJECT, - "Groups", - "The groups the contact belongs to", - gobject.PARAM_READABLE), - - "infos": (gobject.TYPE_PYOBJECT, - "Informations", - "The contact informations", - gobject.PARAM_READABLE), - - "contact-type": (gobject.TYPE_PYOBJECT, - "Contact type", - "The contact automatic update status flag", - gobject.PARAM_READABLE), - - "client-capabilities": (gobject.TYPE_UINT64, - "Client capabilities", - "The client capabilities of the contact 's client", - 0, 0xFFFFFFFF, 0, - gobject.PARAM_READABLE), - - "msn-object": (gobject.TYPE_STRING, - "MSN Object", - "MSN Object attached to the contact, this generally represent " - "its display picture", - "", - gobject.PARAM_READABLE), - } - - def __init__(self, id, network_id, account, display_name, cid=None, - memberships=Membership.NONE, contact_type=ContactType.REGULAR): - """Initializer""" - gobject.GObject.__init__(self) - self._id = id - self._cid = cid or "00000000-0000-0000-0000-000000000000" - self._network_id = network_id - self._account = account - - self._display_name = display_name - self._presence = Presence.OFFLINE - self._personal_message = "" - self._current_media = None - self._groups = set() - - self._memberships = memberships - self._contact_type = contact_type - self._client_capabilities = ClientCapabilities() - self._msn_object = None - self._infos = {} - self._attributes = {'icon_url' : None} - - def __repr__(self): - def memberships_str(): - m = [] - memberships = self._memberships - if memberships & Membership.FORWARD: - m.append('FORWARD') - if memberships & Membership.ALLOW: - m.append('ALLOW') - if memberships & Membership.BLOCK: - m.append('BLOCK') - if memberships & Membership.REVERSE: - m.append('REVERSE') - if memberships & Membership.PENDING: - m.append('PENDING') - return " | ".join(m) - template = "" - return template % (self._id, self._network_id, self._account, memberships_str()) - - @property - def id(self): - """Contact identifier in a GUID form - @type: GUID string""" - return self._id - - @property - def attributes(self): - """Contact attributes - @type: {key: string => value: string}""" - return self._attributes.copy() - - @property - def cid(self): - """Contact ID - @type: GUID string""" - return self._cid - - @property - def network_id(self): - """Contact network ID - @type: L{NetworkID}""" - return self._network_id - - @property - def account(self): - """Contact account - @type: utf-8 encoded string""" - return self._account - - @property - def presence(self): - """Contact presence - @type: L{Presence}""" - return self._presence - - @property - def display_name(self): - """Contact display name - @type: utf-8 encoded string""" - return self._display_name - - @property - def personal_message(self): - """Contact personal message - @type: utf-8 encoded string""" - return self._personal_message - - @property - def current_media(self): - """Contact current media - @type: (artist: string, track: string)""" - return self._current_media - - @property - def groups(self): - """Contact list of groups - @type: set(L{Group}...)""" - return self._groups - - @property - def infos(self): - """Contact informations - @type: {key: string => value: string}""" - return self._infos - - @property - def memberships(self): - """Contact membership value - @type: bitmask of L{Membership}s""" - return self._memberships - - @property - def contact_type(self): - """Contact automatic update status flag - @type: L{ContactType}""" - return self._contact_type - - @property - def client_capabilities(self): - """Contact client capabilities - @type: L{ClientCapabilities}""" - return self._client_capabilities - - @property - def msn_object(self): - """Contact MSN Object - @type: L{MSNObject}""" - return self._msn_object - - @property - def domain(self): - """Contact domain, which is basically the part after @ in the account - @type: utf-8 encoded string""" - result = self._account.split('@', 1) - if len(result) > 1: - return result[1] - else: - return "" - - ### membership management - def is_member(self, memberships): - """Determines if this contact belongs to the specified memberships - @type memberships: bitmask of L{Membership}s""" - return (self.memberships & memberships) == memberships - - def _set_memberships(self, memberships): - self._memberships = memberships - self.notify("memberships") - - def _add_membership(self, membership): - self._memberships |= membership - self.notify("memberships") - - def _remove_membership(self, membership): - """removes the given membership from the contact - - @param membership: the membership to remove - @type membership: int L{Membership}""" - self._memberships ^= membership - self.notify("memberships") - - def _server_property_changed(self, name, value): #FIXME, should not be used for memberships - if name == "client-capabilities": - value = ClientCapabilities(client_id=value) - attr_name = "_" + name.lower().replace("-", "_") - old_value = getattr(self, attr_name) - if value != old_value: - setattr(self, attr_name, value) - self.notify(name) - - def _server_attribute_changed(self, name, value): - self._attributes[name] = value - - def _server_infos_changed(self, updated_infos): - self._infos.update(updated_infos) - self.emit("infos-changed", updated_infos) - self.notify("infos") - - ### group management - def _add_group_ownership(self, group): - self._groups.add(group) - - def _delete_group_ownership(self, group): - self._groups.discard(group) - - def do_get_property(self, pspec): - name = pspec.name.lower().replace("-", "_") - return getattr(self, name) -gobject.type_register(Contact) - - -class Group(gobject.GObject): - """Group - @undocumented: __gsignals__, __gproperties__, do_get_property""" - - __gproperties__ = { - "name": (gobject.TYPE_STRING, - "Group name", - "Name that the user chooses for the group", - "", - gobject.PARAM_READABLE) - } - - def __init__(self, id, name): - """Initializer""" - gobject.GObject.__init__(self) - self._id = id - self._name = name - - @property - def id(self): - """Group identifier in a GUID form - @type: GUID string""" - return self._id - - @property - def name(self): - """Group name - @type: utf-8 encoded string""" - return self._name - - def _server_property_changed(self, name, value): - attr_name = "_" + name.lower().replace("-", "_") - old_value = getattr(self, attr_name) - if value != old_value: - setattr(self, attr_name, value) - self.notify(name) - - def do_get_property(self, pspec): - name = pspec.name.lower().replace("-", "_") - return getattr(self, name) -gobject.type_register(Group) diff --git a/pymsn/pymsn/service/AddressBook/__init__.py b/pymsn/pymsn/service/AddressBook/__init__.py deleted file mode 100644 index 0a673205..00000000 --- a/pymsn/pymsn/service/AddressBook/__init__.py +++ /dev/null @@ -1,22 +0,0 @@ -# -*- coding: utf-8 -*- -# -# pymsn - a python client library for Msn -# -# Copyright (C) 2007 Johann Prieur -# -# 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 - -from constants import * -from address_book import * diff --git a/pymsn/pymsn/service/AddressBook/ab.py b/pymsn/pymsn/service/AddressBook/ab.py deleted file mode 100644 index 81841b49..00000000 --- a/pymsn/pymsn/service/AddressBook/ab.py +++ /dev/null @@ -1,429 +0,0 @@ -# -*- coding: utf-8 -*- -# -# pymsn - a python client library for Msn -# -# Copyright (C) 2007-2008 Johann Prieur -# -# 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 - -from pymsn.service.SOAPService import SOAPService -from pymsn.util.element_tree import XMLTYPE -from pymsn.service.SingleSignOn import * -from pymsn.service.AddressBook.common import * - -from pymsn.service.description.AB.constants import ContactGeneral - -__all__ = ['AB'] - -class ABResult(object): - """ABFindAll Result object""" - def __init__(self, ab, contacts, groups): - self.ab = ab - self.contacts = contacts - self.groups = groups - -class Group(object): - def __init__(self, group): - self.Id = group.findtext("./ab:groupId") - - group_info = group.find("./ab:groupInfo") - - self.Type = group_info.findtext("./ab:groupType") - self.Name = group_info.findtext("./ab:name") - self.IsNotMobileVisible = group_info.findtext("./ab:IsNotMobileVisible", "bool") - self.IsPrivate = group_info.findtext("./ab:IsPrivate", "bool") - - self.Annotations = annotations_to_dict(group_info.find("./ab:Annotations")) - - self.PropertiesChanged = [] #FIXME: implement this - self.Deleted = group.findtext("./ab:fDeleted", "bool") - self.LastChanged = group.findtext("./ab:lastChange", "bool") - - def __hash__(self): - return hash(self.Id) - - def __eq__(self, other): - return self.Id == other.Id - - def __repr__(self): - return "" % self.Id - -class ContactEmail(object): - def __init__(self, email): - self.Type = email.findtext("./ab:contactEmailType") - self.Email = email.findtext("./ab:email") - self.IsMessengerEnabled = email.findtext("./ab:isMessengerEnabled", "bool") - self.Capability = email.findtext("./ab:Capability", "int") - self.MessengerEnabledExternally = email.findtext("./ab:MessengerEnabledExternally", "bool") - -class ContactPhone(object): - def __init__(self, phone): - self.Type = phone.findtext("./ab:contactPhoneType") - self.Number = phone.findtext("./ab:number") - self.IsMessengerEnabled = phone.findtext("./ab:isMessengerEnabled", "bool") - self.PropertiesChanged = phone.findtext("./ab:propertiesChanged").split(' ') - -class ContactLocation(object): - def __init__(self, location): - self.Type = location.findtext("./ab:contactLocationType") - self.Name = location.findtext("./ab:name") - self.City = location.findtext("./ab:city") - self.Country = location.findtext("./ab:country") - self.PostalCode = location.findtext("./ab:postalcode") - self.Changes = location.findtext("./ab:Changes").split(' ') - -class Contact(object): - def __init__(self, contact): - self.Id = contact.findtext("./ab:contactId") - - contact_info = contact.find("./ab:contactInfo") - - self.Groups = [] - groups = contact_info.find("./ab:groupIds") - if groups is not None: - for group in groups: - self.Groups.append(group.text) - - self.Type = contact_info.findtext("./ab:contactType") - self.QuickName = contact_info.findtext("./ab:quickName") - self.PassportName = contact_info.findtext("./ab:passportName") - self.DisplayName = contact_info.findtext("./ab:displayName") - self.IsPassportNameHidden = contact_info.findtext("./ab:IsPassportNameHidden", "bool") - - self.FirstName = contact_info.findtext("./ab:firstName") - self.LastName = contact_info.findtext("./ab:lastName") - - self.PUID = contact_info.findtext("./ab:puid", "int") - self.CID = contact_info.findtext("./ab:CID", "int") - - self.IsNotMobileVisible = contact_info.findtext("./ab:IsNotMobileVisible", "bool") - self.IsMobileIMEnabled = contact_info.findtext("./ab:isMobileIMEnabled", "bool") - self.IsMessengerUser = contact_info.findtext("./ab:isMessengerUser", "bool") - self.IsFavorite = contact_info.findtext("./ab:isFavorite", "bool") - self.IsSmtp = contact_info.findtext("./ab:isSmtp", "bool") - self.HasSpace = contact_info.findtext("./ab:hasSpace", "bool") - - self.SpotWatchState = contact_info.findtext("./ab:spotWatchState") - self.Birthdate = contact_info.findtext("./ab:birthdate", "datetime") - - self.PrimaryEmailType = contact_info.findtext("./ab:primaryEmailType") - self.PrimaryLocation = contact_info.findtext("./ab:PrimaryLocation") - self.PrimaryPhone = contact_info.findtext("./ab:primaryPhone") - - self.IsPrivate = contact_info.findtext("./ab:IsPrivate", "bool") - self.Gender = contact_info.findtext("./ab:Gender") - self.TimeZone = contact_info.findtext("./ab:TimeZone") - - self.Annotations = annotations_to_dict(contact_info.find("./ab:annotations")) - - self.Emails = [] - emails = contact_info.find("./ab:emails") or [] - for contact_email in emails: - self.Emails.append(ContactEmail(contact_email)) - - self.PropertiesChanged = [] #FIXME: implement this - self.Deleted = contact.findtext("./ab:fDeleted", "bool") - self.LastChanged = contact.findtext("./ab:lastChanged", "datetime") - - -class AB(SOAPService): - def __init__(self, sso, proxies=None): - self._sso = sso - self._tokens = {} - SOAPService.__init__(self, "AB", proxies) - - self._last_changes = "0001-01-01T00:00:00.0000000-08:00" - - @RequireSecurityTokens(LiveService.CONTACTS) - def Add(self, callback, errback, scenario, account): - """Creates the address book on the server. - - @param callback: tuple(callable, *args) - @param errback: tuple(callable, *args) - @param scenario: "Initial" - @param account: the owner account""" - self.__soap_request(self._service.ABAll, scenario, (account,), - callback, errback) - - def _HandleABAddResponse(self, callback, errback, response, user_data): - return None - - @RequireSecurityTokens(LiveService.CONTACTS) - def FindAll(self, callback, errback, scenario, deltas_only): - """Requests the contact list. - @param scenario: "Initial" | "ContactSave" ... - @param deltas_only: True if the method should only check changes - since last_change, otherwise False - @param last_change: an ISO 8601 timestamp - (previously sent by the server), or - 0001-01-01T00:00:00.0000000-08:00 to get the whole list - @param callback: tuple(callable, *args) - @param errback: tuple(callable, *args)""" - self.__soap_request(self._service.ABFindAll, scenario, - (XMLTYPE.bool.encode(deltas_only), self._last_changes), - callback, errback) - - def _HandleABFindAllResponse(self, callback, errback, response, user_data): - last_changes = response[0].find("./ab:lastChange") - if last_changes is not None: - self._last_changes = last_changes.text - - groups = [] - contacts = [] - for group in response[1]: - groups.append(Group(group)) - - for contact in response[2]: - contacts.append(Contact(contact)) - - address_book = ABResult(None, contacts, groups) #FIXME: add support for the ab param - callback[0](address_book, *callback[1:]) - - @RequireSecurityTokens(LiveService.CONTACTS) - def ContactAdd(self, callback, errback, scenario, - contact_info, invite_info, auto_manage_allow_list=True): - """Adds a contact to the contact list. - - @param scenario: "ContactSave" | "ContactMsgrAPI" - @param contact_info: info dict concerning the new contact - @param invite_info: info dict concerning the sent invite - @param auto_manage_allow_list: whether to auto add to Allow role or not - @param callback: tuple(callable, *args) - @param errback: tuple(callable, *args) - """ - is_messenger_user = contact_info.get('is_messenger_user', None) - if is_messenger_user is not None: - is_messenger_user = XMLTYPE.bool.encode(is_messenger_user) - self.__soap_request(self._service.ABContactAdd, scenario, - (contact_info.get('passport_name', None), - is_messenger_user, - contact_info.get('contact_type', None), - contact_info.get('first_name', None), - contact_info.get('last_name', None), - contact_info.get('birth_date', None), - contact_info.get('email', None), - contact_info.get('phone', None), - contact_info.get('location', None), - contact_info.get('web_site', None), - contact_info.get('annotation', None), - contact_info.get('comment', None), - contact_info.get('anniversary', None), - invite_info.get('display_name', ''), - invite_info.get('invite_message', ''), - contact_info.get('capability', None), - auto_manage_allow_list), - callback, errback) - - def _HandleABContactAddResponse(self, callback, errback, response, user_data): - callback[0](response.text, *callback[1:]) - - @RequireSecurityTokens(LiveService.CONTACTS) - def ContactDelete(self, callback, errback, scenario, - contact_id): - """Deletes a contact from the contact list. - - @param scenario: "Timer" | ... - @param contact_id: the contact id (a GUID) - @param callback: tuple(callable, *args) - @param errback: tuple(callable, *args) - """ - self.__soap_request(self._service.ABContactDelete, scenario, - (contact_id,), callback, errback) - - def _HandleABContactDeleteResponse(self, callback, errback, response, user_data): - callback[0](*callback[1:]) - - @RequireSecurityTokens(LiveService.CONTACTS) - def ContactUpdate(self, callback, errback, - scenario, contact_id, contact_info, - enable_allow_list_management=False): - """Updates a contact informations. - - @param scenario: "ContactSave" | "Timer" | ... - @param contact_id: the contact id (a GUID) - @param contact_info: info dict - @param callback: tuple(callable, *args) - @param errback: tuple(callable, *args) - """ - if 'is_messenger_user' in contact_info: - contact_info['is_messenger_user'] = \ - XMLTYPE.bool.encode(contact_info['is_messenger_user']) - - self.__soap_request(self._service.ABContactUpdate, scenario, - (contact_id, - contact_info.get('display_name', None), - contact_info.get('is_messenger_user', None), - contact_info.get('contact_type', None), - contact_info.get(ContactGeneral.FIRST_NAME, None), - contact_info.get(ContactGeneral.LAST_NAME, None), - contact_info.get(ContactGeneral.BIRTH_DATE, None), - contact_info.get(ContactGeneral.EMAILS, None), - contact_info.get(ContactGeneral.PHONES, None), - contact_info.get(ContactGeneral.LOCATIONS, None), - contact_info.get(ContactGeneral.WEBSITES, None), - contact_info.get(ContactGeneral.ANNOTATIONS, None), - contact_info.get(ContactGeneral.COMMENT, None), - contact_info.get(ContactGeneral.ANNIVERSARY, None), - contact_info.get('has_space', None), - enable_allow_list_management), - callback, errback) - - def _HandleABContactUpdateResponse(self, callback, errback, response, user_data): - callback[0](*callback[1:]) - - @RequireSecurityTokens(LiveService.CONTACTS) - def GroupAdd(self, callback, errback, scenario, - group_name): - """Adds a group to the address book. - - @param scenario: "GroupSave" | ... - @param group_name: the name of the group - @param callback: tuple(callable, *args) - @param errback: tuple(callable, *args) - """ - self.__soap_request(self._service.ABGroupAdd, scenario, - (group_name,), - callback, errback) - - def _HandleABGroupAddResponse(self, callback, errback, response, user_data): - callback[0](response.text, *callback[1:]) - - @RequireSecurityTokens(LiveService.CONTACTS) - def GroupDelete(self, callback, errback, scenario, - group_id): - """Deletes a group from the address book. - - @param scenario: "Timer" | ... - @param group_id: the id of the group (a GUID) - @param callback: tuple(callable, *args) - @param errback: tuple(callable, *args) - """ - self.__soap_request(self._service.ABGroupDelete, scenario, - (group_id,), callback, errback) - - def _HandleABGroupDeleteResponse(self, callback, errback, response, user_data): - callback[0](*callback[1:]) - - @RequireSecurityTokens(LiveService.CONTACTS) - def GroupUpdate(self, callback, errback, scenario, - group_id, group_name): - """Updates a group name. - - @param scenario: "GroupSave" | ... - @param group_id: the id of the group (a GUID) - @param group_name: the new name for the group - @param callback: tuple(callable, *args) - @param errback: tuple(callable, *args) - """ - self.__soap_request(self._service.ABGroupUpdate, scenario, - (group_id, group_name), callback, errback) - - def _HandleABGroupUpdateResponse(self, callback, errback, response, user_data): - callback[0](*callback[1:]) - - @RequireSecurityTokens(LiveService.CONTACTS) - def GroupContactAdd(self, callback, errback, scenario, - group_id, contact_id): - """Adds a contact to a group. - - @param scenario: "GroupSave" | ... - @param group_id: the id of the group (a GUID) - @param contact_id: the id of the contact to add to the - group (a GUID) - @param callback: tuple(callable, *args) - @param errback: tuple(callable, *args) - """ - self.__soap_request(self._service.ABGroupContactAdd, scenario, - (group_id, contact_id), callback, errback) - - def _HandleABGroupContactAddResponse(self, callback, errback, response, user_data): - callback[0](*callback[1:]) - - @RequireSecurityTokens(LiveService.CONTACTS) - def GroupContactDelete(self, callback, errback, scenario, - group_id, contact_id): - """Deletes a contact from a group. - - @param scenario: "GroupSave" | ... - @param group_id: the id of the group (a GUID) - @param contact_id: the id of the contact to delete from the - group (a GUID) - @param callback: tuple(callable, *args) - @param errback: tuple(callable, *args) - """ - self.__soap_request(self._service.ABGroupContactDelete, scenario, - (group_id, contact_id), callback, errback) - - def _HandleABGroupContactDeleteResponse(self, callback, errback, response, user_data): - callback[0](*callback[1:]) - - def __soap_request(self, method, scenario, args, callback, errback): - token = str(self._tokens[LiveService.CONTACTS]) - - http_headers = method.transport_headers() - soap_action = method.soap_action() - - soap_header = method.soap_header(scenario, token) - soap_body = method.soap_body(*args) - - method_name = method.__name__.rsplit(".", 1)[1] - self._send_request(method_name, - self._service.url, - soap_header, soap_body, soap_action, - callback, errback, - http_headers) - - def _HandleSOAPFault(self, request_id, callback, errback, - soap_response, user_data): - errback[0](soap_response.fault.faultcode, *errback[1:]) - -if __name__ == '__main__': - import sys - import getpass - import signal - import gobject - import logging - from pymsn.service.SingleSignOn import * - - logging.basicConfig(level=logging.DEBUG) - - if len(sys.argv) < 2: - account = raw_input('Account: ') - else: - account = sys.argv[1] - - if len(sys.argv) < 3: - password = getpass.getpass('Password: ') - else: - password = sys.argv[2] - - mainloop = gobject.MainLoop(is_running=True) - - signal.signal(signal.SIGTERM, - lambda *args: gobject.idle_add(mainloop.quit())) - - def ab_callback(contacts, groups): - print contacts - print groups - - sso = SingleSignOn(account, password) - ab = AB(sso) - ab.FindAll((ab_callback,), None, 'Initial', False) - - while mainloop.is_running(): - try: - mainloop.run() - except KeyboardInterrupt: - mainloop.quit() diff --git a/pymsn/pymsn/service/AddressBook/address_book.py b/pymsn/pymsn/service/AddressBook/address_book.py deleted file mode 100644 index a592ca1f..00000000 --- a/pymsn/pymsn/service/AddressBook/address_book.py +++ /dev/null @@ -1,701 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2006-2007 Ali Sabil -# Copyright (C) 2007-2008 Johann Prieur -# Copyright (C) 2007 Ole André Vadla Ravnås -# -# 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 -# - -import ab -import sharing -import scenario - -import pymsn -import pymsn.profile as profile -from pymsn.profile import NetworkID -from pymsn.util.decorator import rw_property -from pymsn.profile import ContactType -from pymsn.service.AddressBook.constants import * -from pymsn.service.description.AB.constants import * -from pymsn.service.AddressBook.scenario.contacts import * - -import gobject - -__all__ = ['AddressBook', 'AddressBookState'] - -class AddressBookStorage(set): - def __init__(self, initial_set=()): - set.__init__(self, initial_set) - - def __repr__(self): - return "AddressBook : %d contact(s)" % len(self) - - def __getitem__(self, key): - i = 0 - for contact in self: - if i == key: - return contact - i += 1 - raise IndexError("Index out of range") - - def __getattr__(self, name): - if name.startswith("search_by_"): - field = name[10:] - def search_by_func(criteria): - return self.search_by(field, criteria) - search_by_func.__name__ = name - return search_by_func - elif name.startswith("group_by_"): - field = name[9:] - def group_by_func(): - return self.group_by(field) - group_by_func.__name__ = name - return group_by_func - else: - raise AttributeError, name - - def search_by_memberships(self, memberships): - result = [] - for contact in self: - if contact.is_member(memberships): - result.append(contact) - # Do not break here, as the account - # might exist in multiple networks - return AddressBookStorage(result) - - def search_by_groups(self, *groups): - result = [] - groups = set(groups) - for contact in self: - if groups <= contact.groups: - result.append(contact) - return AddressBookStorage(result) - - def group_by_group(self): - result = {} - for contact in self: - groups = contact.groups - for group in groups: - if group not in result: - result[group] = set() - result[group].add(contact) - return result - - def search_by_predicate(self, predicate): - result = [] - for contact in self: - if predicate(contact): - result.append(contact) - return AddressBookStorage(result) - - def search_by(self, field, value): - result = [] - if isinstance(value, basestring): - value = value.lower() - for contact in self: - contact_field_value = getattr(contact, field) - if isinstance(contact_field_value, basestring): - contact_field_value = contact_field_value.lower() - if contact_field_value == value: - result.append(contact) - # Do not break here, as the account - # might exist in multiple networks - return AddressBookStorage(result) - - def group_by(self, field): - result = {} - for contact in self: - value = getattr(contact, field) - if value not in result: - result[value] = AddressBookStorage() - result[value].add(contact) - return result - - -class AddressBook(gobject.GObject): - - __gsignals__ = { - "error" : (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - (object,)), - - "messenger-contact-added" : (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - (object,)), -# "email-contact-added" : (gobject.SIGNAL_RUN_FIRST, -# gobject.TYPE_NONE, -# (object,)), -# "mobile-contact-added" : (gobject.SIGNAL_RUN_FIRST, -# gobject.TYPE_NONE, -# (object,)), - - "contact-deleted" : (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - (object,)), - - # FIXME: those signals will be removed in the future and will be - # moved to profile.Contact - "contact-accepted" : (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - (object,)), - "contact-rejected" : (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - (object,)), - "contact-blocked" : (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - (object,)), - "contact-unblocked" : (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - (object,)), - - "group-added" : (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - (object,)), - "group-deleted" : (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - (object,)), - "group-renamed" : (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - (object,)), - - "group-contact-added" : (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - (object, object)), - "group-contact-deleted" : (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - (object, object)) - } - - __gproperties__ = { - "state": (gobject.TYPE_INT, - "State", - "The state of the addressbook.", - 0, 2, AddressBookState.NOT_SYNCHRONIZED, - gobject.PARAM_READABLE) - } - - def __init__(self, sso, proxies=None): - """The address book object.""" - gobject.GObject.__init__(self) - - self._ab = ab.AB(sso, proxies) - self._sharing = sharing.Sharing(sso, proxies) - - self.__state = AddressBookState.NOT_SYNCHRONIZED - - self.groups = set() - self.contacts = AddressBookStorage() - self._profile = None - - # Properties - @property - def state(self): - return self.__state - - @rw_property - def _state(): - def fget(self): - return self.__state - def fset(self, state): - self.__state = state - self.notify("state") - return locals() - - @property - def profile(self): - return self._profile - - def sync(self): - if self._state != AddressBookState.NOT_SYNCHRONIZED: - return - self._state = AddressBookState.SYNCHRONIZING - - def callback(address_book, memberships): - ab = address_book.ab - contacts = address_book.contacts - groups = address_book.groups - for group in groups: - g = profile.Group(group.Id, group.Name.encode("utf-8")) - self.groups.add(g) - for contact in contacts: - c = self.__build_contact(contact) - if c is None: - continue - if contact.Type == ContactType.ME: - self._profile = c - else: - self.contacts.add(c) - self.__update_memberships(memberships) - self._state = AddressBookState.SYNCHRONIZED - - initial_sync = scenario.InitialSyncScenario(self._ab, self._sharing, - (callback,), - (self.__common_errback,)) - initial_sync() - - # Public API - def accept_contact_invitation(self, pending_contact, add_to_contact_list=True): - def callback(contact_infos, memberships): - pending_contact.freeze_notify() - pending_contact._id = contact_infos.Id - pending_contact._cid = contact_infos.CID - pending_contact._set_memberships(memberships) - pending_contact.thaw_notify() - self.emit('contact-accepted', pending_contact) - ai = scenario.AcceptInviteScenario(self._ab, self._sharing, - (callback,), - (self.__common_errback,)) - ai.account = pending_contact.account - ai.network = pending_contact.network_id - ai.memberships = pending_contact.memberships - ai.add_to_contact_list = add_to_contact_list - ai() - - def decline_contact_invitation(self, pending_contact, block=True): - def callback(memberships): - pending_contact._set_memberships(memberships) - self.emit('contact-rejected', pending_contact) - di = scenario.DeclineInviteScenario(self._sharing, - (callback,), - (self.__common_errback,)) - di.account = pending_contact.account - di.network = pending_contact.network_id - di.memberships = pending_contact.memberships - di.block = block - di() - - def add_messenger_contact(self, account, invite_display_name='', - invite_message='', groups=[], network_id=NetworkID.MSN): - def callback(contact_guid, address_book_delta): - contacts = address_book_delta.contacts - for contact in contacts: - if contact.Id != contact_guid: - continue - try: - c = self.contacts.search_by_account(contact.PassportName).\ - search_by_network_id(NetworkID.MSN)[0] - c.freeze_notify() - c._id = contact.Id - c._cid = contact.CID - c._display_name = contact.DisplayName - for group in self.groups: - if group.id in contact.Groups: - c._add_group_ownership(group) - c._add_membership(profile.Membership.FORWARD) - c._add_membership(profile.Membership.ALLOW) - - annotations = contact.Annotations - for key in annotations: - annotations[key] = annotations[key].encode("utf-8") - contact_infos = {ContactGeneral.ANNOTATIONS : annotations} - - c._server_infos_changed(contact_infos) - c.thaw_notify() - self.unblock_contact(c) - except IndexError: - c = self.__build_contact(contact) - if c.is_member(profile.Membership.FORWARD): - c._add_membership(profile.Membership.ALLOW) - if c is None: - continue - self.contacts.add(c) - self.emit('messenger-contact-added', c) - self.unblock_contact(c) - for group in groups: - self.add_contact_to_group(group, c) - - try: - contact = self.contacts.search_by_account(account).\ - search_by_network_id(NetworkID.MSN)[0] - if not contact.is_member(profile.Membership.FORWARD) and \ - contact.id != "00000000-0000-0000-0000-000000000000": - self.__upgrade_mail_contact(contact, groups) - elif contact.id == "00000000-0000-0000-0000-000000000000": - raise IndexError - else: - return - except IndexError: - if network_id == NetworkID.MSN: - scenario_class = MessengerContactAddScenario - elif network_id == NetworkID.EXTERNAL: - scenario_class = ExternalContactAddScenario - s = scenario_class(self._ab, (callback,), (self.__common_errback,)) - s.account = account - s.invite_display_name = invite_display_name - s.invite_message = invite_message - s() - - def __upgrade_mail_contact(self, contact, groups=[]): - def callback(): - contact._add_memberships(profile.Membership.ALLOW) - for group in groups: - self.add_contact_to_group(group, contact) - - up = scenario.ContactUpdatePropertiesScenario(self._ab, - (callback,), (self.__common_errback,)) - up.contact_guid = contact.id - up.contact_properties = { 'is_messenger_user' : True } - up.enable_allow_list_management = True - up() - -# def add_email_contact(self, email_address): -# ae = scenario.EmailContactAddScenario(self._ab, -# (self.__add_email_contact_cb,), -# (self.__common_errback,)) -# ae.email_address = email_address -# ae() - -# def add_mobile_contact(self, phone_number): -# am = scenario.MobileContactAddScenario(self._ab, -# (self.__add_mobile_contact_cb,), -# (self.__common_errback,)) -# am.phone_number = phone_number -# am() - - def delete_contact(self, contact): - def callback(): - self.contacts.discard(contact) - self.emit('contact-deleted', contact) - dc = scenario.ContactDeleteScenario(self._ab, - (callback,), - (self.__common_errback,)) - dc.contact_guid = contact.id - dc() - - def update_contact_infos(self, contact, infos): - def callback(): - contact._server_infos_changed(infos) - up = scenario.ContactUpdatePropertiesScenario(self._ab, - (callback,), - (self.__common_errback,)) - up.contact_guid = contact.id - up.contact_properties = infos - up() - - def block_contact(self, contact): - def callback(memberships): - contact._set_memberships(memberships) - self.emit('contact-blocked', contact) - bc = scenario.BlockContactScenario(self._sharing, - (callback,), - (self.__common_errback,)) - bc.account = contact.account - bc.network = contact.network_id - bc.membership = contact.memberships - bc() - - def unblock_contact(self, contact): - def callback(memberships): - contact._set_memberships(memberships) - self.emit('contact-unblocked', contact) - uc = scenario.UnblockContactScenario(self._sharing, - (callback,), - (self.__common_errback,)) - uc.account = contact.account - uc.network = contact.network_id - uc.membership = contact.memberships - uc() - - def add_group(self, group_name): - def callback(group_id): - group = profile.Group(group_id, group_name) - self.groups.add(group) - self.emit('group-added', group) - ag = scenario.GroupAddScenario(self._ab, - (callback,), - (self.__common_errback,)) - ag.group_name = group_name - ag() - - def delete_group(self, group): - def callback(): - for contact in self.contacts: - contact._delete_group_ownership(group) - self.groups.discard(group) - self.emit('group-deleted', group) - dg = scenario.GroupDeleteScenario(self._ab, - (callback,), - (self.__common_errback,)) - dg.group_guid = group.id - dg() - - def rename_group(self, group, new_name): - def callback(): - group._name = group_name - self.emit('group-renamed', group) - rg = scenario.GroupRenameScenario(self._ab, - (callback,), - (self.__common_errback,)) - rg.group_guid = group.id - rg.group_name = new_name - rg() - - def add_contact_to_group(self, group, contact): - def callback(): - contact._add_group_ownership(group) - self.emit('group-contact-added', group, contact) - ac = scenario.GroupContactAddScenario(self._ab, - (callback,), - (self.__common_errback,)) - ac.group_guid = group.id - ac.contact_guid = contact.id - ac() - - def delete_contact_from_group(self, group, contact): - def callback(): - contact._delete_group_ownership(group) - self.emit('group-contact-deleted', group, contact) - dc = scenario.GroupContactDeleteScenario(self._ab, - (callback,), - (self.__common_errback,)) - dc.group_guid = group.id - dc.contact_guid = contact.id - dc() - # End of public API - - def check_pending_invitations(self): - cp = scenario.CheckPendingInviteScenario(self._sharing, - (self.__update_memberships,), - (self.__common_errback,)) - cp() - - def __build_contact(self, contact): - external_email = None - for email in contact.Emails: - if email.Type == ContactEmailType.EXTERNAL: - external_email = email - break - - if (not contact.IsMessengerUser) and (external_email is not None): - display_name = contact.DisplayName - if display_name == "": - display_name = external_email.Email - - annotations = contact.Annotations - for key in annotations: - annotations[key] = annotations[key].encode("utf-8") - contact_infos = { ContactGeneral.ANNOTATIONS : annotations } - - if contact.IsMessengerUser: - memberships = profile.Membership.FORWARD - else: - memberships = profile.Membership.NONE - c = profile.Contact(contact.Id, - NetworkID.EXTERNAL, - external_email.Email.encode("utf-8"), - display_name.encode("utf-8"), - contact.CID, - memberships, - contact.Type) - c._server_infos_changed(contact_infos) - - for group in self.groups: - if group.id in contact.Groups: - c._add_group_ownership(group) - - return c - - elif contact.PassportName == "": - # FIXME : mobile phone and mail contacts here - return None - else: - display_name = contact.DisplayName - if display_name == "": - display_name = contact.QuickName - if display_name == "": - display_name = contact.PassportName - - annotations = contact.Annotations - for key in annotations: - annotations[key] = annotations[key].encode("utf-8") - contact_infos = {ContactGeneral.ANNOTATIONS : annotations} - - if contact.IsMessengerUser: - memberships = profile.Membership.FORWARD - else: - memberships = profile.Membership.NONE - c = profile.Contact(contact.Id, - NetworkID.MSN, - contact.PassportName.encode("utf-8"), - display_name.encode("utf-8"), - contact.CID, - memberships) - c._server_infos_changed(contact_infos) - - for group in self.groups: - if group.id in contact.Groups: - c._add_group_ownership(group) - - return c - return None - - def __update_memberships(self, memberships): - for member in memberships: - if isinstance(member, sharing.PassportMember): - network = NetworkID.MSN - elif isinstance(member, sharing.EmailMember): - network = NetworkID.EXTERNAL - else: - continue - - try: - contact = self.contacts.search_by_account(member.Account).\ - search_by_network_id(network)[0] - except IndexError: - contact = None - - new_contact = False - if contact is None: - new_contact = True - try: - cid = member.CID - except AttributeError: - cid = None - msg = member.Annotations.get('MSN.IM.InviteMessage', u'') - c = profile.Contact("00000000-0000-0000-0000-000000000000", - network, - member.Account.encode("utf-8"), - member.DisplayName.encode("utf-8"), - cid) - c._server_attribute_changed('invite_message', msg.encode("utf-8")) - self.contacts.add(c) - contact = c - - for role in member.Roles: - if role == "Allow": - membership = profile.Membership.ALLOW - elif role == "Block": - membership = profile.Membership.BLOCK - elif role == "Reverse": - membership = profile.Membership.REVERSE - elif role == "Pending": - membership = profile.Membership.PENDING - else: - raise NotImplementedError("Unknown Membership Type : " + membership) - contact._add_membership(membership) - - if new_contact and self.state == AddressBookState.SYNCHRONIZED: - self.emit('messenger-contact-added', contact) - - # Callbacks - def __common_errback(self, error_code, *args): - self.emit('error', error_code) - -gobject.type_register(AddressBook) - -if __name__ == '__main__': - def get_proxies(): - import urllib - proxies = urllib.getproxies() - result = {} - if 'https' not in proxies and \ - 'http' in proxies: - url = proxies['http'].replace("http://", "https://") - result['https'] = pymsn.Proxy(url) - for type, url in proxies.items(): - if type == 'no': continue - if type == 'https' and url.startswith('http://'): - url = url.replace('http://', 'https://', 1) - result[type] = pymsn.Proxy(url) - return result - - import sys - import getpass - import signal - import gobject - import logging - from pymsn.service.SingleSignOn import * - from pymsn.service.description.AB.constants import ContactGeneral - - logging.basicConfig(level=logging.DEBUG) - - if len(sys.argv) < 2: - account = raw_input('Account: ') - else: - account = sys.argv[1] - - if len(sys.argv) < 3: - password = getpass.getpass('Password: ') - else: - password = sys.argv[2] - - mainloop = gobject.MainLoop(is_running=True) - - signal.signal(signal.SIGTERM, - lambda *args: gobject.idle_add(mainloop.quit())) - - def address_book_state_changed(address_book, pspec): - if address_book.state == AddressBookState.SYNCHRONIZED: - for group in address_book.groups: - print "Group : %s " % group.name - - for contact in address_book.contacts: - print "Contact : %s (%s) %s" % \ - (contact.account, - contact.display_name, - contact.network_id) - - print address_book.contacts[0].account - address_book.update_contact_infos(address_book.contacts[0], {ContactGeneral.FIRST_NAME : "lolibouep"}) - - #address_book._check_pending_invitations() - #address_book.accept_contact_invitation(address_book.pending_contacts.pop()) - #print address_book.pending_contacts.pop() - #address_book.accept_contact_invitation(address_book.pending_contacts.pop()) - #address_book.add_group("ouch2") - #address_book.add_group("callback test6") - #group = address_book.groups.values()[0] - #address_book.delete_group(group) - #address_book.delete_group(group) - #address_book.rename_group(address_book.groups.values()[0], "ouch") - #address_book.add_contact_to_group(address_book.groups.values()[1], - # address_book.contacts[0]) - #contact = address_book.contacts[0] - #address_book.delete_contact_from_group(address_book.groups.values()[0], - # contact) - #address_book.delete_contact_from_group(address_book.groups.values()[0], - # contact) - #address_book.block_contact(address_book.contacts.search_by_account('pymsn.rewrite@yahoo.com')[0]) - #address_book.block_contact(address_book.contacts.search_by_account('pymsn.rewrite@yahoo.com')[0]) - #address_book.unblock_contact(address_book.contacts[0]) - #address_book.block_contact(address_book.contacts[0]) - #contact = address_book.contacts[2] - #address_book.delete_contact(contact) - #address_book.delete_contact(contact) - #g=list(address_book.groups) - #address_book.add_messenger_contact("wikipedia-bot@hotmail.com",groups=g) - - #for i in range(5): - # address_book.delete_contact(address_book.contacts[i]) - #address_book.add_messenger_contact("johanssn.prieur@gmail.com") - - def messenger_contact_added(address_book, contact): - print "Added contact : %s (%s) %s %s" % (contact.account, - contact.display_name, - contact.network_id, - contact.memberships) - - sso = SingleSignOn(account, password, proxies=get_proxies()) - address_book = AddressBook(sso, proxies=get_proxies()) - address_book.connect("notify::state", address_book_state_changed) - address_book.connect("messenger-contact-added", messenger_contact_added) - address_book.sync() - - while mainloop.is_running(): - try: - mainloop.run() - except KeyboardInterrupt: - mainloop.quit() diff --git a/pymsn/pymsn/service/AddressBook/common.py b/pymsn/pymsn/service/AddressBook/common.py deleted file mode 100644 index 6e7f6bd2..00000000 --- a/pymsn/pymsn/service/AddressBook/common.py +++ /dev/null @@ -1,33 +0,0 @@ -# -*- coding: utf-8 -*- -# -# pymsn - a python client library for Msn -# -# Copyright (C) 2007 Johann Prieur -# -# 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 - -__all__ = ['annotations_to_dict'] - -def annotations_to_dict(annotations): - if annotations is None: - return {} - - result = {} - for annotation in annotations: - key = annotation.findtext("./ab:Name") - value = annotation.findtext("./ab:Value") - result[key] = value - return result - diff --git a/pymsn/pymsn/service/AddressBook/constants.py b/pymsn/pymsn/service/AddressBook/constants.py deleted file mode 100644 index 27d913cc..00000000 --- a/pymsn/pymsn/service/AddressBook/constants.py +++ /dev/null @@ -1,51 +0,0 @@ -# -*- coding: utf-8 -*- -# -# pymsn - a python client library for Msn -# -# Copyright (C) 2007 Johann Prieur -# -# 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 - -__all__ = ['AddressBookError', 'AddressBookState'] - -class AddressBookError(object): - "Address book related errors" - UNKNOWN = 0 - - CONTACT_ALREADY_EXISTS = 1 - CONTACT_DOES_NOT_EXIST = 2 - INVALID_CONTACT_ADDRESS = 3 - - GROUP_ALREADY_EXISTS = 4 - GROUP_DOES_NOT_EXIST = 5 - CONTACT_NOT_IN_GROUP = 6 - - MEMBER_ALREADY_EXISTS = 7 - MEMBER_DOES_NOT_EXIST = 8 - - -class AddressBookState(object): - """Addressbook synchronization state. - - An adressbook is said to be synchronized when it - matches the addressbook stored on the server.""" - - NOT_SYNCHRONIZED = 0 - """The addressbook is not synchronized yet""" - SYNCHRONIZING = 1 - """The addressbook is being synchronized""" - SYNCHRONIZED = 2 - """The addressbook is already synchronized""" - diff --git a/pymsn/pymsn/service/AddressBook/scenario/__init__.py b/pymsn/pymsn/service/AddressBook/scenario/__init__.py deleted file mode 100644 index bc483322..00000000 --- a/pymsn/pymsn/service/AddressBook/scenario/__init__.py +++ /dev/null @@ -1,26 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007-2008 Johann Prieur -# -# 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 -# - -name = "scenario" -description = "" - -from sync import * -from contacts import * -from groups import * - diff --git a/pymsn/pymsn/service/AddressBook/scenario/base.py b/pymsn/pymsn/service/AddressBook/scenario/base.py deleted file mode 100644 index 9b06504d..00000000 --- a/pymsn/pymsn/service/AddressBook/scenario/base.py +++ /dev/null @@ -1,50 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# - -__all__ = ['BaseScenario', 'Scenario'] - -class BaseScenario(object): - def __init__(self, partner_scenario, callback, errback): - self._scenario = partner_scenario - self._callback = callback - self._errback = errback - - def __set_scenario(self, scenario): - self._scenario = scenario - def __get_scenario(self): - return self._scenario - scenario = property(__get_scenario, __set_scenario) - - def execute(self): - pass - - def __call__(self): - return self.execute() - -class Scenario(object): - """Scenario label""" - - INITIAL = "Initial" - TIMER = "Timer" - CONTACT_SAVE = "ContactSave" - GROUP_SAVE = "GroupSave" - BLOCK_UNBLOCK = "BlockUnblock" - CONTACT_MSGR_API = "ContactMsgrAPI" - MOBILE_CONTACT_MSGR_API = "MobileContactMsgrAPI" - MESSENGER_PENDING_LIST = "MessengerPendingList" diff --git a/pymsn/pymsn/service/AddressBook/scenario/contacts/__init__.py b/pymsn/pymsn/service/AddressBook/scenario/contacts/__init__.py deleted file mode 100644 index 683c0a93..00000000 --- a/pymsn/pymsn/service/AddressBook/scenario/contacts/__init__.py +++ /dev/null @@ -1,35 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# - -from accept_invite import * -from decline_invite import * -from check_pending_invite import * - -from update_memberships import * -from block_contact import * -from unblock_contact import * - -from contact_update_properties import * -from contact_delete import * - -from email_contact_add import * -from messenger_contact_add import * -from external_contact_add import * -from mobile_contact_add import * - diff --git a/pymsn/pymsn/service/AddressBook/scenario/contacts/accept_invite.py b/pymsn/pymsn/service/AddressBook/scenario/contacts/accept_invite.py deleted file mode 100644 index 4fabb297..00000000 --- a/pymsn/pymsn/service/AddressBook/scenario/contacts/accept_invite.py +++ /dev/null @@ -1,117 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# -from pymsn.service.AddressBook.scenario.base import BaseScenario -from pymsn.service.AddressBook.scenario.base import Scenario -from messenger_contact_add import MessengerContactAddScenario -from external_contact_add import ExternalContactAddScenario -from update_memberships import UpdateMembershipsScenario - -from pymsn.service.AddressBook.constants import * -from pymsn.profile import NetworkID, Membership - -__all__ = ['AcceptInviteScenario'] - -class AcceptInviteScenario(BaseScenario): - def __init__(self, ab, sharing, callback, errback, - account='', - memberships=Membership.NONE, - network=NetworkID.MSN, - state='Accepted'): - """Accepts an invitation. - - @param ab: the address book service - @param sharing: the membership service - @param callback: tuple(callable, *args) - @param errback: tuple(callable, *args) - """ - BaseScenario.__init__(self, Scenario.CONTACT_MSGR_API, callback, errback) - self.__ab = ab - self.__sharing = sharing - - self.add_to_contact_list = True - - self.account = account - self.memberships = memberships - self.network = network - self.state = state - - def execute(self): - if self.add_to_contact_list and not (self.memberships & Membership.FORWARD): - if self.network == NetworkID.MSN: - am = MessengerContactAddScenario(self.__ab, - (self.__add_contact_callback,), - (self.__add_contact_errback,), - self.account) - am() - elif self.network == NetworkID.EXTERNAL: - em = ExternalContactAddScenario(self.__ab, - (self.__add_contact_callback,), - (self.__add_contact_errback,), - self.account) - em() - else: - # FIXME: maybe raise an exception ? - self.__update_memberships() - else: - self.__update_memberships() - - def __update_memberships(self): - new_membership = (self.memberships & ~Membership.PENDING) | \ - Membership.ALLOW | Membership.REVERSE - um = UpdateMembershipsScenario(self.__sharing, - (self.__update_memberships_callback,), - (self.__update_memberships_errback,), - self._scenario, - self.account, - self.network, - self.state, - self.memberships, - new_membership) - um() - - def __add_contact_callback(self, contact_guid, address_book_delta): - contacts = address_book_delta.contacts - self.memberships |= Membership.ALLOW | Membership.FORWARD - for contact in contacts: - if contact.Id != contact_guid: - continue - self._added_contact = contact - break - self.__update_memberships() - - def __add_contact_errback(self, error_code): - errcode = AddressBookError.UNKNOWN - if error_code == 'ContactAlreadyExists': - errcode = AddressBookError.CONTACT_ALREADY_EXISTS - elif error_code == 'InvalidPassportUser': - errcode = AddressBookError.INVALID_CONTACT_ADDRESS - errback = self._errback[0] - args = self._errback[1:] - errback(errcode, *args) - - def __update_memberships_callback(self, memberships): - self.memberships = memberships - contact = self._added_contact - callback[0](contact, memberships, *callback[1:]) - - def __update_memberships_errback(self, error_code, done, failed): - errcode = AddressBookError.UNKNOWN - errback = self._errback[0] - args = self._errback[1:] - errback(errcode, *args) diff --git a/pymsn/pymsn/service/AddressBook/scenario/contacts/block_contact.py b/pymsn/pymsn/service/AddressBook/scenario/contacts/block_contact.py deleted file mode 100644 index 604453f7..00000000 --- a/pymsn/pymsn/service/AddressBook/scenario/contacts/block_contact.py +++ /dev/null @@ -1,63 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007-2008 Johann Prieur -# -# 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 -# -from pymsn.service.AddressBook.scenario.base import BaseScenario -from pymsn.service.AddressBook.scenario.base import Scenario -from update_memberships import UpdateMembershipsScenario - -from pymsn.profile import Membership -from pymsn.profile import NetworkID - -__all__ = ['BlockContactScenario'] - -class BlockContactScenario(BaseScenario): - def __init__(self, sharing, callback, errback, account='', - network=NetworkID.MSN, membership=Membership.NONE, - state='Accepted'): - """Blocks a contact. - - @param sharing: the membership service - @param callback: tuple(callable, *args) - @param errback: tuple(callable, *args) - """ - BaseScenario.__init__(self, Scenario.BLOCK_UNBLOCK, callback, errback) - self.__sharing = sharing - - self.account = account - self.network = network - self.membership = membership - self.state = state - - def execute(self): - new_membership = self.membership & ~Membership.ALLOW | Membership.BLOCK - um = UpdateMembershipsScenario(self.__sharing, - self._callback, - (self.__update_memberships_errback,), - self._scenario, - self.account, - self.network, - self.state, - self.membership, - new_membership) - um() - - def __update_memberships_errback(self, error_code, done, failed): - errcode = AddressBookError.UNKNOWN - errback = self._errback[0] - args = self._errback[1:] - errback(errcode, *args) diff --git a/pymsn/pymsn/service/AddressBook/scenario/contacts/check_pending_invite.py b/pymsn/pymsn/service/AddressBook/scenario/contacts/check_pending_invite.py deleted file mode 100644 index f418949f..00000000 --- a/pymsn/pymsn/service/AddressBook/scenario/contacts/check_pending_invite.py +++ /dev/null @@ -1,51 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# -from pymsn.service.AddressBook.scenario.base import BaseScenario -from pymsn.service.AddressBook.scenario.base import Scenario - -from pymsn.service.AddressBook.constants import * - -__all__ = ['CheckPendingInviteScenario'] - -class CheckPendingInviteScenario(BaseScenario): - def __init__(self, sharing, callback, errback): - """Checks the pending invitations. - - @param sharing: the membership service - @param callback: tuple(callable, *args) - @param errback: tuple(callable, *args) - """ - BaseScenario.__init__(self, Scenario.MESSENGER_PENDING_LIST, callback, errback) - self.__sharing = sharing - - def execute(self): - self.__sharing.FindMembership((self.__membership_findall_callback,), - (self.__membership_findall_errback,), - self._scenario, ['Messenger'], True) - - def __membership_findall_callback(self, result): - callback = self._callback - callback[0](result, *callback[1:]) - - def __membership_findall_errback(self, error_code): - errcode = AddressBookError.UNKNOWN - errback = self._errback[0] - args = self._errback[1:] - errback(errcode, *args) - diff --git a/pymsn/pymsn/service/AddressBook/scenario/contacts/contact_delete.py b/pymsn/pymsn/service/AddressBook/scenario/contacts/contact_delete.py deleted file mode 100644 index 3a1621f2..00000000 --- a/pymsn/pymsn/service/AddressBook/scenario/contacts/contact_delete.py +++ /dev/null @@ -1,54 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# -from pymsn.service.AddressBook.scenario.base import BaseScenario -from pymsn.service.AddressBook.scenario.base import Scenario - -from pymsn.service.AddressBook.constants import * - -__all__ = ['ContactDeleteScenario'] - -class ContactDeleteScenario(BaseScenario): - def __init__(self, ab, callback, errback, contact_guid=''): - """Deletes a contact from the address book. - - @param ab: the address book service - @param callback: tuple(callable, *args) - @param errback: tuple(callable, *args) - @param contact_guid: the guid of the contact to delete""" - BaseScenario.__init__(self, Scenario.TIMER, callback, errback) - self.__ab = ab - - self.contact_guid = contact_guid - - def execute(self): - self.__ab.ContactDelete((self.__contact_delete_callback,), - (self.__contact_delete_errback,), - self._scenario, self.contact_guid) - - def __contact_delete_callback(self): - callback = self._callback - callback[0](*callback[1:]) - - def __contact_delete_errback(self, error_code): - errcode = AddressBookError.UNKNOWN - if error_code == 'ContactDoesNotExist': - errcode = AddressBookError.CONTACT_DOES_NOT_EXIST - errback = self._errback[0] - args = self._errback[1:] - errback(errcode, *args) diff --git a/pymsn/pymsn/service/AddressBook/scenario/contacts/contact_update_properties.py b/pymsn/pymsn/service/AddressBook/scenario/contacts/contact_update_properties.py deleted file mode 100644 index 09f38d0c..00000000 --- a/pymsn/pymsn/service/AddressBook/scenario/contacts/contact_update_properties.py +++ /dev/null @@ -1,59 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# -from pymsn.service.AddressBook.scenario.base import BaseScenario -from pymsn.service.AddressBook.scenario.base import Scenario - -from pymsn.service.AddressBook.constants import * - -__all__ = ['ContactUpdatePropertiesScenario'] - -class ContactUpdatePropertiesScenario(BaseScenario): - def __init__(self, ab, callback, errback, contact_guid='', - contact_properties={}): - """Updates a contact properties - - @param ab: the address book service - @param callback: tuple(callable, *args) - @param errback: tuple(callable, *args) - @param contact_guid: the guid of the contact to update""" - BaseScenario.__init__(self, Scenario.CONTACT_SAVE, callback, errback) - self.__ab = ab - - self.contact_guid = contact_guid - self.contact_properties = contact_properties - self.enable_allow_list_management = False - - def execute(self): - self.__ab.ContactUpdate((self.__contact_update_callback,), - (self.__contact_update_errback,), - self._scenario, self.contact_guid, - self.contact_properties, - self.enable_allow_list_management) - - def __contact_update_callback(self): - callback = self._callback - callback[0](*callback[1:]) - - def __contact_update_errback(self, error_code): - errcode = AddressBookError.UNKNOWN - if error_code == 'ContactDoesNotExist': - errcode = AddressBookError.CONTACT_DOES_NOT_EXIST - errback = self._errback[0] - args = self._errback[1:] - errback(errcode, *args) diff --git a/pymsn/pymsn/service/AddressBook/scenario/contacts/decline_invite.py b/pymsn/pymsn/service/AddressBook/scenario/contacts/decline_invite.py deleted file mode 100644 index 9b6c2b82..00000000 --- a/pymsn/pymsn/service/AddressBook/scenario/contacts/decline_invite.py +++ /dev/null @@ -1,68 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007-2008 Johann Prieur -# -# 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 -# -from pymsn.service.AddressBook.constants import AddressBookError -from pymsn.service.AddressBook.scenario.base import BaseScenario -from pymsn.service.AddressBook.scenario.base import Scenario -from update_memberships import UpdateMembershipsScenario - -from pymsn.profile import Membership -from pymsn.profile import NetworkID - -__all__ = ['DeclineInviteScenario'] - -class DeclineInviteScenario(BaseScenario): - def __init__(self, sharing, callback, errback, account='', - network=NetworkID.MSN, memberships=Membership.NONE, - state='Accepted', block=True): - """Declines an invitation. - - @param sharing: the membership service - @param callback: tuple(callable, *args) - @param errback: tuple(callable, *args) - """ - BaseScenario.__init__(self, Scenario.TIMER, callback, errback) - self.__sharing = sharing - - self.account = account - self.network = network - self.memberships = memberships - self.state = state - self.block = block - - def execute(self): - new_memberships = self.memberships & ~Membership.PENDING - if self.block: - new_memberships |= Membership.BLOCK - um = UpdateMembershipsScenario(self.__sharing, - self._callback, - (self.__update_memberships_errback,), - self._scenario, - self.account, - self.network, - self.state, - self.memberships, - new_memberships) - um() - - def __update_memberships_errback(self, error_code, done, failed): - errcode = AddressBookError.UNKNOWN - errback = self._errback[0] - args = self._errback[1:] - errback(errcode, *args) - diff --git a/pymsn/pymsn/service/AddressBook/scenario/contacts/email_contact_add.py b/pymsn/pymsn/service/AddressBook/scenario/contacts/email_contact_add.py deleted file mode 100644 index ca2b329c..00000000 --- a/pymsn/pymsn/service/AddressBook/scenario/contacts/email_contact_add.py +++ /dev/null @@ -1,77 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# -from pymsn.service.AddressBook.scenario.base import BaseScenario -from pymsn.service.AddressBook.scenario.base import Scenario - -__all__ = ['EmailContactAddScenario'] - -class EmailContactAddScenario(BaseScenario): - def __init__(self, ab, callback, errback, email_address="", contact_info={}): - """Adds a mail contact and updates the address book. - - @param ab: the adress book service - @param callback: tuple(callable, *args) - @param errback: tuple(callable, *args)""" - BaseScenario.__init__(self, Scenario.CONTACT_SAVE, callback, errback) - self.__ab = ab - - self.__email_address = email_address - self.__contact_info = contact_info - - def __set_email_address(self, email_address): - self.__email_address = email_address - def __get_email_address(self): - return self.__email_address - email_address = property(__get_email_address, __set_email_address, - doc="The mail address of the contact") - - def __set_contact_info(self, contact_info): - self.__contact_info = contact_info - def __get_contact_info(self): - return self.__contact_info - contact_info = property(__get_contact_info, __set_contact_info, - doc="A dict which contains addressbook " \ - "information about the contact") - - def execute(self): - contact_info['passport_name'] = self.__email_address - contact_info['is_messenger_user'] = False - self.__ab.ContactAdd((self.__contact_add_callback,), - (self.__contact_add_errback,), - self.__scenario, self.__contact_info, {}) - - def __contact_add_callback(self, stuff): - self._callback(stuff) - # TODO : get the cached lastchanged date to make a delta findall - # or directly call a sync scenario - self.__ab.FindAll(self.__scenario, True, None, - self.__find_all_callback, self.__find_all_errback) - - def __contact_add_errback(self, reason): - # TODO : analyse the reason, and maybe call execute again - # instead of transmitting it via _errback. - self._errback(reason) - - def __find_all_callback(self): - # TODO : complete the contact list in the client, need to access to - # the local address book storage, not the service.. - pass - - def __find_all_errback(self, reason): - self._errback(reason) diff --git a/pymsn/pymsn/service/AddressBook/scenario/contacts/external_contact_add.py b/pymsn/pymsn/service/AddressBook/scenario/contacts/external_contact_add.py deleted file mode 100644 index 30661d16..00000000 --- a/pymsn/pymsn/service/AddressBook/scenario/contacts/external_contact_add.py +++ /dev/null @@ -1,84 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# - -from pymsn.service.AddressBook.scenario.base import BaseScenario -from pymsn.service.AddressBook.scenario.base import Scenario - -from pymsn.service.AddressBook.constants import * -from pymsn.service.description.AB.constants import ContactEmailType -from pymsn.profile import NetworkID - -__all__ = ['ExternalContactAddScenario'] - -class ExternalContactAddScenario(BaseScenario): - def __init__(self, ab, callback, errback, account='', - network_id=NetworkID.EXTERNAL, contact_info={}, - invite_display_name='', invite_message=''): - """Adds an external messenger contact and updates the address book. - - @param ab: the address book service - @param callback: tuple(callable, *args) - @param errback: tuple(callable, *args)""" - BaseScenario.__init__(self, Scenario.CONTACT_MSGR_API, callback, errback) - - self._ab = ab - - self.account = account - self.network_id = network_id - self.contact_info = contact_info - - self.invite_display_name = invite_display_name - self.invite_message = invite_message - - def execute(self): - invite_info = { 'display_name' : self.invite_display_name , - 'invite_message' : self.invite_message } - - if self.contact_info.get('email', None) is None: - self.contact_info['email'] = \ - { ContactEmailType.EXTERNAL : self.account } - else: - self.contact_info['email'][ContactEmailType.EXTERNAL] = self.account - self.contact_info['capability'] = self.network_id - self._ab.ContactAdd((self.__contact_add_callback,), - (self.__contact_add_errback,), - self._scenario, - self.contact_info, - invite_info) - - def __contact_add_callback(self, contact_guid): - self._ab.FindAll((self.__find_all_callback, contact_guid), - (self.__find_all_errback, contact_guid), - self._scenario, True) - - def __contact_add_errback(self, error_code): - errcode = AddressBookError.UNKNOWN - errback = self._errback[0] - args = self._errback[1:] - errback(errcode, *args) - - def __find_all_callback(self, delta, contact_guid): - callback = self._callback - callback[0](contact_guid, delta, *callback[1:]) - - def __find_all_errback(self, error_code): - errcode = AddressBookError.UNKNOWN - errback = self._errback[0] - args = self._errback[1:] - errback(errcode, *args) diff --git a/pymsn/pymsn/service/AddressBook/scenario/contacts/messenger_contact_add.py b/pymsn/pymsn/service/AddressBook/scenario/contacts/messenger_contact_add.py deleted file mode 100644 index d8b525d0..00000000 --- a/pymsn/pymsn/service/AddressBook/scenario/contacts/messenger_contact_add.py +++ /dev/null @@ -1,90 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# - -from pymsn.service.AddressBook.scenario.base import BaseScenario -from pymsn.service.AddressBook.scenario.base import Scenario - -from pymsn.service.AddressBook.constants import * -from pymsn.profile import ContactType - -__all__ = ['MessengerContactAddScenario'] - -class MessengerContactAddScenario(BaseScenario): - def __init__(self, ab, callback, errback, - account='', - contact_type=ContactType.REGULAR, - contact_info={}, - invite_display_name='', - invite_message=''): - """Adds a messenger contact and updates the address book. - - @param ab: the address book service - @param callback: tuple(callable, *args) - @param errback: tuple(callable, *args)""" - BaseScenario.__init__(self, Scenario.CONTACT_SAVE, callback, errback) - - self._ab = ab - - self.account = account - - self.contact_type = contact_type - self.contact_info = contact_info - - self.invite_display_name = invite_display_name - self.invite_message = invite_message - self.auto_manage_allow_list = True - - def execute(self): - invite_info = { 'display_name' : self.invite_display_name , - 'invite_message' : self.invite_message } - - self.contact_info['passport_name'] = self.account - self.contact_info['contact_type'] = self.contact_type - self.contact_info['is_messenger_user'] = True - self._ab.ContactAdd((self.__contact_add_callback,), - (self.__contact_add_errback,), - self._scenario, - self.contact_info, - invite_info, - self.auto_manage_allow_list) - - def __contact_add_callback(self, contact_guid): - self._ab.FindAll((self.__find_all_callback, contact_guid), - (self.__find_all_errback, contact_guid), - self._scenario, True) - - def __contact_add_errback(self, error_code): - errcode = AddressBookError.UNKNOWN - if error_code == 'ContactAlreadyExists': - errcode = AddressBookError.CONTACT_ALREADY_EXISTS - elif error_code == 'InvalidPassportUser': - errcode = AddressBookError.INVALID_CONTACT_ADDRESS - errback = self._errback[0] - args = self._errback[1:] - errback(errcode, *args) - - def __find_all_callback(self, address_book_delta, contact_guid): - callback = self._callback - callback[0](contact_guid, address_book_delta, *callback[1:]) - - def __find_all_errback(self, error_code): - errcode = AddressBookError.UNKNOWN - errback = self._errback[0] - args = self._errback[1:] - errback(errcode, *args) diff --git a/pymsn/pymsn/service/AddressBook/scenario/contacts/mobile_contact_add.py b/pymsn/pymsn/service/AddressBook/scenario/contacts/mobile_contact_add.py deleted file mode 100644 index ab9af251..00000000 --- a/pymsn/pymsn/service/AddressBook/scenario/contacts/mobile_contact_add.py +++ /dev/null @@ -1,80 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# -from pymsn.service.AddressBook.scenario.base import BaseScenario -from pymsn.service.AddressBook.scenario.base import Scenario - -from pymsn.service.description.AB import ContactPhoneType - -__all__ = ['MobileContactAddScenario'] - -class MobileContactAddScenario(BaseScenario): - def __init__(self, ab, callback, errback, - phone_number="", contact_info={}): - """Adds a mobile contact and updates the address book. - - @param ab: the adress book service - @param callback: tuple(callable, *args) - @param errback: tuple(callable, *args)""" - BaseScenario.__init__(self, Scenario.MOBILE_CONTACT_MSGR_API, callback, errback) - self.__ab = ab - - self.__phone_number = phone_number - self.__contact_info = contact_info - - def __set_phone_number(self, phone_number): - self.__phone_number = phone_number - def __get_phone_number(self): - return self.__phone_number - phone_number = property(__get_phone_number, __set_phone_number) - - def __set_contact_info(self, contact_info): - self.__contact_info = contact_info - def __get_contact_info(self): - return self.__contact_info - contact_info = property(__get_contact_info, __set_contact_info) - - def execute(self): - phones = self.__contact_info.get('phone', {}) - phones[ContactPhoneType.MOBILE] = self.__phone_number - # self.__contact_info['phone'] = phones - self.__ab.ContactAdd((self.__contact_add_callback,), - (self.__contact_add_errback,), - self.__scenario, - self.__contact_info, - {}) - - def __contact_add_callback(self, stuff): - self._callback(stuff) - # TODO : get the cached lastchanged date to make a delta findall - # or directly call a sync scenario - self.__ab.FindAll(self.__scenario, True, None, - self.__find_all_callback, self.__find_all_errback) - - def __contact_add_errback(self, reason): - # TODO : analyse the reason, and maybe call execute again - # instead of transmitting it via _errback. - self._errback(reason) - - def __find_all_callback(self): - # TODO : complete the contact list in the client, need to access to - # the local address book storage, not the service.. - pass - - def __find_all_errback(self, reason): - self._errback(reason) diff --git a/pymsn/pymsn/service/AddressBook/scenario/contacts/unblock_contact.py b/pymsn/pymsn/service/AddressBook/scenario/contacts/unblock_contact.py deleted file mode 100644 index 904e6bb3..00000000 --- a/pymsn/pymsn/service/AddressBook/scenario/contacts/unblock_contact.py +++ /dev/null @@ -1,63 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007-2008 Johann Prieur -# -# 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 -# -from pymsn.service.AddressBook.scenario.base import BaseScenario -from pymsn.service.AddressBook.scenario.base import Scenario -from update_memberships import UpdateMembershipsScenario - -from pymsn.profile import Membership -from pymsn.profile import NetworkID - -__all__ = ['UnblockContactScenario'] - -class UnblockContactScenario(BaseScenario): - def __init__(self, sharing, callback, errback, account='', - network=NetworkID.MSN, membership=Membership.NONE, - state='Accepted'): - """Unblocks a contact. - - @param sharing: the membership service - @param callback: tuple(callable, *args) - @param errback: tuple(callable, *args) - """ - BaseScenario.__init__(self, Scenario.BLOCK_UNBLOCK, callback, errback) - self.__sharing = sharing - - self.account = account - self.network = network - self.membership = membership - self.state = state - - def execute(self): - new_membership = self.membership & ~Membership.BLOCK | Membership.ALLOW - um = UpdateMembershipsScenario(self.__sharing, - self._callback, - (self.__update_memberships_errback,), - self._scenario, - self.account, - self.network, - self.state, - self.membership, - new_membership) - um() - - def __update_memberships_errback(self, error_code, done, failed): - errcode = AddressBookError.UNKNOWN - errback = self._errback[0] - args = self._errback[1:] - errback(errcode, *args) diff --git a/pymsn/pymsn/service/AddressBook/scenario/contacts/update_memberships.py b/pymsn/pymsn/service/AddressBook/scenario/contacts/update_memberships.py deleted file mode 100644 index 821ce4cd..00000000 --- a/pymsn/pymsn/service/AddressBook/scenario/contacts/update_memberships.py +++ /dev/null @@ -1,144 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2008 Johann Prieur -# -# 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 -# -from pymsn.service.AddressBook.scenario.base import BaseScenario -from pymsn.service.AddressBook.constants import * - -from pymsn.profile import NetworkID -from pymsn.profile import Membership - -__all__ = ['UpdateMembershipsScenario'] - -class UpdateMembershipsScenario(BaseScenario): - """Scenario used to update contact memberships in a safe way. - @undocumented: __membership_mapping, __contact_type""" - - __mapping = { Membership.FORWARD: "Forward", - Membership.ALLOW: "Allow", - Membership.BLOCK: "Block", - Membership.REVERSE: "Reverse", - Membership.PENDING: "Pending" } - - __contact_type = { NetworkID.MSN: "Passport", - NetworkID.EXTERNAL: "Email" } - - def __init__(self, sharing, callback, errback, scenario, - account, network, state, old_membership, new_membership): - """Updates contact memberships. - - @type scenario: L{Scenario} - @type network: L{NetworkID} - @type old_memberships: bitmask of L{Membership} - @type new_memberships: bitmask of L{Membership} - """ - BaseScenario.__init__(self, scenario, callback, errback) - self.__sharing = sharing - - self.account = account - self.contact_type = UpdateMembershipsScenario.__contact_type[network] - self.old = old_membership - self.new = new_membership - self.state = state - - # We keep a trace of what changes are actually done to pass it through - # the callback or the errback so that the executor of the scenario can - # update the memberships property of the contact. - self.__done = old_membership - - # Subscription to the REVERSE or ALLOW lists can only occur when the - # contact is member of the PENDING list, so when a subscription to the - # REVERSE or ALLOW membership is detected, we delay the eventual deletion - # from the PENDING membership list. - self.__late_pending_delete = False - - def _change(self, membership): - return (membership & (self.old ^ self.new)) - - def _add(self, membership): - return (self._change(membership) and (membership & self.new)) - - def _delete(self, membership): - return (self._change(membership) and (membership & self.old)) - - def execute(self): - if (self._add(Membership.REVERSE) or self._add(Membership.ALLOW)) and \ - self._delete(Membership.PENDING): - self.__late_pending_delete = True - - self.__process_delete(UpdateMembershipsScenario.__mapping.keys(), - Membership.NONE) - - def __process_delete(self, memberships, last): - self.__done &= ~last - - if memberships == []: - self.__process_add(UpdateMembershipsScenario.__mapping.keys(), - Membership.NONE) - return - - current = memberships.pop() - if self._delete(current) and not (current == Membership.PENDING and \ - self.__late_pending_delete): - membership = UpdateMembershipsScenario.__mapping[current] - self.__sharing.DeleteMember((self.__process_delete, memberships, current), - (self.__common_errback, self.__done, current), - self._scenario, membership, - self.contact_type, self.state, - self.account) - else: - self.__process_delete(memberships, Membership.NONE) - - def __process_add(self, memberships, last): - self.__done |= last - - if memberships == []: - if self.__late_pending_delete: - membership = UpdateMembershipsScenario.__mapping[Membership.PENDING] - callback = list(self._callback) - callback.insert(1, self.__done) - self.__sharing.DeleteMember(callback, - (self.__common_errback, self.__done, - Membership.PENDING), - self._scenario, membership, - self.contact_type, self.state, - self.account) - else: - callback = self._callback - callback[0](self.__done, *callback[1:]) - return - - current = memberships.pop() - if self._add(current): - membership = UpdateMembershipsScenario.__mapping[current] - self.__sharing.AddMember((self.__process_add, memberships, current), - (self.__common_errback, self.__done, current), - self._scenario, membership, - self.contact_type, self.state, - self.account) - else: - self.__process_add(memberships, Membership.NONE) - - def __common_errback(self, error_code, done, failed): - errcode = AddressBookError.UNKNOWN - if error_code == 'MemberAlreadyExists': - errcode = AddressBookError.MEMBER_ALREADY_EXISTS - elif error_code == 'MemberDoesNotExist': - errcode = AddressBookError.MEMBER_DOES_NOT_EXIST - errback = self._errback[0] - args = self._errback[1:] - errback(errcode, done, failed, *args) diff --git a/pymsn/pymsn/service/AddressBook/scenario/groups/__init__.py b/pymsn/pymsn/service/AddressBook/scenario/groups/__init__.py deleted file mode 100644 index 492af99a..00000000 --- a/pymsn/pymsn/service/AddressBook/scenario/groups/__init__.py +++ /dev/null @@ -1,25 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# - -from group_add import * -from group_contact_add import * -from group_contact_delete import * -from group_delete import * -from group_rename import * - diff --git a/pymsn/pymsn/service/AddressBook/scenario/groups/group_add.py b/pymsn/pymsn/service/AddressBook/scenario/groups/group_add.py deleted file mode 100644 index e2fc00a4..00000000 --- a/pymsn/pymsn/service/AddressBook/scenario/groups/group_add.py +++ /dev/null @@ -1,52 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# -from pymsn.service.AddressBook.scenario.base import BaseScenario -from pymsn.service.AddressBook import * - -__all__ = ['GroupAddScenario'] - -class GroupAddScenario(BaseScenario): - def __init__(self, ab, callback, errback, group_name=''): - """Adds a group to the address book. - - @param ab: the address book service - @param callback: tuple(callable, *args) - @param errback: tuple(callable, *args) - @param group_name: the name of the new group""" - BaseScenario.__init__(self, 'GroupSave', callback, errback) - self.__ab = ab - - self.group_name = group_name - - def execute(self): - self.__ab.GroupAdd((self.__group_add_callback,), - (self.__group_add_errback,), - self._scenario, self.group_name) - - def __group_add_callback(self, group_guid): - callback = self._callback - callback[0](group_guid, *callback[1:]) - - def __group_add_errback(self, error_code): - errcode = AddressBookError.UNKNOWN - if error_code == 'GroupAlreadyExists': - errcode = AddressBookError.GROUP_ALREADY_EXISTS - errback = self._errback[0] - args = self._errback[1:] - errback(errcode, *args) diff --git a/pymsn/pymsn/service/AddressBook/scenario/groups/group_contact_add.py b/pymsn/pymsn/service/AddressBook/scenario/groups/group_contact_add.py deleted file mode 100644 index 64e5efdd..00000000 --- a/pymsn/pymsn/service/AddressBook/scenario/groups/group_contact_add.py +++ /dev/null @@ -1,53 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# -from pymsn.service.AddressBook.scenario.base import BaseScenario -from pymsn.service.AddressBook import * - -__all__ = ['GroupContactAddScenario'] - -class GroupContactAddScenario(BaseScenario): - def __init__(self, ab, callback, errback, group_guid='', contact_guid=''): - """Adds a contact to a group. - - @param ab: the address book service - @param callback: tuple(callable, *args) - @param errback: tuple(callable, *args) - @param group_guid: the guid of the group - @param contact_guid: the guid of the contact to add to the group""" - BaseScenario.__init__(self, 'GroupSave', callback, errback) - self.__ab = ab - - self.group_guid = group_guid - self.contact_guid = contact_guid - - def execute(self): - self.__ab.GroupContactAdd((self.__group_contact_add_callback,), - (self.__group_contact_add_errback,), - self._scenario, self.group_guid, - self.contact_guid) - - def __group_contact_add_callback(self): - callback = self._callback - callback[0](*callback[1:]) - - def __group_contact_add_errback(self, error_code): - errcode = AddressBookError.UNKNOWN - errback = self._errback[0] - args = self._errback[1:] - errback(errcode, *args) diff --git a/pymsn/pymsn/service/AddressBook/scenario/groups/group_contact_delete.py b/pymsn/pymsn/service/AddressBook/scenario/groups/group_contact_delete.py deleted file mode 100644 index 80d2986a..00000000 --- a/pymsn/pymsn/service/AddressBook/scenario/groups/group_contact_delete.py +++ /dev/null @@ -1,54 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# -from pymsn.service.AddressBook.scenario.base import BaseScenario - -__all__ = ['GroupContactDeleteScenario'] - -class GroupContactDeleteScenario(BaseScenario): - def __init__(self, ab, callback, errback, group_guid='', contact_guid=''): - """Deletes a contact to a group. - - @param ab: the address book service - @param callback: tuple(callable, *args) - @param errback: tuple(callable, *args) - @param group_guid: the guid of the group - @param contact_guid: the guid of the contact to delete from the group""" - BaseScenario.__init__(self, 'GroupSave', callback, errback) - self.__ab = ab - - self.group_guid = group_guid - self.contact_guid = contact_guid - - def execute(self): - self.__ab.GroupContactDelete((self.__group_contact_delete_callback,), - (self.__group_contact_delete_errback,), - self._scenario, self.group_guid, - self.contact_guid) - - def __group_contact_delete_callback(self): - callback = self._callback - callback[0](*callback[1:]) - - def __group_contact_delete_errback(self, error_code): - errcode = AddressBookError.UNKNOWN - if error_code == 'ContactDoesNotExist': - errcode = AddressBookError.CONTACT_NOT_IN_GROUP - errback = self._errback[0] - args = self._errback[1:] - errback(errcode, *args) diff --git a/pymsn/pymsn/service/AddressBook/scenario/groups/group_delete.py b/pymsn/pymsn/service/AddressBook/scenario/groups/group_delete.py deleted file mode 100644 index 548dce0f..00000000 --- a/pymsn/pymsn/service/AddressBook/scenario/groups/group_delete.py +++ /dev/null @@ -1,52 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# -from pymsn.service.AddressBook.scenario.base import BaseScenario -from pymsn.service.AddressBook import * - -__all__ = ['GroupDeleteScenario'] - -class GroupDeleteScenario(BaseScenario): - def __init__(self, ab, callback, errback, group_guid=''): - """Deletes a group from the address book. - - @param ab: the address book service - @param callback: tuple(callable, *args) - @param errback: tuple(callable, *args) - @param group_guid: the guid of the group to delete""" - BaseScenario.__init__(self, 'GroupSave', callback, errback) - self.__ab = ab - - self.group_guid = group_guid - - def execute(self): - self.__ab.GroupDelete((self.__group_delete_callback,), - (self.__group_delete_errback,), - self._scenario, self.group_guid) - - def __group_delete_callback(self): - callback = self._callback - callback[0](*callback[1:]) - - def __group_delete_errback(self, error_code): - errcode = AddressBookError.UNKNOWN - if error_code == 'GroupDoesNotExist': - errcode = AddressBookError.GROUP_DOES_NOT_EXIST - errback = self._errback[0] - args = self._errback[1:] - errback(errcode, *args) diff --git a/pymsn/pymsn/service/AddressBook/scenario/groups/group_rename.py b/pymsn/pymsn/service/AddressBook/scenario/groups/group_rename.py deleted file mode 100644 index 06cf01ef..00000000 --- a/pymsn/pymsn/service/AddressBook/scenario/groups/group_rename.py +++ /dev/null @@ -1,55 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# -from pymsn.service.AddressBook.scenario.base import BaseScenario -from pymsn.service.AddressBook import * - -__all__ = ['GroupRenameScenario'] - -class GroupRenameScenario(BaseScenario): - def __init__(self, ab, callback, errback, group_guid='', group_name=''): - """Renames a group to the address book. - - @param ab: the address book service - @param callback: tuple(callable, *args) - @param errback: tuple(callable, *args) - @param group_guid: the guid of the group to rename - @param group_name: the new name for the group""" - BaseScenario.__init__(self, 'GroupSave', callback, errback) - self.__ab = ab - - self.group_guid = group_guid - self.group_name = group_name - - def execute(self): - self.__ab.GroupUpdate((self.__group_rename_callback,), - (self.__group_rename_errback,), - self._scenario, self.group_guid, - self.group_name) - - def __group_rename_callback(self): - callback = self._callback - callback[0](*callback[1:]) - - def __group_rename_errback(self, error_code): - errcode = AddressBookError.UNKNOWN - if error_code == 'GroupAlreadyExists': - errcode = AddressBookError.GROUP_ALREADY_EXIST - errback = self._errback[0] - args = self._errback[1:] - errback(errcode, *args) diff --git a/pymsn/pymsn/service/AddressBook/scenario/sync/__init__.py b/pymsn/pymsn/service/AddressBook/scenario/sync/__init__.py deleted file mode 100644 index 37bc43cd..00000000 --- a/pymsn/pymsn/service/AddressBook/scenario/sync/__init__.py +++ /dev/null @@ -1,20 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# - -from initial_sync import * diff --git a/pymsn/pymsn/service/AddressBook/scenario/sync/initial_sync.py b/pymsn/pymsn/service/AddressBook/scenario/sync/initial_sync.py deleted file mode 100644 index b906673c..00000000 --- a/pymsn/pymsn/service/AddressBook/scenario/sync/initial_sync.py +++ /dev/null @@ -1,95 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# -from pymsn.service.AddressBook.scenario.base import BaseScenario -from pymsn.service.AddressBook.constants import * - -__all__ = ['InitialSyncScenario'] - -class InitialSyncScenario(BaseScenario): - def __init__(self, address_book, membership, callback, errback, account=''): - """Synchronizes the membership content when logging in. - - @param membership: the address book service - @param callback: tuple(callable, *args) - @param errback: tuple(callable, *args) - """ - BaseScenario.__init__(self, 'Initial', callback, errback) - self.__membership = membership - self.__address_book = address_book - - self.__membership_response = None - self.__ab_response = None - - # FIXME : get the real account for 'Me' - self.__account = account - - def execute(self): - self.__address_book.FindAll((self.__ab_findall_callback,), - (self.__ab_findall_errback,), - self._scenario, False) - self.__membership.FindMembership((self.__membership_findall_callback,), - (self.__membership_findall_errback,), - self._scenario, ['Messenger'], - False) - - def __membership_findall_callback(self, result): - self.__membership_response = result - if self.__ab_response is not None: - callback = self._callback - callback[0](self.__ab_response, - self.__membership_response, *callback[1:]) - self.__membership_response = None - self.__ab_response = None - - def __ab_findall_callback(self, result): - self.__ab_response = result - if self.__membership_response is not None: - callback = self._callback - callback[0](self.__ab_response, - self.__membership_response, *callback[1:]) - self.__membership_response = None - self.__ab_response = None - - def __membership_findall_errback(self, error_code): - self.__sync_errback(error_code) - - def __ab_findall_errback(self, error_code): - self.__sync_errback(error_code) - - def __sync_errback(self, error_code): - errcode = AddressBookError.UNKNOWN - if error_code == 'ABDoesNotExist': - self.__ab.ABAdd((self.__ab_add_callback,), - (self.__ab_add_errback,), - self._scenario, - self._account) - return - errback = self._errback[0] - args = self._errback[1:] - errback(errcode, *args) - - def __ab_add_callback(self, *args): - self.execute() - - def __ab_add_errback(self, error_code): - errcode = AddressBookError.UNKNOWN - errback = self._errback[0] - args = self._errback[1:] - errback(errcode, *args) - diff --git a/pymsn/pymsn/service/AddressBook/sharing.py b/pymsn/pymsn/service/AddressBook/sharing.py deleted file mode 100644 index b5785f23..00000000 --- a/pymsn/pymsn/service/AddressBook/sharing.py +++ /dev/null @@ -1,221 +0,0 @@ -# -*- coding: utf-8 -*- -# -# pymsn - a python client library for Msn -# -# Copyright (C) 2007 Johann Prieur -# -# 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 - -from pymsn.service.SOAPService import SOAPService -from pymsn.util.element_tree import XMLTYPE -from pymsn.service.SingleSignOn import * -from pymsn.service.AddressBook.common import * - -__all__ = ['Sharing'] - -class Member(object): - def __init__(self, member): - self.Roles = {} - self.Account = "" - self.MembershipId = member.findtext("./ab:MembershipId") - self.Type = member.findtext("./ab:Type") - self.DisplayName = member.findtext("./ab:DisplayName") - self.State = member.findtext("./ab:State") - - self.Deleted = member.findtext("./ab:Deleted", "bool") - self.LastChanged = member.findtext("./ab:LastChanged", "datetime") - self.Changes = [] # FIXME: extract the changes - self.Annotations = annotations_to_dict(member.find("./ab:Annotations")) - - def __hash__(self): - return hash(self.Type) ^ hash(self.Account) - - def __eq__(self, other): - return (self.Type == other.Type) and (self.Account == other.Account) - - def __repr__(self): - return "<%sMember account=%s roles=%r>" % (self.Type, self.Account, self.Roles) - - @staticmethod - def new(member): - type = member.findtext("./ab:Type") - if type == "Passport": - return PassportMember(member) - elif type == "Email": - return EmailMember(member) - elif type == "Phone": - return PhoneMember(member) - else: - raise NotImplementedError("Member type not implemented : " + type) - - -class PassportMember(Member): - def __init__(self, member): - Member.__init__(self, member) - self.Id = member.findtext("./ab:PassportId", "int") - self.PassportName = member.findtext("./ab:PassportName") - self.IsPassportNameHidden = member.findtext("./ab:IsPassportNameHidden", "bool") - self.CID = member.findtext("./ab:CID", "int") - self.Changes = [] # FIXME: extract the changes - - self.Account = self.PassportName - -class EmailMember(Member): - def __init__(self, member): - Member.__init__(self, member) - self.Email = member.findtext("./ab:Email") - - self.Account = self.Email - -class PhoneMember(Member): - def __init__(self, member): - Member.__init__(self, member) - self.PhoneNumber = member.findtext("./ab:PhoneNumber") - - -class Sharing(SOAPService): - def __init__(self, sso, proxies=None): - self._sso = sso - self._tokens = {} - SOAPService.__init__(self, "Sharing", proxies) - - self._last_changes = "0001-01-01T00:00:00.0000000-08:00" - - @RequireSecurityTokens(LiveService.CONTACTS) - def FindMembership(self, callback, errback, scenario, services, deltas_only): - """Requests the membership list. - - @param scenario: 'Initial' | ... - @param services: a list containing the services to check in - ['Messenger', 'Invitation', 'SocialNetwork', - 'Space', 'Profile' ] - @param deltas_only: True if the method should only check changes - since last_change, False else - @param callback: tuple(callable, *args) - @param errback: tuple(callable, *args) - """ - self.__soap_request(self._service.FindMembership, scenario, - (services, deltas_only, self._last_changes), callback, errback) - - def _HandleFindMembershipResponse(self, callback, errback, response, user_data): - if response[1] is not None: - self._last_changes = response[1].text - - memberships = {} - for role, members in response[0].iteritems(): - for member in members: - membership_id = XMLTYPE.int.decode(member.find("./ab:MembershipId").text) - member_obj = Member.new(member) - member_id = hash(member_obj) - if member_id in memberships: - memberships[member_id].Roles[role] = membership_id - else: - member_obj.Roles[role] = membership_id - memberships[member_id] = member_obj - callback[0](memberships.values(), *callback[1:]) - - @RequireSecurityTokens(LiveService.CONTACTS) - def AddMember(self, callback, errback, scenario, member_role, type, - state, account): - """Adds a member to a membership list. - - @param scenario: 'Timer' | 'BlockUnblock' | ... - @param member_role: 'Allow' | ... - @param callback: tuple(callable, *args) - @param errback: tuple(callable, *args) - """ - self.__soap_request(self._service.AddMember, scenario, - (member_role, type, state, account), callback, errback) - - def _HandleAddMemberResponse(self, callback, errback, response, user_data): - callback[0](*callback[1:]) - - @RequireSecurityTokens(LiveService.CONTACTS) - def DeleteMember(self, callback, errback, scenario, member_role, type, - state, account): - """Deletes a member from a membership list. - - @param scenario: 'Timer' | 'BlockUnblock' | ... - @param member_role: 'Block' | ... - @param callback: tuple(callable, *args) - @param errback: tuple(callable, *args) - """ - self.__soap_request(self._service.DeleteMember, scenario, - (member_role, type, state, account), - callback, errback) - - def _HandleDeleteMemberResponse(self, callback, errback, response, user_data): - callback[0](*callback[1:]) - - def __soap_request(self, method, scenario, args, callback, errback): - token = str(self._tokens[LiveService.CONTACTS]) - - http_headers = method.transport_headers() - soap_action = method.soap_action() - - soap_header = method.soap_header(scenario, token) - soap_body = method.soap_body(*args) - - method_name = method.__name__.rsplit(".", 1)[1] - self._send_request(method_name, - self._service.url, - soap_header, soap_body, soap_action, - callback, errback, - http_headers) - - def _HandleSOAPFault(self, request_id, callback, errback, - soap_response, user_data): - errback[0](soap_response.fault.faultcode, *errback[1:]) - -if __name__ == '__main__': - import sys - import getpass - import signal - import gobject - import logging - from pymsn.service.SingleSignOn import * - - logging.basicConfig(level=logging.DEBUG) - - if len(sys.argv) < 2: - account = raw_input('Account: ') - else: - account = sys.argv[1] - - if len(sys.argv) < 3: - password = getpass.getpass('Password: ') - else: - password = sys.argv[2] - - mainloop = gobject.MainLoop(is_running=True) - - signal.signal(signal.SIGTERM, - lambda *args: gobject.idle_add(mainloop.quit())) - - def sharing_callback(memberships): - print "Memberships :" - for member in memberships: - print member - - sso = SingleSignOn(account, password) - sharing = Sharing(sso) - sharing.FindMembership((sharing_callback,), None, 'Initial', - ['Messenger', 'Invitation'], False) - - while mainloop.is_running(): - try: - mainloop.run() - except KeyboardInterrupt: - mainloop.quit() diff --git a/pymsn/pymsn/service/ContentRoaming/__init__.py b/pymsn/pymsn/service/ContentRoaming/__init__.py deleted file mode 100644 index 9feb5121..00000000 --- a/pymsn/pymsn/service/ContentRoaming/__init__.py +++ /dev/null @@ -1,21 +0,0 @@ -# -*- coding: utf-8 -*- -# -# pymsn - a python client library for Msn -# -# Copyright (C) 2007 Johann Prieur -# -# 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 - -from content_roaming import * diff --git a/pymsn/pymsn/service/ContentRoaming/constants.py b/pymsn/pymsn/service/ContentRoaming/constants.py deleted file mode 100644 index ef28a654..00000000 --- a/pymsn/pymsn/service/ContentRoaming/constants.py +++ /dev/null @@ -1,36 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Johann Prieur -# -# 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 - -__all__ = ["ContentRoamingError", "ContentRoamingState"] - -class ContentRoamingError(object): - UNKNOWN = 0 - -class ContentRoamingState(object): - """Content roaming service synchronization state. - - The service is said to be synchronized when it - matches the stuff stored on the server.""" - - NOT_SYNCHRONIZED = 0 - """The service is not synchronized yet""" - SYNCHRONIZING = 1 - """The service is being synchronized""" - SYNCHRONIZED = 2 - """The service is already synchronized""" - diff --git a/pymsn/pymsn/service/ContentRoaming/content_roaming.py b/pymsn/pymsn/service/ContentRoaming/content_roaming.py deleted file mode 100644 index abc7baae..00000000 --- a/pymsn/pymsn/service/ContentRoaming/content_roaming.py +++ /dev/null @@ -1,224 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Johann Prieur -# -# 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 storage -import scenario - -from pymsn.service.ContentRoaming.constants import * -from pymsn.service.ContentRoaming.scenario import * - -import gobject -import imghdr - -__all__ = ['ContentRoaming', 'ContentRoamingState', 'ContentRoamingError'] - -class ContentRoaming(gobject.GObject): - - __gproperties__ = { - "state" : (gobject.TYPE_INT, - "State", - "The state of the addressbook.", - 0, 2, ContentRoamingState.NOT_SYNCHRONIZED, - gobject.PARAM_READABLE), - - "display-name" : (gobject.TYPE_STRING, - "Display name", - "The user's display name on storage", - "", - gobject.PARAM_READABLE), - - "personal-message" : (gobject.TYPE_STRING, - "Personal message", - "The user's personal message on storage", - "", - gobject.PARAM_READABLE), - - "display-picture" : (gobject.TYPE_PYOBJECT, - "Display picture", - "The user's display picture on storage", - gobject.PARAM_READABLE) - } - - def __init__(self, sso, ab, proxies=None): - """The content roaming object""" - gobject.GObject.__init__(self) - - self._storage = storage.Storage(sso, proxies) - self._ab = ab - - self.__state = ContentRoamingState.NOT_SYNCHRONIZED - - self.__display_name = '' - self.__personal_message = '' - self.__display_picture = None - - self._profile_id = None - self._expression_profile_id = None - self._display_picture_id = None - - # Properties - def __get_state(self): - return self.__state - def __set_state(self, state): - self.__state = state - self.notify("state") - state = property(__get_state) - _state = property(__get_state, __set_state) - - @property - def display_name(self): - return self.__display_name - - @property - def personal_message(self): - return self.__personal_message - - @property - def display_picture(self): - return self.__display_picture - - def sync(self): - if self._state != ContentRoamingState.NOT_SYNCHRONIZED: - return - self._state = ContentRoamingState.SYNCHRONIZING - - gp = GetStoredProfileScenario(self._storage, - (self.__get_dn_and_pm_cb,), - (self.__get_display_picture_cb,), - (self.__common_errback,)) - gp.cid = self._ab.profile.cid - gp() - - # Public API - def store(self, display_name=None, personal_message=None, - display_picture=None): - if display_name is None: - display_name = self.__display_name - if personal_message is None: - personal_message = self.__personal_message - - if display_picture is not None: - type = imghdr.what('', display_picture) - if type is None: type = 'png' - display_picture = ('image/%s' % type, display_picture) - - def store_profile_cb(): - self.__display_name = display_name - self.__personal_message = personal_message - self.__display_picture = display_picture - - up = StoreProfileScenario(self._storage, - (store_profile_cb,), - (self.__common_errback,), - self._ab.profile.cid, - self._profile_id, - self._expression_profile_id, - self._display_picture_id) - - up.display_name = display_name - up.personal_message = personal_message - up.display_picture = display_picture - - up() - # End of public API - - # Callbacks - def __get_dn_and_pm_cb(self, profile_id, expression_profile_id, - display_name, personal_message, display_picture_id): - self._profile_id = profile_id - self._expression_profile_id = expression_profile_id - self._display_picture_id = display_picture_id - - self.__display_name = display_name - self.notify("display-name") - - self.__personal_message = personal_message - self.notify("personal-message") - - if self._display_picture_id is None: - self._state = ContentRoamingState.SYNCHRONIZED - - def __get_display_picture_cb(self, type, data): - self.__display_picture = (type, data) - self.notify("display-picture") - - self._state = ContentRoamingState.SYNCHRONIZED - - def __common_errback(self, error_code, *args): - print "The content roaming service got the error (%s)" % error_code - -gobject.type_register(ContentRoaming) - -if __name__ == '__main__': - import sys - import getpass - import signal - import gobject - import logging - from pymsn.service.SingleSignOn import * - from pymsn.service.AddressBook import * - - logging.basicConfig(level=logging.DEBUG) - - if len(sys.argv) < 2: - account = raw_input('Account: ') - else: - account = sys.argv[1] - - if len(sys.argv) < 3: - password = getpass.getpass('Password: ') - else: - password = sys.argv[2] - - mainloop = gobject.MainLoop(is_running=True) - - signal.signal(signal.SIGTERM, - lambda *args: gobject.idle_add(mainloop.quit())) - - def address_book_state_changed(address_book, pspec, sso): - if address_book.state == AddressBookState.SYNCHRONIZED: - - def content_roaming_state_changed(cr, pspec): - if cr.state == ContentRoamingState.SYNCHRONIZED: - print "Content roaming service is now synchronized" - -# print cr.display_picture -# type, data = cr.display_picture -# path = '/home/jprieur/projects/pymsn.rewrite/pymsn/service/ContentRoaming/argh.%s' % type.split('/')[1] -# f = open(path, 'w') -# f.write(data) - -# path = '/home/jprieur/projects/pymsn.rewrite/pymsn/service/ContentRoaming/test.jpeg' -# f = open(path, 'r') -# cr.store("Pouet pouet", "Brainy lala brainy...", f.read()) - - cr = ContentRoaming(sso, address_book) - cr.connect("notify::state", content_roaming_state_changed) - cr.sync() - - sso = SingleSignOn(account, password) - - address_book = AddressBook(sso) - address_book.connect("notify::state", address_book_state_changed, sso) - address_book.sync() - - while mainloop.is_running(): - try: - mainloop.run() - except KeyboardInterrupt: - mainloop.quit() diff --git a/pymsn/pymsn/service/ContentRoaming/scenario/__init__.py b/pymsn/pymsn/service/ContentRoaming/scenario/__init__.py deleted file mode 100644 index 3254c2d1..00000000 --- a/pymsn/pymsn/service/ContentRoaming/scenario/__init__.py +++ /dev/null @@ -1,26 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# - -name = "scenario" -description = "" - -import base - -from get_stored_profile import * -from store_profile import * diff --git a/pymsn/pymsn/service/ContentRoaming/scenario/base.py b/pymsn/pymsn/service/ContentRoaming/scenario/base.py deleted file mode 100644 index 91d57e43..00000000 --- a/pymsn/pymsn/service/ContentRoaming/scenario/base.py +++ /dev/null @@ -1,39 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# - -__all__ = ['BaseScenario'] - -class BaseScenario(object): - def __init__(self, partner_scenario, callback, errback): - self._scenario = partner_scenario - self._callback = callback - self._errback = errback - - def __set_scenario(self, scenario): - self._scenario = scenario - def __get_scenario(self): - return self._scenario - scenario = property(__get_scenario, __set_scenario) - - def execute(self): - pass - - def __call__(self): - return self.execute() - diff --git a/pymsn/pymsn/service/ContentRoaming/scenario/get_stored_profile.py b/pymsn/pymsn/service/ContentRoaming/scenario/get_stored_profile.py deleted file mode 100644 index 4d36e39b..00000000 --- a/pymsn/pymsn/service/ContentRoaming/scenario/get_stored_profile.py +++ /dev/null @@ -1,74 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# -from base import * - -from pymsn.service.ContentRoaming.constants import * - -__all__ = ['GetStoredProfileScenario'] - -class GetStoredProfileScenario(BaseScenario): - def __init__(self, storage, callback, dp_callback, errback, cid=''): - """Gets the roaming profile stored on the server - - @param storage: the storage service - @param callback: tuple(callable, *args) - @param errback: tuple(callable, *args) - """ - BaseScenario.__init__(self, 'Initial', callback, errback) - self.__storage = storage - self.__dp_callback = dp_callback - - self.cid = cid - - def execute(self): - self.__storage.GetProfile((self.__get_profile_callback,), - (self.__get_profile_errback,), - self._scenario, self.cid, - True, True, True, True, True, True, - True, True, True, True, True) - - def __get_profile_callback(self, profile_rid, expression_profile_rid, - display_name, personal_msg, photo_rid, - photo_mime_type, photo_data_size, photo_url): - callback = self._callback - callback[0](profile_rid, expression_profile_rid, display_name, - personal_msg, photo_rid, *callback[1:]) - - if photo_rid is not None: - self.__storage.get_display_picture(photo_url, - (self.__get_display_picture_callback,), - (self.__get_display_picture_errback,)) - - def __get_profile_errback(self, error_code): - errcode = ContentRoamingError.UNKNOWN - errback = self._errback[0] - args = self._errback[1:] - errback(errcode, *args) - - def __get_display_picture_callback(self, type, data): - callback = self.__dp_callback - callback[0](type, data, *callback[1:]) - - def __get_display_picture_errback(self, error_code): - # TODO : adapt this to the transport way of handling errors - errcode = ContentRoamingError.UNKNOWN - errback = self._errback[0] - args = self._errback[1:] - errback(errcode, *args) - diff --git a/pymsn/pymsn/service/ContentRoaming/scenario/store_profile.py b/pymsn/pymsn/service/ContentRoaming/scenario/store_profile.py deleted file mode 100644 index 38fd9168..00000000 --- a/pymsn/pymsn/service/ContentRoaming/scenario/store_profile.py +++ /dev/null @@ -1,102 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# -from base import * - -from pymsn.service.ContentRoaming.constants import * - -__all__ = ['StoreProfileScenario'] - -class StoreProfileScenario(BaseScenario): - def __init__(self, storage, callback, errback, - cid, profile_id, expression_profile_id, display_picture_id, - display_name='', personal_message='', display_picture=''): - """Updates the roaming profile stored on the server - - @param storage: the storage service - @param callback: tuple(callable, *args) - @param errback: tuple(callable, *args) - """ - BaseScenario.__init__(self, 'RoamingIdentityChanged', callback, errback) - self.__storage = storage - - self.__cid = cid - self.__profile_id = profile_id - self.__expression_profile_id = expression_profile_id - self.__display_picture_id = display_picture_id - - print self.__cid - print self.__profile_id - print self.__expression_profile_id - print self.__display_picture_id - - self.display_name = display_name - self.personal_message = personal_message - self.display_picture = display_picture - - def execute(self): - self.__storage.UpdateProfile((self.__update_profile_callback,), - (self.__store_profile_errback,), - self._scenario, self.__profile_id, - self.display_name, self.personal_message, - 0) - - def __update_profile_callback(self): - if self.display_picture is not None: - self.__storage.DeleteRelationships( - (self.__delete_relationship_profile_callback,), - (self.__store_profile_errback,), - self._scenario, - self.__display_picture_id, - self.__cid, None) - else: - callback = self._callback - callback[0](*callback[1:]) - - def __delete_relationship_profile_callback(self): - self.__storage.DeleteRelationships( - (self.__delete_relationship_expression_callback,), - (self.__store_profile_errback,), - self._scenario, self.__display_picture_id, - None, self.__expression_profile_id) - - def __delete_relationship_expression_callback(self): - # FIXME : add support for dp name - self.__storage.CreateDocument( - (self.__create_document_callback,), - (self.__store_profile_errback,), - self._scenario, self.__cid, - "roaming", self.display_picture[0], - self.display_picture[1].encode('base64')) - - def __create_document_callback(self, document_rid): - self.__storage.CreateRelationships( - (self.__create_relationship_callback,), - (self.__store_profile_errback,), - self._scenario, self.__expression_profile_id, document_rid) - - def __create_relationship_callback(self): - callback = self._callback - callback[0](*callback[1:]) - - def __store_profile_errback(self, error_code): - errcode = ContentRoamingError.UNKNOWN - errback = self._errback[0] - args = self._errback[1:] - errback(errcode, *args) - diff --git a/pymsn/pymsn/service/ContentRoaming/storage.py b/pymsn/pymsn/service/ContentRoaming/storage.py deleted file mode 100644 index eafd3863..00000000 --- a/pymsn/pymsn/service/ContentRoaming/storage.py +++ /dev/null @@ -1,169 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Johann Prieur -# -# 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 - -from pymsn.service.SOAPService import SOAPService, url_split -from pymsn.util.element_tree import XMLTYPE -from pymsn.service.SingleSignOn import * - -from pymsn.gnet.protocol import ProtocolFactory - -__all__ = ['Storage'] - -class Storage(SOAPService): - - def __init__(self, sso, proxies=None): - self._sso = sso - self._tokens = {} - SOAPService.__init__(self, "SchematizedStore", proxies) - - @RequireSecurityTokens(LiveService.CONTACTS) - def GetProfile(self, callback, errback, scenario, cid, profile_rid, - p_date_modified, expression_rid, e_date_modified, - display_name, dn_last_modified, personal_status, - ps_last_modified, user_tile_url, photo, flags): - self.__soap_request(self._service.GetProfile, scenario, - (cid, - XMLTYPE.bool.encode(profile_rid), - XMLTYPE.bool.encode(p_date_modified), - XMLTYPE.bool.encode(expression_rid), - XMLTYPE.bool.encode(e_date_modified), - XMLTYPE.bool.encode(display_name), - XMLTYPE.bool.encode(dn_last_modified), - XMLTYPE.bool.encode(personal_status), - XMLTYPE.bool.encode(ps_last_modified), - XMLTYPE.bool.encode(user_tile_url), - XMLTYPE.bool.encode(photo), - XMLTYPE.bool.encode(flags)), - callback, errback) - - def _HandleGetProfileResponse(self, callback, errback, response, user_date): - profile_rid = response.findtext('./st:ResourceID') - - expression_profile = response.find('./st:ExpressionProfile') - expression_profile_rid = expression_profile.findtext('./st:ResourceID') - - display_name = expression_profile.findtext('./st:DisplayName') - personal_msg = expression_profile.findtext('./st:PersonalStatus') - - photo = expression_profile.find('./st:Photo') - if photo is not None: - photo_rid = photo.findtext('./st:ResourceID') - document_stream = photo.find('./st:DocumentStreams/st:DocumentStream') - photo_mime_type = document_stream.findtext('./st:MimeType') - photo_data_size = document_stream.findtext('./st:DataSize', "int") - photo_url = document_stream.findtext('./st:PreAuthURL') - else: - photo_rid = photo_mime_type = photo_data_size = photo_url = None - - callback[0](profile_rid, expression_profile_rid, display_name, personal_msg, - photo_rid, photo_mime_type, photo_data_size, photo_url, - *callback[1:]) - - @RequireSecurityTokens(LiveService.CONTACTS) - def UpdateProfile(self, callback, errback, scenario, profile_rid, - display_name, personal_status, flags): - self.__soap_request(self._service.UpdateProfile, scenario, - (profile_rid, display_name, personal_status, flags), - callback, errback) - - def _HandleUpdateProfileResponse(self, callback, errback, response, user_date): - callback[0](*callback[1:]) - - @RequireSecurityTokens(LiveService.CONTACTS) - def CreateRelationships(self, callback, errback, scenario, - source_rid, target_rid): - self.__soap_request(self._service.CreateRelationships, scenario, - (source_rid, target_rid), - callback, errback) - - def _HandleCreateRelationshipsResponse(self, callback, errback, response, user_date): - callback[0](*callback[1:]) - - @RequireSecurityTokens(LiveService.CONTACTS) - def DeleteRelationships(self, callback, errback, scenario, - target_id, cid=None, source_id=None): - self.__soap_request(self._service.DeleteRelationships, scenario, - (cid, source_id, target_id), callback, errback) - - def _HandleDeleteRelationshipsResponse(self, callback, errback, response, user_date): - callback[0](*callback[1:]) - - @RequireSecurityTokens(LiveService.CONTACTS) - def CreateDocument(self, callback, errback, scenario, cid, photo_name, - photo_mime_type, photo_data): - self.__soap_request(self._service.CreateDocument, scenario, - (cid, photo_name, photo_mime_type, photo_data), - callback, errback) - - def _HandleCreateDocumentResponse(self, callback, errback, response, user_date): - document_rid = response.text - callback[0](document_rid, *callback[1:]) - - @RequireSecurityTokens(LiveService.CONTACTS) - def FindDocuments(self, callback, errback, scenario, cid): - self.__soap_request(self._service.FindDocuments, scenario, - (cid,), callback, errback) - - def _HandleFindDocumentsResponse(self, callback, errback, response, user_date): - document = response.find('./st:Document') - - document_rid = response.findtext('./st:ResourceID') - document_name = response.findtext('./st:Name') - callback[0](document_rid, document_name, *callback[1:]) - - def __soap_request(self, method, scenario, args, callback, errback): - token = str(self._tokens[LiveService.CONTACTS]) - - http_headers = method.transport_headers() - soap_action = method.soap_action() - - soap_header = method.soap_header(scenario, token) - soap_body = method.soap_body(*args) - - method_name = method.__name__.rsplit(".", 1)[1] - self._send_request(method_name, - self._service.url, - soap_header, soap_body, soap_action, - callback, errback, http_headers) - - @RequireSecurityTokens(LiveService.CONTACTS) - def get_display_picture(self, pre_auth_url, callback, errback): - token = str(self._tokens[LiveService.CONTACTS]) - - scheme = 'http' - host = 'byfiles.storage.msn.com' - port = 80 - resource = '?'.join([pre_auth_url, token.split('&')[0]]) - - def request_callback(transport, http_response): - type = http_response.get_header('Content-Type')#.split('/')[1] - data = http_response.body - callback[0](type, data, *callback[1:]) - - http_headers = {} - http_headers["Accept"] = "*/*" - http_headers["Proxy-Connection"] = "Keep-Alive" - http_headers["Connection"] = "Keep-Alive" - - proxy = self._proxies.get(scheme, None) - transport = ProtocolFactory(scheme, host, port, proxy=proxy) - transport.connect("response-received", request_callback) - transport.connect("request-sent", self._request_handler) - transport.connect("error", errback[0], *errback[1:]) - - transport.request(resource, http_headers, method='GET') diff --git a/pymsn/pymsn/service/OfflineIM/__init__.py b/pymsn/pymsn/service/OfflineIM/__init__.py deleted file mode 100644 index 7fde863a..00000000 --- a/pymsn/pymsn/service/OfflineIM/__init__.py +++ /dev/null @@ -1,22 +0,0 @@ -# -*- coding: utf-8 -*- -# -# pymsn - a python client library for Msn -# -# Copyright (C) 2007 Johann Prieur -# -# 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 - -from offline_messages_box import * -from constants import * diff --git a/pymsn/pymsn/service/OfflineIM/constants.py b/pymsn/pymsn/service/OfflineIM/constants.py deleted file mode 100644 index 56cce948..00000000 --- a/pymsn/pymsn/service/OfflineIM/constants.py +++ /dev/null @@ -1,42 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# - -__all__ = ["OfflineMessagesBoxState", "OfflineMessagesBoxError"] - -class OfflineMessagesBoxState(object): - """Offline messages box synchronization state. - - The box is said to be synchronized when it - owns the references to all the new messages on the server.""" - - NOT_SYNCHRONIZED = 0 - """The box is not synchronized yet""" - SYNCHRONIZING = 1 - """The box is being synchronized""" - SYNCHRONIZED = 2 - """The box is already synchronized""" - -class OfflineMessagesBoxError(object): - "Offline IM related errors" - UNKNOWN = 0 - AUTHENTICATION_FAILED = 1 - SYSTEM_UNAVAILABLE = 2 - SENDER_THROTTLE_LIMIT_EXCEEDED = 3 - - diff --git a/pymsn/pymsn/service/OfflineIM/offline_messages_box.py b/pymsn/pymsn/service/OfflineIM/offline_messages_box.py deleted file mode 100644 index a06d3927..00000000 --- a/pymsn/pymsn/service/OfflineIM/offline_messages_box.py +++ /dev/null @@ -1,416 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# - -import rsi -import oim -import scenario - -from pymsn.service.SOAPUtils import * -from pymsn.service.OfflineIM.constants import * - -from pymsn.profile import NetworkID - -from pymsn.util.decorator import throttled - -import pymsn.util.element_tree as ElementTree -import pymsn.util.string_io as StringIO -import pymsn.util.guid as guid -import pymsn.util.iso8601 as iso8601 - -import datetime -import gobject - -import logging - -__all__ = ['OfflineMessagesBox', 'OfflineMessage'] - -logger = logging.getLogger('Service') - -class OfflineMessagesStorage(list): - def __init__(self, initial_set=()): - list.__init__(self, initial_set) - -# def __repr__(self): -# return "OfflineMessagesStorage : %d message(s)" % len(self) - - def add(self, message): - self.append(message) - - def __getattr__(self, name): - if name.startswith("search_by_"): - field = name[10:] - def search_by_func(criteria): - return self.search_by(field, criteria) - search_by_func.__name__ = name - return search_by_func - elif name.startswith("group_by_"): - field = name[9:] - def group_by_func(): - return self.group_by(field) - group_by_func.__name__ = name - return group_by_func - else: - raise AttributeError, name - - def search_by(self, field, value): - result = [] - for contact in self: - if getattr(contact, field) == value: - result.append(contact) - return OfflineMessagesStorage(result) - - def group_by(self, field): - result = {} - for contact in self: - value = getattr(contact, field) - if value not in result: - result[value] = OfflineMessagesStorage() - result[value].add(contact) - return result - -class OfflineMessage(object): - - def __init__(self, id, sender, display_name='', date=None): - self._id = id - self._sender = sender - self._display_name = display_name - - if date is None: - self._date = datetime.datetime.utcnow() - else: - date = iso8601.parse_date(date) - self._date = date.replace(tzinfo=None) # FIXME: do not disable the timezone - - self.__text = '' - self.__run_id = '' - self.__sequence_num = -1 - self.__is_mobile = False - - @property - def id(self): - return self._id - - @property - def sender(self): - return self._sender - - @property - def display_name(self): - return self._display_name - - @property - def date(self): - return self._date - - def __get_text(self): - return self.__text - def __set_text(self, text): - self.__text = text - text = property(__get_text) - _text = property(__get_text, __set_text) - - def __get_run_id(self): - return self.__run_id - def __set_run_id(self, run_id): - self.__run_id = run_id - run_id = property(__get_run_id) - _run_id = property(__get_run_id, __set_run_id) - - def __get_sequence_num(self): - return self.__sequence_num - def __set_sequence_num(self, sequence_num): - self.__sequence_num = sequence_num - sequence_num = property(__get_sequence_num) - _sequence_num = property(__get_sequence_num, __set_sequence_num) - - def __get_is_mobile(self): - return self.__is_mobile - def __set_is_mobile(self, is_mobile): - self.__is_mobile = is_mobile - is_mobile = property(__get_is_mobile) - _is_mobile = property(__get_is_mobile, __set_is_mobile) - - def __str__(self): - return self.__text - - def __repr__(self): - return "" % (self.run_id, self.sequence_num) - - def __cmp__(self, other): - if self.run_id == other.run_id: - return self.sequence_num - other.sequence_num - elif self.date >= other.date: - return 1 - return -1 - -class Metadata(ElementTree.XMLResponse): - def __init__(self, metadata): - ElementTree.XMLResponse.__init__(self, metadata) - if self.tree is None: - logger.warning("Metadata: Invalid metadata") - - def is_valid(self): - return self.tree is not None - - def _parse(self, data): - data = StringIO.StringIO(data) - return ElementTree.parse(data) - -class OfflineMessagesBox(gobject.GObject): - - __gsignals__ = { - "error" : (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - (object,)), - - "messages-received" : (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - (object,)), - "messages-fetched" : (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - (object,)), - "messages-deleted" : (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, ()), - "message-sent" : (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - (object, str)) - } - - __gproperties__ = { - "state": (gobject.TYPE_INT, - "State", - "The state of the offline messages box.", - 0, 2, OfflineMessagesBoxState.NOT_SYNCHRONIZED, - gobject.PARAM_READABLE), - "messages" : (gobject.TYPE_PYOBJECT, - "Offline messages", - "The fetched offline messages.", - gobject.PARAM_READABLE) - } - - def __init__(self, sso, client, proxies=None): - gobject.GObject.__init__(self) - - self._client = client - self._rsi = rsi.RSI(sso, proxies) - self._oim = oim.OIM(sso, proxies) - - self.__state = OfflineMessagesBoxState.NOT_SYNCHRONIZED - self.__messages = OfflineMessagesStorage() - - self.__conversations = {} - - # Properties - def __get_state(self): - return self.__state - def __set_state(self, state): - self.__state = state - self.notify("state") - state = property(__get_state) - _state = property(__get_state, __set_state) - - def __get_messages(self): - return self.__messages - def __set_messages(self, messages): - self.__messages = messages - self.notify("messages") - messages = property(__get_messages) - _messages = property(__get_messages, - __set_messages) - - def sync(self, xml_data=None): - if self._state != OfflineMessagesBoxState.NOT_SYNCHRONIZED: - return - self._state = OfflineMessagesBoxState.SYNCHRONIZING - if xml_data is None: - sh = scenario.SyncHeadersScenario(self._rsi, - (self.__parse_metadata,), - (self.__common_errback,)) - sh() - else: - self.__parse_metadata(xml_data) - - def __parse_metadata(self, xml_data): - 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_id = NetworkID.MSN - elif network == (11,7): - # FIXME: What does 11 and 7 mean? We should find out... - # FIXME: My first guess would be Windows Live Communication Server - network_id = NetworkID.LCS - elif network == (13,7): - network_id = NetworkID.EXTERNAL - else: - # This is to ensure that network_id is always set, even if we get unknown values for network - # FIXME: Is NetworkID.MSN the best choice as default value? - network_id = NetworkID.MSN - - account = m.findtext('./E') - - try: - sender = self._client.address_book.contacts.\ - search_by_account(account).\ - search_by_network_id(network_id)[0] - except IndexError: - sender = None - - if network_id == NetworkID.MSN: - name = m.findtext('./N').replace(' ','').\ - split('?')[3].decode('base64').encode('utf-8') - elif network_id == NetworkID.EXTERNAL: - name = m.findtext('./N').encode('utf-8') - - date = m.find('./RT') - if date is not None: - date = date.text - - self.__messages.add(OfflineMessage(id, sender, name, date)) - - self._state = OfflineMessagesBoxState.SYNCHRONIZED - - if len(self.__messages) > 0: - self.emit('messages-received', self.__messages) - - # Public API - def fetch_messages(self, messages=None): - if messages is None: - messages = self.messages - - if len(messages) == 0: - return - - fm = scenario.FetchMessagesScenario(self._rsi, - (self.__fetch_message_cb,), - (self.__common_errback,), - (self.__fetch_messages_cb, messages)) - fm.message_ids = [m.id for m in messages] - fm() - - @throttled(1000, list()) - def send_message(self, recipient, message): - if recipient.network_id == NetworkID.EXTERNAL: - return - - convo = self.__conversations.get(recipient, None) - if convo is None: - run_id = guid.generate_guid() - sequence_num = 1 - self.__conversations[recipient] = [run_id, sequence_num] - else: - (run_id, sequence_num) = convo - convo[1] = convo[1] + 1 - - sm = scenario.SendMessageScenario(self._oim, - self._client, recipient, message, - (self.__send_message_cb, recipient, message), - (self.__common_errback,)) - - sm.run_id = run_id - sm.sequence_num = sequence_num - sm() - - def delete_messages(self, messages=None): - if messages is None: - messages = self.messages - - if len(messages) == 0: - return - - dm = scenario.DeleteMessagesScenario(self._rsi, - (self.__delete_messages_cb, messages), - (self.__common_errback,)) - dm.message_ids = [m.id for m in messages] - dm() - - # Callbacks - def __fetch_message_cb(self, id, run_id, sequence_num, text): - message = self._messages.search_by_id(id)[0] - message._run_id = run_id - message._sequence_num = sequence_num - message._text = text - - def __fetch_messages_cb(self, messages): - messages.sort() - self.emit('messages-fetched', messages) - - def __send_message_cb(self, recipient, message): - self.emit('message-sent', recipient, message) - - def __delete_messages_cb(self, messages): - for message in messages: - try: - self._messages.remove(message) - except ValueError: - pass - self.emit('messages-deleted') - - def __common_callback(self, signal, *args): - self.emit(signal, *args) - - def __common_errback(self, error_code, *args): - self.emit('error', error_code) - -gobject.type_register(OfflineMessagesBox) - - -if __name__ == '__main__': - import sys - import getpass - import signal - import gobject - import logging - from pymsn.service.SingleSignOn import * - from pymsn.service.AddressBook import AddressBook - - logging.basicConfig(level=logging.DEBUG) - - if len(sys.argv) < 2: - account = raw_input('Account: ') - else: - account = sys.argv[1] - - if len(sys.argv) < 3: - password = getpass.getpass('Password: ') - else: - password = sys.argv[2] - - mainloop = gobject.MainLoop(is_running=True) - - signal.signal(signal.SIGTERM, - lambda *args: gobject.idle_add(mainloop.quit())) - - def sso_callback(arg): - print arg - - def sso_errback(): - pass - - sso = SingleSignOn(account, password) - - box = OfflineMessagesBox(sso) - box.sync() - - while mainloop.is_running(): - try: - mainloop.run() - except KeyboardInterrupt: - mainloop.quit() diff --git a/pymsn/pymsn/service/OfflineIM/oim.py b/pymsn/pymsn/service/OfflineIM/oim.py deleted file mode 100644 index 73fd7806..00000000 --- a/pymsn/pymsn/service/OfflineIM/oim.py +++ /dev/null @@ -1,112 +0,0 @@ -# -*- coding: utf-8 -*- -# -# pymsn - a python client library for Msn -# -# Copyright (C) 2007 Johann Prieur -# -# 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 - -from pymsn.service.OfflineIM.constants import * -from pymsn.service.SOAPService import SOAPService -from pymsn.msnp.notification import ProtocolConstant -from pymsn.service.SingleSignOn import * - -__all__ = ['OIM'] - -class OIM(SOAPService): - def __init__(self, sso, proxies=None): - self._sso = sso - self._tokens = {} - self.__lock_key = "" - SOAPService.__init__(self, "OIM", proxies) - - def set_lock_key(self, lock_key): - self.__lock_key = lock_key - - @RequireSecurityTokens(LiveService.MESSENGER_SECURE) - def Store2(self, callback, errback, from_member_name, friendly_name, - to_member_name, session_id, message_number, message_type, message_content): - import base64 - token = str(self._tokens[LiveService.MESSENGER_SECURE]) - fname = "=?utf-8?B?%s?=" % base64.b64encode(friendly_name) - - content = self.__build_mail_data(session_id, message_number, message_content) - - self.__soap_request(self._service.Store2, - (from_member_name, fname, - ProtocolConstant.CVR[4], - ProtocolConstant.VER[0], - ProtocolConstant.CVR[5], - to_member_name, - message_number, - token, - ProtocolConstant.PRODUCT_ID, - self.__lock_key), - (message_type, content), - callback, errback) - - def _HandleStore2Response(self, callback, errback, response, user_data): - callback[0](*callback[1:]) - - def _HandleStore2Fault(self, callback, errback, soap_response, user_data): - error_code = OfflineMessagesBoxError.UNKNOWN - auth_policy = None - lock_key_challenge = None - - if soap_response.fault.faultcode.endswith("AuthenticationFailed"): - error_code = OfflineMessagesBoxError.AUTHENTICATION_FAILED - auth_policy = soap_response.fault.detail.findtext("./oim:RequiredAuthPolicy") - lock_key_challenge = soap_response.fault.detail.findtext("./oim:LockKeyChallenge") - - if auth_policy == "": - auth_policy = None - if lock_key_challenge == "": - lock_key_challenge = None - - #print "Authentication failed - policy = %s - lockkey = %s" % (auth_policy, lock_key_challenge) - elif soap_response.fault.faultcode.endswith("SystemUnavailable"): - error_code = OfflineMessagesBoxError.SYSTEM_UNAVAILABLE - elif soap_response.fault.faultcode.endswith("SenderThrottleLimitExceeded"): - error_code = OfflineMessagesBoxError.SENDER_THROTTLE_LIMIT_EXCEEDED - - errback[0](error_code, auth_policy, lock_key_challenge, *errback[1:]) - - def __build_mail_data(self, run_id, sequence_number, content): - import base64 - mail_data = 'MIME-Version: 1.0\r\n' - mail_data += 'Content-Type: text/plain; charset=UTF-8\r\n' - mail_data += 'Content-Transfer-Encoding: base64\r\n' - mail_data += 'X-OIM-Message-Type: OfflineMessage\r\n' - mail_data += 'X-OIM-Run-Id: {%s}\r\n' % run_id - mail_data += 'X-OIM-Sequence-Num: %s\r\n\r\n' % sequence_number - mail_data += base64.b64encode(content) - return mail_data - - def __soap_request(self, method, header_args, body_args, - callback, errback, user_data=None): - http_headers = method.transport_headers() - soap_action = method.soap_action() - - soap_header = method.soap_header(*header_args) - soap_body = method.soap_body(*body_args) - - method_name = method.__name__.rsplit(".", 1)[1] - self._send_request(method_name, self._service.url, - soap_header, soap_body, soap_action, - callback, errback, http_headers, user_data) - - def _HandleSOAPFault(self, request_id, callback, errback, - soap_response, user_data): - errback[0](None, *errback[1:]) diff --git a/pymsn/pymsn/service/OfflineIM/rsi.py b/pymsn/pymsn/service/OfflineIM/rsi.py deleted file mode 100644 index 97b54eaa..00000000 --- a/pymsn/pymsn/service/OfflineIM/rsi.py +++ /dev/null @@ -1,94 +0,0 @@ -# -*- coding: utf-8 -*- -# -# pymsn - a python client library for Msn -# -# Copyright (C) 2007 Johann Prieur -# -# 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 - -from pymsn.util.element_tree import XMLTYPE -from pymsn.service.SingleSignOn import * -from pymsn.service.SOAPService import SOAPService - -import email - -import logging - -__all__ = ['RSI'] - -logger = logging.getLogger('Service') - -class RSI(SOAPService): - def __init__(self, sso, proxies=None): - self._sso = sso - self._tokens = {} - SOAPService.__init__(self, "RSI", proxies) - - @RequireSecurityTokens(LiveService.MESSENGER) - def GetMetadata(self, callback, errback): - self.__soap_request(self._service.GetMetadata, (), - callback, errback) - - def _HandleGetMetadataResponse(self, callback, errback, response, user_data): - callback[0](response.text, *callback[1:]) - - @RequireSecurityTokens(LiveService.MESSENGER) - def GetMessage(self, callback, errback, message_id, mark_as_read): - self.__soap_request(self._service.GetMessage, - (message_id, XMLTYPE.bool.encode(mark_as_read)), - callback, errback) - - def _HandleGetMessageResponse(self, callback, errback, response, user_data): - m = email.message_from_string(response.text) - run_id = m.get('X-OIM-Run-Id')[1:-1] - seq_num = int(m.get('X-OIM-Sequence-Num')) - if m.get_content_type().split('/')[1] == 'vnd.ms-msnipg': - # FIXME : process the IPG data - # http://www.amsn-project.net/forums/viewtopic.php?p=21744 - # set a mobile sender flag - return - callback[0](run_id, seq_num, m.get_payload().decode('base64'), - *callback[1:]) - - @RequireSecurityTokens(LiveService.MESSENGER) - def DeleteMessages(self, callback, errback, message_ids): - self.__soap_request(self._service.DeleteMessages, (message_ids,), - callback, errback) - - def _HandleDeleteMessagesResponse(self, callback, errback, response, user_data): - callback[0](*callback[1:]) - - def __soap_request(self, method, args, callback, errback): - token = str(self._tokens[LiveService.MESSENGER]) - - http_headers = method.transport_headers() - soap_action = method.soap_action() - - soap_header = method.soap_header(token) - soap_body = method.soap_body(*args) - - method_name = method.__name__.rsplit(".", 1)[1] - self._send_request(method_name, self._service.url, - soap_header, soap_body, soap_action, - callback, errback, http_headers) - - def _HandleSOAPFault(self, request_id, callback, errback, - soap_response, user_data): - errback[0](None, *errback[1:]) - - - - - diff --git a/pymsn/pymsn/service/OfflineIM/scenario/__init__.py b/pymsn/pymsn/service/OfflineIM/scenario/__init__.py deleted file mode 100644 index 750164fe..00000000 --- a/pymsn/pymsn/service/OfflineIM/scenario/__init__.py +++ /dev/null @@ -1,28 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# - -name = "scenario" -description = "" - -import base - -from sync_headers import * -from fetch_messages import * -from send_message import * -from delete_messages import * diff --git a/pymsn/pymsn/service/OfflineIM/scenario/base.py b/pymsn/pymsn/service/OfflineIM/scenario/base.py deleted file mode 100644 index 03b56ed0..00000000 --- a/pymsn/pymsn/service/OfflineIM/scenario/base.py +++ /dev/null @@ -1,32 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# - -__all__ = ['BaseScenario'] - -class BaseScenario(object): - def __init__(self, callback, errback): - self._callback = callback - self._errback = errback - - def execute(self): - pass - - def __call__(self): - return self.execute() - diff --git a/pymsn/pymsn/service/OfflineIM/scenario/delete_messages.py b/pymsn/pymsn/service/OfflineIM/scenario/delete_messages.py deleted file mode 100644 index 08f0692a..00000000 --- a/pymsn/pymsn/service/OfflineIM/scenario/delete_messages.py +++ /dev/null @@ -1,50 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# -from pymsn.service.OfflineIM.constants import * -from pymsn.service.OfflineIM.scenario.base import BaseScenario - -__all__ = ['DeleteMessagesScenario'] - -class DeleteMessagesScenario(BaseScenario): - def __init__(self, rsi, callback, errback, message_ids=[]): - """Accepts an invitation. - - @param rsi: the rsi service - @param callback: tuple(callable, *args) - @param errback: tuple(callable, *args) - """ - BaseScenario.__init__(self, callback, errback) - self.__rsi = rsi - - self.message_ids = message_ids - - def execute(self): - self.__rsi.DeleteMessages((self.__delete_messages_callback,), - (self.__delete_messages_errback,), - self.message_ids) - - def __delete_messages_callback(self): - callback = self._callback - callback[0](*callback[1:]) - - def __delete_messages_errback(self, error_code): - errcode = OfflineMessagesBoxError.UNKNOWN - errback = self._errback[0] - args = self._errback[1:] - errback(errcode, *args) diff --git a/pymsn/pymsn/service/OfflineIM/scenario/fetch_messages.py b/pymsn/pymsn/service/OfflineIM/scenario/fetch_messages.py deleted file mode 100644 index ad7cfef8..00000000 --- a/pymsn/pymsn/service/OfflineIM/scenario/fetch_messages.py +++ /dev/null @@ -1,57 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# -from pymsn.service.OfflineIM.constants import * -from pymsn.service.OfflineIM.scenario.base import BaseScenario - -__all__ = ['FetchMessagesScenario'] - -class FetchMessagesScenario(BaseScenario): - def __init__(self, rsi, callback, errback, global_callback, message_ids=[]): - """Accepts an invitation. - - @param rsi: the rsi service - @param message_ids: id list of messages to fetch - @param callback: tuple(callable, *args) - @param errback: tuple(callable, *args) - """ - BaseScenario.__init__(self, callback, errback) - self.__rsi = rsi - self.__global_callback = global_callback - - self.message_ids = message_ids - - def execute(self): - for message_id in self.message_ids: - self.__rsi.GetMessage((self.__get_message_callback, message_id), - (self.__get_message_errback,), - message_id, False) - - def __get_message_callback(self, run_id, seq_num, message, id): - callback = self._callback - callback[0](id, run_id, seq_num, message, *callback[1:]) - self.message_ids.remove(id) - if self.message_ids == []: - global_callback = self.__global_callback - global_callback[0](*global_callback[1:]) - - def __get_message_errback(self, error_code): - errcode = OfflineMessagesBoxError.UNKNOWN - errback = self._errback[0] - args = self._errback[1:] - errback(errcode, *args) diff --git a/pymsn/pymsn/service/OfflineIM/scenario/send_message.py b/pymsn/pymsn/service/OfflineIM/scenario/send_message.py deleted file mode 100644 index f23cd30a..00000000 --- a/pymsn/pymsn/service/OfflineIM/scenario/send_message.py +++ /dev/null @@ -1,75 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# -from pymsn.service.OfflineIM.constants import * -from pymsn.service.OfflineIM.scenario.base import BaseScenario -from pymsn.msnp.challenge import _msn_challenge - -__all__ = ['SendMessageScenario'] - -class SendMessageScenario(BaseScenario): - def __init__(self, oim, client, recipient, message, callback, errback): - """Accepts an invitation. - - @param oim: the oim service - @param client: the client object sending the OIM - @param recipient: the contact to send the OIM to - @param message: the message to send - @param callback: tuple(callable, *args) - @param errback: tuple(callable, *args) - """ - BaseScenario.__init__(self, callback, errback) - self.__oim = oim - self.__client = client - self.__from = client.profile - self.__to = recipient - - self.run_id = "" - self.sequence_num = -1 - - self.__msg = message - - def execute(self): - self.__oim.Store2((self.__store2_callback,), - (self.__store2_errback,), - self.__from.account, - self.__from.display_name, - self.__to.account, - self.run_id, - self.sequence_num, - "text", - self.__msg) - - def __store2_callback(self): - callback = self._callback - callback[0](*callback[1:]) - - def __store2_errback(self, error_code, auth_policy, lock_key_challenge): - if error_code == OfflineMessagesBoxError.AUTHENTICATION_FAILED: - if lock_key_challenge != None: - self.__oim.set_lock_key(_msn_challenge(lock_key_challenge)) - if auth_policy != None: - self._client._sso.DiscardSecurityTokens([LiveService.CONTACTS]) - self._client._sso.RequestMultipleSecurityTokens((self.execute, ), None, LiveService.CONTACTS) - return - - self.execute() - return - - errback = self._errback - errback[0](error_code, *errback[1:]) diff --git a/pymsn/pymsn/service/OfflineIM/scenario/sync_headers.py b/pymsn/pymsn/service/OfflineIM/scenario/sync_headers.py deleted file mode 100644 index 19d7ad79..00000000 --- a/pymsn/pymsn/service/OfflineIM/scenario/sync_headers.py +++ /dev/null @@ -1,47 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# -from pymsn.service.OfflineIM.constants import * -from pymsn.service.OfflineIM.scenario.base import BaseScenario - -__all__ = ['SyncHeadersScenario'] - -class SyncHeadersScenario(BaseScenario): - def __init__(self, rsi, callback, errback): - """Accepts an invitation. - - @param rsi: the rsi service - @param callback: tuple(callable, *args) - @param errback: tuple(callable, *args) - """ - BaseScenario.__init__(self, callback, errback) - self.__rsi = rsi - - def execute(self): - self.__rsi.GetMetadata((self.__get_metadata_callback,), - (self.__get_metadata_errback,)) - - def __get_metadata_callback(self, metadata): - callback = self._callback - callback[0](metadata, *callback[1:]) - - def __get_metadata_errback(self, error_code): - errcode = OfflineMessagesBoxError.UNKNOWN - errback = self._errback[0] - args = self._errback[1:] - errback(errcode, *args) diff --git a/pymsn/pymsn/service/SOAPService.py b/pymsn/pymsn/service/SOAPService.py deleted file mode 100644 index 7c31a61d..00000000 --- a/pymsn/pymsn/service/SOAPService.py +++ /dev/null @@ -1,263 +0,0 @@ -# -*- coding: utf-8 -*- -# -# pymsn - a python client library for Msn -# -# Copyright (C) 2005-2007 Ali Sabil -# Copyright (C) 2007 Johann Prieur -# -# 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 description -from SOAPUtils import * - -import pymsn.gnet.protocol -import pymsn.util.element_tree as ElementTree -import pymsn.util.string_io as StringIO -import re -import logging - -__all__ = ['SOAPService', 'SOAPResponse'] - -logger = logging.getLogger('Service') - -def url_split(url, default_scheme='http'): - from urlparse import urlsplit, urlunsplit - if "://" not in url: # fix a bug in urlsplit - url = default_scheme + "://" + url - protocol, host, path, query, fragment = urlsplit(url) - if path == "": path = "/" - try: - host, port = host.rsplit(":", 1) - port = int(port) - except: - port = None - resource = urlunsplit(('', '', path, query, fragment)) - return protocol, host, port, resource - -def compress_xml(xml_string): - space_regex = [(re.compile('>\s+<'), '><'), - (re.compile('>\s+'), '>'), - (re.compile('\s+<'), '<')] - - for regex, replacement in space_regex: - xml_string = regex.sub(replacement, xml_string) - return xml_string - -soap_template = """ - - - %s - - - %s - -""" - -class SOAPFault(object): - def __init__(self, tree): - self.tree = tree - self.faultcode = None - self.faultstring = None - self.faultactor = None - self.detail = None - - if tree is not None: - self.faultcode = tree.findtext("./faultcode") - self.faultstring = tree.findtext("./faultstring") - self.faultactor = tree.findtext("./faultactor") - self.detail = tree.find("./detail") - - def is_fault(self): - return self.tree is not None - - def __repr__(self): - return """ fault code : %s - fault string : %s - fault actor : %s - detail : %s""" % ( - self.faultcode, self.faultstring, self.faultactor, self.detail) - - def __str__(self): - return self.__repr__() - - -class SOAPResponse(ElementTree.XMLResponse): - NS_SHORTHANDS = {"soap" : XMLNS.SOAP.ENVELOPE, - "xmlenc" : XMLNS.ENCRYPTION.BASE, - "wsse" : XMLNS.WS.SECEXT, - "wst" : XMLNS.WS.TRUST, - "wsa" : XMLNS.WS.ADDRESSING, - "wsp" : XMLNS.WS.POLICY, - "wsi" : XMLNS.WS.ISSUE, - "wsu" : XMLNS.WS.UTILITY, - "ps" : XMLNS.MICROSOFT.PASSPORT, - "psf" : XMLNS.MICROSOFT.PASSPORT_FAULT, - "ab" : XMLNS.MICROSOFT.LIVE.ADDRESSBOOK, - "st" : XMLNS.MICROSOFT.LIVE.STORAGE, - "oim" : XMLNS.MICROSOFT.LIVE.OIM, - "rsi" : XMLNS.MICROSOFT.LIVE.RSI, - "spaces" : XMLNS.MICROSOFT.LIVE.SPACES } - - def __init__(self, soap_data): - ElementTree.XMLResponse.__init__(self, soap_data, self.NS_SHORTHANDS) - try: - self.header = self.tree.find("./soap:Header") - self.body = self.tree.find("./soap:Body") - try: - self.fault = SOAPFault(self.body.find("./soap:Fault")) - except: - self.fault = SOAPFault(self.tree.find("./soap:Fault")) - except: - self.tree = None - self.header = None - self.body = None - self.fault = None - logger.warning("SOAPResponse: Invalid xml+soap data : %s" % soap_data) - - def is_fault(self): - return self.fault.is_fault() - - def is_valid(self): - return ((self.header is not None) or \ - (self.fault is not None) or \ - (self.body is not None)) \ - and self.tree is not None - - def _parse(self, data): - events = ("start", "end", "start-ns", "end-ns") - ns = [] - data = StringIO.StringIO(data) - context = ElementTree.iterparse(data, events=events) - for event, elem in context: - if event == "start-ns": - ns.append(elem) - elif event == "end-ns": - ns.pop() - elif event == "start": - elem.set("(xmlns)", tuple(ns)) - data.close() - return context.root - -class SOAPService(object): - - def __init__(self, name, proxies=None): - self._name = name - self._service = getattr(description, self._name) - self._active_transports = {} - self._proxies = proxies or {} - - def _send_request(self, name, url, soap_header, soap_body, soap_action, - callback, errback=None, transport_headers={}, user_data=None): - - scheme, host, port, resource = url_split(url) - http_headers = transport_headers.copy() - if soap_action is not None: - http_headers["SOAPAction"] = str(soap_action) - http_headers["Content-Type"] = "text/xml; charset=utf-8" - http_headers["Cache-Control"] = "no-cache" - if "Accept" not in http_headers: - http_headers["Accept"] = "text/*" - http_headers["Proxy-Connection"] = "Keep-Alive" - http_headers["Connection"] = "Keep-Alive" - - request = compress_xml(soap_template % (soap_header, soap_body)) - - transport = self._get_transport(name, scheme, host, port, - callback, errback, user_data) - transport.request(resource, http_headers, request, 'POST') - - def _response_handler(self, transport, http_response): - logger.debug("<<< " + str(http_response)) - soap_response = SOAPResponse(http_response.body) - request_id, callback, errback, user_data = self._unref_transport(transport) - - if not soap_response.is_valid(): - logger.warning("Invalid SOAP Response") - return #FIXME: propagate the error up - - if not soap_response.is_fault(): - handler = getattr(self, - "_Handle" + request_id + "Response", - None) - method = getattr(self._service, request_id) - response = method.process_response(soap_response) - - if handler is not None: - handler(callback, errback, response, user_data) - else: - self._HandleUnhandledResponse(request_id, callback, errback, - response, user_data) - else: - handler = getattr(self, - "_Handle" + request_id + "Fault", - None) - if handler is not None: - handler(callback, errback, soap_response, user_data) - else: - self._HandleSOAPFault(request_id, callback, errback, soap_response, - user_data) - - def _request_handler(self, transport, http_request): - logger.debug(">>> " + str(http_request)) - - def _error_handler(self, transport, error): - logger.warning("Transport Error :" + str(error)) - request_id, callback, errback, user_data = self._unref_transport(transport) - return request_id, callback, errback #FIXME: do something sensible here - - # Handlers - def _HandleSOAPFault(self, request_id, callback, errback, - soap_response, user_data): - logger.warning("Unhandled SOAPFault to %s" % request_id) - - def _HandleUnhandledResponse(self, request_id, callback, errback, - response, user_data): - logger.warning("Unhandled Response to %s" % request_id) - - # Transport management - def _get_transport(self, request_id, scheme, host, port, - callback, errback, user_data): - key = (scheme, host, port) - if key in self._active_transports: - trans = self._active_transports[key] - transport = trans[0] - trans[1].append((request_id, callback, errback, user_data)) - else: - proxy = self._proxies.get(scheme, None) - transport = pymsn.gnet.protocol.ProtocolFactory(scheme, - host, port, proxy=proxy) - handler_id = [transport.connect("response-received", - self._response_handler), - transport.connect("request-sent", self._request_handler), - transport.connect("error", self._error_handler)] - - trans = [transport, [(request_id, callback, errback, user_data)], handler_id] - self._active_transports[key] = trans - return transport - - def _unref_transport(self, transport): - for key, trans in self._active_transports.iteritems(): - if trans[0] == transport: - response = trans[1].pop(0) - - if len(trans[1]) != 0: - return response - - for handle in trans[2]: - transport.disconnect(handle) - del self._active_transports[key] - return response - return None - diff --git a/pymsn/pymsn/service/SOAPUtils.py b/pymsn/pymsn/service/SOAPUtils.py deleted file mode 100644 index 3720f507..00000000 --- a/pymsn/pymsn/service/SOAPUtils.py +++ /dev/null @@ -1,62 +0,0 @@ -# -*- coding: utf-8 -*- -# -# pymsn - a python client library for Msn -# -# Copyright (C) 2005-2007 Ali Sabil -# Copyright (C) 2007 Johann Prieur -# -# 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 - - -__all__ = ['XMLNS'] - -class XMLNS(object): - - class SOAP(object): - ENVELOPE = "http://schemas.xmlsoap.org/soap/envelope/" - ENCODING = "http://schemas.xmlsoap.org/soap/encoding/" - ACTOR_NEXT = "http://schemas.xmlsoap.org/soap/actor/next" - - class SCHEMA(object): - XSD1 = "http://www.w3.org/1999/XMLSchema" - XSD2 = "http://www.w3.org/2000/10/XMLSchema" - XSD3 = "http://www.w3.org/2001/XMLSchema" - - XSI1 = "http://www.w3.org/1999/XMLSchema-instance" - XSI2 = "http://www.w3.org/2000/10/XMLSchema-instance" - XSI3 = "http://www.w3.org/2001/XMLSchema-instance" - - class ENCRYPTION(object): - BASE = "http://www.w3.org/2001/04/xmlenc#" - - class WS: - SECEXT = "http://schemas.xmlsoap.org/ws/2003/06/secext" - TRUST = "http://schemas.xmlsoap.org/ws/2004/04/trust" - ADDRESSING = "http://schemas.xmlsoap.org/ws/2004/03/addressing" - POLICY = "http://schemas.xmlsoap.org/ws/2002/12/policy" - ISSUE = "http://schemas.xmlsoap.org/ws/2004/04/security/trust/Issue" - UTILITY = "http://docs.oasis-open.org/wss/2004/01/" + \ - "oasis-200401-wss-wssecurity-utility-1.0.xsd" - - class MICROSOFT: - PASSPORT = "http://schemas.microsoft.com/Passport/SoapServices/PPCRL" - PASSPORT_FAULT = "http://schemas.microsoft.com/Passport/SoapServices/SOAPFault" - - class LIVE: - ADDRESSBOOK = "http://www.msn.com/webservices/AddressBook" - STORAGE = "http://www.msn.com/webservices/storage/w10" - OIM = "http://messenger.msn.com/ws/2004/09/oim/" - RSI = "http://www.hotmail.msn.com/ws/2004/09/oim/rsi" - SPACES = "http://www.msn.com/webservices/spaces/v1/" diff --git a/pymsn/pymsn/service/SingleSignOn.py b/pymsn/pymsn/service/SingleSignOn.py deleted file mode 100644 index 54282ad3..00000000 --- a/pymsn/pymsn/service/SingleSignOn.py +++ /dev/null @@ -1,270 +0,0 @@ -# -*- coding: utf-8 -*- -# -# pymsn - a python client library for Msn -# -# Copyright (C) 2005-2007 Ali Sabil -# -# 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 - -from SOAPService import * -from description.SingleSignOn.RequestMultipleSecurityTokens import LiveService -import pymsn.storage - -import base64 -import struct -import time -import datetime -import sys -import Crypto.Util.randpool as randpool -from Crypto.Hash import HMAC, SHA -from Crypto.Cipher import DES3 - -__all__ = ['SingleSignOn', 'LiveService', 'RequireSecurityTokens'] - -class SecurityToken(object): - - def __init__(self): - self.type = "" - self.service_address = "" - self.lifetime = [0, 0] - self.security_token = "" - self.proof_token = "" - - def is_expired(self): - return datetime.datetime.utcnow() + datetime.timedelta(seconds=60) \ - >= self.lifetime[1] - - def mbi_crypt(self, nonce): - WINCRYPT_CRYPT_MODE_CBC = 1 - WINCRYPT_CALC_3DES = 0x6603 - WINCRYPT_CALC_SHA1 = 0x8004 - - # Read key and generate two derived keys - key1 = base64.b64decode(self.proof_token) - key2 = self._derive_key(key1, "WS-SecureConversationSESSION KEY HASH") - key3 = self._derive_key(key1, "WS-SecureConversationSESSION KEY ENCRYPTION") - - # Create a HMAC-SHA-1 hash of nonce using key2 - hash = HMAC.new(key2, nonce, SHA).digest() - - # - # Encrypt nonce with DES3 using key3 - # - - # IV (Initialization Vector): 8 bytes of random data - iv = randpool.RandomPool().get_bytes(8) - obj = DES3.new(key3, DES3.MODE_CBC, iv) - - # XXX: win32's Crypt API seems to pad the input with 0x08 bytes - # to align on 72/36/18/9 boundary - ciph = obj.encrypt(nonce + "\x08\x08\x08\x08\x08\x08\x08\x08") - - blob = struct.pack("" % \ - (self.type, self.service_address, str(self.lifetime)) - - -class RequireSecurityTokens(object): - def __init__(self, *tokens): - assert(len(tokens) > 0) - self._tokens = tokens - - def __call__(self, func): - def sso_callback(tokens, object, user_callback, user_errback, - user_args, user_kwargs): - object._tokens.update(tokens) - func(object, user_callback, user_errback, *user_args, **user_kwargs) - - def method(object, callback, errback, *args, **kwargs): - callback = (sso_callback, object, callback, errback, args, kwargs) - object._sso.RequestMultipleSecurityTokens(callback, - None, *self._tokens) - method.__name__ = func.__name__ - method.__doc__ = func.__doc__ - method.__dict__.update(func.__dict__) - return method - - -class SingleSignOn(SOAPService): - def __init__(self, username, password, proxies=None): - self.__credentials = (username, password) - self.__storage = pymsn.storage.get_storage(username, password, - "security-tokens") - - self.__pending_response = False - self.__pending_requests = [] - SOAPService.__init__(self, "SingleSignOn", proxies) - self.url = self._service.url - - def RequestMultipleSecurityTokens(self, callback, errback, *services): - """Requests multiple security tokens from the single sign on service. - @param callback: tuple(callable, *args) - @param errback: tuple(callable, *args) - @param services: one or more L{LiveService}""" - - #FIXME: we should instead check what are the common requested tokens - # and if some tokens are not common then do a parallel request, needs - # to be fixed later, for now, we just queue the requests - if self.__pending_response: - self.__pending_requests.append((callback, errback, services)) - return - - method = self._service.RequestMultipleSecurityTokens - - response_tokens = {} - - requested_services = services - services = list(services) - for service in services: # filter already available tokens - service_url = service[0] - if service_url in self.__storage: - try: - token = self.__storage[service_url] - except pymsn.storage.DecryptError: - continue - if not token.is_expired(): - services.remove(service) - response_tokens[service] = token - - if len(services) == 0: - self._HandleRequestMultipleSecurityTokensResponse(callback, - errback, [], (requested_services, response_tokens)) - return - - http_headers = method.transport_headers() - soap_action = method.soap_action() - - soap_header = method.soap_header(*self.__credentials) - soap_body = method.soap_body(*services) - - self.__pending_response = True - self._send_request("RequestMultipleSecurityTokens", self.url, - soap_header, soap_body, soap_action, - callback, errback, http_headers, (requested_services, response_tokens)) - - def _HandleRequestMultipleSecurityTokensResponse(self, callback, errback, - response, user_data): - requested_services, response_tokens = user_data - result = {} - for node in response: - token = SecurityToken() - token.type = node.findtext("./wst:TokenType") - token.service_address = node.findtext("./wsp:AppliesTo" - "/wsa:EndpointReference/wsa:Address") - token.lifetime[0] = node.findtext("./wst:LifeTime/wsu:Created", "datetime") - token.lifetime[1] = node.findtext("./wst:LifeTime/wsu:Expires", "datetime") - - try: - token.security_token = node.findtext("./wst:RequestedSecurityToken" - "/wsse:BinarySecurityToken") - except AttributeError: - token.security_token = node.findtext("./wst:RequestedSecurityToken" - "/xmlenc:EncryptedData/xmlenc:CipherData" - "/xmlenc:CipherValue") - - try: - token.proof_token = node.findtext("./wst:RequestedProofToken/wst:BinarySecret") - except AttributeError: - pass - - service = LiveService.url_to_service(token.service_address) - assert(service != None), "Unknown service URL : " + \ - token.service_address - self.__storage[token.service_address] = token - result[service] = token - result.update(response_tokens) - - self.__pending_response = False - - if callback is not None: - callback[0](result, *callback[1:]) - - if len(self.__pending_requests): - callback, errback, services = self.__pending_requests.pop(0) - self.RequestMultipleSecurityTokens(callback, errback, *services) - - def DiscardSecurityTokens(self, services): - for service in services: - del self.__storage[service[0]] - - def _HandleSOAPFault(self, request_id, callback, errback, - soap_response, user_data): - if soap_response.fault.faultcode.endswith("FailedAuthentication"): - errback[0](*errback[1:]) - elif soap_response.fault.faultcode.endswith("Redirect"): - requested_services, response_tokens = user_data - self.url = soap_response.fault.tree.findtext("psf:redirectUrl") - self.__pending_response = False - self.RequestMultipleSecurityTokens(callback, errback, *requested_services) - - - - - -if __name__ == '__main__': - import sys - import getpass - import signal - import gobject - import logging - - def sso_cb(tokens): - print "Received tokens : " - for token in tokens: - print "token %s : %s" % (token, str(tokens[token])) - - logging.basicConfig(level=logging.DEBUG) - - if len(sys.argv) < 2: - account = raw_input('Account: ') - else: - account = sys.argv[1] - - if len(sys.argv) < 3: - password = getpass.getpass('Password: ') - else: - password = sys.argv[2] - - mainloop = gobject.MainLoop(is_running=True) - - signal.signal(signal.SIGTERM, - lambda *args: gobject.idle_add(mainloop.quit())) - - sso = SingleSignOn(account, password) - sso.RequestMultipleSecurityTokens((sso_cb,), None, LiveService.VOICE) - - while mainloop.is_running(): - try: - mainloop.run() - except KeyboardInterrupt: - mainloop.quit() - diff --git a/pymsn/pymsn/service/Spaces/__init__.py b/pymsn/pymsn/service/Spaces/__init__.py deleted file mode 100644 index de9565e2..00000000 --- a/pymsn/pymsn/service/Spaces/__init__.py +++ /dev/null @@ -1,22 +0,0 @@ -# -*- coding: utf-8 -*- -# -# pymsn - a python client library for Msn -# -# Copyright (C) 2007 Youness Alaoui -# -# 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 - - -from spaces import * diff --git a/pymsn/pymsn/service/Spaces/contactcardservice.py b/pymsn/pymsn/service/Spaces/contactcardservice.py deleted file mode 100644 index d3e6c1ce..00000000 --- a/pymsn/pymsn/service/Spaces/contactcardservice.py +++ /dev/null @@ -1,133 +0,0 @@ -# -*- coding: utf-8 -*- -# -# pymsn - a python client library for Msn -# -# Copyright (C) 2007 Youness Alaoui -# -# 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 - -from pymsn.service.SOAPService import SOAPService -from pymsn.service.SingleSignOn import * - -__all__ = ['ContactCardService'] - -class ContactCardSubElement(object): - def __init__(self, xml): - self.type = xml.element.attrib["type"] - self.last_updated = xml.element.attrib["lastUpdated"] - self.description = xml.findtext("./spaces:description") - self.title = xml.findtext("./spaces:title") - self.tooltip = xml.findtext("./spaces:tooltip") - self.url = xml.findtext("./spaces:url") - - if self.type == "Photo": - self.thumbnail_url = xml.findtext("./spaces:thumbnailUrl") - self.web_ready_url = xml.findtext("./spaces:webReadyUrl") - self.album_name = xml.findtext("./spaces:albumName") - # FIXME The "xsi:type" attribute is missing.. but do we need it ? what is it ? - - def __str__(self): - ret = " SubElement type %s - title %s - url %s - tooltip %s\n" % (self.type, self.title, self.url, self.tooltip) - try: - ret += " album name %s - thumbnail url %s - web ready url %s\n" % (self.album_name, self.thumbnail_url, self.web_ready_url) - except AttributeError: - pass - - return ret - -class ContactCardElement(object): - def __init__(self, xml): - self.type = xml.element.attrib["type"] - self.title = xml.findtext("./spaces:title") - self.url = xml.findtext("./spaces:url") - self.total_new_items = xml.findtext("./spaces:totalNewItems") - - self.sub_elements = [] - all_subelements = xml.findall("./spaces:subElement") - for e in all_subelements: - self.sub_elements.append(ContactCardSubElement(e)) - - def __str__(self): - ret = " Element type %s - title %s - url %s\n" % (self.type, self.title, self.url) - for s in self.sub_elements: - ret += "%s\n" % str(s) - - return ret - -class ContactCard(object): - def __init__(self, xml): - self.storage_auth_cache = xml.findtext("./spaces:storageAuthCache") - self.last_update = xml.findtext("./spaces:lastUpdate") - - elements_xml = xml.find("./spaces:elements") - self.returned_matches = elements_xml.element.attrib["returnedMatches"]; - self.total_matches = elements_xml.element.attrib["totalMatches"]; - try: - self.display_name = elements_xml.element.attrib["displayName"]; - except KeyError: - self.display_name = "" - - try: - self.display_picture_url = elements_xml.element.attrib["displayPictureUrl"]; - except KeyError: - self.display_picture_url = "" - - self.elements = [] - all_elements = xml.findall("./spaces:elements/spaces:element") - for e in all_elements: - self.elements.append(ContactCardElement(e)) - - def __str__(self): - ret = "matches %s\n" % self.returned_matches - for s in self.elements: - ret += "%s\n" % str(s) - return ret - -class ContactCardService(SOAPService): - def __init__(self, sso, proxies=None): - self._sso = sso - self._tokens = {} - SOAPService.__init__(self, "Spaces", proxies) - - - @RequireSecurityTokens(LiveService.SPACES) - def GetXmlFeed(self, callback, errback, contact): - self.__soap_request(self._service.GetXmlFeed, - (contact.cid), - callback, errback) - - def _HandleGetXmlFeedResponse(self, callback, errback, response, user_data): - if response is not None: - callback[0](ContactCard(response.find("./spaces:contactCard")), *callback[1:]) - else: - callback[0](None, *callback[1:]) - - def __soap_request(self, method, cid, - callback, errback, user_data=None): - token = str(self._tokens[LiveService.SPACES]) - http_headers = method.transport_headers() - soap_action = method.soap_action() - - soap_header = method.soap_header(token) - soap_body = method.soap_body(cid) - - method_name = method.__name__.rsplit(".", 1)[1] - self._send_request(method_name, self._service.url, - soap_header, soap_body, soap_action, - callback, errback, http_headers, user_data) - - def _HandleSOAPFault(self, request_id, callback, errback, - soap_response, user_data): - errback[0](None, *errback[1:]) diff --git a/pymsn/pymsn/service/Spaces/scenario/__init__.py b/pymsn/pymsn/service/Spaces/scenario/__init__.py deleted file mode 100644 index d7e79048..00000000 --- a/pymsn/pymsn/service/Spaces/scenario/__init__.py +++ /dev/null @@ -1,25 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Youness Alaoui -# -# 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 -# - -name = "scenario" -description = "" - -import base - -from get_contact_card import * diff --git a/pymsn/pymsn/service/Spaces/scenario/base.py b/pymsn/pymsn/service/Spaces/scenario/base.py deleted file mode 100644 index 180d0879..00000000 --- a/pymsn/pymsn/service/Spaces/scenario/base.py +++ /dev/null @@ -1,32 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Youness Alaoui -# -# 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 -# - -__all__ = ['BaseScenario'] - -class BaseScenario(object): - def __init__(self, callback, errback): - self._callback = callback - self._errback = errback - - def execute(self): - pass - - def __call__(self): - return self.execute() - diff --git a/pymsn/pymsn/service/Spaces/scenario/get_contact_card.py b/pymsn/pymsn/service/Spaces/scenario/get_contact_card.py deleted file mode 100644 index 9b42acd7..00000000 --- a/pymsn/pymsn/service/Spaces/scenario/get_contact_card.py +++ /dev/null @@ -1,49 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Youness Alaoui -# -# 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 -# -from pymsn.service.Spaces.scenario.base import BaseScenario - -__all__ = ['GetContactCardScenario'] - -class GetContactCardScenario(BaseScenario): - def __init__(self, ccard, contact, callback, errback): - """Accepts an invitation. - - @param ccard: the contactcard service - @param contact: the contact to fetch his CCard - @param callback: tuple(callable, *args) - @param errback: tuple(callable, *args) - """ - BaseScenario.__init__(self, callback, errback) - self.__ccard = ccard - self.__contact = contact - - def execute(self): - self.__ccard.GetXmlFeed((self.__get_xml_feed_callback,), - (self.__get_xml_feed_errback,), - self.__contact) - pass - - def __get_xml_feed_callback(self, ccard): - callback = self._callback - callback[0](ccard, *callback[1:]) - - def __get_xml_feed_errback(self, error_code): - errback = self._errback[0] - args = self._errback[1:] - errback(error_code, *args) diff --git a/pymsn/pymsn/service/Spaces/spaces.py b/pymsn/pymsn/service/Spaces/spaces.py deleted file mode 100644 index d4081354..00000000 --- a/pymsn/pymsn/service/Spaces/spaces.py +++ /dev/null @@ -1,105 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# - -import contactcardservice -import scenario - -from pymsn.service.SOAPUtils import * - -import pymsn.util.element_tree as ElementTree -import pymsn.util.string_io as StringIO -import gobject - -import logging - -__all__ = ['Spaces'] - -class Spaces(gobject.GObject): - - __gsignals__ = { - "contact-card-retrieved" : (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - (object, object)) - } - - def __init__(self, sso, proxies=None): - gobject.GObject.__init__(self) - - self._ccard = contactcardservice.ContactCardService(sso, proxies) - - # Public API - def get_contact_card(self, contact): - ccs = scenario.GetContactCardScenario(self._ccard, contact, - (self.__get_contact_card_cb, contact), - (self.__common_errback,)) - ccs() - # End of public API - - # Callbacks - def __get_contact_card_cb(self, ccard, contact): - print "Contact card retrieved : \n%s\n" % str(ccard) - self.emit('contact-card-retrieved', contact, ccard) - - def __common_errback(self, error_code, *args): - print "The fetching of the contact card returned an error (%s)" % error_code - - -gobject.type_register(Spaces) - -if __name__ == '__main__': - import sys - import getpass - import signal - import gobject - import logging - from pymsn.service.SingleSignOn import * - from pymsn.service.AddressBook import * - - logging.basicConfig(level=logging.DEBUG) - - if len(sys.argv) < 2: - account = raw_input('Account: ') - else: - account = sys.argv[1] - - if len(sys.argv) < 3: - password = getpass.getpass('Password: ') - else: - password = sys.argv[2] - - mainloop = gobject.MainLoop(is_running=True) - - signal.signal(signal.SIGTERM, - lambda *args: gobject.idle_add(mainloop.quit())) - - def address_book_state_changed(address_book, pspec, sso): - if address_book.state == AddressBookState.SYNCHRONIZED: - pass - - sso = SingleSignOn(account, password) - - address_book = AddressBook(sso) - address_book.connect("notify::state", address_book_state_changed, sso) - address_book.sync() - - while mainloop.is_running(): - try: - mainloop.run() - except KeyboardInterrupt: - mainloop.quit() diff --git a/pymsn/pymsn/service/__init__.py b/pymsn/pymsn/service/__init__.py deleted file mode 100644 index 796f8a0d..00000000 --- a/pymsn/pymsn/service/__init__.py +++ /dev/null @@ -1,27 +0,0 @@ -# -*- coding: utf-8 -*- -# -# pymsn - a python client library for Msn -# -# Copyright (C) 2005-2006 Ali Sabil -# -# 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 - -"""Windows Live Services clients. -Contains a set of clients to connect and interact with Windows Live services. -""" - -# import AddressBook -# import OfflineIM -# import ContentRoaming diff --git a/pymsn/pymsn/service/description/AB/ABAdd.py b/pymsn/pymsn/service/description/AB/ABAdd.py deleted file mode 100644 index 22ffe74b..00000000 --- a/pymsn/pymsn/service/description/AB/ABAdd.py +++ /dev/null @@ -1,52 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# - -from common import * -from constants import * - -def transport_headers(): - """Returns a dictionary, containing transport (http) headers - to use for the request""" - - return {} - -def soap_action(): - """Returns the SOAPAction value to pass to the transport - or None if no SOAPAction needs to be specified""" - - return "http://www.msn.com/webservices/AddressBook/ABAdd" - -def soap_body(account): - return """ - - - 0 - - %s - - true - - """ % account - -def process_response(soap_response): - body = soap_response.body - try: - return body.find("./ab:ABAddResponse/ab:ABAddResult") - except AttributeError: - return None diff --git a/pymsn/pymsn/service/description/AB/ABContactAdd.py b/pymsn/pymsn/service/description/AB/ABContactAdd.py deleted file mode 100644 index 92cf14fb..00000000 --- a/pymsn/pymsn/service/description/AB/ABContactAdd.py +++ /dev/null @@ -1,190 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# - -from common import * -from constants import * - -import xml.sax.saxutils as xml - -def transport_headers(): - """Returns a dictionary, containing transport (http) headers - to use for the request""" - - return {} - -def soap_action(): - """Returns the SOAPAction value to pass to the transport - or None if no SOAPAction needs to be specified""" - - return "http://www.msn.com/webservices/AddressBook/ABContactAdd" - -def soap_body(passport_name, is_messenger_user, contact_type, first_name, - last_name, birth_date, email, phone, location, web_site, - annotation, comment, anniversary, display_name, invite_message, - capability, enable_allow_list_management=True): - """Returns the SOAP xml body - - @param passport_name: the passport adress if the contact to add - @param is_messenger_user: True if this is a messenger contact, - otherwise False (only a Live mail contact) - @param contact_type:: 'Me' | 'Regular' | 'Messenger' | 'Messenger2' - @param first_name: string - @param last_name: string - @param birth_date: an ISO 8601 timestamp - @param email: { ContactEmailType : well-formed email string } - @param phone: { ContactPhoneType : well-formed phone string } - @param location: { ContactLocation.Type : { ContactLocation : string } } - @param web_site: { ContactWebSite : url string } - @param annotation: { ContactAnnotations : string } - @param comment: string - @param anniversary: yyyy/mm/dd - @param display_name: display name used in the invitation - @param invite_message: message sent for the invitation""" - - contact_info = '' - if passport_name is not None: - contact_info += "%s" % passport_name - - if is_messenger_user is not None: - contact_info += "%s" % is_messenger_user - - if contact_type is not None: - contact_info += "%s" % contact_type - - if first_name is not None: - contact_info += "%s" % xml.escape(first_name) - - if last_name is not None: - contact_info += "%s" % xml.escape(last_name) - - if birth_date is not None: - contact_info += "%s" % birth_date - - if email is not None: - emails = "" - for type, email in email.iteritems(): - yahoo_tags = changed = "" - if type == ContactEmailType.EXTERNAL: - yahoo_tags = """ - true - - - %s - """ % capability - changed = " IsMessengerEnabled Capability" - emails += """ - %s - %s - %s - Email%s - """ % (type, email, yahoo_tags, changed) - contact_info += "%s" % emails - - if phone is not None: - phones = "" - for type, number in phone.iteritems(): - phones += """ - %s - %s - Number - """ % (type, number) - contact_info += "%s" % phones - - if location is not None: - locations = "" - for type, parts in location.iteritems(): - items = changes = "" - for item, value in parts.iteritems(): - items += "<%s>%s" % (item, value, item) - changes += " %s%s" % (item[0].upper(), item[1:len(item)]) - locations += """ - %s - %s - %s - """ % (type, items, changes) - contact_info += "%s" % locations - - if web_site is not None: - web_sites = "" - for type, url in web_site.iteritems(): - websites += """ - %s - %s - """ % (type, xml.escape(url)) - contact_info += "%s" % web_sites - - if annotation is not None: - annotations = "" - for name, value in annotation.iteritems(): - annotations += """ - %s - %s - """ % (name, xml.escape(value)) - contact_info += "%s" % annotations - - if comment is not None: - contact_info += "%s" % xml.escape(comment) - - if anniversary is not None: - contact_info += "%s" % anniversary - - invite_info = '' - invite_info += """ - - - - MSN.IM.InviteMessage - - - %(invite_message)s - - - - - %(display_name)s - - """ % { 'invite_message' : xml.escape(invite_message), - 'display_name' : xml.escape(display_name) } - - return """ - - 00000000-0000-0000-0000-000000000000 - - - - %(contact_info)s - %(invite_info)s - - - - - - %(allow_list_management)s - - - """ % { 'contact_info' : contact_info, - 'invite_info' : invite_info, - 'allow_list_management' : str(enable_allow_list_management).lower()} - -def process_response(soap_response): - body = soap_response.body - try: - return body.find("./ab:ABContactAddResponse/ab:ABContactAddResult/ab:guid") - except AttributeError: - return None diff --git a/pymsn/pymsn/service/description/AB/ABContactDelete.py b/pymsn/pymsn/service/description/AB/ABContactDelete.py deleted file mode 100644 index 086cbd10..00000000 --- a/pymsn/pymsn/service/description/AB/ABContactDelete.py +++ /dev/null @@ -1,52 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# - -from common import * - -def transport_headers(): - """Returns a dictionary, containing transport (http) headers - to use for the request""" - - return {} - -def soap_action(): - """Returns the SOAPAction value to pass to the transport - or None if no SOAPAction needs to be specified""" - - return "http://www.msn.com/webservices/AddressBook/ABContactDelete" - -def soap_body(contact_id): - """Returns the SOAP xml body""" - - return """ - - - 00000000-0000-0000-0000-000000000000 - - - - - %(contact_id)s - - - - """ % { 'contact_id' : contact_id } - -def process_response(soap_response): - return None diff --git a/pymsn/pymsn/service/description/AB/ABContactUpdate.py b/pymsn/pymsn/service/description/AB/ABContactUpdate.py deleted file mode 100644 index 9682d280..00000000 --- a/pymsn/pymsn/service/description/AB/ABContactUpdate.py +++ /dev/null @@ -1,186 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# - -from common import * -from constants import * - -import xml.sax.saxutils as xml - -def transport_headers(): - """Returns a dictionary, containing transport (http) headers - to use for the request""" - - return {} - -def soap_action(): - """Returns the SOAPAction value to pass to the transport - or None if no SOAPAction needs to be specified""" - - return "http://www.msn.com/webservices/AddressBook/ABContactUpdate" - -def soap_body(contact_id, display_name, is_messenger_user, contact_type, - first_name, last_name, birth_date, email, phone, location, - web_site, annotation, comment, anniversary, has_space, - enable_allow_list_management=False): - """Returns the SOAP xml body - - @param contact_id: a contact GUID string - @param display_name: string - @param is_messenger_user: "true" if messenger user | - "false" if live mail contact only - @param contact_type: 'Me' | 'Regular' | 'Messenger' | 'Messenger2' - @param first_name: string - @param last_name: string - @param birth_date: an ISO 8601 timestamp - @param email: { ContactEmailType : well-formed email string } - @param phone: { ContactPhoneType : well-formed phone string } - @param location: { ContactLocation.Type : { ContactLocation : string } } - @param web_site: { ContactWebSite : url string } - @param annotation: { ContactAnnotations : string } - @param comment: string - @param anniversary: yyyy/mm/dd - @param has_space: "true" | "false" """ - - contact_info = "" - properties_changed = "" - - if display_name is not None: - contact_info += "%s" % xml.escape(display_name) - properties_changed += " DisplayName" - - if has_space is not None: - contact_info += "%s" % has_space - properties_changed += " HasSpace" - - if is_messenger_user is not None: - contact_info += "%s" % is_messenger_user - properties_changed += " IsMessengerUser" - - if contact_type is not None: - contact_info += "%s" % contact_type - - if first_name is not None: - contact_info += "%s" % xml.escape(first_name) - properties_changed += " ContactFirstName" - - if last_name is not None: - contact_info += "%s" % xml.escape(last_name) - properties_changed += " ContactLastName" - - if birth_date is not None: - contact_info += "%s" % birth_date - properties_changed += " ContactBirthDate" - - if email is not None: - emails = "" - for type, email in email.iteritems(): - emails += """ - %s - %s - Email - """ % (type, email) - contact_info += "%s" % emails - properties_changed += " ContactEmail" - - if phone is not None: - phones = "" - for type, number in phone.iteritems(): - phones += """ - %s - %s - Number - """ % (type, number) - contact_info += "%s" % phones - properties_changed += " ContactPhone" - - if location is not None: - locations = "" - for type, parts in location.iteritems(): - items = changes = "" - for item, value in parts.iteritems(): - items += "<%s>%s" % (item, value, item) - changes += " %s%s" % (item[0].upper(), item[1:len(item)]) - locations += """ - %s - %s - %s - """ % (type, items, changes) - contact_info += "%s" % locations - properties_changed += " ContactLocation" - - if web_site is not None: - web_sites = "" - for type, url in web_site.iteritems(): - websites += """ - %s - %s - """ % (type, xml.escape(url)) - contact_info += "%s" % web_sites - properties_changed += " ContactWebSite" - - if annotation is not None: - annotations = "" - for name, value in annotation.iteritems(): - if value == None or value == "": - value = "" - else: - value = "%s" % value - annotations += """ - %s - %s - """ % (name, value) - contact_info += "%s" % annotations - properties_changed += " Annotation" - - if comment is not None: - contact_info += "%s" % xml.escape(comment) - properties_changed += " Comment" - - if anniversary is not None: - contact_info += "%s" % anniversary - properties_changed += " Anniversary" - - return """ - - 00000000-0000-0000-0000-000000000000 - - - - %(contact_id)s - - - %(contact_info)s - - - %(properties_changed)s - - - - - - %(allow_list_management)s - - - """ % { 'contact_id' : contact_id, - 'contact_info' : contact_info, - 'properties_changed' : properties_changed, - 'allow_list_management' : str(enable_allow_list_management).lower() } - -def process_response(soap_response): - return None diff --git a/pymsn/pymsn/service/description/AB/ABFindAll.py b/pymsn/pymsn/service/description/AB/ABFindAll.py deleted file mode 100644 index 7ac3ab63..00000000 --- a/pymsn/pymsn/service/description/AB/ABFindAll.py +++ /dev/null @@ -1,61 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# - -from common import * - -import xml.sax.saxutils as xml - -def transport_headers(): - """Returns a dictionary, containing transport (http) headers - to use for the request""" - - return {} - -def soap_action(): - """Returns the SOAPAction value to pass to the transport - or None if no SOAPAction needs to be specified""" - - return "http://www.msn.com/webservices/AddressBook/ABFindAll" - -def soap_body(deltas_only, last_change): - """Returns the SOAP xml body""" - - return """ - - 00000000-0000-0000-0000-000000000000 - Full - %(deltas_only)s - %(last_change)s - """ % {'deltas_only' : deltas_only, - 'last_change' : last_change} - -def process_response(soap_response): - find_all_result = soap_response.body.\ - find("./ab:ABFindAllResponse/ab:ABFindAllResult") - - path = "./ab:groups/ab:Group" - groups = find_all_result.findall(path) - - path = "./ab:contacts/ab:Contact" - contacts = find_all_result.findall(path) - - path = "./ab:ab" - ab = find_all_result.find(path) - - return (ab, groups, contacts) diff --git a/pymsn/pymsn/service/description/AB/ABGroupAdd.py b/pymsn/pymsn/service/description/AB/ABGroupAdd.py deleted file mode 100644 index 0bf3b30e..00000000 --- a/pymsn/pymsn/service/description/AB/ABGroupAdd.py +++ /dev/null @@ -1,73 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# - -from common import * - -import xml.sax.saxutils as xml - -def transport_headers(): - """Returns a dictionary, containing transport (http) headers - to use for the request""" - - return {} - -def soap_action(): - """Returns the SOAPAction value to pass to the transport - or None if no SOAPAction needs to be specified""" - - return "http://www.msn.com/webservices/AddressBook/ABGroupAdd" - -def soap_body(group_name): - """Returns the SOAP xml body""" - - return """ - - 00000000-0000-0000-0000-000000000000 - - false - - - - - %(group_name)s - - - C8529CE2-6EAD-434d-881F-341E17DB3FF8 - - - false - - - - - MSN.IM.Display - - - 1 - - - - - - """ % { 'group_name' : xml.escape(group_name) } - -def process_response(soap_response): - guid = soap_response.body.find("./ab:ABGroupAddResponse/" - "ab:ABGroupAddResult/ab:guid") - return guid diff --git a/pymsn/pymsn/service/description/AB/ABGroupContactAdd.py b/pymsn/pymsn/service/description/AB/ABGroupContactAdd.py deleted file mode 100644 index dfb74ea0..00000000 --- a/pymsn/pymsn/service/description/AB/ABGroupContactAdd.py +++ /dev/null @@ -1,69 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# - -from common import * -from constants import * - -def transport_headers(): - """Returns a dictionary, containing transport (http) headers - to use for the request""" - - return {} - -def soap_action(): - """Returns the SOAPAction value to pass to the transport - or None if no SOAPAction needs to be specified""" - - return "http://www.msn.com/webservices/AddressBook/ABGroupContactAdd" - -# TODO : complete the method to be able to add a contact (like ABContactAdd) -# directly when adding him to his original group - -def soap_body(group_id, contact_id): - """Returns the SOAP xml body""" - - return """ - - - 00000000-0000-0000-0000-000000000000 - - - - - %(group_id)s - - - - - - - %(contact_id)s - - - - """ % { 'group_id' : group_id, - 'contact_id' : contact_id } - -def process_response(soap_response): - body = soap_response.body - try: - return body.find("./ab:ABGroupContactAddResponse/" \ - "ab:ABGroupContactAddResult/ab:guid").text - except: - return None diff --git a/pymsn/pymsn/service/description/AB/ABGroupContactDelete.py b/pymsn/pymsn/service/description/AB/ABGroupContactDelete.py deleted file mode 100644 index 693883b8..00000000 --- a/pymsn/pymsn/service/description/AB/ABGroupContactDelete.py +++ /dev/null @@ -1,59 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# - -from common import * - -def transport_headers(): - """Returns a dictionary, containing transport (http) headers - to use for the request""" - - return {} - -def soap_action(): - """Returns the SOAPAction value to pass to the transport - or None if no SOAPAction needs to be specified""" - - return "http://www.msn.com/webservices/AddressBook/ABGroupContactDelete" - -def soap_body(group_id, contact_id): - """Returns the SOAP xml body""" - - return """ - - - 00000000-0000-0000-0000-000000000000 - - - - - %s - - - - - - - %s - - - - """ % (contact_id, group_id) - -def process_response(soap_response): - return None diff --git a/pymsn/pymsn/service/description/AB/ABGroupDelete.py b/pymsn/pymsn/service/description/AB/ABGroupDelete.py deleted file mode 100644 index 90b8b2f4..00000000 --- a/pymsn/pymsn/service/description/AB/ABGroupDelete.py +++ /dev/null @@ -1,52 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# - -from common import * - -def transport_headers(): - """Returns a dictionary, containing transport (http) headers - to use for the request""" - - return {} - -def soap_action(): - """Returns the SOAPAction value to pass to the transport - or None if no SOAPAction needs to be specified""" - - return "http://www.msn.com/webservices/AddressBook/ABGroupDelete" - -def soap_body(group_id): - """Returns the SOAP xml body""" - - return """ - - - 00000000-0000-0000-0000-000000000000 - - - - - %(group_id)s - - - - """ % { 'group_id' : group_id } - -def process_response(soap_response): - return None diff --git a/pymsn/pymsn/service/description/AB/ABGroupUpdate.py b/pymsn/pymsn/service/description/AB/ABGroupUpdate.py deleted file mode 100644 index f17f21a8..00000000 --- a/pymsn/pymsn/service/description/AB/ABGroupUpdate.py +++ /dev/null @@ -1,63 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# - -from common import * - -import xml.sax.saxutils as xml - -def transport_headers(): - """Returns a dictionary, containing transport (http) headers - to use for the request""" - - return {} - -def soap_action(): - """Returns the SOAPAction value to pass to the transport - or None if no SOAPAction needs to be specified""" - - return "http://www.msn.com/webservices/AddressBook/ABGroupUpdate" - -def soap_body(group_id, group_name): - """Returns the SOAP xml body""" - - return """ - - - 00000000-0000-0000-0000-000000000000 - - - - - %(group_id)s - - - - %(group_name)s - - - - GroupName - - - - """ % { 'group_id' : group_id, - 'group_name' : xml.escape(group_name) } - -def process_response(soap_response): - return None diff --git a/pymsn/pymsn/service/description/AB/__init__.py b/pymsn/pymsn/service/description/AB/__init__.py deleted file mode 100644 index 48af7e24..00000000 --- a/pymsn/pymsn/service/description/AB/__init__.py +++ /dev/null @@ -1,39 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# - -name = "AB" -description = "Hotmail address book service" - -url = "http://contacts.msn.com/abservice/abservice.asmx" - -from constants import * - -import ABAdd - -import ABFindAll - -import ABContactAdd -import ABContactDelete -import ABContactUpdate - -import ABGroupAdd -import ABGroupDelete -import ABGroupUpdate -import ABGroupContactAdd -import ABGroupContactDelete diff --git a/pymsn/pymsn/service/description/AB/common.py b/pymsn/pymsn/service/description/AB/common.py deleted file mode 100644 index 5e17e3e6..00000000 --- a/pymsn/pymsn/service/description/AB/common.py +++ /dev/null @@ -1,35 +0,0 @@ -# -*- coding: utf-8 -*- -# -# pymsn - a python client library for Msn -# -# Copyright (C) 2005-2006 Ali Sabil -# -# 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 xml.sax.saxutils as xml - -def soap_header(scenario, security_token): - """Returns the SOAP xml header""" - - return """ - - 996CDE1E-AA53-4477-B943-2BE802EA6166 - false - %s - - - false - %s - """ % (xml.escape(scenario), xml.escape(security_token)) diff --git a/pymsn/pymsn/service/description/AB/constants.py b/pymsn/pymsn/service/description/AB/constants.py deleted file mode 100644 index 6cce72cf..00000000 --- a/pymsn/pymsn/service/description/AB/constants.py +++ /dev/null @@ -1,71 +0,0 @@ -# -*- coding: utf-8 -*- -# -# pymsn - a python client library for Msn -# -# Copyright (C) 2007 Johann Prieur -# -# 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 - -__all__ = ['ContactAnnotations', 'ContactEmailType', 'ContactPhoneType', - 'ContactLocation', 'ContactWebSiteType', 'ContactGeneral'] - -class ContactGeneral(object): - FIRST_NAME = 0 - LAST_NAME = 1 - BIRTH_DATE = 2 - EMAILS = 3 - PHONES = 4 - LOCATIONS = 5 - WEBSITES = 6 - ANNOTATIONS = 7 - COMMENT = 8 - ANNIVERSARY = 9 - -class ContactAnnotations(object): - NICKNAME = "AB.NickName" - JOB_TITLE = "AB.JobTitle" - SPOUSE = "AB.Spouse" - -class ContactEmailType(object): - BUSINESS = "ContactEmailBusiness" - MESSENGER = "ContactEmailMessenger" - OTHER = "ContactEmailOther" - PERSONAL = "ContactEmailPersonal" - EXTERNAL = "Messenger2" - -class ContactPhoneType(object): - BUSINESS = "ContactPhoneBusiness" - FAX = "ContactPhoneFax" - MOBILE = "ContactPhoneMobile" - OTHER = "ContactPhoneOther" - PAGER = "ContactPhonePager" - PERSONAL = "ContactPhonePersonal" - -class ContactLocation(object): - class Type(object): - BUSINESS = "ContactLocationBusiness" - PERSONAL = "ContactLocationPersonal" - - NAME = "name" - STREET = "street" - CITY = "city" - STATE = "state" - COUNTRY = "country" - POSTAL_CODE = "postalCode" - -class ContactWebSiteType(object): - BUSINESS = "ContactWebSiteBusiness" - PERSONAL = "ContactWebSitePersonal" - diff --git a/pymsn/pymsn/service/description/OIM/Store2.py b/pymsn/pymsn/service/description/OIM/Store2.py deleted file mode 100644 index ac8437f4..00000000 --- a/pymsn/pymsn/service/description/OIM/Store2.py +++ /dev/null @@ -1,70 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# - -import xml.sax.saxutils as xml -def soap_header(from_member_name, friendly_name, proxy, msnp_ver, build_ver, - to_member_name, message_number, security_token, app_id, - lock_key): - """Returns the SOAP xml header""" - - # FIXME : escape the parameters - - return """ - - - - - http://messenger.msn.com - - %(message_number)s - """ % { 'from_member_name' : from_member_name, - 'friendly_name' : friendly_name, - 'proxy' : proxy, - 'msnp_ver' : msnp_ver, - 'build_ver' : build_ver, - 'to_member_name' : to_member_name, - 'passport' : xml.escape(security_token), - 'app_id' : app_id, - 'lock_key' : lock_key, - 'message_number' : message_number } - -def transport_headers(): - """Returns a dictionary, containing transport (http) headers - to use for the request""" - - return {} - -def soap_action(): - """Returns the SOAPAction value to pass to the transport - or None if no SOAPAction needs to be specified""" - - return "http://messenger.live.com/ws/2006/09/oim/Store2" - -def soap_body(message_type, message_content): - """Returns the SOAP xml body""" - - return """ - %s - - - %s - """ % (message_type, message_content) - -def process_response(soap_response): - return None diff --git a/pymsn/pymsn/service/description/OIM/__init__.py b/pymsn/pymsn/service/description/OIM/__init__.py deleted file mode 100644 index 8ab4b7f5..00000000 --- a/pymsn/pymsn/service/description/OIM/__init__.py +++ /dev/null @@ -1,25 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# - -name = "OIM" -description = "Offline messages service" - -url = "https://ows.messenger.msn.com/OimWS/oim.asmx" - -import Store2 diff --git a/pymsn/pymsn/service/description/RSI/DeleteMessages.py b/pymsn/pymsn/service/description/RSI/DeleteMessages.py deleted file mode 100644 index 080ee9b1..00000000 --- a/pymsn/pymsn/service/description/RSI/DeleteMessages.py +++ /dev/null @@ -1,49 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# - -from common import * - -def transport_headers(): - """Returns a dictionary, containing transport (http) headers - to use for the request""" - - return {} - -def soap_action(): - """Returns the SOAPAction value to pass to the transport - or None if no SOAPAction needs to be specified""" - - return "http://www.hotmail.msn.com/ws/2004/09/oim/rsi/DeleteMessages" - -def soap_body(message_ids): - """Returns the SOAP xml body""" - - ids = "" - for message_id in message_ids: - ids += "%s" % message_id - - return """ - - - %s - - """ % ids - -def process_response(soap_response): - return None diff --git a/pymsn/pymsn/service/description/RSI/GetMessage.py b/pymsn/pymsn/service/description/RSI/GetMessage.py deleted file mode 100644 index 12dd0e05..00000000 --- a/pymsn/pymsn/service/description/RSI/GetMessage.py +++ /dev/null @@ -1,52 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# - -from common import * - -def transport_headers(): - """Returns a dictionary, containing transport (http) headers - to use for the request""" - - return {} - -def soap_action(): - """Returns the SOAPAction value to pass to the transport - or None if no SOAPAction needs to be specified""" - - return "http://www.hotmail.msn.com/ws/2004/09/oim/rsi/GetMessage" - -def soap_body(message_id, also_mark_as_read): - """Returns the SOAP xml body - - @param message_id: the id of the message to get (guid) - @param also_mark_as_read: "true if the message should be marked as read - "false else - """ - return """ - - %s - %s - """ % (message_id, also_mark_as_read) - -def process_response(soap_response): - body = soap_response.body - try: - return body.find("./rsi:GetMessageResponse/rsi:GetMessageResult") - except AttributeError: - return None diff --git a/pymsn/pymsn/service/description/RSI/GetMetadata.py b/pymsn/pymsn/service/description/RSI/GetMetadata.py deleted file mode 100644 index e85302f9..00000000 --- a/pymsn/pymsn/service/description/RSI/GetMetadata.py +++ /dev/null @@ -1,46 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# - -from common import * - -def transport_headers(): - """Returns a dictionary, containing transport (http) headers - to use for the request""" - - return {} - -def soap_action(): - """Returns the SOAPAction value to pass to the transport - or None if no SOAPAction needs to be specified""" - - return "http://www.hotmail.msn.com/ws/2004/09/oim/rsi/GetMetadata" - -def soap_body(): - """Returns the SOAP xml body""" - - return """ - """ - -def process_response(soap_response): - body = soap_response.body - try: - return body.find("./rsi:GetMetadataResponse") - except AttributeError: - return None - diff --git a/pymsn/pymsn/service/description/RSI/__init__.py b/pymsn/pymsn/service/description/RSI/__init__.py deleted file mode 100644 index 2365cda2..00000000 --- a/pymsn/pymsn/service/description/RSI/__init__.py +++ /dev/null @@ -1,27 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# - -name = "RSI" -description = "Offline message service" - -url = "https://rsi.hotmail.com/rsi/rsi.asmx" - -import GetMetadata -import GetMessage -import DeleteMessages diff --git a/pymsn/pymsn/service/description/RSI/common.py b/pymsn/pymsn/service/description/RSI/common.py deleted file mode 100644 index 75e5d895..00000000 --- a/pymsn/pymsn/service/description/RSI/common.py +++ /dev/null @@ -1,33 +0,0 @@ -# -*- coding: utf-8 -*- -# -# pymsn - a python client library for Msn -# -# Copyright (C) 2007 Johann Prieur -# -# 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 xml.sax.saxutils as xml - -def soap_header(security_token): - """Returns the SOAP xml header""" - - t, p = security_token.split('&') - - return """ - - %s -

%s

-
""" % (xml.escape(t[2:]), - xml.escape(p[2:])) diff --git a/pymsn/pymsn/service/description/SchematizedStore/CreateDocument.py b/pymsn/pymsn/service/description/SchematizedStore/CreateDocument.py deleted file mode 100644 index 9e09bcac..00000000 --- a/pymsn/pymsn/service/description/SchematizedStore/CreateDocument.py +++ /dev/null @@ -1,82 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# - -from common import * - -def transport_headers(): - """Returns a dictionary, containing transport (http) headers - to use for the request""" - - return {} - -def soap_action(): - """Returns the SOAPAction value to pass to the transport - or None if no SOAPAction needs to be specified""" - - return "http://www.msn.com/webservices/storage/w10/CreateDocument" - -def soap_body(cid, photo_name, photo_mime_type, photo_data): - """Returns the SOAP xml body - """ - return """ - - - /UserTiles - - - - %s - - - MyCidStuff - - - - - - %s - - - - - UserTileStatic - - - %s - - - %s - - - 0 - - - - - - Messenger User Tile - - """ % (cid, photo_name, photo_mime_type, photo_data) - -def process_response(soap_response): - body = soap_response.body - try: - return body.find("./st:CreateDocumentResponse/st:CreateDocumentResult") - except AttributeError: - return None diff --git a/pymsn/pymsn/service/description/SchematizedStore/CreateRelationships.py b/pymsn/pymsn/service/description/SchematizedStore/CreateRelationships.py deleted file mode 100644 index 05dce82b..00000000 --- a/pymsn/pymsn/service/description/SchematizedStore/CreateRelationships.py +++ /dev/null @@ -1,60 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# - -from common import * - -def transport_headers(): - """Returns a dictionary, containing transport (http) headers - to use for the request""" - - return {} - -def soap_action(): - """Returns the SOAPAction value to pass to the transport - or None if no SOAPAction needs to be specified""" - - return "http://www.msn.com/webservices/storage/w10/CreateRelationships" - -def soap_body(source_rid, target_rid): - """Returns the SOAP xml body - """ - return """ - - - - %s - - - SubProfile - - - %s - - - Photo - - - ProfilePhoto - - - - """ % (source_rid, target_rid) - -def process_response(soap_response): - return None diff --git a/pymsn/pymsn/service/description/SchematizedStore/DeleteRelationships.py b/pymsn/pymsn/service/description/SchematizedStore/DeleteRelationships.py deleted file mode 100644 index d9d086e7..00000000 --- a/pymsn/pymsn/service/description/SchematizedStore/DeleteRelationships.py +++ /dev/null @@ -1,66 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# - -from common import * - -def transport_headers(): - """Returns a dictionary, containing transport (http) headers - to use for the request""" - - return {} - -def soap_action(): - """Returns the SOAPAction value to pass to the transport - or None if no SOAPAction needs to be specified""" - - return "http://www.msn.com/webservices/storage/w10/DeleteRelationships" - -def soap_body(cid, source_rid, target_rid): - """Returns the SOAP xml body - """ - if cid is not None: - source_handle = """ - /UserTiles - - - - %s - - - MyCidStuff - - """ % cid - else: - source_handle = "%s" % source_rid - - return """ - - %s - - - - - %s - - - - """ % (source_handle, target_rid) - -def process_response(soap_response): - return None diff --git a/pymsn/pymsn/service/description/SchematizedStore/FindDocuments.py b/pymsn/pymsn/service/description/SchematizedStore/FindDocuments.py deleted file mode 100644 index 0c0591ab..00000000 --- a/pymsn/pymsn/service/description/SchematizedStore/FindDocuments.py +++ /dev/null @@ -1,85 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# - -from common import * - -def transport_headers(): - """Returns a dictionary, containing transport (http) headers - to use for the request""" - - return {} - -def soap_action(): - """Returns the SOAPAction value to pass to the transport - or None if no SOAPAction needs to be specified""" - - return "http://www.msn.com/webservices/storage/w10/FindDocument" - -def soap_body(cid): - """Returns the SOAP xml body - """ - return """ - - - /UserTiles - - - - %s - - - MyCidStuff - - - - - - true - - - true - - - - - None - - - - - DateModified - - - - - Default - - - 25 - - - """ % cid - -def process_response(soap_response): - body = soap_response.body - try: - return body.find("./st:FindDocumentsResponse/st:FindDocumentsResult") - except AttributeError: - return None - diff --git a/pymsn/pymsn/service/description/SchematizedStore/GetProfile.py b/pymsn/pymsn/service/description/SchematizedStore/GetProfile.py deleted file mode 100644 index 2d07b9a0..00000000 --- a/pymsn/pymsn/service/description/SchematizedStore/GetProfile.py +++ /dev/null @@ -1,81 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# - -from common import * - -def transport_headers(): - """Returns a dictionary, containing transport (http) headers - to use for the request""" - - return {} - -def soap_action(): - """Returns the SOAPAction value to pass to the transport - or None if no SOAPAction needs to be specified""" - - return "http://www.msn.com/webservices/storage/w10/GetProfile" - -def soap_body(cid, profile_rid, p_date_modified, expression_rid, - e_date_modified, display_name, dn_last_modified, - personal_status, ps_last_modified, user_tile_url, - photo, flags): - """Returns the SOAP xml body - """ - return """ - - - %(cid)s - MyCidStuff - - MyProfile - - - %(profile_rid)s - %(p_date_modified)s - - %(expression_rid)s - %(e_date_modified)s - %(display_name)s - %(dn_last_modified)s - %(personal_status)s - %(ps_last_modified)s - %(user_tile_url)s - %(photo)s - %(flags)s - - - """ % { 'cid' : cid, - 'profile_rid' : profile_rid, - 'p_date_modified' : p_date_modified, - 'expression_rid' : expression_rid, - 'e_date_modified' : e_date_modified, - 'display_name' : display_name, - 'dn_last_modified' : dn_last_modified, - 'personal_status' : personal_status, - 'ps_last_modified' : ps_last_modified, - 'user_tile_url' : user_tile_url, - 'photo' : photo, - 'flags' : flags } - -def process_response(soap_response): - body = soap_response.body - try: - return body.find("./st:GetProfileResponse/st:GetProfileResult") - except AttributeError: - return None diff --git a/pymsn/pymsn/service/description/SchematizedStore/UpdateProfile.py b/pymsn/pymsn/service/description/SchematizedStore/UpdateProfile.py deleted file mode 100644 index 1719d235..00000000 --- a/pymsn/pymsn/service/description/SchematizedStore/UpdateProfile.py +++ /dev/null @@ -1,61 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# - -from common import * - -def transport_headers(): - """Returns a dictionary, containing transport (http) headers - to use for the request""" - - return {} - -def soap_action(): - """Returns the SOAPAction value to pass to the transport - or None if no SOAPAction needs to be specified""" - - return "http://www.msn.com/webservices/storage/w10/UpdateProfile" - -def soap_body(profile_rid, display_name, personal_status, flags=0): - """Returns the SOAP xml body - """ - return """ - - - - %s - - - - Update - - - %s - - - %s - - - %s - - - - """ % (profile_rid, display_name, personal_status, flags) - -def process_response(soap_response): - return None diff --git a/pymsn/pymsn/service/description/SchematizedStore/__init__.py b/pymsn/pymsn/service/description/SchematizedStore/__init__.py deleted file mode 100644 index 4cc4a721..00000000 --- a/pymsn/pymsn/service/description/SchematizedStore/__init__.py +++ /dev/null @@ -1,32 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# - -name = "SchematizedStore" -description = "MSN Storage service" - -url = "https://storage.msn.com/storageservice/SchematizedStore.asmx" - -import GetProfile -import UpdateProfile - -import DeleteRelationships -import CreateRelationships - -import CreateDocument -import FindDocuments diff --git a/pymsn/pymsn/service/description/SchematizedStore/common.py b/pymsn/pymsn/service/description/SchematizedStore/common.py deleted file mode 100644 index dddb8b19..00000000 --- a/pymsn/pymsn/service/description/SchematizedStore/common.py +++ /dev/null @@ -1,37 +0,0 @@ -# -*- coding: utf-8 -*- -# -# pymsn - a python client library for Msn -# -# Copyright (C) 2007 Johann Prieur -# -# 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 xml.sax.saxutils as xml - -def soap_header(scenario, security_token): - """Returns the SOAP xml header""" - - return """ - Messenger Client 8.0 - - %s - - - - 0 - - %s - - """ % (xml.escape(scenario), xml.escape(security_token)) diff --git a/pymsn/pymsn/service/description/Sharing/AddMember.py b/pymsn/pymsn/service/description/Sharing/AddMember.py deleted file mode 100644 index 278ea9a0..00000000 --- a/pymsn/pymsn/service/description/Sharing/AddMember.py +++ /dev/null @@ -1,80 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# - -from common import * - -def transport_headers(): - """Returns a dictionary, containing transport (http) headers - to use for the request""" - - return {} - -def soap_action(): - """Returns the SOAPAction value to pass to the transport - or None if no SOAPAction needs to be specified""" - - return "http://www.msn.com/webservices/AddressBook/AddMember" - -def soap_body(member_role, type, state, account): - """Returns the SOAP xml body""" - stuff = "" - - if type == 'Passport': - stuff = "%s" % account - elif type == 'Email': - stuff = """%s - - MSN.IM.BuddyType - 32: - """ % account - - return """ - - - - 0 - - - Messenger - - - - - - - - %s - - - - - %s - - - %s - - %s - - - - - """ % (member_role, type, type, state, stuff) - -def process_response(soap_response): - return None diff --git a/pymsn/pymsn/service/description/Sharing/DeleteMember.py b/pymsn/pymsn/service/description/Sharing/DeleteMember.py deleted file mode 100644 index 5e1669c4..00000000 --- a/pymsn/pymsn/service/description/Sharing/DeleteMember.py +++ /dev/null @@ -1,76 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# - -from common import * - -def transport_headers(): - """Returns a dictionary, containing transport (http) headers - to use for the request""" - - return {} - -def soap_action(): - """Returns the SOAPAction value to pass to the transport - or None if no SOAPAction needs to be specified""" - - return "http://www.msn.com/webservices/AddressBook/DeleteMember" - -def soap_body(member_role, type, state, account): - """Returns the SOAP xml body""" - address = "" - - if account is not None: - if type == 'Passport': - address = "%s" % account - elif type == 'Email': - address = "%s" % account - - member = """ - %s - %s - %s - """ % (type, type, state, address) - - return """ - - - - 0 - - - Messenger - - - - - - - - %(member_role)s - - - %(member)s - - - - """ % { 'member_role' : member_role, - 'member' : member } - -def process_response(soap_response): - return None diff --git a/pymsn/pymsn/service/description/Sharing/FindMembership.py b/pymsn/pymsn/service/description/Sharing/FindMembership.py deleted file mode 100644 index 31901412..00000000 --- a/pymsn/pymsn/service/description/Sharing/FindMembership.py +++ /dev/null @@ -1,88 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# - -from common import * -from pymsn.profile import Membership - -import xml.sax.saxutils as xml - -def transport_headers(): - """Returns a dictionary, containing transport (http) headers - to use for the request""" - - return {} - -def soap_action(): - """Returns the SOAPAction value to pass to the transport - or None if no SOAPAction needs to be specified""" - - return "http://www.msn.com/webservices/AddressBook/FindMembership" - -def soap_body(services_types, deltas_only, last_change): - """Returns the SOAP xml body""" - - services = '' - for service in services_types: - services += """ - %s - """ % xml.escape(service) - - deltas = '' - if deltas_only: - deltas = """ - Full - - - true - - - %s - """ % last_change - - return """ - - - - %(services)s - - - %(deltas)s - """ % {'services' : services, 'deltas' : deltas} - -def process_response(soap_response): - # FIXME: don't pick the 1st service only, we need to extract them all - result = {} - service = soap_response.body.find("./ab:FindMembershipResponse/" - "ab:FindMembershipResult/ab:Services/" - "ab:Service") - if service is not None: - memberships = service.find("./ab:Memberships") - for membership in memberships: - role = membership.find("./ab:MemberRole") - members = membership.findall("./ab:Members/ab:Member") - if role is None or len(members) == 0: - continue - result[role.text] = members - last_changes = service.find("./ab:LastChange") - else: - last_changes = {'text':"0-0-0T0:0:0.0-0:0"} - result = {'Allow':{},'Block':{},'Reverse':{},'Pending':{}} - return (result, last_changes) - - diff --git a/pymsn/pymsn/service/description/Sharing/__init__.py b/pymsn/pymsn/service/description/Sharing/__init__.py deleted file mode 100644 index 20423c95..00000000 --- a/pymsn/pymsn/service/description/Sharing/__init__.py +++ /dev/null @@ -1,27 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# - -name = "Sharing" -description = "Membership address book service" - -url = "http://contacts.msn.com/abservice/SharingService.asmx" - -import FindMembership -import AddMember -import DeleteMember diff --git a/pymsn/pymsn/service/description/Sharing/common.py b/pymsn/pymsn/service/description/Sharing/common.py deleted file mode 100644 index 74b95b5c..00000000 --- a/pymsn/pymsn/service/description/Sharing/common.py +++ /dev/null @@ -1,37 +0,0 @@ -# -*- coding: utf-8 -*- -# -# pymsn - a python client library for Msn -# -# Copyright (C) 2007 Johann Prieur -# -# 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 xml.sax.saxutils as xml - -__all__ = ['soap_header'] - -def soap_header(scenario, security_token): - """Returns the SOAP xml header""" - - return """ - - 996CDE1E-AA53-4477-B943-2BE802EA6166 - false - %s - - - false - %s - """ % (xml.escape(scenario), xml.escape(security_token)) diff --git a/pymsn/pymsn/service/description/SingleSignOn/RequestMultipleSecurityTokens.py b/pymsn/pymsn/service/description/SingleSignOn/RequestMultipleSecurityTokens.py deleted file mode 100644 index 6fc7cc73..00000000 --- a/pymsn/pymsn/service/description/SingleSignOn/RequestMultipleSecurityTokens.py +++ /dev/null @@ -1,121 +0,0 @@ -# -*- coding: utf-8 -*- -# -# pymsn - a python client library for Msn -# -# Copyright (C) 2005-2006 Ali Sabil -# -# 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 xml.sax.saxutils as xml - -class LiveService(object): - CONTACTS = ("contacts.msn.com", "?fs=1&id=24000&kv=7&rn=93S9SWWw&tw=0&ver=2.1.6000.1") - MESSENGER = ("messenger.msn.com", "?id=507") - MESSENGER_CLEAR = ("messengerclear.live.com", "MBI_KEY_OLD") - MESSENGER_SECURE = ("messengersecure.live.com", "MBI_SSL") - SPACES = ("spaces.live.com", "MBI") - TB = ("http://Passport.NET/tb", None) - VOICE = ("voice.messenger.msn.com", "?id=69264") - - @classmethod - def url_to_service(cls, url): - for attr_name in dir(cls): - if attr_name.startswith('_'): - continue - attr = getattr(cls, attr_name) - if isinstance(attr, tuple) and attr[0] == url: - return attr - return None - -def transport_headers(): - """Returns a dictionary, containing transport (http) headers - to use for the request""" - - return {} - -def soap_action(): - """Returns the SOAPAction value to pass to the transport - or None if no SOAPAction needs to be specified""" - - return None - -def soap_header(account, password): - """Returns the SOAP xml header""" - - return """ - - {7108E71A-9926-4FCB-BCC9-9A9D3F32E423} - 4 - 1 - - AQAAAAIAAABsYwQAAAAxMDMz - - - - %(account)s - %(password)s - - """ % {'account': xml.escape(account), - 'password': xml.escape(password)} - -def soap_body(*tokens): - """Returns the SOAP xml body""" - - token_template = """ - - http://schemas.xmlsoap.org/ws/2004/04/security/trust/Issue - - - %(address)s - - - %(policy_reference)s - """ - policy_reference_template = """ - """ - - tokens = list(tokens) - if LiveService.TB in tokens: - tokens.remove(LiveService.TB) - - assert(len(tokens) >= 1) - - body = token_template % \ - {'id': 0, - 'address': xml.escape(LiveService.TB[0]), - 'policy_reference': ''} - - for id, token in enumerate(tokens): - if token[1] is not None: - policy_reference = policy_reference_template % \ - {'uri': xml.quoteattr(token[1])} - else: - policy_reference = "" - - t = token_template % \ - {'id': id + 1, - 'address': xml.escape(token[0]), - 'policy_reference': policy_reference} - body += t - - return '%s' % body - -def process_response(soap_response): - body = soap_response.body - return body.findall("./wst:RequestSecurityTokenResponseCollection/" \ - "wst:RequestSecurityTokenResponse") - diff --git a/pymsn/pymsn/service/description/SingleSignOn/__init__.py b/pymsn/pymsn/service/description/SingleSignOn/__init__.py deleted file mode 100644 index 06f59a8c..00000000 --- a/pymsn/pymsn/service/description/SingleSignOn/__init__.py +++ /dev/null @@ -1,27 +0,0 @@ -# -*- coding: utf-8 -*- -# -# pymsn - a python client library for Msn -# -# Copyright (C) 2005-2006 Ali Sabil -# -# 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 - - -name = "SingleSignOn" -description = "Microsoft Passport single sign on service." - -url = "https://login.live.com/RST.srf" - -import RequestMultipleSecurityTokens diff --git a/pymsn/pymsn/service/description/Spaces/GetXmlFeed.py b/pymsn/pymsn/service/description/Spaces/GetXmlFeed.py deleted file mode 100644 index 2eecc9d8..00000000 --- a/pymsn/pymsn/service/description/Spaces/GetXmlFeed.py +++ /dev/null @@ -1,82 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Youness Alaoui -# -# 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 -# - -import xml.sax.saxutils as xml - -#from pymsn.util.element_tree import XMLTYPE - -def soap_header(security_token): - """Returns the SOAP xml header""" - - return """ - - %s - - """ % (xml.escape(security_token)) - -def transport_headers(): - """Returns a dictionary, containing transport (http) headers - to use for the request""" - - return {} - -def soap_action(): - """Returns the SOAPAction value to pass to the transport - or None if no SOAPAction needs to be specified""" - - return "http://www.msn.com/webservices/spaces/v1/GetXmlFeed" - -def soap_body(cid, market = "en-US", max_elements = 2, max_chars = 200, max_images = 6): - """Returns the SOAP xml body""" - # , last_viewed, app_id = "Messenger Client 8.0", update_access_time = True, is_active_contact = False - return """ - - - %(cid)s - - %(market)s - - %(max_element_count)d - %(max_character_count)d - %(max_image_count)d - - - """ % {'cid' : cid, - 'market' : market, - 'max_element_count' : max_elements, - 'max_character_count' : max_chars, - 'max_image_count' : max_images} - -# %(application_id)s -# %(update_accessed_time)s -# %(space_last_viewed)s -# %(is_active_contact)s - -# 'appliation_id' : app_id, -# 'update_accessed_time' : XMLTYPE.boolean.encode(update_access_time), -# 'space_last_viewed' : last_viewed, -# 'is_active_contact' : XMLTYPE.boolean.encode(is_active_contact) - -def process_response(soap_response): - body = soap_response.body - try: - response = body.find("./spaces:GetXmlFeedResponse/spaces:GetXmlFeedResult") - return response - except AttributeError: - return None diff --git a/pymsn/pymsn/service/description/Spaces/__init__.py b/pymsn/pymsn/service/description/Spaces/__init__.py deleted file mode 100644 index e957d013..00000000 --- a/pymsn/pymsn/service/description/Spaces/__init__.py +++ /dev/null @@ -1,25 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Youness Alaoui -# -# 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 SingleSignOn - -import AB -import Sharing - -import SchematizedStore - -import RSI -import OIM -import Spaces diff --git a/pymsn/pymsn/storage.py b/pymsn/pymsn/storage.py deleted file mode 100644 index b4c76a48..00000000 --- a/pymsn/pymsn/storage.py +++ /dev/null @@ -1,211 +0,0 @@ -# -*- coding: utf-8 -*- -# -# pymsn - a python client library for Msn -# -# Copyright (C) 2006 Ali Sabil -# -# 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 pymsn.util.string_io as StringIO -try: - from cPickle import Pickler, Unpickler -except ImportError: - from pickle import Pickler, Unpickler - -import UserDict -import anydbm -import random -from Crypto.Hash import SHA -from Crypto.Cipher import Blowfish - -__all__ = ['MemoryStorage', 'DbmStorage', 'DecryptError'] - -_storage = None - -def set_storage(klass): - global _storage - _storage = klass - -def get_storage(*args): - global _storage - if _storage is None: - _storage = MemoryStorage - if len(args) > 0: - return _storage(*args) - else: - return _storage - - -class BlowfishCipher: - def __init__(self, key): - self._cipher = Blowfish.new(key) - - def encrypt(self, data): - return self._cipher.encrypt(self.__add_padding(data)) - - def decrypt(self, data): - return self.__remove_padding(self._cipher.decrypt(data)) - - def __add_padding(self, data): - padding_length = 8 - (len(data) % 8) - for i in range(padding_length - 1): - data += chr(random.randrange(0, 256)) - data += chr(padding_length) - return data - - def __remove_padding(self, data): - padding_length = ord(data[-1]) % 8 - if padding_length == 0: - padding_length = 8 - return data[:-padding_length] - - -class DecryptError(Exception): - pass - - -class AbstractStorage(UserDict.DictMixin): - """Base class for storage objects, storage objects are - a way to let pymsn and the client agree on how data may - be stored. This data included security tokens, cached - display pictures ...""" - - def __init__(self, account, password, identifier): - """Initializer - - @param identifier: the identifier of this storage instance - @type identifier: string""" - self.account = account - self.cipher = BlowfishCipher(SHA.new(password).digest()) - self.storage_id = identifier - - def keys(self): - raise NotImplementedError("Abstract method call") - - def has_key(self, key): - return key in self.keys() - - def get(self, key, default=None): - if self.has_key(key): - return self[key] - return default - - def __len__(self): - return len(self.keys) - - def __contains__(self, key): - return self.has_key(key) - - def __getitem__(self, key): - raise NotImplementedError("Abstract method call") - - def __setitem__(self, key, value): - raise NotImplementedError("Abstract method call") - - def __delitem__(self, key): - raise NotImplementedError("Abstract method call") - - def __del__(self): - raise NotImplementedError("Abstract method call") - - def close(self): - pass - - # Helper functions - def _pickle_encrypt(self, value): - f = StringIO.StringIO() - pickler = Pickler(f, -1) - pickler.dump(value) - data = self.account + f.getvalue() # prepend a known value to check decrypt - return self.cipher.encrypt(data) - - def _unpickle_decrypt(self, data): - data = self.cipher.decrypt(data) - - if not data.startswith(self.account): - raise DecryptError() - data = data[len(self.account):] - return Unpickler(StringIO.StringIO(data)).load() - - -_MemoryStorageDict = {} -class MemoryStorage(AbstractStorage): - """In memory storage type""" - - def __init__(self, account, password, identifier): - AbstractStorage.__init__(self, account, password, identifier) - if account + "/" + identifier not in _MemoryStorageDict: - _MemoryStorageDict[account + "/" + identifier] = {} - self._dict = _MemoryStorageDict[self.account + "/" + self.storage_id] - - def keys(self): - return self._dict.keys() - - def __getitem__(self, key): - return self._unpickle_decrypt(self._dict[key]) - - def __setitem__(self, key, value): - self._dict[key] = self._pickle_encrypt(value) - - def __delitem__(self, key): - del self._dict[key] - - def __del__(self): - pass - - def close(self): - pass - - -class DbmStorage(AbstractStorage): - STORAGE_PATH = "~/.pymsn" - - def __init__(self, account, password, identifier): - import os.path - AbstractStorage.__init__(self, account, password, identifier) - - storage_path = os.path.expanduser(self.STORAGE_PATH) - - file_dir = os.path.join(storage_path, self.account) - file_path = os.path.join(file_dir, self.storage_id) - try: - import os - os.makedirs(file_dir) - except: - pass - self._dict = anydbm.open(file_path, 'c') - - def keys(self): - return self._dict.keys() - - def __getitem__(self, key): - return self._unpickle_decrypt(self._dict[str(key)]) # some dbm don't support int keys - - def __setitem__(self, key, value): - self._dict[str(key)] = self._pickle_encrypt(value) - if hasattr(self._dict, 'sync'): - self._dict.sync() - - def __delitem__(self, key): - del self._dict[str(key)] - - def __del__(self): - self.close() - - def close(self): - if hasattr(self._dict, 'sync'): - self._dict.sync() - self._dict.close() - diff --git a/pymsn/pymsn/switchboard_manager.py b/pymsn/pymsn/switchboard_manager.py deleted file mode 100644 index 4f76dc5b..00000000 --- a/pymsn/pymsn/switchboard_manager.py +++ /dev/null @@ -1,366 +0,0 @@ -# -*- coding: utf-8 -*- -# -# pymsn - a python client library for Msn -# -# Copyright (C) 2005-2007 Ali Sabil -# Copyright (C) 2007 Johann Prieur -# -# 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 - -"""Switchboard Manager -The switchboard manager is responsible for managing all the switchboards in -use, it simplifies the complexity of the switchboard crack.""" - -import logging -import gobject -import weakref - -import pymsn.msnp as msnp -from pymsn.transport import ServerType -from pymsn.util.weak import WeakSet -from pymsn.event import ConversationErrorType, ContactInviteError, MessageError - -__all__ = ['SwitchboardManager'] - -logger = logging.getLogger('protocol:switchboard_manager') - -class SwitchboardClient(object): - def __init__(self, client, contacts, priority=99): - self._client = client - self._switchboard_manager = weakref.proxy(self._client._switchboard_manager) - self.__switchboard = None - self._switchboard_requested = False - self._switchboard_priority = priority - - self._pending_invites = set(contacts) - self._pending_messages = [] - - self.participants = set() - self._process_pending_queues() - - @staticmethod - def _can_handle_message(message, switchboard_client=None): - return False - - # properties - @property - def total_participants(self): - return self.participants | self._pending_invites - - def __get_switchboard(self): - return self.__switchboard - def __set_switchboard(self, switchboard): - self.__switchboard = weakref.proxy(switchboard) - self._switchboard_requested = False - self.participants = set(switchboard.participants.values()) - - self.switchboard.connect("notify::inviting", - lambda sb, pspec: self.__on_user_inviting_changed()) - self.switchboard.connect("user-joined", - lambda sb, contact: self.__on_user_joined(contact)) - self.switchboard.connect("user-left", - lambda sb, contact: self.__on_user_left(contact)) - self.switchboard.connect("user-invitation-failed", - lambda sb, contact: self.__on_user_invitation_failed(contact)) - self.switchboard.connect("message-undelivered", - lambda sb, command: self.__on_message_undelivered(command)) - logger.info("New switchboard attached") - def process_pending_queues(): - self._process_pending_queues() - return False - gobject.idle_add(process_pending_queues) - - _switchboard = property(__get_switchboard, __set_switchboard) - switchboard = property(__get_switchboard) - - # protected - def _send_message(self, content_type, body, headers={}, - ack=msnp.MessageAcknowledgement.HALF): - message = msnp.Message(self._client.profile) - for key, value in headers.iteritems(): - message.add_header(key, value) - message.content_type = content_type - message.body = body - - self._pending_messages.append((message, ack)) - self._process_pending_queues() - - def _invite_user(self, contact): - self._pending_invites.add(contact) - self._process_pending_queues() - - def _leave(self): - self._switchboard_manager.close_handler(self) - - # callbacks - def _on_message_received(self, message): - raise NotImplementedError - - def _on_message_sent(self, message): - raise NotImplementedError - - def _on_contact_joined(self, contact): - raise NotImplementedError - - def _on_contact_left(self, contact): - raise NotImplementedError - - def _on_error(self, error_type, error): - raise NotImplementedError - - # private - def __on_user_inviting_changed(self): - if not self.switchboard.inviting: - self._process_pending_queues() - - def __on_user_joined(self, contact): - self.participants.add(contact) - self._pending_invites.discard(contact) - self._on_contact_joined(contact) - - def __on_user_left(self, contact): - self._on_contact_left(contact) - self.participants.remove(contact) - if len(self.participants) == 0: - self._pending_invites.add(contact) - try: - self._switchboard.leave() - except: - pass - - def __on_user_invitation_failed(self, contact): - self._pending_invites.discard(contact) - self._on_error(ConversationErrorType.CONTACT_INVITE, - ContactInviteError.NOT_AVAILABLE) - - def __on_message_undelivered(self, command): - self._on_error(ConversationErrorType.MESSAGE, - MessageError.DELIVERY_FAILED) - - # Helper functions - def _process_pending_queues(self): - if len(self._pending_invites) == 0 and \ - len(self._pending_messages) == 0: - return - - if self._request_switchboard(): - return - - for contact in self._pending_invites: - if contact not in self.participants: - self.switchboard.invite_user(contact) - self._pending_invites = set() - - if not self.switchboard.inviting: - for message, ack in self._pending_messages: - self.switchboard.send_message(message, ack) - self._pending_messages = [] - - def _request_switchboard(self): - if (self.switchboard is not None) and \ - self.switchboard.state == msnp.ProtocolState.OPEN: - return False - if self._switchboard_requested: - return True - logger.info("requesting new switchboard") - self._switchboard_requested = True - self._pending_invites |= self.participants - self.participants = set() - self._switchboard_manager.request_switchboard(self, self._switchboard_priority) # may set the switchboard immediatly - return self._switchboard_requested - - -class SwitchboardManager(gobject.GObject): - """Switchboard management - - @undocumented: do_get_property, do_set_property - @group Handlers: _handle_*, _default_handler, _error_handler""" - __gsignals__ = { - "handler-created": (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - (object, object)) - } - - def __init__(self, client): - """Initializer - - @param client: the main Client instance""" - gobject.GObject.__init__(self) - self._client = weakref.proxy(client) - - self._handlers_class = set() - self._orphaned_handlers = WeakSet() - self._switchboards = {} - self._orphaned_switchboards = set() - self._pending_switchboards = {} - - self._client._protocol.connect("switchboard-invitation-received", - self._ns_switchboard_invite) - - def close(self): - for switchboard in self._orphaned_switchboards: - switchboard.leave() - for switchboard in self._pending_switchboards: - switchboard.leave() - for switchboard in self._switchboards: - switchboard.leave() - - def register_handler(self, handler_class, *extra_arguments): - self._handlers_class.add((handler_class, extra_arguments)) - - def request_switchboard(self, handler, priority=99): - handler_participants = handler.total_participants - - # If the Handler was orphan, then it is no more - self._orphaned_handlers.discard(handler) - - # Check already open switchboards - for switchboard in self._switchboards.keys(): - switchboard_participants = set(switchboard.participants.values()) - if handler_participants == switchboard_participants: - self._switchboards[switchboard].add(handler) - handler._switchboard = switchboard - return - - # Check Orphaned switchboards - for switchboard in list(self._orphaned_switchboards): - switchboard_participants = set(switchboard.participants.values()) - if handler_participants == switchboard_participants: - self._switchboards[switchboard] = set([handler]) #FIXME: WeakSet ? - self._orphaned_switchboards.discard(switchboard) - handler._switchboard = switchboard - return - - # Check being requested switchboards - for switchboard, handlers in self._pending_switchboards.iteritems(): - pending_handler = handlers.pop() - handlers.add(pending_handler) - switchboard_participants = pending_handler.total_participants - if handler_participants == switchboard_participants: - self._pending_switchboards[switchboard].add(handler) - return - - self._client._protocol.\ - request_switchboard(priority, self._ns_switchboard_request_response, handler) - - def close_handler(self, handler): - self._orphaned_handlers.discard(handler) - for switchboard in self._switchboards.keys(): - handlers = self._switchboards[switchboard] - handlers.discard(handler) - if len(handlers) == 0: - switchboard.leave() - del self._switchboards[switchboard] - self._orphaned_switchboards.add(switchboard) - - for switchboard in self._pending_switchboards.keys(): - handlers = self._pending_switchboards[switchboard] - handlers.discard(handler) - if len(handlers) == 0: - del self._pending_switchboards[switchboard] - self._orphaned_switchboards.add(switchboard) - - def _ns_switchboard_request_response(self, session, handler): - switchboard = self._build_switchboard(session) - self._pending_switchboards[switchboard] = set([handler]) #FIXME: WeakSet ? - - def _ns_switchboard_invite(self, protocol, session, inviter): - switchboard = self._build_switchboard(session) - self._orphaned_switchboards.add(switchboard) - - def _build_switchboard(self, session): - server, session_id, key = session - client = self._client - proxies = client._proxies - - transport_class = client._transport_class - transport = transport_class(server, ServerType.SWITCHBOARD, proxies) - switchboard = msnp.SwitchboardProtocol(client, transport, - session_id, key, proxies) - switchboard.connect("notify::state", self._sb_state_changed) - switchboard.connect("message-received", self._sb_message_received) - transport.establish_connection() - return switchboard - - def _sb_state_changed(self, switchboard, param_spec): - state = switchboard.state - if state == msnp.ProtocolState.OPEN: - self._switchboards[switchboard] = set() #FIXME: WeakSet ? - - # Requested switchboards - if switchboard in self._pending_switchboards: - handlers = self._pending_switchboards[switchboard] - while True: - try: - handler = handlers.pop() - self._switchboards[switchboard].add(handler) - handler._switchboard = switchboard - except KeyError: - break - del self._pending_switchboards[switchboard] - - # Orphaned Handlers - for handler in list(self._orphaned_handlers): - switchboard_participants = set(switchboard.participants.values()) - handler_participants = handler.total_participants - if handler_participants == switchboard_participants: - self._switchboards[switchboard].add(handler) - self._orphaned_handlers.discard(handler) - self._orphaned_switchboards.discard(switchboard) - handler._switchboard = switchboard - - # no one wants it, it is an orphan - if len(self._switchboards[switchboard]) == 0: - del self._switchboards[switchboard] - self._orphaned_switchboards.add(switchboard) - - elif state == msnp.ProtocolState.CLOSED: - if switchboard in self._switchboards.keys(): - for handler in self._switchboards[switchboard]: - self._orphaned_handlers.add(handler) - del self._switchboards[switchboard] - self._orphaned_switchboards.discard(switchboard) - - def _sb_message_received(self, switchboard, message): - switchboard_participants = set(switchboard.participants.values()) - if switchboard in self._switchboards.keys(): - handlers = self._switchboards[switchboard] - handlers_class = [type(handler) for handler in handlers] - for handler in list(handlers): - if not handler._can_handle_message(message, handler): - continue - handler._on_message_received(message) - for handler_class, extra_args in self._handlers_class: - if handler_class in handlers_class: - continue - if not handler_class._can_handle_message(message): - continue - handler = handler_class(self._client, (), *extra_args) - handlers.add(handler) - handler._switchboard = switchboard - self.emit("handler-created", handler_class, handler) - handler._on_message_received(message) - - if switchboard in list(self._orphaned_switchboards): - for handler_class, extra_args in self._handlers_class: - if not handler_class._can_handle_message(message): - continue - handler = handler_class(self._client, (), *extra_args) - self._switchboards[switchboard] = set([handler]) #FIXME: WeakSet ? - self._orphaned_switchboards.discard(switchboard) - handler._switchboard = switchboard - self.emit("handler-created", handler_class, handler) - handler._on_message_received(message) - diff --git a/pymsn/pymsn/transport.py b/pymsn/pymsn/transport.py deleted file mode 100644 index 214f885d..00000000 --- a/pymsn/pymsn/transport.py +++ /dev/null @@ -1,487 +0,0 @@ -# -*- coding: utf-8 -*- -# -# pymsn - a python client library for Msn -# -# Copyright (C) 2005-2006 Ali Sabil -# Copyright (C) 2006 Johann Prieur -# Copyright (C) 2006 Ole André Vadla Ravnås -# -# 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 - -"""Network Transport Layer - -This module provides an abstraction of the transport to be used to communicate -with the MSN servers, actually MSN servers can communicate either through -direct connection using TCP/1863 or using TCP/80 by tunelling the protocol -inside HTTP POST requests. - -The classes of this module are structured as follow: -G{classtree BaseTransport}""" - -import gnet -import gnet.protocol -import msnp - -import logging -import gobject - -__all__=['ServerType', 'DirectConnection'] - -logger = logging.getLogger('Transport') - -class ServerType(object): - """""" - SWITCHBOARD = 'SB' - NOTIFICATION = 'NS' - -TransportError = gnet.IoError - -class BaseTransport(gobject.GObject): - """Abstract Base Class that modelize a connection to the MSN service, this - abstraction is used to build various transports that expose the same - interface, basically a transport is created using its constructor then it - simply emits signals when network events (or even abstracted network events) - occur, for example a Transport that successfully connected to the MSN - service will emit a connection-success signal, and when that transport - received a meaningful message it would emit a command-received signal. - - @ivar server: the server being used to connect to - @type server: tuple(host, port) - - @ivar server_type: the server that we are connecting to, either - Notification or switchboard. - @type server_type: L{ServerType} - - @ivar proxies: proxies that we can use to connect - @type proxies: dict(type => L{gnet.proxy.ProxyInfos}) - - @ivar transaction_id: the current transaction ID - @type transaction_id: integer - - - @cvar connection-failure: signal emitted when the connection fails - @type connection-failure: () - - @cvar connection-success: signal emitted when the connection succeed - @type connection-success: () - - @cvar connection-reset: signal emitted when the connection is being - reset - @type connection-reset: () - - @cvar connection-lost: signal emitted when the connection was lost - @type connection-lost: () - - @cvar command-received: signal emitted when a command is received - @type command-received: FIXME - - @cvar command-sent: signal emitted when a command was successfully - transmitted to the server - @type command-sent: FIXME - - @undocumented: __gsignals__""" - - __gsignals__ = { - "connection-failure" : (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - (object,)), - - "connection-success" : (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - ()), - - "connection-reset" : (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - ()), - - "connection-lost" : (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - (object,)), - - "command-received": (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - (object,)), - - "command-sent": (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - (object,)), - } - - def __init__(self, server, server_type=ServerType.NOTIFICATION, proxies={}): - """Connection initialization - - @param server: the server to connect to. - @type server: (host: string, port: integer) - - @param server_type: the server that we are connecting to, either - Notification or switchboard. - @type server_type: L{ServerType} - - @param proxies: proxies that we can use to connect - @type proxies: {type: string => L{gnet.network.ProxyInfos}}""" - gobject.GObject.__init__(self) - self.server = server - self.server_type = server_type - self.proxies = proxies - self._transaction_id = 0 - - @property - def transaction_id(self): - return self._transaction_id - - # Connection - def establish_connection(self): - """Connect to the server server""" - raise NotImplementedError - - def lose_connection(self): - """Disconnect from the server""" - raise NotImplementedError - - def reset_connection(self, server=None): - """Reset the connection - - @param server: when set, reset the connection and - connect to this new server - @type server: tuple(host, port)""" - raise NotImplementedError - - # Command Sending - def send_command(self, command, increment=True, callback=None, *cb_args): - """ - Sends a L{msnp.Command} to the server. - - @param command: command to send - @type command: L{msnp.Command} - - @param increment: if False, the transaction ID is not incremented - @type increment: bool - - @param callback: callback to be used when the command has been - transmitted - @type callback: callable - - @param cb_args: callback arguments - @type cb_args: Any, ... - """ - raise NotImplementedError - - def send_command_ex(self, command, arguments=(), payload=None, - increment=True, callback=None, *cb_args): - """ - Builds a command object then send it to the server. - - @param command: the command name, must be a 3 letters - uppercase string. - @type command: string - - @param arguments: command arguments - @type arguments: (string, ...) - - @param payload: payload data - @type payload: string - - @param increment: if False, the transaction ID is not incremented - @type increment: bool - - @param callback: callback to be used when the command has been - transmitted - @type callback: callable - - @param cb_args: callback arguments - @type cb_args: tuple - """ - cmd = msnp.Command() - cmd.build(command, self._transaction_id, payload, *arguments) - self.send_command(cmd, increment, callback, *cb_args) - return cmd - - def enable_ping(self): - pass - - def _increment_transaction_id(self): - """Increments the Transaction ID then return it. - - @rtype: integer""" - self._transaction_id += 1 - return self._transaction_id -gobject.type_register(BaseTransport) - - -class DirectConnection(BaseTransport): - """Implements a direct connection to the net using TCP/1863""" - - def __init__(self, server, server_type=ServerType.NOTIFICATION, proxies={}): - BaseTransport.__init__(self, server, server_type, proxies) - - transport = gnet.io.TCPClient(server[0], server[1]) - transport.connect("notify::status", self.__on_status_change) - transport.connect("error", self.__on_error) - - receiver = gnet.parser.DelimiterParser(transport) - receiver.connect("received", self.__on_received) - - self._receiver = receiver - self._receiver.delimiter = "\r\n" - self._transport = transport - self.__pending_chunk = None - self.__resetting = False - self.__error = False - self.__png_timeout = None - - __init__.__doc__ = BaseTransport.__init__.__doc__ - - ### public commands - - def establish_connection(self): - logger.debug('<-> Connecting to %s:%d' % self.server ) - self._transport.open() - - def lose_connection(self): - self._transport.close() - if self.__png_timeout is not None: - gobject.source_remove(self.__png_timeout) - self.__png_timeout = None - - def reset_connection(self, server=None): - if server: - self._transport.set_property("host", server[0]) - self._transport.set_property("port", server[1]) - self.server = server - self.__resetting = True - if self.__png_timeout is not None: - gobject.source_remove(self.__png_timeout) - self.__png_timeout = None - self._transport.close() - self._transport.open() - - def send_command(self, command, increment=True, callback=None, *cb_args): - logger.debug('>>> ' + repr(command)) - our_cb_args = (command, callback, cb_args) - self._transport.send(str(command), self.__on_command_sent, *our_cb_args) - if increment: - self._increment_transaction_id() - - def enable_ping(self): - cmd = msnp.Command() - cmd.build("PNG", None) - self.send_command(cmd, False) - self.__png_timeout = None - return False - - def __on_command_sent(self, command, user_callback, user_cb_args): - self.emit("command-sent", command) - if user_callback: - user_callback(*user_cb_args) - - def __handle_ping_reply(self, command): - timeout = int(command.arguments[0]) - self.__png_timeout = gobject.timeout_add(timeout * 1000, self.enable_ping) - - ### callbacks - def __on_status_change(self, transport, param): - status = transport.get_property("status") - if status == gnet.IoStatus.OPEN: - if self.__resetting: - self.emit("connection-reset") - self.__resetting = False - self.emit("connection-success") - elif status == gnet.IoStatus.CLOSED: - if not self.__resetting and not self.__error: - self.emit("connection-lost", None) - self.__error = False - - def __on_error(self, transport, reason): - status = transport.get_property("status") - self.__error = True - if status == gnet.IoStatus.OPEN: - self.emit("connection-lost", reason) - else: - self.emit("connection-failure", reason) - - def __on_received(self, receiver, chunk): - cmd = msnp.Command() - if self.__pending_chunk: - chunk = self.__pending_chunk + "\r\n" + chunk - cmd.parse(chunk) - self.__pending_chunk = None - self._receiver.delimiter = "\r\n" - else: - cmd.parse(chunk) - if cmd.name in msnp.Command.INCOMING_PAYLOAD or \ - (cmd.is_error() and (cmd.arguments is not None) and len(cmd.arguments) > 0): - try: - payload_len = int(cmd.arguments[-1]) - except: - payload_len = 0 - if payload_len > 0: - self.__pending_chunk = chunk - self._receiver.delimiter = payload_len - return - logger.debug('<<< ' + repr(cmd)) - if cmd.name == 'QNG': - self.__handle_ping_reply(cmd) - else: - self.emit("command-received", cmd) -gobject.type_register(DirectConnection) - - -class HTTPPollConnection(BaseTransport): - """Implements an HTTP polling transport, basically it encapsulates the MSNP - commands into an HTTP request, and receive responses by polling a specific - url""" - def __init__(self, server, server_type=ServerType.NOTIFICATION, proxies={}): - self._target_server = server - server = ("gateway.messenger.hotmail.com", 80) - BaseTransport.__init__(self, server, server_type, proxies) - self._setup_transport() - - self._command_queue = [] - self._waiting_for_response = False # are we waiting for a response - self._session_id = None - self.__error = False - - def _setup_transport(self): - server = self.server - proxies = self.proxies - if 'http' in proxies: - transport = gnet.protocol.HTTP(server[0], server[1], proxies['http']) - else: - transport = gnet.protocol.HTTP(server[0], server[1]) - transport.connect("response-received", self.__on_received) - transport.connect("request-sent", self.__on_sent) - transport.connect("error", self.__on_error) - self._transport = transport - - def establish_connection(self): - logger.debug('<-> Connecting to %s:%d' % self.server) - self._polling_source_id = gobject.timeout_add(5000, self._poll) - self.emit("connection-success") - - def lose_connection(self): - gobject.source_remove(self._polling_source_id) - del self._polling_source_id - if not self.__error: - self.emit("connection-lost", None) - self.__error = False - - def reset_connection(self, server=None): - if server: - self._target_server = server - self.emit("connection-reset") - - def send_command(self, command, increment=True, callback=None, *cb_args): - self._command_queue.append((command, increment, callback, cb_args)) - self._send_command() - - def _send_command(self): - if len(self._command_queue) == 0 or self._waiting_for_response: - return - command, increment = self._command_queue[0][0:2] - resource = "/gateway/gateway.dll" - headers = { - "Accept": "*/*", - "Accept-Language": "en-us", - #"User-Agent": "MSMSGS", - "Connection": "Keep-Alive", - "Pragma": "no-cache", - "Content-Type": "application/x-msn-messenger", - "Proxy-Connection": "Keep-Alive" - } - - str_command = str(command) - if self._session_id is None: - resource += "?Action=open&Server=%s&IP=%s" % (self.server_type, - self._target_server[0]) - elif command == None:# Polling the server for queued messages - resource += "?Action=poll&SessionID=%s" % self._session_id - str_command = "" - else: - resource += "?SessionID=%s" % self._session_id - - self._transport.request(resource, headers, str_command, "POST") - self._waiting_for_response = True - - if command is not None: - logger.debug('>>> ' + repr(command)) - - if increment: - self._increment_transaction_id() - - def _poll(self): - if not self._waiting_for_response: - self.send_command(None) - return True - - def __on_error(self, transport, reason): - self.__error = True - self.emit("connection-lost", reason) - - def __on_received(self, transport, http_response): - if http_response.status == 403: - self.emit("connection-lost", TransportError.PROXY_FORBIDDEN) - self.lose_connection() - if 'X-MSN-Messenger' in http_response.headers: - data = http_response.headers['X-MSN-Messenger'].split(";") - for elem in data: - key, value = [p.strip() for p in elem.split('=', 1)] - if key == 'SessionID': - self._session_id = value - elif key == 'GW-IP': - self.server = (value, self.server[1]) - self._setup_transport() - elif key == 'Session'and value == 'close': - #self.lose_connection() - pass - - self._waiting_for_response = False - - commands = http_response.body - while len(commands) != 0: - commands = self.__extract_command(commands) - - self._send_command() - - def __on_sent(self, transport, http_request): - if len(self._command_queue) == 0: - return - command, increment, callback, cb_args = self._command_queue.pop(0) - if command is not None: - if callback: - callback(*cb_args) - self.emit("command-sent", command) - - def __extract_command(self, data): - first, rest = data.split('\r\n', 1) - cmd = msnp.Command() - cmd.parse(first.strip()) - if cmd.name in msnp.Command.INCOMING_PAYLOAD or \ - (cmd.is_error() and (cmd.arguments is not None) and len(cmd.arguments) > 0): - try: - payload_len = int(cmd.arguments[-1]) - except: - payload_len = 0 - if payload_len > 0: - cmd.payload = rest[:payload_len].strip() - logger.debug('<<< ' + repr(cmd)) - self.emit("command-received", cmd) - return rest[payload_len:] - else: - logger.debug('<<< ' + repr(cmd)) - self.emit("command-received", cmd) - return rest - - - diff --git a/pymsn/pymsn/util/__init__.py b/pymsn/pymsn/util/__init__.py deleted file mode 100644 index a27e2007..00000000 --- a/pymsn/pymsn/util/__init__.py +++ /dev/null @@ -1,20 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2006 Ali Sabil -# -# 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 -# - -"""Convenience import modules""" diff --git a/pymsn/pymsn/util/debug.py b/pymsn/pymsn/util/debug.py deleted file mode 100644 index 3ec5bcbf..00000000 --- a/pymsn/pymsn/util/debug.py +++ /dev/null @@ -1,46 +0,0 @@ -# -*- coding: utf-8 -*- -# -# pymsn - a python client library for Msn -# -# Copyright (C) 2007 Ole André Vadla Ravnås -# -# 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 struct - -"""Utility functions used for debug output processing""" - -def escape_string(string): - result = "" - for c in string: - v = ord(c) - if (v >= 32 and v <= 127) or c in ("\t", "\r", "\n"): - result += c - else: - result += "\\x%02x" % ord(c) - - return result - -def hexify_string(string): - result = "" - for i, c in enumerate(string): - if result: - if i % 16 != 0: - result += " " - else: - result += "\r\n" - result += "%02x" % ord(c) - return result - diff --git a/pymsn/pymsn/util/decorator.py b/pymsn/pymsn/util/decorator.py deleted file mode 100644 index 0d66c79f..00000000 --- a/pymsn/pymsn/util/decorator.py +++ /dev/null @@ -1,121 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2006 Ali Sabil -# -# 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 -# - -"""Useful decorators""" - -import sys -import warnings -import time - -import gobject - - -def decorator(function): - """decorator to be used on decorators, it preserves the docstring and - function attributes of functions to which it is applied.""" - def new_decorator(f): - g = function(f) - g.__name__ = f.__name__ - g.__doc__ = f.__doc__ - g.__dict__.update(f.__dict__) - return g - new_decorator.__name__ = function.__name__ - new_decorator.__doc__ = function.__doc__ - new_decorator.__dict__.update(function.__dict__) - return new_decorator - - -def rw_property(function): - """This decorator implements read/write properties, as follow: - - @rw_property - def my_property(): - "Documentation" - def fget(self): - return self._my_property - def fset(self, value): - self._my_property = value - return locals() - """ - return property(**function()) - -@decorator -def deprecated(func): - """This is a decorator which can be used to mark functions as deprecated. - It will result in a warning being emitted when the function is used.""" - def new_function(*args, **kwargs): - warnings.warn("Call to deprecated function %s." % func.__name__, - category=DeprecationWarning) - return func(*args, **kwargs) - return new_function - -@decorator -def unstable(func): - """This is a decorator which can be used to mark functions as unstable API - wise. It will result in a warning being emitted when the function is used.""" - def new_function(*args, **kwargs): - warnings.warn("Call to unstable API function %s." % func.__name__, - category=FutureWarning) - return func(*args, **kwargs) - return new_function - -@decorator -def async(func): - """Make a function mainloop friendly. the function will be called at the - next mainloop idle state.""" - def new_function(*args, **kwargs): - def async_function(): - func(*args, **kwargs) - return False - gobject.idle_add(async_function) - return new_function - -class throttled(object): - """Throttle the calls to a function by queueing all the calls that happen - before the minimum delay.""" - - def __init__(self, min_delay, queue): - self._min_delay = min_delay - self._queue = queue - self._last_call_time = None - - def __call__(self, func): - def process_queue(): - if len(self._queue) != 0: - func, args, kwargs = self._queue.pop(0) - self._last_call_time = time.time() * 1000 - func(*args, **kwargs) - return False - - def new_function(*args, **kwargs): - now = time.time() * 1000 - if self._last_call_time is None or \ - now - self._last_call_time >= self._min_delay: - self._last_call_time = now - func(*args, **kwargs) - else: - self._queue.append((func, args, kwargs)) - last_call_delta = now - self._last_call_time - process_queue_timeout = int(self._min_delay * len(self._queue) - last_call_delta) - gobject.timeout_add(process_queue_timeout, process_queue) - - new_function.__name__ = func.__name__ - new_function.__doc__ = func.__doc__ - new_function.__dict__.update(func.__dict__) - return new_function diff --git a/pymsn/pymsn/util/element_tree.py b/pymsn/pymsn/util/element_tree.py deleted file mode 100644 index 0e83c907..00000000 --- a/pymsn/pymsn/util/element_tree.py +++ /dev/null @@ -1,152 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2006 Ali Sabil -# Copyright (C) 2007 Johann Prieur -# -# 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 -# - -"""ElementTree independent from the available distribution""" - -try: - from xml.etree.cElementTree import * -except ImportError: - try: - from cElementTree import * - except ImportError: - from elementtree.ElementTree import * - -__all__ = ["XMLTYPE", "XMLResponse"] - -import iso8601 - -class XMLTYPE(object): - - class bool(object): - @staticmethod - def encode(boolean): - if boolean: - return "true" - return "false" - - @staticmethod - def decode(boolean_str): - false_set = ("false", "f", "no", "n", "0", "") - if str(boolean_str).strip().lower() not in false_set: - return True - return False - - class int(object): - @staticmethod - def encode(integer): - return str(integer) - - @staticmethod - def decode(integer_str): - try: - return int(integer_str) - except ValueError: - return 0 - - class datetime(object): - @staticmethod - def encode(datetime): - return datetime.isoformat() - - @staticmethod - def decode(date_str): - result = iso8601.parse_date(date_str.strip()) - return result.replace(tzinfo=None) # FIXME: do not disable the timezone - -class _Element(object): - def __init__(self, element, ns_shorthands): - self.element = element - self.ns_shorthands = ns_shorthands.copy() - - def __getattr__(self, name): - return getattr(self.element, name) - - def __getitem__(self, name): - return self.element[name] - - def __iter__(self): - for node in self.element: - yield _Element(node, self.ns_shorthands) - - def __contains__(self, node): - return node in self.element - - def __repr__(self): - return "" % (self.element.tag,) - - def _process_path(self, path): - for sh, ns in self.ns_shorthands.iteritems(): - path = path.replace("/%s:" % sh, "/{%s}" % ns) - if path.startswith("%s:" % sh): - path = path.replace("%s:" % sh, "{%s}" % ns, 1) - return path - - def find(self, path): - path = self._process_path(path) - node = self.element.find(path) - if node is None: - return None - return _Element(node, self.ns_shorthands) - - def findall(self, path): - path = self._process_path(path) - - result = [] - nodes = self.element.findall(path) - for node in nodes: - result.append(_Element(node, self.ns_shorthands)) - return result - - def findtext(self, path, type=None): - result = self.find(path) - if result is None: - return "" - result = result.text - - if type is None: - return result - return getattr(XMLTYPE, type).decode(result) - -class XMLResponse(object): - - def __init__(self, data, ns_shorthands={}): - try: - tree = self._parse(data) - self.tree = _Element(tree, ns_shorthands) - except: - self.tree = None - - def __getitem__(self, name): - return self.tree[name] - - def find(self, path): - return self.tree.find(path) - - def findall(self, path): - return self.tree.findall(path) - - def findtext(self, path, type=None): - return self.tree.findtext(path, type) - - def is_valid(self): - return self.tree is not None - - def _parse(self, data): - pass diff --git a/pymsn/pymsn/util/guid.py b/pymsn/pymsn/util/guid.py deleted file mode 100644 index b9cd20b3..00000000 --- a/pymsn/pymsn/util/guid.py +++ /dev/null @@ -1,32 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Johann Prieur -# -# 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 -# -import random - -def generate_guid(): - bytes = [random.randrange(256) for i in range(16)] - - data1 = ("%02X" * 4) % tuple(bytes[0:4]) - data2 = ("%02X" * 2) % tuple(bytes[4:6]) - data3 = ("%02X" * 2) % tuple(bytes[6:8]) - data4 = ("%02X" * 2) % tuple(bytes[8:10]) - data5 = ("%02X" * 6) % tuple(bytes[10:]) - - data3 = "4" + data3[1:] - - return "%s-%s-%s-%s-%s" % (data1, data2, data3, data4, data5) diff --git a/pymsn/pymsn/util/iso8601/LICENSE b/pymsn/pymsn/util/iso8601/LICENSE deleted file mode 100644 index 5ca93dae..00000000 --- a/pymsn/pymsn/util/iso8601/LICENSE +++ /dev/null @@ -1,20 +0,0 @@ -Copyright (c) 2007 Michael Twomey - -Permission is hereby granted, free of charge, to any person obtaining a -copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be included -in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/pymsn/pymsn/util/iso8601/README b/pymsn/pymsn/util/iso8601/README deleted file mode 100644 index 5ec9d455..00000000 --- a/pymsn/pymsn/util/iso8601/README +++ /dev/null @@ -1,26 +0,0 @@ -A simple package to deal with ISO 8601 date time formats. - -ISO 8601 defines a neutral, unambiguous date string format, which also -has the property of sorting naturally. - -e.g. YYYY-MM-DDTHH:MM:SSZ or 2007-01-25T12:00:00Z - -Currently this covers only the most common date formats encountered, not -all of ISO 8601 is handled. - -Currently the following formats are handled: - -* 2006-01-01T00:00:00Z -* 2006-01-01T00:00:00[+-]00:00 - -I'll add more as I encounter them in my day to day life. Patches with -new formats and tests will be gratefully accepted of course :) - -References: - -* http://www.cl.cam.ac.uk/~mgk25/iso-time.html - simple overview - -* http://hydracen.com/dx/iso8601.htm - more detailed enumeration of - valid formats. - -See the LICENSE file for the license this package is released under. diff --git a/pymsn/pymsn/util/iso8601/__init__.py b/pymsn/pymsn/util/iso8601/__init__.py deleted file mode 100644 index e72e3563..00000000 --- a/pymsn/pymsn/util/iso8601/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from iso8601 import * diff --git a/pymsn/pymsn/util/iso8601/iso8601.py b/pymsn/pymsn/util/iso8601/iso8601.py deleted file mode 100644 index 68ed05a0..00000000 --- a/pymsn/pymsn/util/iso8601/iso8601.py +++ /dev/null @@ -1,102 +0,0 @@ -"""ISO 8601 date time string parsing - -Basic usage: ->>> import iso8601 ->>> iso8601.parse_date("2007-01-25T12:00:00Z") -datetime.datetime(2007, 1, 25, 12, 0, tzinfo=) ->>> - -""" - -from datetime import datetime, timedelta, tzinfo -import re - -__all__ = ["parse_date", "ParseError"] - -# Adapted from http://delete.me.uk/2005/03/iso8601.html -ISO8601_REGEX = re.compile(r"(?P[0-9]{4})(-(?P[0-9]{1,2})(-(?P[0-9]{1,2})" - r"((?P.)(?P[0-9]{2}):(?P[0-9]{2})(:(?P[0-9]{2})(\.(?P[0-9]+))?)?" - r"(?PZ|(([-+])([0-9]{2}):([0-9]{2})))?)?)?)?" -) -TIMEZONE_REGEX = re.compile("(?P[+-])(?P[0-9]{2}).(?P[0-9]{2})") - -class ParseError(Exception): - """Raised when there is a problem parsing a date string""" - -# Yoinked from python docs -ZERO = timedelta(0) -class Utc(tzinfo): - """UTC - - """ - def utcoffset(self, dt): - return ZERO - - def tzname(self, dt): - return "UTC" - - def dst(self, dt): - return ZERO -UTC = Utc() - -class FixedOffset(tzinfo): - """Fixed offset in hours and minutes from UTC - - """ - def __init__(self, offset_hours, offset_minutes, name): - self.__offset = timedelta(hours=offset_hours, minutes=offset_minutes) - self.__name = name - - def utcoffset(self, dt): - return self.__offset - - def tzname(self, dt): - return self.__name - - def dst(self, dt): - return ZERO - - def __repr__(self): - return "" % self.__name - -def parse_timezone(tzstring, default_timezone=UTC): - """Parses ISO 8601 time zone specs into tzinfo offsets - - """ - if tzstring == "Z": - return default_timezone - # This isn't strictly correct, but it's common to encounter dates without - # timezones so I'll assume the default (which defaults to UTC). - # Addresses issue 4. - if tzstring is None: - return default_timezone - m = TIMEZONE_REGEX.match(tzstring) - prefix, hours, minutes = m.groups() - hours, minutes = int(hours), int(minutes) - if prefix == "-": - hours = -hours - minutes = -minutes - return FixedOffset(hours, minutes, tzstring) - -def parse_date(datestring, default_timezone=UTC): - """Parses ISO 8601 dates into datetime objects - - The timezone is parsed from the date string. However it is quite common to - have dates without a timezone (not strictly correct). In this case the - default timezone specified in default_timezone is used. This is UTC by - default. - """ - if not isinstance(datestring, basestring): - raise ParseError("Expecting a string %r" % datestring) - m = ISO8601_REGEX.match(datestring) - if not m: - raise ParseError("Unable to parse date string %r" % datestring) - groups = m.groupdict() - tz = parse_timezone(groups["timezone"], default_timezone=UTC) - if groups["fraction"] is None: - groups["fraction"] = 0 - frac = int(groups["fraction"]) - groups["fraction"] = int (frac / 10 ** (len(str(frac)) - 6)) - return datetime(int(groups["year"]), int(groups["month"]), int(groups["day"]), - int(groups["hour"]), int(groups["minute"]), int(groups["second"]), - int(groups["fraction"]), tz) diff --git a/pymsn/pymsn/util/iso8601/test_iso8601.py b/pymsn/pymsn/util/iso8601/test_iso8601.py deleted file mode 100644 index 9534f959..00000000 --- a/pymsn/pymsn/util/iso8601/test_iso8601.py +++ /dev/null @@ -1,106 +0,0 @@ -import iso8601 - -def test_iso8601_regex(): - assert iso8601.ISO8601_REGEX.match("2006-10-11T00:14:33Z") - -def test_timezone_regex(): - assert iso8601.TIMEZONE_REGEX.match("+01:00") - assert iso8601.TIMEZONE_REGEX.match("+00:00") - assert iso8601.TIMEZONE_REGEX.match("+01:20") - assert iso8601.TIMEZONE_REGEX.match("-01:00") - -def test_parse_date(): - d = iso8601.parse_date("2006-10-20T15:34:56Z") - assert d.year == 2006 - assert d.month == 10 - assert d.day == 20 - assert d.hour == 15 - assert d.minute == 34 - assert d.second == 56 - assert d.tzinfo == iso8601.UTC - -def test_parse_date_fraction(): - d = iso8601.parse_date("2006-10-20T15:34:56.123Z") - assert d.year == 2006 - assert d.month == 10 - assert d.day == 20 - assert d.hour == 15 - assert d.minute == 34 - assert d.second == 56 - assert d.microsecond == 123 - assert d.tzinfo == iso8601.UTC - -def test_parse_date_fraction_2(): - """From bug 6 - - """ - d = iso8601.parse_date("2007-5-7T11:43:55.328Z'") - assert d.year == 2007 - assert d.month == 5 - assert d.day == 7 - assert d.hour == 11 - assert d.minute == 43 - assert d.second == 55 - assert d.microsecond == 328 - assert d.tzinfo == iso8601.UTC - -def test_parse_date_tz(): - d = iso8601.parse_date("2006-10-20T15:34:56.123+02:30") - assert d.year == 2006 - assert d.month == 10 - assert d.day == 20 - assert d.hour == 15 - assert d.minute == 34 - assert d.second == 56 - assert d.microsecond == 123 - assert d.tzinfo.tzname(None) == "+02:30" - offset = d.tzinfo.utcoffset(None) - assert offset.days == 0 - assert offset.seconds == 60 * 60 * 2.5 - -def test_parse_invalid_date(): - try: - iso8601.parse_date(None) - except iso8601.ParseError: - pass - else: - assert 1 == 2 - -def test_parse_invalid_date2(): - try: - iso8601.parse_date("23") - except iso8601.ParseError: - pass - else: - assert 1 == 2 - -def test_parse_no_timezone(): - """issue 4 - Handle datetime string without timezone - - This tests what happens when you parse a date with no timezone. While not - strictly correct this is quite common. I'll assume UTC for the time zone - in this case. - """ - d = iso8601.parse_date("2007-01-01T08:00:00") - assert d.year == 2007 - assert d.month == 1 - assert d.day == 1 - assert d.hour == 8 - assert d.minute == 0 - assert d.second == 0 - assert d.microsecond == 0 - assert d.tzinfo == iso8601.UTC - -def test_space_separator(): - """Handle a separator other than T - - """ - d = iso8601.parse_date("2007-06-23 06:40:34.00Z") - assert d.year == 2007 - assert d.month == 6 - assert d.day == 23 - assert d.hour == 6 - assert d.minute == 40 - assert d.second == 34 - assert d.microsecond == 0 - assert d.tzinfo == iso8601.UTC diff --git a/pymsn/pymsn/util/queue.py b/pymsn/pymsn/util/queue.py deleted file mode 100644 index 88a3ffeb..00000000 --- a/pymsn/pymsn/util/queue.py +++ /dev/null @@ -1,64 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2006 Ali Sabil -# -# 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 -# - -"""Useful queues""" - -__all__ = ['PriorityQueue', 'LastElementQueue'] - -import bisect - -class PriorityQueue(object): - def __init__(self, iterable=()): - self.queue = list(iterable) - - def add(self, item, priority=0): - bisect.insort(self.queue, (priority, item)) - - def append(self, item): - self.add(item) - - def pop(self, n): - return self.queue.pop(n)[1] - - def __len__(self): - return len(self.queue) - - @property - def empty(self): - return len(self.queue) == 0 - -class LastElementQueue(object): - def __init__(self, iterable=()): - self.queue = list(iterable)[-1:] - - def append(self, item): - self.queue = [item] - - def pop(self, n): - return self.queue.pop(n) - - def __len__(self): - return len(self.queue) - - @property - def empty(self): - return len(self.queue) == 0 - - - diff --git a/pymsn/pymsn/util/string_io.py b/pymsn/pymsn/util/string_io.py deleted file mode 100644 index ec775a21..00000000 --- a/pymsn/pymsn/util/string_io.py +++ /dev/null @@ -1,26 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2005 Ole André Vadla Ravnås -# Copyright (C) 2006 Ali Sabil -# -# 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 -# - -"""Imports the fastest StringIO available""" - -try: - from cStringIO import * -except ImportError: - from StringIO import * diff --git a/pymsn/pymsn/util/weak.py b/pymsn/pymsn/util/weak.py deleted file mode 100644 index 6e612513..00000000 --- a/pymsn/pymsn/util/weak.py +++ /dev/null @@ -1,35 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2006 Ali Sabil -# -# 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 -# - -"""Some missing weak refs""" - -from weakref import WeakKeyDictionary -from sets import Set - -__all__ = ['WeakSet'] - -class WeakSet(Set): - def __init__(self, iterable=None): - Set.__init__(self) - self._data = WeakKeyDictionary() - if iterable is not None: - self._update(iterable) - - def __hash__(self): - raise TypeError, "Can't hash a WeakSet." diff --git a/pymsn/setup.py b/pymsn/setup.py deleted file mode 100644 index fbe57165..00000000 --- a/pymsn/setup.py +++ /dev/null @@ -1,89 +0,0 @@ -from distutils.core import setup -from doc import make_doc_command, BuildAllDocCommand -import os - -import pymsn - - -# Metadata -NAME = "pymsn" -VERSION = pymsn.__version__ -DESCRIPTION = "Python msn client library" -AUTHOR = "Ali Sabil" -AUTHOR_EMAIL = "ali.sabil@gmail.com" -URL = pymsn.__url__ -LICENSE = pymsn.__license__ - -# Documentation -doc_commands = { - 'doc_user_api': make_doc_command( - name='user_api', - description='the pymsn user API documentation', - config='doc/user-api.conf'), - 'doc': BuildAllDocCommand -} -for name in doc_commands.keys(): - if name != 'doc': - BuildAllDocCommand.sub_commands.append((name, None)) - -# Compile the list of packages available, because distutils doesn't have -# an easy way to do this. -def path_split(path, result=None): - """ - Split a pathname into components (the opposite of os.path.join) in a - platform-neutral way. - """ - if result is None: - result = [] - head, tail = os.path.split(path) - if head == '': - return [tail] + result - if head == path: - return result - return path_split(head, [tail] + result) - -packages, data_files = [], [] -root_dir = os.path.dirname(__file__) -pieces = path_split(root_dir) -if pieces[-1] == '': - len_root_dir = len(pieces) - 1 -else: - len_root_dir = len(pieces) - -pymsn_dir = os.path.join(root_dir, 'pymsn') -for dirpath, dirnames, filenames in os.walk(pymsn_dir): - # Ignore dirnames that start with '.' - for i, dirname in enumerate(dirnames): - if dirname.startswith('.'): - del dirnames[i] - if '__init__.py' in filenames: - packages.append('.'.join(path_split(dirpath)[len_root_dir:])) - elif filenames: - data_files.append([dirpath, [os.path.join(dirpath, f) for f in filenames]]) - -# Setup -setup(name=NAME, - version=VERSION, - description=DESCRIPTION, - author=AUTHOR, - author_email=AUTHOR_EMAIL, - url=URL, - license=LICENSE, - platforms=["any"], - packages=packages, - cmdclass=doc_commands, - classifiers=[ - 'Development Status :: 3 - Alpha', - 'Environment :: Console', - 'Intended Audience :: Developers', - 'Intended Audience :: Telecommunications Industry', - 'License :: OSI Approved :: GNU General Public License (GPL)', - 'Operating System :: POSIX', - 'Operating System :: MacOS :: MacOS X', - 'Operating System :: Microsoft :: Windows', - 'Programming Language :: Python', - 'Topic :: Communications :: Chat', - 'Topic :: Communications :: Telephony', - 'Topic :: Internet', - 'Topic :: Software Development :: Libraries :: Python Modules' - ]) diff --git a/pymsn/test.py b/pymsn/test.py deleted file mode 100755 index ba5fffd1..00000000 --- a/pymsn/test.py +++ /dev/null @@ -1,145 +0,0 @@ -#!/usr/bin/env python - -import pymsn -import pymsn.event - - -import logging -import gobject - -logging.basicConfig(level=logging.DEBUG) - -finished = False - -def get_proxies(): - import urllib - proxies = urllib.getproxies() - result = {} - if 'https' not in proxies and \ - 'http' in proxies: - url = proxies['http'].replace("http://", "https://") - result['https'] = pymsn.Proxy(url) - for type, url in proxies.items(): - if type == 'no': continue - if type == 'https' and url.startswith('http://'): - url = url.replace('http://', 'https://', 1) - result[type] = pymsn.Proxy(url) - return result - - -class ClientEvents(pymsn.event.ClientEventInterface): - def on_client_state_changed(self, state): - if state == pymsn.event.ClientState.CLOSED: - self._client.quit() - elif state == pymsn.event.ClientState.OPEN: - self._client.profile.display_name = "Kimbix" - self._client.profile.presence = pymsn.Presence.ONLINE - self._client.profile.current_media = ("I listen to", "Nothing") - for contact in self._client.address_book.contacts: - print contact - #self._client.profile.personal_message = "Testing pymsn, and freeing the pandas!" - gobject.timeout_add(5000, self._client.start_conversation) - - def on_client_error(self, error_type, error): - print "ERROR :", error_type, " ->", error - -class AnnoyingConversation(pymsn.event.ConversationEventInterface): - def on_conversation_user_joined(self, contact): - gobject.timeout_add(5000, self.annoy_user) - - def annoy_user(self): - msg = "Let's free the pandas ! (testing pymsn)" - formatting = pymsn.TextFormat("Comic Sans MS", - pymsn.TextFormat.UNDERLINE | pymsn.TextFormat.BOLD, - 'FF0000') - self._client.send_text_message(pymsn.ConversationMessage(msg, formatting)) -# self._client.send_nudge() -# self._client.send_typing_notification() - return True - - def on_conversation_user_typing(self, contact): - pass - - def on_conversation_message_received(self, sender, message): - pass - - def on_conversation_error(self, error_type, error): - print "ERROR :", error_type, " ->", error - -class Client(pymsn.Client): - def __init__(self, account, quit, http_mode=False): - server = ('messenger.hotmail.com', 1863) - self.quit = quit - self.account = account - if http_mode: - from pymsn.transport import HTTPPollConnection - pymsn.Client.__init__(self, server, get_proxies(), HTTPPollConnection) - else: - pymsn.Client.__init__(self, server, proxies = get_proxies()) - self._event_handler = ClientEvents(self) - gobject.idle_add(self._connect) - - def _connect(self): - self.login(*self.account) - return False - - def start_conversation(self): - contacts = self.address_book.contacts.\ - search_by_presence(pymsn.Presence.ONLINE) - for c in self.address_book.contacts: - if c.account == "@hotmail.com": - print "Fetching space of : %s with cid %s\n" % (c.display_name, c.cid) - self.spaces_service.get_contact_card(c) - - if len(contacts) == 0: - print "No online contacts" - return True - else: - for contact in contacts: - if contact.account == "johann.prieur@gmail.com": - print "Inviting %s for a conversation" % contact.display_name - self.conv = pymsn.Conversation(self, [contact]) - self._convo_events = AnnoyingConversation(self.conv) - return False - -def main(): - import sys - import getpass - import signal - - if "--http" in sys.argv: - http_mode = True - sys.argv.remove('--http') - else: - http_mode = False - - if len(sys.argv) < 2: - account = raw_input('Account: ') - else: - account = sys.argv[1] - - if len(sys.argv) < 3: - passwd = getpass.getpass('Password: ') - else: - passwd = sys.argv[2] - - mainloop = gobject.MainLoop(is_running=True) - - def quit(): - mainloop.quit() - - def sigterm_cb(): - gobject.idle_add(quit) - - signal.signal(signal.SIGTERM, sigterm_cb) - - n = Client((account, passwd), quit, http_mode) - - while mainloop.is_running(): - try: - mainloop.run() - except KeyboardInterrupt: - quit() - -if __name__ == '__main__': - main()