Compare commits
	
		
			158 Commits
		
	
	
		
			sleek-1.0-
			...
			sleek-1.0-
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					d8d9e8df16 | ||
| 
						 | 
					58aa944a5e | ||
| 
						 | 
					dd41a85efc | ||
| 
						 | 
					e2d18170b0 | ||
| 
						 | 
					e219c0f976 | ||
| 
						 | 
					4266ee0fa4 | ||
| 
						 | 
					3a62908703 | ||
| 
						 | 
					1469323350 | ||
| 
						 | 
					a81162edd2 | ||
| 
						 | 
					8080b4cae2 | ||
| 
						 | 
					1735c194cd | ||
| 
						 | 
					6997b2fbf8 | ||
| 
						 | 
					b81ab97900 | ||
| 
						 | 
					384e1a92b7 | ||
| 
						 | 
					ec9aed5b75 | ||
| 
						 | 
					7152d93dd0 | ||
| 
						 | 
					4bb226147a | ||
| 
						 | 
					6b274a2543 | ||
| 
						 | 
					6a07e7cbe3 | ||
| 
						 | 
					9f1648328f | ||
| 
						 | 
					8e9b3d0760 | ||
| 
						 | 
					5399fdd3a9 | ||
| 
						 | 
					016aac69f6 | ||
| 
						 | 
					1d891858b6 | ||
| 
						 | 
					f02b0564e0 | ||
| 
						 | 
					2e1befc8c6 | ||
| 
						 | 
					87ccd804ff | ||
| 
						 | 
					d7ba7cc72a | ||
| 
						 | 
					d94811d81d | ||
| 
						 | 
					6d45971411 | ||
| 
						 | 
					84e2589f22 | ||
| 
						 | 
					a3d111be12 | ||
| 
						 | 
					4916a12b6f | ||
| 
						 | 
					d6f2e51b05 | ||
| 
						 | 
					feb7f892ea | ||
| 
						 | 
					a420771665 | ||
| 
						 | 
					f2449009d1 | ||
| 
						 | 
					833f95b53a | ||
| 
						 | 
					4b1fadde4b | ||
| 
						 | 
					86a6b40fd8 | ||
| 
						 | 
					7ef6abb2a3 | ||
| 
						 | 
					dbf6780345 | ||
| 
						 | 
					450c313340 | ||
| 
						 | 
					996ca52471 | ||
| 
						 | 
					6244857746 | ||
| 
						 | 
					5635265203 | ||
| 
						 | 
					45ccb31356 | ||
| 
						 | 
					1a81b2f464 | ||
| 
						 | 
					77251452c1 | ||
| 
						 | 
					4df3aa569b | ||
| 
						 | 
					2e2e16e281 | ||
| 
						 | 
					d709f8db65 | ||
| 
						 | 
					75584d7ad7 | ||
| 
						 | 
					e0f9025e7c | ||
| 
						 | 
					9004e8bbf2 | ||
| 
						 | 
					8b5511c7ec | ||
| 
						 | 
					34f6195ca5 | ||
| 
						 | 
					70af52d74c | ||
| 
						 | 
					ca2b4a188a | ||
| 
						 | 
					0d32638379 | ||
| 
						 | 
					c4b1212c44 | ||
| 
						 | 
					3463bf46c6 | ||
| 
						 | 
					13a01beb07 | ||
| 
						 | 
					145f577bde | ||
| 
						 | 
					30da68f47b | ||
| 
						 | 
					72ead3d598 | ||
| 
						 | 
					4b71fba64c | ||
| 
						 | 
					1ed06bebcd | ||
| 
						 | 
					aa1996eba6 | ||
| 
						 | 
					683f717cf7 | ||
| 
						 | 
					8dbe6f6546 | ||
| 
						 | 
					5313338c3a | ||
| 
						 | 
					cd800d636a | ||
| 
						 | 
					40642b2cd1 | ||
| 
						 | 
					35ef8f9090 | ||
| 
						 | 
					38dc35840e | ||
| 
						 | 
					b4004cd4d6 | ||
| 
						 | 
					0c8a8314b2 | ||
| 
						 | 
					4e757c2b56 | ||
| 
						 | 
					c3be6ea0b2 | ||
| 
						 | 
					da332365d4 | ||
| 
						 | 
					f7e7bf601e | ||
| 
						 | 
					6f4c2f22f3 | ||
| 
						 | 
					493df57035 | ||
| 
						 | 
					897a9ac333 | ||
| 
						 | 
					acc2d071ac | ||
| 
						 | 
					d3b1f8c476 | ||
| 
						 | 
					f1db2fc156 | ||
| 
						 | 
					2004ddd678 | ||
| 
						 | 
					cb85d4a529 | ||
| 
						 | 
					ead3af3135 | ||
| 
						 | 
					a2891d7608 | ||
| 
						 | 
					d7dea0c6cc | ||
| 
						 | 
					632827f213 | ||
| 
						 | 
					b71550cec7 | ||
| 
						 | 
					b68e7bed40 | ||
| 
						 | 
					4be6482ff3 | ||
| 
						 | 
					a21178007f | ||
| 
						 | 
					2e6c27f665 | ||
| 
						 | 
					0a3a7b5a70 | ||
| 
						 | 
					3a12cdbd13 | ||
| 
						 | 
					7d93d1824b | ||
| 
						 | 
					ba0d699d83 | ||
| 
						 | 
					c6ac40c476 | ||
| 
						 | 
					fe3f8dde4b | ||
| 
						 | 
					acdf9e2d22 | ||
| 
						 | 
					2076d506b4 | ||
| 
						 | 
					68ce47c905 | ||
| 
						 | 
					7c7fa0f008 | ||
| 
						 | 
					a8e3657487 | ||
| 
						 | 
					13a2f719f4 | ||
| 
						 | 
					2908751020 | ||
| 
						 | 
					8b29431cde | ||
| 
						 | 
					4b145958fa | ||
| 
						 | 
					e08b0054b2 | ||
| 
						 | 
					596e135a03 | ||
| 
						 | 
					e55e213c78 | ||
| 
						 | 
					8749f5e09b | ||
| 
						 | 
					b3353183f3 | ||
| 
						 | 
					f97f6e5985 | ||
| 
						 | 
					34c374a1e1 | ||
| 
						 | 
					506eccf84d | ||
| 
						 | 
					982bf3b2ec | ||
| 
						 | 
					53a5026301 | ||
| 
						 | 
					0aee445e69 | ||
| 
						 | 
					cbc42c29fb | ||
| 
						 | 
					874c51d74d | ||
| 
						 | 
					f9ac95ddb7 | ||
| 
						 | 
					0ea014fe41 | ||
| 
						 | 
					62b190d0ff | ||
| 
						 | 
					4b57b8131f | ||
| 
						 | 
					988a90a176 | ||
| 
						 | 
					67775fb8bd | ||
| 
						 | 
					e81683beee | ||
| 
						 | 
					d9c25ee65c | ||
| 
						 | 
					1ebc7f4d4b | ||
| 
						 | 
					2c5b77ae2e | ||
| 
						 | 
					d8aae88526 | ||
| 
						 | 
					2f4bdfee1b | ||
| 
						 | 
					f4451fe6b7 | ||
| 
						 | 
					8d4e77aba6 | ||
| 
						 | 
					f474d378ef | ||
| 
						 | 
					defc252c7d | ||
| 
						 | 
					19bd1e0485 | ||
| 
						 | 
					5f2fc67c40 | ||
| 
						 | 
					8ead33fc3b | ||
| 
						 | 
					ab25301953 | ||
| 
						 | 
					291b118aca | ||
| 
						 | 
					db7fb10e95 | ||
| 
						 | 
					60d3afe6b6 | ||
| 
						 | 
					afeb8f3f7c | ||
| 
						 | 
					cdbc0570ca | ||
| 
						 | 
					e648f08bad | ||
| 
						 | 
					7ba6d5e02d | ||
| 
						 | 
					ea48bb5ac5 | ||
| 
						 | 
					6ee8a2980c | ||
| 
						 | 
					b8114b25ed | ||
| 
						 | 
					45991e47ee | 
@@ -33,7 +33,7 @@ class testps(sleekxmpp.ClientXMPP):
 | 
			
		||||
		self.node = "pstestnode_%s"
 | 
			
		||||
		self.pshost = pshost
 | 
			
		||||
		if pshost is None:
 | 
			
		||||
			self.pshost = self.server
 | 
			
		||||
			self.pshost = self.boundjid.host
 | 
			
		||||
		self.nodenum = int(nodenum)
 | 
			
		||||
		self.leafnode = self.nodenum + 1
 | 
			
		||||
		self.collectnode = self.nodenum + 2
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										199
									
								
								examples/adhoc_provider.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										199
									
								
								examples/adhoc_provider.py
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,199 @@
 | 
			
		||||
#!/usr/bin/env python
 | 
			
		||||
# -*- coding: utf-8 -*-
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
    SleekXMPP: The Sleek XMPP Library
 | 
			
		||||
    Copyright (C) 2010  Nathanael C. Fritz
 | 
			
		||||
    This file is part of SleekXMPP.
 | 
			
		||||
 | 
			
		||||
    See the file LICENSE for copying permission.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
import sys
 | 
			
		||||
import logging
 | 
			
		||||
import time
 | 
			
		||||
import getpass
 | 
			
		||||
from optparse import OptionParser
 | 
			
		||||
 | 
			
		||||
import sleekxmpp
 | 
			
		||||
 | 
			
		||||
# Python versions before 3.0 do not use UTF-8 encoding
 | 
			
		||||
# by default. To ensure that Unicode is handled properly
 | 
			
		||||
# throughout SleekXMPP, we will set the default encoding
 | 
			
		||||
# ourselves to UTF-8.
 | 
			
		||||
if sys.version_info < (3, 0):
 | 
			
		||||
    reload(sys)
 | 
			
		||||
    sys.setdefaultencoding('utf8')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class CommandBot(sleekxmpp.ClientXMPP):
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    A simple SleekXMPP bot that provides a basic
 | 
			
		||||
    adhoc command.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    def __init__(self, jid, password):
 | 
			
		||||
        sleekxmpp.ClientXMPP.__init__(self, jid, password)
 | 
			
		||||
 | 
			
		||||
        # The session_start event will be triggered when
 | 
			
		||||
        # the bot establishes its connection with the server
 | 
			
		||||
        # and the XML streams are ready for use. We want to
 | 
			
		||||
        # listen for this event so that we we can intialize
 | 
			
		||||
        # our roster.
 | 
			
		||||
        self.add_event_handler("session_start", self.start)
 | 
			
		||||
 | 
			
		||||
    def start(self, event):
 | 
			
		||||
        """
 | 
			
		||||
        Process the session_start event.
 | 
			
		||||
 | 
			
		||||
        Typical actions for the session_start event are
 | 
			
		||||
        requesting the roster and broadcasting an intial
 | 
			
		||||
        presence stanza.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            event -- An empty dictionary. The session_start
 | 
			
		||||
                     event does not provide any additional
 | 
			
		||||
                     data.
 | 
			
		||||
        """
 | 
			
		||||
        self.send_presence()
 | 
			
		||||
        self.get_roster()
 | 
			
		||||
 | 
			
		||||
        # We add the command after session_start has fired
 | 
			
		||||
        # to ensure that the correct full JID is used.
 | 
			
		||||
 | 
			
		||||
        # If using a component, may also pass jid keyword parameter.
 | 
			
		||||
 | 
			
		||||
        self['xep_0050'].add_command(node='greeting',
 | 
			
		||||
                                     name='Greeting',
 | 
			
		||||
                                     handler=self._handle_command)
 | 
			
		||||
 | 
			
		||||
    def _handle_command(self, iq, session):
 | 
			
		||||
        """
 | 
			
		||||
        Respond to the intial request for a command.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            iq      -- The iq stanza containing the command request.
 | 
			
		||||
            session -- A dictionary of data relevant to the command
 | 
			
		||||
                       session. Additional, custom data may be saved
 | 
			
		||||
                       here to persist across handler callbacks.
 | 
			
		||||
        """
 | 
			
		||||
        form = self['xep_0004'].makeForm('form', 'Greeting')
 | 
			
		||||
        form.addField(var='greeting',
 | 
			
		||||
                      ftype='text-single',
 | 
			
		||||
                      label='Your greeting')
 | 
			
		||||
 | 
			
		||||
        session['payload'] = form
 | 
			
		||||
        session['next'] = self._handle_command_complete
 | 
			
		||||
        session['has_next'] = False
 | 
			
		||||
 | 
			
		||||
        # Other useful session values:
 | 
			
		||||
        # session['to']                    -- The JID that received the
 | 
			
		||||
        #                                     command request.
 | 
			
		||||
        # session['from']                  -- The JID that sent the
 | 
			
		||||
        #                                     command request.
 | 
			
		||||
        # session['has_next'] = True       -- There are more steps to complete
 | 
			
		||||
        # session['allow_complete'] = True -- Allow user to finish immediately
 | 
			
		||||
        #                                     and possibly skip steps
 | 
			
		||||
        # session['cancel'] = handler      -- Assign a handler for if the user
 | 
			
		||||
        #                                     cancels the command.
 | 
			
		||||
        # session['notes'] = [             -- Add informative notes about the
 | 
			
		||||
        #   ('info', 'Info message'),         command's results.
 | 
			
		||||
        #   ('warning', 'Warning message'),
 | 
			
		||||
        #   ('error', 'Error message')]
 | 
			
		||||
 | 
			
		||||
        return session
 | 
			
		||||
 | 
			
		||||
    def _handle_command_complete(self, payload, session):
 | 
			
		||||
        """
 | 
			
		||||
        Process a command result from the user.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            payload -- Either a single item, such as a form, or a list
 | 
			
		||||
                       of items or forms if more than one form was
 | 
			
		||||
                       provided to the user. The payload may be any
 | 
			
		||||
                       stanza, such as jabber:x:oob for out of band
 | 
			
		||||
                       data, or jabber:x:data for typical data forms.
 | 
			
		||||
            session -- A dictionary of data relevant to the command
 | 
			
		||||
                       session. Additional, custom data may be saved
 | 
			
		||||
                       here to persist across handler callbacks.
 | 
			
		||||
        """
 | 
			
		||||
 | 
			
		||||
        # In this case (as is typical), the payload is a form
 | 
			
		||||
        form = payload
 | 
			
		||||
 | 
			
		||||
        greeting = form['values']['greeting']
 | 
			
		||||
        self.send_message(mto=session['from'],
 | 
			
		||||
                          mbody="%s, World!" % greeting)
 | 
			
		||||
 | 
			
		||||
        # Having no return statement is the same as unsetting the 'payload'
 | 
			
		||||
        # and 'next' session values and returning the session.
 | 
			
		||||
 | 
			
		||||
        # Unless it is the final step, always return the session dictionary.
 | 
			
		||||
 | 
			
		||||
        session['payload'] = None
 | 
			
		||||
        session['next'] = None
 | 
			
		||||
 | 
			
		||||
        return session
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
if __name__ == '__main__':
 | 
			
		||||
    # Setup the command line arguments.
 | 
			
		||||
    optp = OptionParser()
 | 
			
		||||
 | 
			
		||||
    # Output verbosity options.
 | 
			
		||||
    optp.add_option('-q', '--quiet', help='set logging to ERROR',
 | 
			
		||||
                    action='store_const', dest='loglevel',
 | 
			
		||||
                    const=logging.ERROR, default=logging.INFO)
 | 
			
		||||
    optp.add_option('-d', '--debug', help='set logging to DEBUG',
 | 
			
		||||
                    action='store_const', dest='loglevel',
 | 
			
		||||
                    const=logging.DEBUG, default=logging.INFO)
 | 
			
		||||
    optp.add_option('-v', '--verbose', help='set logging to COMM',
 | 
			
		||||
                    action='store_const', dest='loglevel',
 | 
			
		||||
                    const=5, default=logging.INFO)
 | 
			
		||||
 | 
			
		||||
    # JID and password options.
 | 
			
		||||
    optp.add_option("-j", "--jid", dest="jid",
 | 
			
		||||
                    help="JID to use")
 | 
			
		||||
    optp.add_option("-p", "--password", dest="password",
 | 
			
		||||
                    help="password to use")
 | 
			
		||||
 | 
			
		||||
    opts, args = optp.parse_args()
 | 
			
		||||
 | 
			
		||||
    # Setup logging.
 | 
			
		||||
    logging.basicConfig(level=opts.loglevel,
 | 
			
		||||
                        format='%(levelname)-8s %(message)s')
 | 
			
		||||
 | 
			
		||||
    if opts.jid is None:
 | 
			
		||||
        opts.jid = raw_input("Username: ")
 | 
			
		||||
    if opts.password is None:
 | 
			
		||||
        opts.password = getpass.getpass("Password: ")
 | 
			
		||||
 | 
			
		||||
    # Setup the CommandBot and register plugins. Note that while plugins may
 | 
			
		||||
    # have interdependencies, the order in which you register them does
 | 
			
		||||
    # not matter.
 | 
			
		||||
    xmpp = CommandBot(opts.jid, opts.password)
 | 
			
		||||
    xmpp.register_plugin('xep_0030') # Service Discovery
 | 
			
		||||
    xmpp.register_plugin('xep_0004') # Data Forms
 | 
			
		||||
    xmpp.register_plugin('xep_0050') # Adhoc Commands
 | 
			
		||||
 | 
			
		||||
    # If you are working with an OpenFire server, you may need
 | 
			
		||||
    # to adjust the SSL version used:
 | 
			
		||||
    # xmpp.ssl_version = ssl.PROTOCOL_SSLv3
 | 
			
		||||
 | 
			
		||||
    # If you want to verify the SSL certificates offered by a server:
 | 
			
		||||
    # xmpp.ca_certs = "path/to/ca/cert"
 | 
			
		||||
 | 
			
		||||
    # Connect to the XMPP server and start processing XMPP stanzas.
 | 
			
		||||
    if xmpp.connect():
 | 
			
		||||
        # If you do not have the pydns library installed, you will need
 | 
			
		||||
        # to manually specify the name of the server if it does not match
 | 
			
		||||
        # the one in the JID. For example, to use Google Talk you would
 | 
			
		||||
        # need to use:
 | 
			
		||||
        #
 | 
			
		||||
        # if xmpp.connect(('talk.google.com', 5222)):
 | 
			
		||||
        #     ...
 | 
			
		||||
        xmpp.process(threaded=False)
 | 
			
		||||
        print("Done")
 | 
			
		||||
    else:
 | 
			
		||||
        print("Unable to connect.")
 | 
			
		||||
							
								
								
									
										208
									
								
								examples/adhoc_user.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										208
									
								
								examples/adhoc_user.py
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,208 @@
 | 
			
		||||
#!/usr/bin/env python
 | 
			
		||||
# -*- coding: utf-8 -*-
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
    SleekXMPP: The Sleek XMPP Library
 | 
			
		||||
    Copyright (C) 2010  Nathanael C. Fritz
 | 
			
		||||
    This file is part of SleekXMPP.
 | 
			
		||||
 | 
			
		||||
    See the file LICENSE for copying permission.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
import sys
 | 
			
		||||
import logging
 | 
			
		||||
import time
 | 
			
		||||
import getpass
 | 
			
		||||
from optparse import OptionParser
 | 
			
		||||
 | 
			
		||||
import sleekxmpp
 | 
			
		||||
 | 
			
		||||
# Python versions before 3.0 do not use UTF-8 encoding
 | 
			
		||||
# by default. To ensure that Unicode is handled properly
 | 
			
		||||
# throughout SleekXMPP, we will set the default encoding
 | 
			
		||||
# ourselves to UTF-8.
 | 
			
		||||
if sys.version_info < (3, 0):
 | 
			
		||||
    reload(sys)
 | 
			
		||||
    sys.setdefaultencoding('utf8')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class CommandUserBot(sleekxmpp.ClientXMPP):
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    A simple SleekXMPP bot that uses the adhoc command
 | 
			
		||||
    provided by the adhoc_provider.py example.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    def __init__(self, jid, password, other, greeting):
 | 
			
		||||
        sleekxmpp.ClientXMPP.__init__(self, jid, password)
 | 
			
		||||
 | 
			
		||||
        self.command_provider = other
 | 
			
		||||
        self.greeting = greeting
 | 
			
		||||
 | 
			
		||||
        # The session_start event will be triggered when
 | 
			
		||||
        # the bot establishes its connection with the server
 | 
			
		||||
        # and the XML streams are ready for use. We want to
 | 
			
		||||
        # listen for this event so that we we can intialize
 | 
			
		||||
        # our roster.
 | 
			
		||||
        self.add_event_handler("session_start", self.start)
 | 
			
		||||
        self.add_event_handler("message", self.message)
 | 
			
		||||
 | 
			
		||||
    def start(self, event):
 | 
			
		||||
        """
 | 
			
		||||
        Process the session_start event.
 | 
			
		||||
 | 
			
		||||
        Typical actions for the session_start event are
 | 
			
		||||
        requesting the roster and broadcasting an intial
 | 
			
		||||
        presence stanza.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            event -- An empty dictionary. The session_start
 | 
			
		||||
                     event does not provide any additional
 | 
			
		||||
                     data.
 | 
			
		||||
        """
 | 
			
		||||
        self.send_presence()
 | 
			
		||||
        self.get_roster()
 | 
			
		||||
 | 
			
		||||
        # We first create a session dictionary containing:
 | 
			
		||||
        #   'next'  -- the handler to execute on a successful response
 | 
			
		||||
        #   'error' -- the handler to execute if an error occurs
 | 
			
		||||
 | 
			
		||||
        # The session may also contain custom data.
 | 
			
		||||
 | 
			
		||||
        session = {'greeting': self.greeting,
 | 
			
		||||
                   'next': self._command_start,
 | 
			
		||||
                   'error': self._command_error}
 | 
			
		||||
 | 
			
		||||
        self['xep_0050'].start_command(jid=self.command_provider,
 | 
			
		||||
                                       node='greeting',
 | 
			
		||||
                                       session=session)
 | 
			
		||||
 | 
			
		||||
    def message(self, msg):
 | 
			
		||||
        """
 | 
			
		||||
        Process incoming message stanzas.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            msg -- The received message stanza.
 | 
			
		||||
        """
 | 
			
		||||
        logging.info(msg['body'])
 | 
			
		||||
 | 
			
		||||
    def _command_start(self, iq, session):
 | 
			
		||||
        """
 | 
			
		||||
        Process the initial command result.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            iq      -- The iq stanza containing the command result.
 | 
			
		||||
            session -- A dictionary of data relevant to the command
 | 
			
		||||
                       session. Additional, custom data may be saved
 | 
			
		||||
                       here to persist across handler callbacks.
 | 
			
		||||
        """
 | 
			
		||||
 | 
			
		||||
        # The greeting command provides a form with a single field:
 | 
			
		||||
        # <x xmlns="jabber:x:data" type="form">
 | 
			
		||||
        #   <field var="greeting"
 | 
			
		||||
        #          type="text-single"
 | 
			
		||||
        #          label="Your greeting" />
 | 
			
		||||
        # </x>
 | 
			
		||||
 | 
			
		||||
        form = self['xep_0004'].makeForm(ftype='submit')
 | 
			
		||||
        form.addField(var='greeting',
 | 
			
		||||
                      value=session['greeting'])
 | 
			
		||||
 | 
			
		||||
        session['payload'] = form
 | 
			
		||||
 | 
			
		||||
        # We don't need to process the next result.
 | 
			
		||||
        session['next'] = None
 | 
			
		||||
 | 
			
		||||
        # Other options include using:
 | 
			
		||||
        # continue_command() -- Continue to the next step in the workflow
 | 
			
		||||
        # cancel_command()   -- Stop command execution.
 | 
			
		||||
 | 
			
		||||
        self['xep_0050'].complete_command(session)
 | 
			
		||||
 | 
			
		||||
    def _command_error(self, iq, session):
 | 
			
		||||
        """
 | 
			
		||||
        Process an error that occurs during command execution.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            iq      -- The iq stanza containing the error.
 | 
			
		||||
            session -- A dictionary of data relevant to the command
 | 
			
		||||
                       session. Additional, custom data may be saved
 | 
			
		||||
                       here to persist across handler callbacks.
 | 
			
		||||
        """
 | 
			
		||||
        logging.error("COMMAND: %s %s" % (iq['error']['condition'],
 | 
			
		||||
                                          iq['error']['text']))
 | 
			
		||||
 | 
			
		||||
        # Terminate the command's execution and clear its session.
 | 
			
		||||
        # The session will automatically be cleared if no error
 | 
			
		||||
        # handler is provided.
 | 
			
		||||
        self['xep_0050'].terminate_command(session)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
if __name__ == '__main__':
 | 
			
		||||
    # Setup the command line arguments.
 | 
			
		||||
    optp = OptionParser()
 | 
			
		||||
 | 
			
		||||
    # Output verbosity options.
 | 
			
		||||
    optp.add_option('-q', '--quiet', help='set logging to ERROR',
 | 
			
		||||
                    action='store_const', dest='loglevel',
 | 
			
		||||
                    const=logging.ERROR, default=logging.INFO)
 | 
			
		||||
    optp.add_option('-d', '--debug', help='set logging to DEBUG',
 | 
			
		||||
                    action='store_const', dest='loglevel',
 | 
			
		||||
                    const=logging.DEBUG, default=logging.INFO)
 | 
			
		||||
    optp.add_option('-v', '--verbose', help='set logging to COMM',
 | 
			
		||||
                    action='store_const', dest='loglevel',
 | 
			
		||||
                    const=5, default=logging.INFO)
 | 
			
		||||
 | 
			
		||||
    # JID and password options.
 | 
			
		||||
    optp.add_option("-j", "--jid", dest="jid",
 | 
			
		||||
                    help="JID to use")
 | 
			
		||||
    optp.add_option("-p", "--password", dest="password",
 | 
			
		||||
                    help="password to use")
 | 
			
		||||
    optp.add_option("-o", "--other", dest="other",
 | 
			
		||||
                    help="JID providing commands")
 | 
			
		||||
    optp.add_option("-g", "--greeting", dest="greeting",
 | 
			
		||||
                    help="Greeting")
 | 
			
		||||
 | 
			
		||||
    opts, args = optp.parse_args()
 | 
			
		||||
 | 
			
		||||
    # Setup logging.
 | 
			
		||||
    logging.basicConfig(level=opts.loglevel,
 | 
			
		||||
                        format='%(levelname)-8s %(message)s')
 | 
			
		||||
 | 
			
		||||
    if opts.jid is None:
 | 
			
		||||
        opts.jid = raw_input("Username: ")
 | 
			
		||||
    if opts.password is None:
 | 
			
		||||
        opts.password = getpass.getpass("Password: ")
 | 
			
		||||
    if opts.other is None:
 | 
			
		||||
        opts.other = raw_input("JID Providing Commands: ")
 | 
			
		||||
    if opts.greeting is None:
 | 
			
		||||
        opts.other = raw_input("Greeting: ")
 | 
			
		||||
 | 
			
		||||
    # Setup the CommandBot and register plugins. Note that while plugins may
 | 
			
		||||
    # have interdependencies, the order in which you register them does
 | 
			
		||||
    # not matter.
 | 
			
		||||
    xmpp = CommandUserBot(opts.jid, opts.password, opts.other, opts.greeting)
 | 
			
		||||
    xmpp.register_plugin('xep_0030') # Service Discovery
 | 
			
		||||
    xmpp.register_plugin('xep_0004') # Data Forms
 | 
			
		||||
    xmpp.register_plugin('xep_0050') # Adhoc Commands
 | 
			
		||||
 | 
			
		||||
    # If you are working with an OpenFire server, you may need
 | 
			
		||||
    # to adjust the SSL version used:
 | 
			
		||||
    # xmpp.ssl_version = ssl.PROTOCOL_SSLv3
 | 
			
		||||
 | 
			
		||||
    # If you want to verify the SSL certificates offered by a server:
 | 
			
		||||
    # xmpp.ca_certs = "path/to/ca/cert"
 | 
			
		||||
 | 
			
		||||
    # Connect to the XMPP server and start processing XMPP stanzas.
 | 
			
		||||
    if xmpp.connect():
 | 
			
		||||
        # If you do not have the pydns library installed, you will need
 | 
			
		||||
        # to manually specify the name of the server if it does not match
 | 
			
		||||
        # the one in the JID. For example, to use Google Talk you would
 | 
			
		||||
        # need to use:
 | 
			
		||||
        #
 | 
			
		||||
        # if xmpp.connect(('talk.google.com', 5222)):
 | 
			
		||||
        #     ...
 | 
			
		||||
        xmpp.process(threaded=False)
 | 
			
		||||
        print("Done")
 | 
			
		||||
    else:
 | 
			
		||||
        print("Unable to connect.")
 | 
			
		||||
							
								
								
									
										198
									
								
								examples/disco_browser.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										198
									
								
								examples/disco_browser.py
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,198 @@
 | 
			
		||||
#!/usr/bin/env python
 | 
			
		||||
# -*- coding: utf-8 -*-
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
    SleekXMPP: The Sleek XMPP Library
 | 
			
		||||
    Copyright (C) 2010  Nathanael C. Fritz
 | 
			
		||||
    This file is part of SleekXMPP.
 | 
			
		||||
 | 
			
		||||
    See the file LICENSE for copying permission.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
import sys
 | 
			
		||||
import time
 | 
			
		||||
import logging
 | 
			
		||||
import getpass
 | 
			
		||||
from optparse import OptionParser
 | 
			
		||||
 | 
			
		||||
import sleekxmpp
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# Python versions before 3.0 do not use UTF-8 encoding
 | 
			
		||||
# by default. To ensure that Unicode is handled properly
 | 
			
		||||
# throughout SleekXMPP, we will set the default encoding
 | 
			
		||||
# ourselves to UTF-8.
 | 
			
		||||
if sys.version_info < (3, 0):
 | 
			
		||||
    reload(sys)
 | 
			
		||||
    sys.setdefaultencoding('utf8')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Disco(sleekxmpp.ClientXMPP):
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    A demonstration for using basic service discovery.
 | 
			
		||||
 | 
			
		||||
    Send a disco#info and disco#items request to a JID/node combination,
 | 
			
		||||
    and print out the results.
 | 
			
		||||
 | 
			
		||||
    May also request only particular info categories such as just features,
 | 
			
		||||
    or just items.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    def __init__(self, jid, password, target_jid, target_node='', get=''):
 | 
			
		||||
        sleekxmpp.ClientXMPP.__init__(self, jid, password)
 | 
			
		||||
 | 
			
		||||
        # Using service discovery requires the XEP-0030 plugin.
 | 
			
		||||
        self.register_plugin('xep_0030')
 | 
			
		||||
 | 
			
		||||
        self.get = get
 | 
			
		||||
        self.target_jid = target_jid
 | 
			
		||||
        self.target_node = target_node
 | 
			
		||||
 | 
			
		||||
        # Values to control which disco entities are reported
 | 
			
		||||
        self.info_types = ['', 'all', 'info', 'identities', 'features']
 | 
			
		||||
        self.identity_types = ['', 'all', 'info', 'identities']
 | 
			
		||||
        self.feature_types = ['', 'all', 'info', 'features']
 | 
			
		||||
        self.items_types = ['', 'all', 'items']
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        # The session_start event will be triggered when
 | 
			
		||||
        # the bot establishes its connection with the server
 | 
			
		||||
        # and the XML streams are ready for use. We want to
 | 
			
		||||
        # listen for this event so that we we can intialize
 | 
			
		||||
        # our roster.
 | 
			
		||||
        self.add_event_handler("session_start", self.start)
 | 
			
		||||
 | 
			
		||||
    def start(self, event):
 | 
			
		||||
        """
 | 
			
		||||
        Process the session_start event.
 | 
			
		||||
 | 
			
		||||
        Typical actions for the session_start event are
 | 
			
		||||
        requesting the roster and broadcasting an intial
 | 
			
		||||
        presence stanza.
 | 
			
		||||
 | 
			
		||||
        In this case, we send disco#info and disco#items
 | 
			
		||||
        stanzas to the requested JID and print the results.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            event -- An empty dictionary. The session_start
 | 
			
		||||
                     event does not provide any additional
 | 
			
		||||
                     data.
 | 
			
		||||
        """
 | 
			
		||||
        self.get_roster()
 | 
			
		||||
        self.send_presence()
 | 
			
		||||
 | 
			
		||||
        if self.get in self.info_types:
 | 
			
		||||
            # By using block=True, the result stanza will be
 | 
			
		||||
            # returned. Execution will block until the reply is
 | 
			
		||||
            # received. Non-blocking options would be to listen
 | 
			
		||||
            # for the disco_info event, or passing a handler
 | 
			
		||||
            # function using the callback parameter.
 | 
			
		||||
            info = self['xep_0030'].get_info(jid=self.target_jid,
 | 
			
		||||
                                             node=self.target_node,
 | 
			
		||||
                                             block=True)
 | 
			
		||||
        if self.get in self.items_types:
 | 
			
		||||
            # The same applies from above. Listen for the
 | 
			
		||||
            # disco_items event or pass a callback function
 | 
			
		||||
            # if you need to process a non-blocking request.
 | 
			
		||||
            items = self['xep_0030'].get_items(jid=self.target_jid,
 | 
			
		||||
                                               node=self.target_node,
 | 
			
		||||
                                               block=True)
 | 
			
		||||
        else:
 | 
			
		||||
            logging.error("Invalid disco request type.")
 | 
			
		||||
            self.disconnect()
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
        header = 'XMPP Service Discovery: %s' % self.target_jid
 | 
			
		||||
        print(header)
 | 
			
		||||
        print('-' * len(header))
 | 
			
		||||
        if self.target_node != '':
 | 
			
		||||
            print('Node: %s' % self.target_node)
 | 
			
		||||
            print('-' * len(header))
 | 
			
		||||
 | 
			
		||||
        if self.get in self.identity_types:
 | 
			
		||||
            print('Identities:')
 | 
			
		||||
            for identity in info['disco_info']['identities']:
 | 
			
		||||
                print('  - %s' % str(identity))
 | 
			
		||||
 | 
			
		||||
        if self.get in self.feature_types:
 | 
			
		||||
            print('Features:')
 | 
			
		||||
            for feature in info['disco_info']['features']:
 | 
			
		||||
                print('  - %s' % feature)
 | 
			
		||||
 | 
			
		||||
        if self.get in self.items_types:
 | 
			
		||||
            print('Items:')
 | 
			
		||||
            for item in items['disco_items']['items']:
 | 
			
		||||
                print('  - %s' % str(item))
 | 
			
		||||
 | 
			
		||||
        self.disconnect()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
if __name__ == '__main__':
 | 
			
		||||
    # Setup the command line arguments.
 | 
			
		||||
    optp = OptionParser()
 | 
			
		||||
    optp.version = '%%prog 0.1'
 | 
			
		||||
    optp.usage = "Usage: %%prog [options] %s <jid> [<node>]" % \
 | 
			
		||||
                             'all|info|items|identities|features'
 | 
			
		||||
 | 
			
		||||
    optp.add_option('-q','--quiet', help='set logging to ERROR',
 | 
			
		||||
                    action='store_const',
 | 
			
		||||
                    dest='loglevel',
 | 
			
		||||
                    const=logging.ERROR,
 | 
			
		||||
                    default=logging.ERROR)
 | 
			
		||||
    optp.add_option('-d','--debug', help='set logging to DEBUG',
 | 
			
		||||
                    action='store_const',
 | 
			
		||||
                    dest='loglevel',
 | 
			
		||||
                    const=logging.DEBUG,
 | 
			
		||||
                    default=logging.ERROR)
 | 
			
		||||
    optp.add_option('-v','--verbose', help='set logging to COMM',
 | 
			
		||||
                    action='store_const',
 | 
			
		||||
                    dest='loglevel',
 | 
			
		||||
                    const=5,
 | 
			
		||||
                    default=logging.ERROR)
 | 
			
		||||
 | 
			
		||||
    # JID and password options.
 | 
			
		||||
    optp.add_option("-j", "--jid", dest="jid",
 | 
			
		||||
                    help="JID to use")
 | 
			
		||||
    optp.add_option("-p", "--password", dest="password",
 | 
			
		||||
                    help="password to use")
 | 
			
		||||
    opts,args = optp.parse_args()
 | 
			
		||||
 | 
			
		||||
    # Setup logging.
 | 
			
		||||
    logging.basicConfig(level=opts.loglevel,
 | 
			
		||||
                        format='%(levelname)-8s %(message)s')
 | 
			
		||||
 | 
			
		||||
    if len(args) < 2:
 | 
			
		||||
        optp.print_help()
 | 
			
		||||
        exit()
 | 
			
		||||
 | 
			
		||||
    if len(args) == 2:
 | 
			
		||||
        args = (args[0], args[1], '')
 | 
			
		||||
 | 
			
		||||
    if opts.jid is None:
 | 
			
		||||
        opts.jid = raw_input("Username: ")
 | 
			
		||||
    if opts.password is None:
 | 
			
		||||
        opts.password = getpass.getpass("Password: ")
 | 
			
		||||
 | 
			
		||||
    # Setup the Disco browser.
 | 
			
		||||
    xmpp = Disco(opts.jid, opts.password, args[1], args[2], args[0])
 | 
			
		||||
 | 
			
		||||
    # If you are working with an OpenFire server, you may need
 | 
			
		||||
    # to adjust the SSL version used:
 | 
			
		||||
    # xmpp.ssl_version = ssl.PROTOCOL_SSLv3
 | 
			
		||||
 | 
			
		||||
    # If you want to verify the SSL certificates offered by a server:
 | 
			
		||||
    # xmpp.ca_certs = "path/to/ca/cert"
 | 
			
		||||
 | 
			
		||||
    # Connect to the XMPP server and start processing XMPP stanzas.
 | 
			
		||||
    if xmpp.connect():
 | 
			
		||||
        # If you do not have the pydns library installed, you will need
 | 
			
		||||
        # to manually specify the name of the server if it does not match
 | 
			
		||||
        # the one in the JID. For example, to use Google Talk you would
 | 
			
		||||
        # need to use:
 | 
			
		||||
        #
 | 
			
		||||
        # if xmpp.connect(('talk.google.com', 5222)):
 | 
			
		||||
        #     ...
 | 
			
		||||
        xmpp.process(threaded=False)
 | 
			
		||||
    else:
 | 
			
		||||
        print("Unable to connect.")
 | 
			
		||||
@@ -12,6 +12,7 @@
 | 
			
		||||
import sys
 | 
			
		||||
import logging
 | 
			
		||||
import time
 | 
			
		||||
import getpass
 | 
			
		||||
from optparse import OptionParser
 | 
			
		||||
 | 
			
		||||
import sleekxmpp
 | 
			
		||||
@@ -60,8 +61,8 @@ class EchoBot(sleekxmpp.ClientXMPP):
 | 
			
		||||
                     event does not provide any additional
 | 
			
		||||
                     data.
 | 
			
		||||
        """
 | 
			
		||||
        self.getRoster()
 | 
			
		||||
        self.sendPresence()
 | 
			
		||||
        self.send_presence()
 | 
			
		||||
        self.get_roster()
 | 
			
		||||
 | 
			
		||||
    def message(self, msg):
 | 
			
		||||
        """
 | 
			
		||||
@@ -105,14 +106,26 @@ if __name__ == '__main__':
 | 
			
		||||
    logging.basicConfig(level=opts.loglevel,
 | 
			
		||||
                        format='%(levelname)-8s %(message)s')
 | 
			
		||||
 | 
			
		||||
    if opts.jid is None:
 | 
			
		||||
        opts.jid = raw_input("Username: ")
 | 
			
		||||
    if opts.password is None:
 | 
			
		||||
        opts.password = getpass.getpass("Password: ")
 | 
			
		||||
 | 
			
		||||
    # Setup the EchoBot and register plugins. Note that while plugins may
 | 
			
		||||
    # have interdependencies, the order in which you register them does
 | 
			
		||||
    # not matter.
 | 
			
		||||
    xmpp = EchoBot(opts.jid, opts.password)
 | 
			
		||||
    xmpp.registerPlugin('xep_0030') # Service Discovery
 | 
			
		||||
    xmpp.registerPlugin('xep_0004') # Data Forms
 | 
			
		||||
    xmpp.registerPlugin('xep_0060') # PubSub
 | 
			
		||||
    xmpp.registerPlugin('xep_0199') # XMPP Ping
 | 
			
		||||
    xmpp.register_plugin('xep_0030') # Service Discovery
 | 
			
		||||
    xmpp.register_plugin('xep_0004') # Data Forms
 | 
			
		||||
    xmpp.register_plugin('xep_0060') # PubSub
 | 
			
		||||
    xmpp.register_plugin('xep_0199') # XMPP Ping
 | 
			
		||||
 | 
			
		||||
    # If you are working with an OpenFire server, you may need
 | 
			
		||||
    # to adjust the SSL version used:
 | 
			
		||||
    # xmpp.ssl_version = ssl.PROTOCOL_SSLv3
 | 
			
		||||
 | 
			
		||||
    # If you want to verify the SSL certificates offered by a server:
 | 
			
		||||
    # xmpp.ca_certs = "path/to/ca/cert"
 | 
			
		||||
 | 
			
		||||
    # Connect to the XMPP server and start processing XMPP stanzas.
 | 
			
		||||
    if xmpp.connect():
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										186
									
								
								examples/muc.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										186
									
								
								examples/muc.py
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,186 @@
 | 
			
		||||
#!/usr/bin/env python
 | 
			
		||||
# -*- coding: utf-8 -*-
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
    SleekXMPP: The Sleek XMPP Library
 | 
			
		||||
    Copyright (C) 2010  Nathanael C. Fritz
 | 
			
		||||
    This file is part of SleekXMPP.
 | 
			
		||||
 | 
			
		||||
    See the file LICENSE for copying permission.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
import sys
 | 
			
		||||
import logging
 | 
			
		||||
import time
 | 
			
		||||
from optparse import OptionParser
 | 
			
		||||
 | 
			
		||||
import sleekxmpp
 | 
			
		||||
 | 
			
		||||
# Python versions before 3.0 do not use UTF-8 encoding
 | 
			
		||||
# by default. To ensure that Unicode is handled properly
 | 
			
		||||
# throughout SleekXMPP, we will set the default encoding
 | 
			
		||||
# ourselves to UTF-8.
 | 
			
		||||
if sys.version_info < (3, 0):
 | 
			
		||||
    reload(sys)
 | 
			
		||||
    sys.setdefaultencoding('utf8')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class MUCBot(sleekxmpp.ClientXMPP):
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    A simple SleekXMPP bot that will greets those
 | 
			
		||||
    who enter the room, and acknowledge any messages
 | 
			
		||||
    that mentions the bot's nickname.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    def __init__(self, jid, password, room, nick):
 | 
			
		||||
        sleekxmpp.ClientXMPP.__init__(self, jid, password)
 | 
			
		||||
 | 
			
		||||
        self.room = room
 | 
			
		||||
        self.nick = nick
 | 
			
		||||
 | 
			
		||||
        # The session_start event will be triggered when
 | 
			
		||||
        # the bot establishes its connection with the server
 | 
			
		||||
        # and the XML streams are ready for use. We want to
 | 
			
		||||
        # listen for this event so that we we can intialize
 | 
			
		||||
        # our roster.
 | 
			
		||||
        self.add_event_handler("session_start", self.start)
 | 
			
		||||
 | 
			
		||||
        # The groupchat_message event is triggered whenever a message
 | 
			
		||||
        # stanza is received from any chat room. If you also also
 | 
			
		||||
        # register a handler for the 'message' event, MUC messages
 | 
			
		||||
        # will be processed by both handlers.
 | 
			
		||||
        self.add_event_handler("groupchat_message", self.muc_message)
 | 
			
		||||
 | 
			
		||||
        # The groupchat_presence event is triggered whenever a
 | 
			
		||||
        # presence stanza is received from any chat room, including
 | 
			
		||||
        # any presences you send yourself. To limit event handling
 | 
			
		||||
        # to a single room, use the events muc::room@server::presence,
 | 
			
		||||
        # muc::room@server::got_online, or muc::room@server::got_offline.
 | 
			
		||||
        self.add_event_handler("muc::%s::got_online" % self.room,
 | 
			
		||||
                               self.muc_online)
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
    def start(self, event):
 | 
			
		||||
        """
 | 
			
		||||
        Process the session_start event.
 | 
			
		||||
 | 
			
		||||
        Typical actions for the session_start event are
 | 
			
		||||
        requesting the roster and broadcasting an intial
 | 
			
		||||
        presence stanza.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            event -- An empty dictionary. The session_start
 | 
			
		||||
                     event does not provide any additional
 | 
			
		||||
                     data.
 | 
			
		||||
        """
 | 
			
		||||
        self.getRoster()
 | 
			
		||||
        self.sendPresence()
 | 
			
		||||
        self.plugin['xep_0045'].joinMUC(self.room, 
 | 
			
		||||
                                        self.nick, 
 | 
			
		||||
                                        # If a room password is needed, use:
 | 
			
		||||
                                        # password=the_room_password,
 | 
			
		||||
                                        wait=True)
 | 
			
		||||
 | 
			
		||||
    def muc_message(self, msg):
 | 
			
		||||
        """
 | 
			
		||||
        Process incoming message stanzas from any chat room. Be aware 
 | 
			
		||||
        that if you also have any handlers for the 'message' event,
 | 
			
		||||
        message stanzas may be processed by both handlers, so check
 | 
			
		||||
        the 'type' attribute when using a 'message' event handler.
 | 
			
		||||
 | 
			
		||||
        Whenever the bot's nickname is mentioned, respond to
 | 
			
		||||
        the message.
 | 
			
		||||
 | 
			
		||||
        IMPORTANT: Always check that a message is not from yourself,
 | 
			
		||||
                   otherwise you will create an infinite loop responding
 | 
			
		||||
                   to your own messages.
 | 
			
		||||
 | 
			
		||||
        This handler will reply to messages that mention 
 | 
			
		||||
        the bot's nickname.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            msg -- The received message stanza. See the documentation
 | 
			
		||||
                   for stanza objects and the Message stanza to see
 | 
			
		||||
                   how it may be used.
 | 
			
		||||
        """
 | 
			
		||||
        if msg['mucnick'] != self.nick and self.nick in msg['body']:
 | 
			
		||||
            self.send_message(mto=msg['from'].bare,
 | 
			
		||||
                              mbody="I heard that, %s." % msg['mucnick'],
 | 
			
		||||
                              mtype='groupchat')
 | 
			
		||||
 | 
			
		||||
    def muc_online(self, presence):
 | 
			
		||||
        """
 | 
			
		||||
        Process a presence stanza from a chat room. In this case,
 | 
			
		||||
        presences from users that have just come online are 
 | 
			
		||||
        handled by sending a welcome message that includes
 | 
			
		||||
        the user's nickname and role in the room.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            presence -- The received presence stanza. See the 
 | 
			
		||||
                        documentation for the Presence stanza
 | 
			
		||||
                        to see how else it may be used.
 | 
			
		||||
        """
 | 
			
		||||
        if presence['muc']['nick'] != self.nick:
 | 
			
		||||
            self.send_message(mto=presence['from'].bare,
 | 
			
		||||
                              mbody="Hello, %s %s" % (presence['muc']['role'],
 | 
			
		||||
                                                      presence['muc']['nick']),
 | 
			
		||||
                              mtype='groupchat')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
if __name__ == '__main__':
 | 
			
		||||
    # Setup the command line arguments.
 | 
			
		||||
    optp = OptionParser()
 | 
			
		||||
 | 
			
		||||
    # Output verbosity options.
 | 
			
		||||
    optp.add_option('-q', '--quiet', help='set logging to ERROR',
 | 
			
		||||
                    action='store_const', dest='loglevel',
 | 
			
		||||
                    const=logging.ERROR, default=logging.INFO)
 | 
			
		||||
    optp.add_option('-d', '--debug', help='set logging to DEBUG',
 | 
			
		||||
                    action='store_const', dest='loglevel',
 | 
			
		||||
                    const=logging.DEBUG, default=logging.INFO)
 | 
			
		||||
    optp.add_option('-v', '--verbose', help='set logging to COMM',
 | 
			
		||||
                    action='store_const', dest='loglevel',
 | 
			
		||||
                    const=5, default=logging.INFO)
 | 
			
		||||
 | 
			
		||||
    # JID and password options.
 | 
			
		||||
    optp.add_option("-j", "--jid", dest="jid",
 | 
			
		||||
                    help="JID to use")
 | 
			
		||||
    optp.add_option("-p", "--password", dest="password",
 | 
			
		||||
                    help="password to use")
 | 
			
		||||
    optp.add_option("-r", "--room", dest="room",
 | 
			
		||||
                    help="MUC room to join")
 | 
			
		||||
    optp.add_option("-n", "--nick", dest="nick",
 | 
			
		||||
                    help="MUC nickname")
 | 
			
		||||
 | 
			
		||||
    opts, args = optp.parse_args()
 | 
			
		||||
 | 
			
		||||
    # Setup logging.
 | 
			
		||||
    logging.basicConfig(level=opts.loglevel,
 | 
			
		||||
                        format='%(levelname)-8s %(message)s')
 | 
			
		||||
 | 
			
		||||
    if None in [opts.jid, opts.password, opts.room, opts.nick]:
 | 
			
		||||
        optp.print_help()
 | 
			
		||||
        sys.exit(1)
 | 
			
		||||
 | 
			
		||||
    # Setup the MUCBot and register plugins. Note that while plugins may
 | 
			
		||||
    # have interdependencies, the order in which you register them does
 | 
			
		||||
    # not matter.
 | 
			
		||||
    xmpp = MUCBot(opts.jid, opts.password, opts.room, opts.nick)
 | 
			
		||||
    xmpp.register_plugin('xep_0030') # Service Discovery
 | 
			
		||||
    xmpp.register_plugin('xep_0045') # Multi-User Chat
 | 
			
		||||
    xmpp.register_plugin('xep_0199') # XMPP Ping
 | 
			
		||||
 | 
			
		||||
    # Connect to the XMPP server and start processing XMPP stanzas.
 | 
			
		||||
    if xmpp.connect():
 | 
			
		||||
        # If you do not have the pydns library installed, you will need
 | 
			
		||||
        # to manually specify the name of the server if it does not match
 | 
			
		||||
        # the one in the JID. For example, to use Google Talk you would
 | 
			
		||||
        # need to use:
 | 
			
		||||
        #
 | 
			
		||||
        # if xmpp.connect(('talk.google.com', 5222)):
 | 
			
		||||
        #     ...
 | 
			
		||||
        xmpp.process(threaded=False)
 | 
			
		||||
        print("Done")
 | 
			
		||||
    else:
 | 
			
		||||
        print("Unable to connect.")
 | 
			
		||||
							
								
								
									
										140
									
								
								examples/ping.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										140
									
								
								examples/ping.py
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,140 @@
 | 
			
		||||
#!/usr/bin/env python
 | 
			
		||||
# -*- coding: utf-8 -*-
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
    SleekXMPP: The Sleek XMPP Library
 | 
			
		||||
    Copyright (C) 2010  Nathanael C. Fritz
 | 
			
		||||
    This file is part of SleekXMPP.
 | 
			
		||||
 | 
			
		||||
    See the file LICENSE for copying permission.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
import sys
 | 
			
		||||
import logging
 | 
			
		||||
import time
 | 
			
		||||
import getpass
 | 
			
		||||
from optparse import OptionParser
 | 
			
		||||
 | 
			
		||||
import sleekxmpp
 | 
			
		||||
 | 
			
		||||
# Python versions before 3.0 do not use UTF-8 encoding
 | 
			
		||||
# by default. To ensure that Unicode is handled properly
 | 
			
		||||
# throughout SleekXMPP, we will set the default encoding
 | 
			
		||||
# ourselves to UTF-8.
 | 
			
		||||
if sys.version_info < (3, 0):
 | 
			
		||||
    reload(sys)
 | 
			
		||||
    sys.setdefaultencoding('utf8')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class PingTest(sleekxmpp.ClientXMPP):
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    A simple SleekXMPP bot that will send a ping request
 | 
			
		||||
    to a given JID.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    def __init__(self, jid, password, pingjid):
 | 
			
		||||
        sleekxmpp.ClientXMPP.__init__(self, jid, password)
 | 
			
		||||
        if pingjid is None:
 | 
			
		||||
            pingjid = self.jid
 | 
			
		||||
        self.pingjid = pingjid
 | 
			
		||||
 | 
			
		||||
        # The session_start event will be triggered when
 | 
			
		||||
        # the bot establishes its connection with the server
 | 
			
		||||
        # and the XML streams are ready for use. We want to
 | 
			
		||||
        # listen for this event so that we we can intialize
 | 
			
		||||
        # our roster.
 | 
			
		||||
        self.add_event_handler("session_start", self.start)
 | 
			
		||||
 | 
			
		||||
    def start(self, event):
 | 
			
		||||
        """
 | 
			
		||||
        Process the session_start event.
 | 
			
		||||
 | 
			
		||||
        Typical actions for the session_start event are
 | 
			
		||||
        requesting the roster and broadcasting an intial
 | 
			
		||||
        presence stanza.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            event -- An empty dictionary. The session_start
 | 
			
		||||
                     event does not provide any additional
 | 
			
		||||
                     data.
 | 
			
		||||
        """
 | 
			
		||||
        self.send_presence()
 | 
			
		||||
        self.get_roster()
 | 
			
		||||
        result = self['xep_0199'].send_ping(self.pingjid,
 | 
			
		||||
                                            timeout=10,
 | 
			
		||||
                                            errorfalse=True)
 | 
			
		||||
        logging.info("Pinging...")
 | 
			
		||||
        if result is False:
 | 
			
		||||
            logging.info("Couldn't ping.")
 | 
			
		||||
            self.disconnect()
 | 
			
		||||
            sys.exit(1)
 | 
			
		||||
        else:
 | 
			
		||||
            logging.info("Success! RTT: %s" % str(result))
 | 
			
		||||
            self.disconnect()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
if __name__ == '__main__':
 | 
			
		||||
    # Setup the command line arguments.
 | 
			
		||||
    optp = OptionParser()
 | 
			
		||||
 | 
			
		||||
    # Output verbosity options.
 | 
			
		||||
    optp.add_option('-q', '--quiet', help='set logging to ERROR',
 | 
			
		||||
                    action='store_const', dest='loglevel',
 | 
			
		||||
                    const=logging.ERROR, default=logging.INFO)
 | 
			
		||||
    optp.add_option('-d', '--debug', help='set logging to DEBUG',
 | 
			
		||||
                    action='store_const', dest='loglevel',
 | 
			
		||||
                    const=logging.DEBUG, default=logging.INFO)
 | 
			
		||||
    optp.add_option('-v', '--verbose', help='set logging to COMM',
 | 
			
		||||
                    action='store_const', dest='loglevel',
 | 
			
		||||
                    const=5, default=logging.INFO)
 | 
			
		||||
    optp.add_option('-t', '--pingto', help='set jid to ping',
 | 
			
		||||
                    action='store', type='string', dest='pingjid',
 | 
			
		||||
                    default=None)
 | 
			
		||||
 | 
			
		||||
    # JID and password options.
 | 
			
		||||
    optp.add_option("-j", "--jid", dest="jid",
 | 
			
		||||
                    help="JID to use")
 | 
			
		||||
    optp.add_option("-p", "--password", dest="password",
 | 
			
		||||
                    help="password to use")
 | 
			
		||||
 | 
			
		||||
    opts, args = optp.parse_args()
 | 
			
		||||
 | 
			
		||||
    # Setup logging.
 | 
			
		||||
    logging.basicConfig(level=opts.loglevel,
 | 
			
		||||
                        format='%(levelname)-8s %(message)s')
 | 
			
		||||
 | 
			
		||||
    if opts.jid is None:
 | 
			
		||||
        opts.jid = raw_input("Username: ")
 | 
			
		||||
    if opts.password is None:
 | 
			
		||||
        opts.password = getpass.getpass("Password: ")
 | 
			
		||||
 | 
			
		||||
    # Setup the PingTest and register plugins. Note that while plugins may
 | 
			
		||||
    # have interdependencies, the order in which you register them does
 | 
			
		||||
    # not matter.
 | 
			
		||||
    xmpp = PingTest(opts.jid, opts.password, opts.pingjid)
 | 
			
		||||
    xmpp.register_plugin('xep_0030')  # Service Discovery
 | 
			
		||||
    xmpp.register_plugin('xep_0004')  # Data Forms
 | 
			
		||||
    xmpp.register_plugin('xep_0060')  # PubSub
 | 
			
		||||
    xmpp.register_plugin('xep_0199')  # XMPP Ping
 | 
			
		||||
 | 
			
		||||
    # If you are working with an OpenFire server, you may need
 | 
			
		||||
    # to adjust the SSL version used:
 | 
			
		||||
    # xmpp.ssl_version = ssl.PROTOCOL_SSLv3
 | 
			
		||||
 | 
			
		||||
    # If you want to verify the SSL certificates offered by a server:
 | 
			
		||||
    # xmpp.ca_certs = "path/to/ca/cert"
 | 
			
		||||
 | 
			
		||||
    # Connect to the XMPP server and start processing XMPP stanzas.
 | 
			
		||||
    if xmpp.connect():
 | 
			
		||||
        # If you do not have the pydns library installed, you will need
 | 
			
		||||
        # to manually specify the name of the server if it does not match
 | 
			
		||||
        # the one in the JID. For example, to use Google Talk you would
 | 
			
		||||
        # need to use:
 | 
			
		||||
        #
 | 
			
		||||
        # if xmpp.connect(('talk.google.com', 5222)):
 | 
			
		||||
        #     ...
 | 
			
		||||
        xmpp.process(threaded=False)
 | 
			
		||||
        print("Done")
 | 
			
		||||
    else:
 | 
			
		||||
        print("Unable to connect.")
 | 
			
		||||
							
								
								
									
										44
									
								
								examples/rpc_async.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								examples/rpc_async.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,44 @@
 | 
			
		||||
"""
 | 
			
		||||
    SleekXMPP: The Sleek XMPP Library
 | 
			
		||||
    Copyright (C) 2011  Dann Martens
 | 
			
		||||
    This file is part of SleekXMPP.
 | 
			
		||||
 | 
			
		||||
    See the file LICENSE for copying permission.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
from sleekxmpp.plugins.xep_0009.remote import Endpoint, remote, Remote, \
 | 
			
		||||
    ANY_ALL, Future
 | 
			
		||||
import time
 | 
			
		||||
 | 
			
		||||
class Boomerang(Endpoint):
 | 
			
		||||
    
 | 
			
		||||
    def FQN(self):
 | 
			
		||||
        return 'boomerang'
 | 
			
		||||
          
 | 
			
		||||
    @remote
 | 
			
		||||
    def throw(self):
 | 
			
		||||
        print "Duck!"
 | 
			
		||||
            
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def main():
 | 
			
		||||
 | 
			
		||||
    session = Remote.new_session('kangaroo@xmpp.org/rpc', '*****')
 | 
			
		||||
 | 
			
		||||
    session.new_handler(ANY_ALL, Boomerang)    
 | 
			
		||||
    
 | 
			
		||||
    boomerang = session.new_proxy('kangaroo@xmpp.org/rpc', Boomerang)
 | 
			
		||||
        
 | 
			
		||||
    callback = Future()
 | 
			
		||||
        
 | 
			
		||||
    boomerang.async(callback).throw()
 | 
			
		||||
    
 | 
			
		||||
    time.sleep(10)
 | 
			
		||||
    
 | 
			
		||||
    session.close()
 | 
			
		||||
    
 | 
			
		||||
    
 | 
			
		||||
    
 | 
			
		||||
if __name__ == '__main__':
 | 
			
		||||
    main()
 | 
			
		||||
    
 | 
			
		||||
							
								
								
									
										53
									
								
								examples/rpc_client_side.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								examples/rpc_client_side.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,53 @@
 | 
			
		||||
"""
 | 
			
		||||
    SleekXMPP: The Sleek XMPP Library
 | 
			
		||||
    Copyright (C) 2011  Dann Martens
 | 
			
		||||
    This file is part of SleekXMPP.
 | 
			
		||||
 | 
			
		||||
    See the file LICENSE for copying permission.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
from sleekxmpp.plugins.xep_0009.remote import Endpoint, remote, Remote, \
 | 
			
		||||
    ANY_ALL
 | 
			
		||||
import threading
 | 
			
		||||
import time
 | 
			
		||||
 | 
			
		||||
class Thermostat(Endpoint):
 | 
			
		||||
    
 | 
			
		||||
    def FQN(self):
 | 
			
		||||
        return 'thermostat'
 | 
			
		||||
    
 | 
			
		||||
    def __init(self, initial_temperature):
 | 
			
		||||
        self._temperature = initial_temperature
 | 
			
		||||
        self._event = threading.Event()        
 | 
			
		||||
    
 | 
			
		||||
    @remote
 | 
			
		||||
    def set_temperature(self, temperature):
 | 
			
		||||
        return NotImplemented
 | 
			
		||||
    
 | 
			
		||||
    @remote
 | 
			
		||||
    def get_temperature(self):
 | 
			
		||||
        return NotImplemented
 | 
			
		||||
 | 
			
		||||
    @remote(False)
 | 
			
		||||
    def release(self):
 | 
			
		||||
        return NotImplemented
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def main():
 | 
			
		||||
 | 
			
		||||
    session = Remote.new_session('operator@xmpp.org/rpc', '*****')
 | 
			
		||||
    
 | 
			
		||||
    thermostat = session.new_proxy('thermostat@xmpp.org/rpc', Thermostat)
 | 
			
		||||
    
 | 
			
		||||
    print("Current temperature is %s" % thermostat.get_temperature())
 | 
			
		||||
    
 | 
			
		||||
    thermostat.set_temperature(20)
 | 
			
		||||
    
 | 
			
		||||
    time.sleep(10)
 | 
			
		||||
    
 | 
			
		||||
    session.close()
 | 
			
		||||
    
 | 
			
		||||
if __name__ == '__main__':
 | 
			
		||||
    main()
 | 
			
		||||
    
 | 
			
		||||
							
								
								
									
										52
									
								
								examples/rpc_server_side.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								examples/rpc_server_side.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,52 @@
 | 
			
		||||
"""
 | 
			
		||||
    SleekXMPP: The Sleek XMPP Library
 | 
			
		||||
    Copyright (C) 2011  Dann Martens
 | 
			
		||||
    This file is part of SleekXMPP.
 | 
			
		||||
 | 
			
		||||
    See the file LICENSE for copying permission.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
from sleekxmpp.plugins.xep_0009.remote import Endpoint, remote, Remote, \
 | 
			
		||||
    ANY_ALL
 | 
			
		||||
import threading
 | 
			
		||||
 | 
			
		||||
class Thermostat(Endpoint):
 | 
			
		||||
    
 | 
			
		||||
    def FQN(self):
 | 
			
		||||
        return 'thermostat'
 | 
			
		||||
    
 | 
			
		||||
    def __init(self, initial_temperature):
 | 
			
		||||
        self._temperature = initial_temperature
 | 
			
		||||
        self._event = threading.Event()        
 | 
			
		||||
    
 | 
			
		||||
    @remote
 | 
			
		||||
    def set_temperature(self, temperature):
 | 
			
		||||
        print("Setting temperature to %s" % temperature)
 | 
			
		||||
        self._temperature = temperature
 | 
			
		||||
    
 | 
			
		||||
    @remote
 | 
			
		||||
    def get_temperature(self):
 | 
			
		||||
        return self._temperature
 | 
			
		||||
 | 
			
		||||
    @remote(False)
 | 
			
		||||
    def release(self):
 | 
			
		||||
        self._event.set() 
 | 
			
		||||
        
 | 
			
		||||
    def wait_for_release(self):
 | 
			
		||||
        self._event.wait()        
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def main():
 | 
			
		||||
 | 
			
		||||
    session = Remote.new_session('sleek@xmpp.org/rpc', '*****')
 | 
			
		||||
    
 | 
			
		||||
    thermostat = session.new_handler(ANY_ALL, Thermostat, 18)
 | 
			
		||||
    
 | 
			
		||||
    thermostat.wait_for_release()
 | 
			
		||||
    
 | 
			
		||||
    session.close()
 | 
			
		||||
    
 | 
			
		||||
if __name__ == '__main__':
 | 
			
		||||
    main()
 | 
			
		||||
    
 | 
			
		||||
							
								
								
									
										17
									
								
								setup.py
									
									
									
									
									
								
							
							
						
						
									
										17
									
								
								setup.py
									
									
									
									
									
								
							@@ -12,6 +12,8 @@
 | 
			
		||||
from distutils.core import setup
 | 
			
		||||
import sys
 | 
			
		||||
 | 
			
		||||
import sleekxmpp
 | 
			
		||||
 | 
			
		||||
# if 'cygwin' in sys.platform.lower():
 | 
			
		||||
#     min_version = '0.6c6'
 | 
			
		||||
# else:
 | 
			
		||||
@@ -25,7 +27,7 @@ import sys
 | 
			
		||||
#
 | 
			
		||||
# from setuptools import setup, find_packages, Extension, Feature
 | 
			
		||||
 | 
			
		||||
VERSION          = '1.0.0.0'
 | 
			
		||||
VERSION          = sleekxmpp.__version__
 | 
			
		||||
DESCRIPTION      = 'SleekXMPP is an elegant Python library for XMPP (aka Jabber, Google Talk, etc).'
 | 
			
		||||
LONG_DESCRIPTION = """
 | 
			
		||||
SleekXMPP is an elegant Python library for XMPP (aka Jabber, Google Talk, etc).
 | 
			
		||||
@@ -38,13 +40,24 @@ CLASSIFIERS      = [ 'Intended Audience :: Developers',
 | 
			
		||||
                   ]
 | 
			
		||||
 | 
			
		||||
packages     = [ 'sleekxmpp',
 | 
			
		||||
                 'sleekxmpp/plugins',
 | 
			
		||||
                 'sleekxmpp/stanza',
 | 
			
		||||
                 'sleekxmpp/test',
 | 
			
		||||
                 'sleekxmpp/xmlstream',
 | 
			
		||||
                 'sleekxmpp/xmlstream/matcher',
 | 
			
		||||
                 'sleekxmpp/xmlstream/handler',
 | 
			
		||||
                 'sleekxmpp/thirdparty',
 | 
			
		||||
                 'sleekxmpp/plugins',
 | 
			
		||||
                 'sleekxmpp/plugins/xep_0009',
 | 
			
		||||
                 'sleekxmpp/plugins/xep_0009/stanza',
 | 
			
		||||
                 'sleekxmpp/plugins/xep_0030',
 | 
			
		||||
                 'sleekxmpp/plugins/xep_0030/stanza',
 | 
			
		||||
                 'sleekxmpp/plugins/xep_0050',
 | 
			
		||||
                 'sleekxmpp/plugins/xep_0059',
 | 
			
		||||
                 'sleekxmpp/plugins/xep_0085',
 | 
			
		||||
                 'sleekxmpp/plugins/xep_0086',
 | 
			
		||||
                 'sleekxmpp/plugins/xep_0092',
 | 
			
		||||
                 'sleekxmpp/plugins/xep_0128',
 | 
			
		||||
                 'sleekxmpp/plugins/xep_0199',
 | 
			
		||||
                 ]
 | 
			
		||||
 | 
			
		||||
if sys.version_info < (3, 0):
 | 
			
		||||
 
 | 
			
		||||
@@ -14,3 +14,6 @@ from sleekxmpp.xmlstream.handler import *
 | 
			
		||||
from sleekxmpp.xmlstream import XMLStream, RestartStream
 | 
			
		||||
from sleekxmpp.xmlstream.matcher import *
 | 
			
		||||
from sleekxmpp.xmlstream.stanzabase import StanzaBase, ET
 | 
			
		||||
 | 
			
		||||
__version__ = '1.0beta5'
 | 
			
		||||
__version_info__ = (1, 0, 0, 'beta5', 0)
 | 
			
		||||
 
 | 
			
		||||
@@ -15,7 +15,7 @@ import logging
 | 
			
		||||
import sleekxmpp
 | 
			
		||||
from sleekxmpp import plugins
 | 
			
		||||
 | 
			
		||||
from sleekxmpp.stanza import Message, Presence, Iq, Error
 | 
			
		||||
from sleekxmpp.stanza import Message, Presence, Iq, Error, StreamError
 | 
			
		||||
from sleekxmpp.stanza.roster import Roster
 | 
			
		||||
from sleekxmpp.stanza.nick import Nick
 | 
			
		||||
from sleekxmpp.stanza.htmlim import HTMLIM
 | 
			
		||||
@@ -90,26 +90,14 @@ class BaseXMPP(XMLStream):
 | 
			
		||||
 | 
			
		||||
        # To comply with PEP8, method names now use underscores.
 | 
			
		||||
        # Deprecated method names are re-mapped for backwards compatibility.
 | 
			
		||||
        self.registerPlugin = self.register_plugin
 | 
			
		||||
        self.makeIq = self.make_iq
 | 
			
		||||
        self.makeIqGet = self.make_iq_get
 | 
			
		||||
        self.makeIqResult = self.make_iq_result
 | 
			
		||||
        self.makeIqSet = self.make_iq_set
 | 
			
		||||
        self.makeIqError = self.make_iq_error
 | 
			
		||||
        self.makeIqQuery = self.make_iq_query
 | 
			
		||||
        self.makeQueryRoster = self.make_query_roster
 | 
			
		||||
        self.makeMessage = self.make_message
 | 
			
		||||
        self.makePresence = self.make_presence
 | 
			
		||||
        self.sendMessage = self.send_message
 | 
			
		||||
        self.sendPresence = self.send_presence
 | 
			
		||||
        self.sendPresenceSubscription = self.send_presence_subscription
 | 
			
		||||
 | 
			
		||||
        self.default_ns = default_ns
 | 
			
		||||
        self.stream_ns = 'http://etherx.jabber.org/streams'
 | 
			
		||||
 | 
			
		||||
        self.boundjid = JID("")
 | 
			
		||||
 | 
			
		||||
        self.plugin = {}
 | 
			
		||||
        self.plugin_config = {}
 | 
			
		||||
        self.plugin_whitelist = []
 | 
			
		||||
        self.roster = {}
 | 
			
		||||
        self.is_component = False
 | 
			
		||||
        self.auto_authorize = True
 | 
			
		||||
@@ -126,6 +114,10 @@ class BaseXMPP(XMLStream):
 | 
			
		||||
            Callback('Presence',
 | 
			
		||||
                     MatchXPath("{%s}presence" % self.default_ns),
 | 
			
		||||
                     self._handle_presence))
 | 
			
		||||
        self.register_handler(
 | 
			
		||||
            Callback('Stream Error',
 | 
			
		||||
                     MatchXPath("{%s}error" % self.stream_ns),
 | 
			
		||||
                     self._handle_stream_error))
 | 
			
		||||
 | 
			
		||||
        self.add_event_handler('presence_subscribe',
 | 
			
		||||
                               self._handle_subscribe)
 | 
			
		||||
@@ -133,9 +125,10 @@ class BaseXMPP(XMLStream):
 | 
			
		||||
                               self._handle_disconnected)
 | 
			
		||||
 | 
			
		||||
        # Set up the XML stream with XMPP's root stanzas.
 | 
			
		||||
        self.registerStanza(Message)
 | 
			
		||||
        self.registerStanza(Iq)
 | 
			
		||||
        self.registerStanza(Presence)
 | 
			
		||||
        self.register_stanza(Message)
 | 
			
		||||
        self.register_stanza(Iq)
 | 
			
		||||
        self.register_stanza(Presence)
 | 
			
		||||
        self.register_stanza(StreamError)
 | 
			
		||||
 | 
			
		||||
        # Initialize a few default stanza plugins.
 | 
			
		||||
        register_stanza_plugin(Iq, Roster)
 | 
			
		||||
@@ -243,7 +236,7 @@ class BaseXMPP(XMLStream):
 | 
			
		||||
        """Create a Presence stanza associated with this stream."""
 | 
			
		||||
        return Presence(self, *args, **kwargs)
 | 
			
		||||
 | 
			
		||||
    def make_iq(self, id=0, ifrom=None):
 | 
			
		||||
    def make_iq(self, id=0, ifrom=None, ito=None, itype=None, iquery=None):
 | 
			
		||||
        """
 | 
			
		||||
        Create a new Iq stanza with a given Id and from JID.
 | 
			
		||||
 | 
			
		||||
@@ -251,11 +244,19 @@ class BaseXMPP(XMLStream):
 | 
			
		||||
            id     -- An ideally unique ID value for this stanza thread.
 | 
			
		||||
                      Defaults to 0.
 | 
			
		||||
            ifrom  -- The from JID to use for this stanza.
 | 
			
		||||
            ito    -- The destination JID for this stanza.
 | 
			
		||||
            itype  -- The Iq's type, one of: get, set, result, or error.
 | 
			
		||||
            iquery -- Optional namespace for adding a query element.
 | 
			
		||||
        """
 | 
			
		||||
        return self.Iq()._set_stanza_values({'id': str(id),
 | 
			
		||||
                                             'from': ifrom})
 | 
			
		||||
        iq = self.Iq()
 | 
			
		||||
        iq['id'] = str(id)
 | 
			
		||||
        iq['to'] = ito
 | 
			
		||||
        iq['from'] = ifrom
 | 
			
		||||
        iq['type'] = itype
 | 
			
		||||
        iq['query'] = iquery
 | 
			
		||||
        return iq
 | 
			
		||||
 | 
			
		||||
    def make_iq_get(self, queryxmlns=None):
 | 
			
		||||
    def make_iq_get(self, queryxmlns=None, ito=None, ifrom=None, iq=None):
 | 
			
		||||
        """
 | 
			
		||||
        Create an Iq stanza of type 'get'.
 | 
			
		||||
 | 
			
		||||
@@ -263,21 +264,45 @@ class BaseXMPP(XMLStream):
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            queryxmlns -- The namespace of the query to use.
 | 
			
		||||
            ito        -- The destination JID for this stanza.
 | 
			
		||||
            ifrom      -- The from JID to use for this stanza.
 | 
			
		||||
            iq         -- Optionally use an existing stanza instead
 | 
			
		||||
                          of generating a new one.
 | 
			
		||||
        """
 | 
			
		||||
        return self.Iq()._set_stanza_values({'type': 'get',
 | 
			
		||||
                                             'query': queryxmlns})
 | 
			
		||||
        if not iq:
 | 
			
		||||
            iq = self.Iq()
 | 
			
		||||
        iq['type'] = 'get'
 | 
			
		||||
        iq['query'] = queryxmlns
 | 
			
		||||
        if ito:
 | 
			
		||||
            iq['to'] = ito
 | 
			
		||||
        if ifrom:
 | 
			
		||||
            iq['from'] = ifrom
 | 
			
		||||
        return iq
 | 
			
		||||
 | 
			
		||||
    def make_iq_result(self, id):
 | 
			
		||||
    def make_iq_result(self, id=None, ito=None, ifrom=None, iq=None):
 | 
			
		||||
        """
 | 
			
		||||
        Create an Iq stanza of type 'result' with the given ID value.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            id    -- An ideally unique ID value. May use self.new_id().
 | 
			
		||||
            ito   -- The destination JID for this stanza.
 | 
			
		||||
            ifrom -- The from JID to use for this stanza.
 | 
			
		||||
            iq    -- Optionally use an existing stanza instead
 | 
			
		||||
                     of generating a new one.
 | 
			
		||||
        """
 | 
			
		||||
        return self.Iq()._set_stanza_values({'id': id,
 | 
			
		||||
                                             'type': 'result'})
 | 
			
		||||
        if not iq:
 | 
			
		||||
            iq = self.Iq()
 | 
			
		||||
            if id is None:
 | 
			
		||||
                id = self.new_id()
 | 
			
		||||
            iq['id'] = id
 | 
			
		||||
        iq['type'] = 'result'
 | 
			
		||||
        if ito:
 | 
			
		||||
            iq['to'] = ito
 | 
			
		||||
        if ifrom:
 | 
			
		||||
            iq['from'] = ifrom
 | 
			
		||||
        return iq
 | 
			
		||||
 | 
			
		||||
    def make_iq_set(self, sub=None):
 | 
			
		||||
    def make_iq_set(self, sub=None, ito=None, ifrom=None, iq=None):
 | 
			
		||||
        """
 | 
			
		||||
        Create an Iq stanza of type 'set'.
 | 
			
		||||
 | 
			
		||||
@@ -286,14 +311,25 @@ class BaseXMPP(XMLStream):
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            sub   -- A stanza or XML object to use as the Iq's payload.
 | 
			
		||||
            ito   -- The destination JID for this stanza.
 | 
			
		||||
            ifrom -- The from JID to use for this stanza.
 | 
			
		||||
            iq    -- Optionally use an existing stanza instead
 | 
			
		||||
                     of generating a new one.
 | 
			
		||||
        """
 | 
			
		||||
        iq = self.Iq()._set_stanza_values({'type': 'set'})
 | 
			
		||||
        if not iq:
 | 
			
		||||
            iq = self.Iq()
 | 
			
		||||
        iq['type'] = 'set'
 | 
			
		||||
        if sub != None:
 | 
			
		||||
            iq.append(sub)
 | 
			
		||||
        if ito:
 | 
			
		||||
            iq['to'] = ito
 | 
			
		||||
        if ifrom:
 | 
			
		||||
            iq['from'] = ifrom
 | 
			
		||||
        return iq
 | 
			
		||||
 | 
			
		||||
    def make_iq_error(self, id, type='cancel',
 | 
			
		||||
                      condition='feature-not-implemented', text=None):
 | 
			
		||||
                      condition='feature-not-implemented',
 | 
			
		||||
                      text=None, ito=None, ifrom=None, iq=None):
 | 
			
		||||
        """
 | 
			
		||||
        Create an Iq stanza of type 'error'.
 | 
			
		||||
 | 
			
		||||
@@ -304,14 +340,24 @@ class BaseXMPP(XMLStream):
 | 
			
		||||
            condition -- The error condition.
 | 
			
		||||
                         Defaults to 'feature-not-implemented'.
 | 
			
		||||
            text      -- A message describing the cause of the error.
 | 
			
		||||
            ito       -- The destination JID for this stanza.
 | 
			
		||||
            ifrom     -- The from JID to use for this stanza.
 | 
			
		||||
            iq        -- Optionally use an existing stanza instead
 | 
			
		||||
                         of generating a new one.
 | 
			
		||||
        """
 | 
			
		||||
        iq = self.Iq()._set_stanza_values({'id': id})
 | 
			
		||||
        iq['error']._set_stanza_values({'type': type,
 | 
			
		||||
                                        'condition': condition,
 | 
			
		||||
                                        'text': text})
 | 
			
		||||
        if not iq:
 | 
			
		||||
            iq = self.Iq()
 | 
			
		||||
        iq['id'] = id
 | 
			
		||||
        iq['error']['type'] = type
 | 
			
		||||
        iq['error']['condition'] = condition
 | 
			
		||||
        iq['error']['text'] = text
 | 
			
		||||
        if ito:
 | 
			
		||||
            iq['to'] = ito
 | 
			
		||||
        if ifrom:
 | 
			
		||||
            iq['from'] = ifrom
 | 
			
		||||
        return iq
 | 
			
		||||
 | 
			
		||||
    def make_iq_query(self, iq=None, xmlns=''):
 | 
			
		||||
    def make_iq_query(self, iq=None, xmlns='', ito=None, ifrom=None):
 | 
			
		||||
        """
 | 
			
		||||
        Create or modify an Iq stanza to use the given
 | 
			
		||||
        query namespace.
 | 
			
		||||
@@ -320,10 +366,16 @@ class BaseXMPP(XMLStream):
 | 
			
		||||
            iq    -- Optional Iq stanza to modify. A new
 | 
			
		||||
                     stanza is created otherwise.
 | 
			
		||||
            xmlns -- The query's namespace.
 | 
			
		||||
            ito   -- The destination JID for this stanza.
 | 
			
		||||
            ifrom -- The from JID to use for this stanza.
 | 
			
		||||
        """
 | 
			
		||||
        if not iq:
 | 
			
		||||
            iq = self.Iq()
 | 
			
		||||
        iq['query'] = xmlns
 | 
			
		||||
        if ito:
 | 
			
		||||
            iq['to'] = ito
 | 
			
		||||
        if ifrom:
 | 
			
		||||
            iq['from'] = ifrom
 | 
			
		||||
        return iq
 | 
			
		||||
 | 
			
		||||
    def make_query_roster(self, iq=None):
 | 
			
		||||
@@ -518,6 +570,9 @@ class BaseXMPP(XMLStream):
 | 
			
		||||
        """When disconnected, reset the roster"""
 | 
			
		||||
        self.roster = {}
 | 
			
		||||
 | 
			
		||||
    def _handle_stream_error(self, error):
 | 
			
		||||
        self.event('stream_error', error)
 | 
			
		||||
 | 
			
		||||
    def _handle_message(self, msg):
 | 
			
		||||
        """Process incoming message stanzas."""
 | 
			
		||||
        self.event('message', msg)
 | 
			
		||||
@@ -559,7 +614,7 @@ class BaseXMPP(XMLStream):
 | 
			
		||||
                                'in_roster': False}
 | 
			
		||||
 | 
			
		||||
        # Alias to simplify some references.
 | 
			
		||||
        connections = self.roster[jid]['presence']
 | 
			
		||||
        connections = self.roster[jid].get('presence', {})
 | 
			
		||||
 | 
			
		||||
        # Determine if the user has just come online.
 | 
			
		||||
        if not resource in connections:
 | 
			
		||||
@@ -585,7 +640,8 @@ class BaseXMPP(XMLStream):
 | 
			
		||||
            log.debug("%s %s got offline" % (jid, resource))
 | 
			
		||||
            del connections[resource]
 | 
			
		||||
 | 
			
		||||
            if not connections and not self.roster[jid]['in_roster']:
 | 
			
		||||
            if not connections and \
 | 
			
		||||
               not self.roster[jid].get('in_roster', False):
 | 
			
		||||
                del self.roster[jid]
 | 
			
		||||
            if not was_offline:
 | 
			
		||||
                self.event("got_offline", presence)
 | 
			
		||||
@@ -632,3 +688,19 @@ class BaseXMPP(XMLStream):
 | 
			
		||||
 | 
			
		||||
# Restore the old, lowercased name for backwards compatibility.
 | 
			
		||||
basexmpp = BaseXMPP
 | 
			
		||||
 | 
			
		||||
# To comply with PEP8, method names now use underscores.
 | 
			
		||||
# Deprecated method names are re-mapped for backwards compatibility.
 | 
			
		||||
BaseXMPP.registerPlugin = BaseXMPP.register_plugin
 | 
			
		||||
BaseXMPP.makeIq = BaseXMPP.make_iq
 | 
			
		||||
BaseXMPP.makeIqGet = BaseXMPP.make_iq_get
 | 
			
		||||
BaseXMPP.makeIqResult = BaseXMPP.make_iq_result
 | 
			
		||||
BaseXMPP.makeIqSet = BaseXMPP.make_iq_set
 | 
			
		||||
BaseXMPP.makeIqError = BaseXMPP.make_iq_error
 | 
			
		||||
BaseXMPP.makeIqQuery = BaseXMPP.make_iq_query
 | 
			
		||||
BaseXMPP.makeQueryRoster = BaseXMPP.make_query_roster
 | 
			
		||||
BaseXMPP.makeMessage = BaseXMPP.make_message
 | 
			
		||||
BaseXMPP.makePresence = BaseXMPP.make_presence
 | 
			
		||||
BaseXMPP.sendMessage = BaseXMPP.send_message
 | 
			
		||||
BaseXMPP.sendPresence = BaseXMPP.send_presence
 | 
			
		||||
BaseXMPP.sendPresenceSubscription = BaseXMPP.send_presence_subscription
 | 
			
		||||
 
 | 
			
		||||
@@ -68,13 +68,6 @@ class ClientXMPP(BaseXMPP):
 | 
			
		||||
        """
 | 
			
		||||
        BaseXMPP.__init__(self, 'jabber:client')
 | 
			
		||||
 | 
			
		||||
        # To comply with PEP8, method names now use underscores.
 | 
			
		||||
        # Deprecated method names are re-mapped for backwards compatibility.
 | 
			
		||||
        self.updateRoster = self.update_roster
 | 
			
		||||
        self.delRosterItem = self.del_roster_item
 | 
			
		||||
        self.getRoster = self.get_roster
 | 
			
		||||
        self.registerFeature = self.register_feature
 | 
			
		||||
 | 
			
		||||
        self.set_jid(jid)
 | 
			
		||||
        self.password = password
 | 
			
		||||
        self.escape_quotes = escape_quotes
 | 
			
		||||
@@ -82,9 +75,6 @@ class ClientXMPP(BaseXMPP):
 | 
			
		||||
        self.plugin_whitelist = plugin_whitelist
 | 
			
		||||
        self.srv_support = SRV_SUPPORT
 | 
			
		||||
 | 
			
		||||
        self.session_started_event = threading.Event()
 | 
			
		||||
        self.session_started_event.clear()
 | 
			
		||||
 | 
			
		||||
        self.stream_header = "<stream:stream to='%s' %s %s version='1.0'>" % (
 | 
			
		||||
                self.boundjid.host,
 | 
			
		||||
                "xmlns:stream='%s'" % self.stream_ns,
 | 
			
		||||
@@ -139,7 +129,7 @@ class ClientXMPP(BaseXMPP):
 | 
			
		||||
            log.debug("Session start has taken more than 15 seconds")
 | 
			
		||||
            self.disconnect(reconnect=self.auto_reconnect)
 | 
			
		||||
 | 
			
		||||
    def connect(self, address=tuple()):
 | 
			
		||||
    def connect(self, address=tuple(), reattempt=True, use_tls=True):
 | 
			
		||||
        """
 | 
			
		||||
        Connect to the XMPP server.
 | 
			
		||||
 | 
			
		||||
@@ -149,6 +139,10 @@ class ClientXMPP(BaseXMPP):
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            address   -- A tuple containing the server's host and port.
 | 
			
		||||
            reattempt -- If True, reattempt the connection if an
 | 
			
		||||
                         error occurs. Defaults to True.
 | 
			
		||||
            use_tls   -- Indicates if TLS should be used for the
 | 
			
		||||
                         connection. Defaults to True.
 | 
			
		||||
        """
 | 
			
		||||
        self.session_started_event.clear()
 | 
			
		||||
        if not address or len(address) < 2:
 | 
			
		||||
@@ -162,11 +156,13 @@ class ClientXMPP(BaseXMPP):
 | 
			
		||||
                log.debug("Since no address is supplied," + \
 | 
			
		||||
                              "attempting SRV lookup.")
 | 
			
		||||
                try:
 | 
			
		||||
                    xmpp_srv = "_xmpp-client._tcp.%s" % self.server
 | 
			
		||||
                    xmpp_srv = "_xmpp-client._tcp.%s" % self.boundjid.host
 | 
			
		||||
                    answers = dns.resolver.query(xmpp_srv, dns.rdatatype.SRV)
 | 
			
		||||
                except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer):
 | 
			
		||||
                    log.debug("No appropriate SRV record found." + \
 | 
			
		||||
                                  " Using JID server name.")
 | 
			
		||||
                except (dns.exception.Timeout,):
 | 
			
		||||
                    log.debug("DNS resolution timed out.")
 | 
			
		||||
                else:
 | 
			
		||||
                    # Pick a random server, weighted by priority.
 | 
			
		||||
 | 
			
		||||
@@ -190,7 +186,8 @@ class ClientXMPP(BaseXMPP):
 | 
			
		||||
            # If all else fails, use the server from the JID.
 | 
			
		||||
            address = (self.boundjid.host, 5222)
 | 
			
		||||
 | 
			
		||||
        return XMLStream.connect(self, address[0], address[1], use_tls=True)
 | 
			
		||||
        return XMLStream.connect(self, address[0], address[1],
 | 
			
		||||
                                 use_tls=use_tls, reattempt=reattempt)
 | 
			
		||||
 | 
			
		||||
    def register_feature(self, mask, pointer, breaker=False):
 | 
			
		||||
        """
 | 
			
		||||
@@ -206,7 +203,8 @@ class ClientXMPP(BaseXMPP):
 | 
			
		||||
                                         pointer,
 | 
			
		||||
                                         breaker))
 | 
			
		||||
 | 
			
		||||
    def update_roster(self, jid, name=None, subscription=None, groups=[]):
 | 
			
		||||
    def update_roster(self, jid, name=None, subscription=None, groups=[],
 | 
			
		||||
                            block=True, timeout=None, callback=None):
 | 
			
		||||
        """
 | 
			
		||||
        Add or change a roster item.
 | 
			
		||||
 | 
			
		||||
@@ -217,12 +215,24 @@ class ClientXMPP(BaseXMPP):
 | 
			
		||||
                            'to', 'from', 'both', or 'none'. If set
 | 
			
		||||
                            to 'remove', the entry will be deleted.
 | 
			
		||||
            groups       -- The roster groups that contain this item.
 | 
			
		||||
            block        -- Specify if the roster request will block
 | 
			
		||||
                            until a response is received, or a timeout
 | 
			
		||||
                            occurs. Defaults to True.
 | 
			
		||||
            timeout      -- The length of time (in seconds) to wait
 | 
			
		||||
                            for a response before continuing if blocking
 | 
			
		||||
                            is used. Defaults to self.response_timeout.
 | 
			
		||||
            callback     -- Optional reference to a stream handler function.
 | 
			
		||||
                            Will be executed when the roster is received.
 | 
			
		||||
                            Implies block=False.
 | 
			
		||||
        """
 | 
			
		||||
        iq = self.Iq()._set_stanza_values({'type': 'set'})
 | 
			
		||||
        iq = self.Iq()
 | 
			
		||||
        iq['type'] = 'set'
 | 
			
		||||
        iq['roster']['items'] = {jid: {'name': name,
 | 
			
		||||
                                       'subscription': subscription,
 | 
			
		||||
                                       'groups': groups}}
 | 
			
		||||
        response = iq.send()
 | 
			
		||||
        response = iq.send(block, timeout, callback)
 | 
			
		||||
        if response in [False, None] or not isinstance(response, Iq):
 | 
			
		||||
            return response
 | 
			
		||||
        return response['type'] == 'result'
 | 
			
		||||
 | 
			
		||||
    def del_roster_item(self, jid):
 | 
			
		||||
@@ -235,11 +245,33 @@ class ClientXMPP(BaseXMPP):
 | 
			
		||||
        """
 | 
			
		||||
        return self.update_roster(jid, subscription='remove')
 | 
			
		||||
 | 
			
		||||
    def get_roster(self):
 | 
			
		||||
        """Request the roster from the server."""
 | 
			
		||||
        iq = self.Iq()._set_stanza_values({'type': 'get'}).enable('roster')
 | 
			
		||||
        response = iq.send()
 | 
			
		||||
        self._handle_roster(response, request=True)
 | 
			
		||||
    def get_roster(self, block=True, timeout=None, callback=None):
 | 
			
		||||
        """
 | 
			
		||||
        Request the roster from the server.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            block    -- Specify if the roster request will block until a
 | 
			
		||||
                        response is received, or a timeout occurs.
 | 
			
		||||
                        Defaults to True.
 | 
			
		||||
            timeout  -- The length of time (in seconds) to wait for a response
 | 
			
		||||
                        before continuing if blocking is used.
 | 
			
		||||
                        Defaults to self.response_timeout.
 | 
			
		||||
            callback -- Optional reference to a stream handler function. Will
 | 
			
		||||
                        be executed when the roster is received.
 | 
			
		||||
                        Implies block=False.
 | 
			
		||||
        """
 | 
			
		||||
        iq = self.Iq()
 | 
			
		||||
        iq['type'] = 'get'
 | 
			
		||||
        iq.enable('roster')
 | 
			
		||||
        response = iq.send(block, timeout, callback)
 | 
			
		||||
 | 
			
		||||
        if response == False:
 | 
			
		||||
            self.event('roster_timeout')
 | 
			
		||||
 | 
			
		||||
        if response in [False, None] or not isinstance(response, Iq):
 | 
			
		||||
            return response
 | 
			
		||||
        else:
 | 
			
		||||
            return self._handle_roster(response, request=True)
 | 
			
		||||
 | 
			
		||||
    def _handle_stream_features(self, features):
 | 
			
		||||
        """
 | 
			
		||||
@@ -270,13 +302,15 @@ class ClientXMPP(BaseXMPP):
 | 
			
		||||
        Arguments:
 | 
			
		||||
            xml -- The STARTLS proceed element.
 | 
			
		||||
        """
 | 
			
		||||
        if not self.authenticated and self.ssl_support:
 | 
			
		||||
        if not self.use_tls:
 | 
			
		||||
            return False
 | 
			
		||||
        elif not self.authenticated and self.ssl_support:
 | 
			
		||||
            tls_ns = 'urn:ietf:params:xml:ns:xmpp-tls'
 | 
			
		||||
            self.add_handler("<proceed xmlns='%s' />" % tls_ns,
 | 
			
		||||
                             self._handle_tls_start,
 | 
			
		||||
                             name='TLS Proceed',
 | 
			
		||||
                             instream=True)
 | 
			
		||||
            self.send_xml(xml)
 | 
			
		||||
            self.send_xml(xml, now=True)
 | 
			
		||||
            return True
 | 
			
		||||
        else:
 | 
			
		||||
            log.warning("The module tlslite is required to log in" +\
 | 
			
		||||
@@ -300,7 +334,8 @@ class ClientXMPP(BaseXMPP):
 | 
			
		||||
        Arguments:
 | 
			
		||||
            xml -- The SASL mechanisms stanza.
 | 
			
		||||
        """
 | 
			
		||||
        if '{urn:ietf:params:xml:ns:xmpp-tls}starttls' in self.features:
 | 
			
		||||
        if self.use_tls and \
 | 
			
		||||
           '{urn:ietf:params:xml:ns:xmpp-tls}starttls' in self.features:
 | 
			
		||||
            return False
 | 
			
		||||
 | 
			
		||||
        log.debug("Starting SASL Auth")
 | 
			
		||||
@@ -331,11 +366,13 @@ class ClientXMPP(BaseXMPP):
 | 
			
		||||
 | 
			
		||||
                self.send("<auth xmlns='%s' mechanism='PLAIN'>%s</auth>" % (
 | 
			
		||||
                    sasl_ns,
 | 
			
		||||
                    auth))
 | 
			
		||||
                    auth),
 | 
			
		||||
                    now=True)
 | 
			
		||||
            elif 'sasl:ANONYMOUS' in self.features and not self.boundjid.user:
 | 
			
		||||
                self.send("<auth xmlns='%s' mechanism='%s' />" % (
 | 
			
		||||
                    sasl_ns,
 | 
			
		||||
                    'ANONYMOUS'))
 | 
			
		||||
                    'ANONYMOUS'),
 | 
			
		||||
                    now=True)
 | 
			
		||||
            else:
 | 
			
		||||
                log.error("No appropriate login method.")
 | 
			
		||||
                self.disconnect()
 | 
			
		||||
@@ -378,13 +415,13 @@ class ClientXMPP(BaseXMPP):
 | 
			
		||||
            res.text = self.boundjid.resource
 | 
			
		||||
            xml.append(res)
 | 
			
		||||
        iq.append(xml)
 | 
			
		||||
        response = iq.send()
 | 
			
		||||
        response = iq.send(now=True)
 | 
			
		||||
 | 
			
		||||
        bind_ns = 'urn:ietf:params:xml:ns:xmpp-bind'
 | 
			
		||||
        self.set_jid(response.xml.find('{%s}bind/{%s}jid' % (bind_ns,
 | 
			
		||||
                                                             bind_ns)).text)
 | 
			
		||||
        self.bound = True
 | 
			
		||||
        log.info("Node set to: %s" % self.boundjid.fulljid)
 | 
			
		||||
        log.info("Node set to: %s" % self.boundjid.full)
 | 
			
		||||
        session_ns = 'urn:ietf:params:xml:ns:xmpp-session'
 | 
			
		||||
        if "{%s}session" % session_ns not in self.features or self.bindfail:
 | 
			
		||||
            log.debug("Established Session")
 | 
			
		||||
@@ -401,7 +438,7 @@ class ClientXMPP(BaseXMPP):
 | 
			
		||||
        """
 | 
			
		||||
        if self.authenticated and self.bound:
 | 
			
		||||
            iq = self.makeIqSet(xml)
 | 
			
		||||
            response = iq.send()
 | 
			
		||||
            response = iq.send(now=True)
 | 
			
		||||
            log.debug("Established Session")
 | 
			
		||||
            self.sessionstarted = True
 | 
			
		||||
            self.session_started_event.set()
 | 
			
		||||
@@ -428,9 +465,19 @@ class ClientXMPP(BaseXMPP):
 | 
			
		||||
                                        'presence': {},
 | 
			
		||||
                                        'in_roster': True}
 | 
			
		||||
                self.roster[jid].update(iq['roster']['items'][jid])
 | 
			
		||||
            self.event('roster_received', iq)
 | 
			
		||||
 | 
			
		||||
        self.event("roster_update", iq)
 | 
			
		||||
        if iq['type'] == 'set':
 | 
			
		||||
            iq.reply()
 | 
			
		||||
            iq.enable('roster')
 | 
			
		||||
            iq.send()
 | 
			
		||||
        return True
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# To comply with PEP8, method names now use underscores.
 | 
			
		||||
# Deprecated method names are re-mapped for backwards compatibility.
 | 
			
		||||
ClientXMPP.updateRoster = ClientXMPP.update_roster
 | 
			
		||||
ClientXMPP.delRosterItem = ClientXMPP.del_roster_item
 | 
			
		||||
ClientXMPP.getRoster = ClientXMPP.get_roster
 | 
			
		||||
ClientXMPP.registerFeature = ClientXMPP.register_feature
 | 
			
		||||
 
 | 
			
		||||
@@ -129,7 +129,7 @@ class ComponentXMPP(BaseXMPP):
 | 
			
		||||
 | 
			
		||||
        handshake = ET.Element('{jabber:component:accept}handshake')
 | 
			
		||||
        handshake.text = hashlib.sha1(pre_hash).hexdigest().lower()
 | 
			
		||||
        self.send_xml(handshake)
 | 
			
		||||
        self.send_xml(handshake, now=True)
 | 
			
		||||
 | 
			
		||||
    def _handle_handshake(self, xml):
 | 
			
		||||
        """
 | 
			
		||||
@@ -138,4 +138,5 @@ class ComponentXMPP(BaseXMPP):
 | 
			
		||||
        Arguments:
 | 
			
		||||
            xml -- The reply handshake stanza.
 | 
			
		||||
        """
 | 
			
		||||
        self.session_started_event.set()
 | 
			
		||||
        self.event("session_start")
 | 
			
		||||
 
 | 
			
		||||
@@ -21,7 +21,8 @@ class XMPPError(Exception):
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    def __init__(self, condition='undefined-condition', text=None, etype=None,
 | 
			
		||||
                 extension=None, extension_ns=None, extension_args=None):
 | 
			
		||||
                 extension=None, extension_ns=None, extension_args=None,
 | 
			
		||||
                 clear=True):
 | 
			
		||||
        """
 | 
			
		||||
        Create a new XMPPError exception.
 | 
			
		||||
 | 
			
		||||
@@ -37,6 +38,9 @@ class XMPPError(Exception):
 | 
			
		||||
            extension_args -- Content and attributes for the extension
 | 
			
		||||
                              element. Same as the additional arguments to
 | 
			
		||||
                              the ET.Element constructor.
 | 
			
		||||
            clear          -- Indicates if the stanza's contents should be
 | 
			
		||||
                              removed before replying with an error.
 | 
			
		||||
                              Defaults to True.
 | 
			
		||||
        """
 | 
			
		||||
        if extension_args is None:
 | 
			
		||||
            extension_args = {}
 | 
			
		||||
@@ -44,6 +48,7 @@ class XMPPError(Exception):
 | 
			
		||||
        self.condition = condition
 | 
			
		||||
        self.text = text
 | 
			
		||||
        self.etype = etype
 | 
			
		||||
        self.clear = clear
 | 
			
		||||
        self.extension = extension
 | 
			
		||||
        self.extension_ns = extension_ns
 | 
			
		||||
        self.extension_args = extension_args
 | 
			
		||||
 
 | 
			
		||||
@@ -5,6 +5,6 @@
 | 
			
		||||
 | 
			
		||||
    See the file LICENSE for copying permission.
 | 
			
		||||
"""
 | 
			
		||||
__all__ = ['xep_0004', 'xep_0012', 'xep_0030', 'xep_0033', 'xep_0045',
 | 
			
		||||
           'xep_0050', 'xep_0085', 'xep_0092', 'xep_0199', 'gmail_notify',
 | 
			
		||||
           'xep_0060', 'xep_0202']
 | 
			
		||||
__all__ = ['xep_0004', 'xep_0009', 'xep_0012', 'xep_0030', 'xep_0033',
 | 
			
		||||
           'xep_0045', 'xep_0050', 'xep_0060', 'xep_0085', 'xep_0086',
 | 
			
		||||
           'xep_0092', 'xep_0128', 'xep_0199', 'xep_0202', 'gmail_notify']
 | 
			
		||||
 
 | 
			
		||||
@@ -9,7 +9,63 @@
 | 
			
		||||
 | 
			
		||||
class base_plugin(object):
 | 
			
		||||
 | 
			
		||||
    def __init__(self, xmpp, config):
 | 
			
		||||
    """
 | 
			
		||||
    The base_plugin class serves as a base for user created plugins
 | 
			
		||||
    that provide support for existing or experimental XEPS.
 | 
			
		||||
 | 
			
		||||
    Each plugin has a dictionary for configuration options, as well
 | 
			
		||||
    as a name and description.
 | 
			
		||||
 | 
			
		||||
    The lifecycle of a plugin is:
 | 
			
		||||
        1. The plugin is instantiated during registration.
 | 
			
		||||
        2. Once the XML stream begins processing, the method
 | 
			
		||||
           plugin_init() is called (if the plugin is configured
 | 
			
		||||
           as enabled with {'enable': True}).
 | 
			
		||||
        3. After all plugins have been initialized, the
 | 
			
		||||
           method post_init() is called.
 | 
			
		||||
 | 
			
		||||
    Recommended event handlers:
 | 
			
		||||
        session_start -- Plugins which require the use of the current
 | 
			
		||||
                         bound JID SHOULD wait for the session_start
 | 
			
		||||
                         event to perform any initialization (or
 | 
			
		||||
                         resetting). This is a transitive recommendation,
 | 
			
		||||
                         plugins that use other plugins which use the
 | 
			
		||||
                         bound JID should also wait for session_start
 | 
			
		||||
                         before making such calls.
 | 
			
		||||
        session_end   -- If the plugin keeps any per-session state,
 | 
			
		||||
                         such as joined MUC rooms, such state SHOULD
 | 
			
		||||
                         be cleared when the session_end event is raised.
 | 
			
		||||
 | 
			
		||||
    Attributes:
 | 
			
		||||
        xep          -- The XEP number the plugin implements, if any.
 | 
			
		||||
        description  -- A short description of the plugin, typically
 | 
			
		||||
                        the long name of the implemented XEP.
 | 
			
		||||
        xmpp         -- The main SleekXMPP instance.
 | 
			
		||||
        config       -- A dictionary of custom configuration values.
 | 
			
		||||
                        The value 'enable' is special and controls
 | 
			
		||||
                        whether or not the plugin is initialized
 | 
			
		||||
                        after registration.
 | 
			
		||||
        post_initted -- Executed after all plugins have been initialized
 | 
			
		||||
                        to handle any cross-plugin interactions, such as
 | 
			
		||||
                        registering service discovery items.
 | 
			
		||||
        enable       -- Indicates that the plugin is enabled for use and
 | 
			
		||||
                        will be initialized after registration.
 | 
			
		||||
 | 
			
		||||
    Methods:
 | 
			
		||||
        plugin_init  -- Initialize the plugin state.
 | 
			
		||||
        post_init    -- Handle any cross-plugin concerns.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    def __init__(self, xmpp, config=None):
 | 
			
		||||
        """
 | 
			
		||||
        Instantiate a new plugin and store the given configuration.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            xmpp   -- The main SleekXMPP instance.
 | 
			
		||||
            config -- A dictionary of configuration values.
 | 
			
		||||
        """
 | 
			
		||||
        if config is None:
 | 
			
		||||
            config = {}
 | 
			
		||||
        self.xep = 'base'
 | 
			
		||||
        self.description = 'Base Plugin'
 | 
			
		||||
        self.xmpp = xmpp
 | 
			
		||||
@@ -20,7 +76,15 @@ class base_plugin(object):
 | 
			
		||||
            self.plugin_init()
 | 
			
		||||
 | 
			
		||||
    def plugin_init(self):
 | 
			
		||||
        """
 | 
			
		||||
        Initialize plugin state, such as registering any stream or
 | 
			
		||||
        event handlers, or new stanza types.
 | 
			
		||||
        """
 | 
			
		||||
        pass
 | 
			
		||||
 | 
			
		||||
    def post_init(self):
 | 
			
		||||
        """
 | 
			
		||||
        Perform any cross-plugin interactions, such as registering
 | 
			
		||||
        service discovery identities or items.
 | 
			
		||||
        """
 | 
			
		||||
        self.post_inited = True
 | 
			
		||||
 
 | 
			
		||||
@@ -143,7 +143,7 @@ class gmail_notify(base.base_plugin):
 | 
			
		||||
            log.info('Gmail: Searching for emails matching: "%s"' % query)
 | 
			
		||||
        iq = self.xmpp.Iq()
 | 
			
		||||
        iq['type'] = 'get'
 | 
			
		||||
        iq['to'] = self.xmpp.jid
 | 
			
		||||
        iq['to'] = self.xmpp.boundjid.bare
 | 
			
		||||
        iq['gmail']['q'] = query
 | 
			
		||||
        iq['gmail']['newer-than-time'] = newer
 | 
			
		||||
        return iq.send()
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,6 @@
 | 
			
		||||
from . import base
 | 
			
		||||
import logging
 | 
			
		||||
from xml.etree import cElementTree as ET
 | 
			
		||||
import types
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
log = logging.getLogger(__name__)
 | 
			
		||||
@@ -43,7 +42,7 @@ class jobs(base.base_plugin):
 | 
			
		||||
		iq['psstate']['item'] = jobid
 | 
			
		||||
		iq['psstate']['payload'] = state
 | 
			
		||||
		result = iq.send()
 | 
			
		||||
		if result is None or type(result) == types.BooleanType or result['type'] != 'result':
 | 
			
		||||
		if result is None or type(result) == bool or result['type'] != 'result':
 | 
			
		||||
			log.error("Unable to change %s:%s to %s" % (node, jobid, state))
 | 
			
		||||
			return False
 | 
			
		||||
		return True
 | 
			
		||||
 
 | 
			
		||||
@@ -6,7 +6,7 @@
 | 
			
		||||
    See the file LICENSE for copying permission.
 | 
			
		||||
"""
 | 
			
		||||
from . import base
 | 
			
		||||
import log
 | 
			
		||||
import logging
 | 
			
		||||
from xml.etree import cElementTree as ET
 | 
			
		||||
import copy
 | 
			
		||||
import logging
 | 
			
		||||
 
 | 
			
		||||
@@ -11,7 +11,7 @@ import logging
 | 
			
		||||
from xml.etree import cElementTree as ET
 | 
			
		||||
import time
 | 
			
		||||
 | 
			
		||||
class xep_0050(base.base_plugin):
 | 
			
		||||
class old_0050(base.base_plugin):
 | 
			
		||||
	"""
 | 
			
		||||
	XEP-0050 Ad-Hoc Commands
 | 
			
		||||
	"""
 | 
			
		||||
@@ -110,7 +110,7 @@ class xep_0050(base.base_plugin):
 | 
			
		||||
		if not id:
 | 
			
		||||
			id = self.xmpp.getNewId()
 | 
			
		||||
		iq = self.xmpp.makeIqResult(id)
 | 
			
		||||
		iq.attrib['from'] = self.xmpp.fulljid
 | 
			
		||||
		iq.attrib['from'] = self.xmpp.boundjid.full
 | 
			
		||||
		iq.attrib['to'] = to
 | 
			
		||||
		command = ET.Element('{http://jabber.org/protocol/commands}command')
 | 
			
		||||
		command.attrib['node'] = node
 | 
			
		||||
@@ -237,6 +237,8 @@ class Unsubscribe(ElementBase):
 | 
			
		||||
	def getJid(self):
 | 
			
		||||
		return JID(self._getAttr('jid'))
 | 
			
		||||
 | 
			
		||||
registerStanzaPlugin(Pubsub, Unsubscribe)
 | 
			
		||||
 | 
			
		||||
class Subscribe(ElementBase):
 | 
			
		||||
	namespace = 'http://jabber.org/protocol/pubsub'
 | 
			
		||||
	name = 'subscribe'
 | 
			
		||||
 
 | 
			
		||||
@@ -13,7 +13,6 @@ from .. xmlstream.handler.callback import Callback
 | 
			
		||||
from .. xmlstream.matcher.xpath import MatchXPath
 | 
			
		||||
from .. xmlstream.stanzabase import registerStanzaPlugin, ElementBase, ET, JID
 | 
			
		||||
from .. stanza.message import Message
 | 
			
		||||
import types
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
log = logging.getLogger(__name__)
 | 
			
		||||
@@ -58,6 +57,7 @@ class Form(ElementBase):
 | 
			
		||||
		return field
 | 
			
		||||
 | 
			
		||||
	def getXML(self, type='submit'):
 | 
			
		||||
		self['type'] = type
 | 
			
		||||
		log.warning("Form.getXML() is deprecated API compatibility with plugins/old_0004.py")
 | 
			
		||||
		return self.xml
 | 
			
		||||
 | 
			
		||||
@@ -203,7 +203,7 @@ class Form(ElementBase):
 | 
			
		||||
 | 
			
		||||
	def merge(self, other):
 | 
			
		||||
		new = copy.copy(self)
 | 
			
		||||
		if type(other) == types.DictType:
 | 
			
		||||
		if type(other) == dict:
 | 
			
		||||
			new.setValues(other)
 | 
			
		||||
			return new
 | 
			
		||||
		nfields = new.getFields(use_dict=True)
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										11
									
								
								sleekxmpp/plugins/xep_0009/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								sleekxmpp/plugins/xep_0009/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,11 @@
 | 
			
		||||
"""
 | 
			
		||||
    SleekXMPP: The Sleek XMPP Library
 | 
			
		||||
    Copyright (C) 2011 Nathanael C. Fritz, Dann Martens (TOMOTON).
 | 
			
		||||
    This file is part of SleekXMPP.
 | 
			
		||||
 | 
			
		||||
    See the file LICENSE for copying permission.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
from sleekxmpp.plugins.xep_0009 import stanza
 | 
			
		||||
from sleekxmpp.plugins.xep_0009.rpc import xep_0009
 | 
			
		||||
from sleekxmpp.plugins.xep_0009.stanza import RPCQuery, MethodCall, MethodResponse
 | 
			
		||||
							
								
								
									
										166
									
								
								sleekxmpp/plugins/xep_0009/binding.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										166
									
								
								sleekxmpp/plugins/xep_0009/binding.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,166 @@
 | 
			
		||||
"""
 | 
			
		||||
    SleekXMPP: The Sleek XMPP Library
 | 
			
		||||
    Copyright (C) 2011 Nathanael C. Fritz, Dann Martens (TOMOTON).
 | 
			
		||||
    This file is part of SleekXMPP.
 | 
			
		||||
 | 
			
		||||
    See the file LICENSE for copying permission.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
from xml.etree import cElementTree as ET
 | 
			
		||||
import base64
 | 
			
		||||
import logging
 | 
			
		||||
import time
 | 
			
		||||
 | 
			
		||||
log = logging.getLogger(__name__)
 | 
			
		||||
 | 
			
		||||
_namespace = 'jabber:iq:rpc'
 | 
			
		||||
 | 
			
		||||
def fault2xml(fault):
 | 
			
		||||
    value = dict()
 | 
			
		||||
    value['faultCode'] = fault['code']
 | 
			
		||||
    value['faultString'] = fault['string']
 | 
			
		||||
    fault = ET.Element("fault", {'xmlns': _namespace})
 | 
			
		||||
    fault.append(_py2xml((value)))
 | 
			
		||||
    return fault
 | 
			
		||||
 | 
			
		||||
def xml2fault(params):
 | 
			
		||||
    vals = []
 | 
			
		||||
    for value in params.findall('{%s}value' % _namespace):
 | 
			
		||||
        vals.append(_xml2py(value))
 | 
			
		||||
    fault = dict()
 | 
			
		||||
    fault['code'] = vals[0]['faultCode']
 | 
			
		||||
    fault['string'] = vals[0]['faultString']
 | 
			
		||||
    return fault
 | 
			
		||||
 | 
			
		||||
def py2xml(*args):
 | 
			
		||||
    params = ET.Element("{%s}params" % _namespace)
 | 
			
		||||
    for x in args:
 | 
			
		||||
        param = ET.Element("{%s}param" % _namespace)
 | 
			
		||||
        param.append(_py2xml(x))
 | 
			
		||||
        params.append(param) #<params><param>...
 | 
			
		||||
    return params
 | 
			
		||||
 | 
			
		||||
def _py2xml(*args):
 | 
			
		||||
    for x in args:
 | 
			
		||||
        val = ET.Element("value")
 | 
			
		||||
        if x is None:
 | 
			
		||||
            nil = ET.Element("nil")
 | 
			
		||||
            val.append(nil)
 | 
			
		||||
        elif type(x) is int:
 | 
			
		||||
            i4 = ET.Element("i4")
 | 
			
		||||
            i4.text = str(x)
 | 
			
		||||
            val.append(i4)
 | 
			
		||||
        elif type(x) is bool:
 | 
			
		||||
            boolean = ET.Element("boolean")
 | 
			
		||||
            boolean.text = str(int(x))
 | 
			
		||||
            val.append(boolean)
 | 
			
		||||
        elif type(x) is str:
 | 
			
		||||
            string = ET.Element("string")
 | 
			
		||||
            string.text = x
 | 
			
		||||
            val.append(string)
 | 
			
		||||
        elif type(x) is float:
 | 
			
		||||
            double = ET.Element("double")
 | 
			
		||||
            double.text = str(x)
 | 
			
		||||
            val.append(double)
 | 
			
		||||
        elif type(x) is rpcbase64:
 | 
			
		||||
            b64 = ET.Element("Base64")
 | 
			
		||||
            b64.text = x.encoded()
 | 
			
		||||
            val.append(b64)
 | 
			
		||||
        elif type(x) is rpctime:
 | 
			
		||||
            iso = ET.Element("dateTime.iso8601")
 | 
			
		||||
            iso.text = str(x)
 | 
			
		||||
            val.append(iso)
 | 
			
		||||
        elif type(x) in (list, tuple):
 | 
			
		||||
            array = ET.Element("array")
 | 
			
		||||
            data = ET.Element("data")
 | 
			
		||||
            for y in x:
 | 
			
		||||
                data.append(_py2xml(y))
 | 
			
		||||
            array.append(data)
 | 
			
		||||
            val.append(array)
 | 
			
		||||
        elif type(x) is dict:
 | 
			
		||||
            struct = ET.Element("struct")
 | 
			
		||||
            for y in x.keys():
 | 
			
		||||
                member = ET.Element("member")
 | 
			
		||||
                name = ET.Element("name")
 | 
			
		||||
                name.text = y
 | 
			
		||||
                member.append(name)
 | 
			
		||||
                member.append(_py2xml(x[y]))
 | 
			
		||||
                struct.append(member)
 | 
			
		||||
            val.append(struct)
 | 
			
		||||
        return val
 | 
			
		||||
 | 
			
		||||
def xml2py(params):
 | 
			
		||||
    namespace = 'jabber:iq:rpc'
 | 
			
		||||
    vals = []
 | 
			
		||||
    for param in params.findall('{%s}param' % namespace):
 | 
			
		||||
        vals.append(_xml2py(param.find('{%s}value' % namespace)))
 | 
			
		||||
    return vals
 | 
			
		||||
 | 
			
		||||
def _xml2py(value):
 | 
			
		||||
    namespace = 'jabber:iq:rpc'
 | 
			
		||||
    if value.find('{%s}nil' % namespace) is not None:
 | 
			
		||||
        return None
 | 
			
		||||
    if value.find('{%s}i4' % namespace) is not None:
 | 
			
		||||
        return int(value.find('{%s}i4' % namespace).text)
 | 
			
		||||
    if value.find('{%s}int' % namespace) is not None:
 | 
			
		||||
        return int(value.find('{%s}int' % namespace).text)
 | 
			
		||||
    if value.find('{%s}boolean' % namespace) is not None:
 | 
			
		||||
        return bool(value.find('{%s}boolean' % namespace).text)
 | 
			
		||||
    if value.find('{%s}string' % namespace) is not None:
 | 
			
		||||
        return value.find('{%s}string' % namespace).text
 | 
			
		||||
    if value.find('{%s}double' % namespace) is not None:
 | 
			
		||||
        return float(value.find('{%s}double' % namespace).text)
 | 
			
		||||
    if value.find('{%s}Base64') is not None:
 | 
			
		||||
        return rpcbase64(value.find('Base64' % namespace).text)
 | 
			
		||||
    if value.find('{%s}dateTime.iso8601') is not None:
 | 
			
		||||
        return rpctime(value.find('{%s}dateTime.iso8601'))
 | 
			
		||||
    if value.find('{%s}struct' % namespace) is not None:
 | 
			
		||||
        struct = {}
 | 
			
		||||
        for member in value.find('{%s}struct' % namespace).findall('{%s}member' % namespace):
 | 
			
		||||
            struct[member.find('{%s}name' % namespace).text] = _xml2py(member.find('{%s}value' % namespace))
 | 
			
		||||
        return struct
 | 
			
		||||
    if value.find('{%s}array' % namespace) is not None:
 | 
			
		||||
        array = []
 | 
			
		||||
        for val in value.find('{%s}array' % namespace).find('{%s}data' % namespace).findall('{%s}value' % namespace):
 | 
			
		||||
            array.append(_xml2py(val))
 | 
			
		||||
        return array
 | 
			
		||||
    raise ValueError()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class rpcbase64(object):
 | 
			
		||||
 | 
			
		||||
    def __init__(self, data):
 | 
			
		||||
        #base 64 encoded string
 | 
			
		||||
        self.data = data
 | 
			
		||||
 | 
			
		||||
    def decode(self):
 | 
			
		||||
        return base64.decodestring(self.data)
 | 
			
		||||
 | 
			
		||||
    def __str__(self):
 | 
			
		||||
        return self.decode()
 | 
			
		||||
 | 
			
		||||
    def encoded(self):
 | 
			
		||||
        return self.data
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class rpctime(object):
 | 
			
		||||
 | 
			
		||||
    def __init__(self,data=None):
 | 
			
		||||
        #assume string data is in iso format YYYYMMDDTHH:MM:SS
 | 
			
		||||
        if type(data) is str:
 | 
			
		||||
            self.timestamp = time.strptime(data,"%Y%m%dT%H:%M:%S")
 | 
			
		||||
        elif type(data) is time.struct_time:
 | 
			
		||||
            self.timestamp = data
 | 
			
		||||
        elif data is None:
 | 
			
		||||
            self.timestamp = time.gmtime()
 | 
			
		||||
        else:
 | 
			
		||||
            raise ValueError()
 | 
			
		||||
 | 
			
		||||
    def iso8601(self):
 | 
			
		||||
        #return a iso8601 string
 | 
			
		||||
        return time.strftime("%Y%m%dT%H:%M:%S",self.timestamp)
 | 
			
		||||
 | 
			
		||||
    def __str__(self):
 | 
			
		||||
        return self.iso8601()
 | 
			
		||||
							
								
								
									
										739
									
								
								sleekxmpp/plugins/xep_0009/remote.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										739
									
								
								sleekxmpp/plugins/xep_0009/remote.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,739 @@
 | 
			
		||||
"""
 | 
			
		||||
    SleekXMPP: The Sleek XMPP Library
 | 
			
		||||
    Copyright (C) 2011 Nathanael C. Fritz, Dann Martens (TOMOTON).
 | 
			
		||||
    This file is part of SleekXMPP.
 | 
			
		||||
 | 
			
		||||
    See the file LICENSE for copying permission.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
from binding import py2xml, xml2py, xml2fault, fault2xml
 | 
			
		||||
from threading import RLock
 | 
			
		||||
import abc
 | 
			
		||||
import inspect
 | 
			
		||||
import logging
 | 
			
		||||
import sleekxmpp
 | 
			
		||||
import sys
 | 
			
		||||
import threading
 | 
			
		||||
import traceback
 | 
			
		||||
 | 
			
		||||
log = logging.getLogger(__name__)
 | 
			
		||||
 | 
			
		||||
def _intercept(method, name, public):
 | 
			
		||||
    def _resolver(instance, *args, **kwargs):
 | 
			
		||||
        log.debug("Locally calling %s.%s with arguments %s." % (instance.FQN(), method.__name__, args))
 | 
			
		||||
        try:
 | 
			
		||||
            value = method(instance, *args, **kwargs)
 | 
			
		||||
            if value == NotImplemented:
 | 
			
		||||
                raise InvocationException("Local handler does not implement %s.%s!" % (instance.FQN(), method.__name__))
 | 
			
		||||
            return value
 | 
			
		||||
        except InvocationException:
 | 
			
		||||
            raise
 | 
			
		||||
        except Exception as e:
 | 
			
		||||
            raise InvocationException("A problem occured calling %s.%s!" % (instance.FQN(), method.__name__), e)
 | 
			
		||||
    _resolver._rpc = public
 | 
			
		||||
    _resolver._rpc_name = method.__name__ if name is None else name
 | 
			
		||||
    return _resolver
 | 
			
		||||
 | 
			
		||||
def remote(function_argument, public = True):
 | 
			
		||||
    '''
 | 
			
		||||
    Decorator for methods which are remotely callable. This decorator
 | 
			
		||||
    works in conjunction with classes which extend ABC Endpoint.
 | 
			
		||||
    Example:
 | 
			
		||||
 | 
			
		||||
        @remote
 | 
			
		||||
        def remote_method(arg1, arg2)
 | 
			
		||||
 | 
			
		||||
    Arguments:
 | 
			
		||||
        function_argument -- a stand-in for either the actual method
 | 
			
		||||
            OR a new name (string) for the method. In that case the
 | 
			
		||||
            method is considered mapped:
 | 
			
		||||
            Example:
 | 
			
		||||
 | 
			
		||||
            @remote("new_name")
 | 
			
		||||
            def remote_method(arg1, arg2)
 | 
			
		||||
 | 
			
		||||
        public -- A flag which indicates if this method should be part
 | 
			
		||||
            of the known dictionary of remote methods. Defaults to True.
 | 
			
		||||
            Example:
 | 
			
		||||
 | 
			
		||||
            @remote(False)
 | 
			
		||||
            def remote_method(arg1, arg2)
 | 
			
		||||
 | 
			
		||||
    Note: renaming and revising (public vs. private) can be combined.
 | 
			
		||||
    Example:
 | 
			
		||||
 | 
			
		||||
            @remote("new_name", False)
 | 
			
		||||
            def remote_method(arg1, arg2)
 | 
			
		||||
    '''
 | 
			
		||||
    if hasattr(function_argument, '__call__'):
 | 
			
		||||
        return _intercept(function_argument, None, public)
 | 
			
		||||
    else:
 | 
			
		||||
        if not isinstance(function_argument, basestring):
 | 
			
		||||
            if not isinstance(function_argument, bool):
 | 
			
		||||
                raise Exception('Expected an RPC method name or visibility modifier!')
 | 
			
		||||
            else:
 | 
			
		||||
                def _wrap_revised(function):
 | 
			
		||||
                    function = _intercept(function, None, function_argument)
 | 
			
		||||
                    return function
 | 
			
		||||
                return _wrap_revised
 | 
			
		||||
        def _wrap_remapped(function):
 | 
			
		||||
            function = _intercept(function, function_argument, public)
 | 
			
		||||
            return function
 | 
			
		||||
        return _wrap_remapped
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ACL:
 | 
			
		||||
    '''
 | 
			
		||||
    An Access Control List (ACL) is a list of rules, which are evaluated
 | 
			
		||||
    in order until a match is found. The policy of the matching rule
 | 
			
		||||
    is then applied.
 | 
			
		||||
 | 
			
		||||
    Rules are 3-tuples, consisting of a policy enumerated type, a JID
 | 
			
		||||
    expression and a RCP resource expression.
 | 
			
		||||
 | 
			
		||||
    Examples:
 | 
			
		||||
    [ (ACL.ALLOW, '*', '*') ] allow everyone everything, no restrictions
 | 
			
		||||
    [ (ACL.DENY, '*', '*') ] deny everyone everything, no restrictions
 | 
			
		||||
    [ (ACL.ALLOW, 'test@xmpp.org/unit', 'test.*'),
 | 
			
		||||
      (ACL.DENY, '*', '*') ] deny everyone everything, except named
 | 
			
		||||
        JID, which is allowed access to endpoint 'test' only.
 | 
			
		||||
 | 
			
		||||
    The use of wildcards is allowed in expressions, as follows:
 | 
			
		||||
    '*' everyone, or everything (= all endpoints and methods)
 | 
			
		||||
    'test@xmpp.org/*' every JID regardless of JID resource
 | 
			
		||||
    '*@xmpp.org/rpc' every JID from domain xmpp.org with JID res 'rpc'
 | 
			
		||||
    'frank@*' every 'frank', regardless of domain or JID res
 | 
			
		||||
    'system.*' all methods of endpoint 'system'
 | 
			
		||||
    '*.reboot' all methods reboot regardless of endpoint
 | 
			
		||||
    '''
 | 
			
		||||
    ALLOW = True
 | 
			
		||||
    DENY = False
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def check(cls, rules, jid, resource):
 | 
			
		||||
        if rules is None:
 | 
			
		||||
            return cls.DENY                  # No rules means no access!
 | 
			
		||||
        for rule in rules:
 | 
			
		||||
            policy = cls._check(rule, jid, resource)
 | 
			
		||||
            if policy is not None:
 | 
			
		||||
                return policy
 | 
			
		||||
        return cls.DENY   # By default if not rule matches, deny access.
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def _check(cls, rule, jid, resource):
 | 
			
		||||
        if cls._match(jid, rule[1]) and cls._match(resource, rule[2]):
 | 
			
		||||
            return rule[0]
 | 
			
		||||
        else:
 | 
			
		||||
            return None
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def _next_token(cls, expression, index):
 | 
			
		||||
        new_index = expression.find('*', index)
 | 
			
		||||
        if new_index == 0:
 | 
			
		||||
            return ''
 | 
			
		||||
        else:
 | 
			
		||||
            if new_index == -1:
 | 
			
		||||
                return expression[index : ]
 | 
			
		||||
            else:
 | 
			
		||||
                return expression[index : new_index]
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def _match(cls, value, expression):
 | 
			
		||||
        #! print "_match [VALUE] %s [EXPR] %s" % (value, expression)
 | 
			
		||||
        index = 0
 | 
			
		||||
        position = 0
 | 
			
		||||
        while index < len(expression):
 | 
			
		||||
            token = cls._next_token(expression, index)
 | 
			
		||||
            #! print "[TOKEN] '%s'" % token
 | 
			
		||||
            size = len(token)
 | 
			
		||||
            if size > 0:
 | 
			
		||||
                token_index = value.find(token, position)
 | 
			
		||||
                if token_index == -1:
 | 
			
		||||
                    return False
 | 
			
		||||
                else:
 | 
			
		||||
                    #! print "[INDEX-OF] %s" % token_index
 | 
			
		||||
                    position = token_index + len(token)
 | 
			
		||||
                pass
 | 
			
		||||
            if size == 0:
 | 
			
		||||
                index += 1
 | 
			
		||||
            else:
 | 
			
		||||
                index += size
 | 
			
		||||
        #! print "index %s position %s" % (index, position)
 | 
			
		||||
        return True
 | 
			
		||||
 | 
			
		||||
ANY_ALL = [ (ACL.ALLOW, '*', '*') ]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class RemoteException(Exception):
 | 
			
		||||
    '''
 | 
			
		||||
    Base exception for RPC. This exception is raised when a problem
 | 
			
		||||
    occurs in the network layer.
 | 
			
		||||
    '''
 | 
			
		||||
 | 
			
		||||
    def __init__(self, message="", cause=None):
 | 
			
		||||
        '''
 | 
			
		||||
        Initializes a new RemoteException.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            message -- The message accompanying this exception.
 | 
			
		||||
            cause -- The underlying cause of this exception.
 | 
			
		||||
        '''
 | 
			
		||||
        self._message = message
 | 
			
		||||
        self._cause = cause
 | 
			
		||||
        pass
 | 
			
		||||
 | 
			
		||||
    def __str__(self):
 | 
			
		||||
        return repr(self._message)
 | 
			
		||||
 | 
			
		||||
    def get_message(self):
 | 
			
		||||
        return self._message
 | 
			
		||||
 | 
			
		||||
    def get_cause(self):
 | 
			
		||||
        return self._cause
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class InvocationException(RemoteException):
 | 
			
		||||
    '''
 | 
			
		||||
    Exception raised when a problem occurs during the remote invocation
 | 
			
		||||
    of a method.
 | 
			
		||||
    '''
 | 
			
		||||
    pass
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class AuthorizationException(RemoteException):
 | 
			
		||||
    '''
 | 
			
		||||
    Exception raised when the caller is not authorized to invoke the
 | 
			
		||||
    remote method.
 | 
			
		||||
    '''
 | 
			
		||||
    pass
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TimeoutException(Exception):
 | 
			
		||||
    '''
 | 
			
		||||
    Exception raised when the synchronous execution of a method takes
 | 
			
		||||
    longer than the given threshold because an underlying asynchronous
 | 
			
		||||
    reply did not arrive in time.
 | 
			
		||||
    '''
 | 
			
		||||
    pass
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Callback(object):
 | 
			
		||||
    '''
 | 
			
		||||
    A base class for callback handlers.
 | 
			
		||||
    '''
 | 
			
		||||
    __metaclass__ = abc.ABCMeta
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    @abc.abstractproperty
 | 
			
		||||
    def set_value(self, value):
 | 
			
		||||
        return NotImplemented
 | 
			
		||||
 | 
			
		||||
    @abc.abstractproperty
 | 
			
		||||
    def cancel_with_error(self, exception):
 | 
			
		||||
        return NotImplemented
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Future(Callback):
 | 
			
		||||
    '''
 | 
			
		||||
    Represents the result of an asynchronous computation.
 | 
			
		||||
    '''
 | 
			
		||||
 | 
			
		||||
    def __init__(self):
 | 
			
		||||
        '''
 | 
			
		||||
        Initializes a new Future.
 | 
			
		||||
        '''
 | 
			
		||||
        self._value = None
 | 
			
		||||
        self._exception = None
 | 
			
		||||
        self._event = threading.Event()
 | 
			
		||||
        pass
 | 
			
		||||
 | 
			
		||||
    def set_value(self, value):
 | 
			
		||||
        '''
 | 
			
		||||
        Sets the value of this Future. Once the value is set, a caller
 | 
			
		||||
        blocked on get_value will be able to continue.
 | 
			
		||||
        '''
 | 
			
		||||
        self._value = value
 | 
			
		||||
        self._event.set()
 | 
			
		||||
 | 
			
		||||
    def get_value(self, timeout=None):
 | 
			
		||||
        '''
 | 
			
		||||
        Gets the value of this Future. This call will block until
 | 
			
		||||
        the result is available, or until an optional timeout expires.
 | 
			
		||||
        When this Future is cancelled with an error,
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            timeout -- The maximum waiting time to obtain the value.
 | 
			
		||||
        '''
 | 
			
		||||
        self._event.wait(timeout)
 | 
			
		||||
        if self._exception:
 | 
			
		||||
            raise self._exception
 | 
			
		||||
        if not self._event.is_set():
 | 
			
		||||
            raise TimeoutException
 | 
			
		||||
        return self._value
 | 
			
		||||
 | 
			
		||||
    def is_done(self):
 | 
			
		||||
        '''
 | 
			
		||||
        Returns true if a value has been returned.
 | 
			
		||||
        '''
 | 
			
		||||
        return self._event.is_set()
 | 
			
		||||
 | 
			
		||||
    def cancel_with_error(self, exception):
 | 
			
		||||
        '''
 | 
			
		||||
        Cancels the Future because of an error. Once cancelled, a
 | 
			
		||||
        caller blocked on get_value will be able to continue.
 | 
			
		||||
        '''
 | 
			
		||||
        self._exception = exception
 | 
			
		||||
        self._event.set()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Endpoint(object):
 | 
			
		||||
    '''
 | 
			
		||||
    The Endpoint class is an abstract base class for all objects
 | 
			
		||||
    participating in an RPC-enabled XMPP network.
 | 
			
		||||
 | 
			
		||||
    A user subclassing this class is required to implement the method:
 | 
			
		||||
        FQN(self)
 | 
			
		||||
    where FQN stands for Fully Qualified Name, an unambiguous name
 | 
			
		||||
    which specifies which object an RPC call refers to. It is the
 | 
			
		||||
    first part in a RPC method name '<fqn>.<method>'.
 | 
			
		||||
    '''
 | 
			
		||||
    __metaclass__ = abc.ABCMeta
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def __init__(self, session, target_jid):
 | 
			
		||||
        '''
 | 
			
		||||
        Initialize a new Endpoint. This constructor should never be
 | 
			
		||||
        invoked by a user, instead it will be called by the factories
 | 
			
		||||
        which instantiate the RPC-enabled objects, of which only
 | 
			
		||||
        the classes are provided by the user.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            session -- An RPC session instance.
 | 
			
		||||
            target_jid -- the identity of the remote XMPP entity.
 | 
			
		||||
        '''
 | 
			
		||||
        self.session = session
 | 
			
		||||
        self.target_jid = target_jid
 | 
			
		||||
 | 
			
		||||
    @abc.abstractproperty
 | 
			
		||||
    def FQN(self):
 | 
			
		||||
        return NotImplemented
 | 
			
		||||
 | 
			
		||||
    def get_methods(self):
 | 
			
		||||
        '''
 | 
			
		||||
        Returns a dictionary of all RPC method names provided by this
 | 
			
		||||
        class. This method returns the actual  method names as found
 | 
			
		||||
        in the class definition which have been decorated with:
 | 
			
		||||
 | 
			
		||||
            @remote
 | 
			
		||||
            def some_rpc_method(arg1, arg2)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        Unless:
 | 
			
		||||
            (1) the name has been remapped, in which case the new
 | 
			
		||||
                name will be returned.
 | 
			
		||||
 | 
			
		||||
                    @remote("new_name")
 | 
			
		||||
                    def some_rpc_method(arg1, arg2)
 | 
			
		||||
 | 
			
		||||
            (2) the method is set to hidden
 | 
			
		||||
 | 
			
		||||
                    @remote(False)
 | 
			
		||||
                    def some_hidden_method(arg1, arg2)
 | 
			
		||||
        '''
 | 
			
		||||
        result = dict()
 | 
			
		||||
        for function in dir(self):
 | 
			
		||||
            test_attr = getattr(self, function, None)
 | 
			
		||||
            try:
 | 
			
		||||
                if test_attr._rpc:
 | 
			
		||||
                    result[test_attr._rpc_name] = test_attr
 | 
			
		||||
            except Exception:
 | 
			
		||||
                pass
 | 
			
		||||
        return result
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Proxy(Endpoint):
 | 
			
		||||
    '''
 | 
			
		||||
    Implementation of the Proxy pattern which is intended to wrap
 | 
			
		||||
    around Endpoints in order to intercept calls, marshall them and
 | 
			
		||||
    forward them to the remote object.
 | 
			
		||||
    '''
 | 
			
		||||
 | 
			
		||||
    def __init__(self, endpoint, callback = None):
 | 
			
		||||
        '''
 | 
			
		||||
        Initializes a new Proxy.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            endpoint -- The endpoint which is proxified.
 | 
			
		||||
        '''
 | 
			
		||||
        self._endpoint = endpoint
 | 
			
		||||
        self._callback = callback
 | 
			
		||||
 | 
			
		||||
    def __getattribute__(self, name, *args):
 | 
			
		||||
        if name in ('__dict__', '_endpoint', 'async', '_callback'):
 | 
			
		||||
            return object.__getattribute__(self, name)
 | 
			
		||||
        else:
 | 
			
		||||
            attribute = self._endpoint.__getattribute__(name)
 | 
			
		||||
            if hasattr(attribute, '__call__'):
 | 
			
		||||
                try:
 | 
			
		||||
                    if attribute._rpc:
 | 
			
		||||
                        def _remote_call(*args, **kwargs):
 | 
			
		||||
                            log.debug("Remotely calling '%s.%s' with arguments %s." % (self._endpoint.FQN(), attribute._rpc_name, args))
 | 
			
		||||
                            return self._endpoint.session._call_remote(self._endpoint.target_jid, "%s.%s" % (self._endpoint.FQN(), attribute._rpc_name), self._callback, *args, **kwargs)
 | 
			
		||||
                        return _remote_call
 | 
			
		||||
                except:
 | 
			
		||||
                    pass   # If the attribute doesn't exist, don't care!
 | 
			
		||||
            return attribute
 | 
			
		||||
 | 
			
		||||
    def async(self, callback):
 | 
			
		||||
        return Proxy(self._endpoint, callback)
 | 
			
		||||
 | 
			
		||||
    def get_endpoint(self):
 | 
			
		||||
        '''
 | 
			
		||||
        Returns the proxified endpoint.
 | 
			
		||||
        '''
 | 
			
		||||
        return self._endpoint
 | 
			
		||||
 | 
			
		||||
    def FQN(self):
 | 
			
		||||
        return self._endpoint.FQN()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class JabberRPCEntry(object):
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def __init__(self, endpoint_FQN, call):
 | 
			
		||||
        self._endpoint_FQN = endpoint_FQN
 | 
			
		||||
        self._call = call
 | 
			
		||||
 | 
			
		||||
    def call_method(self, args):
 | 
			
		||||
        return_value = self._call(*args)
 | 
			
		||||
        if return_value is None:
 | 
			
		||||
            return return_value
 | 
			
		||||
        else:
 | 
			
		||||
            return self._return(return_value)
 | 
			
		||||
 | 
			
		||||
    def get_endpoint_FQN(self):
 | 
			
		||||
        return self._endpoint_FQN
 | 
			
		||||
 | 
			
		||||
    def _return(self, *args):
 | 
			
		||||
        return args
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class RemoteSession(object):
 | 
			
		||||
    '''
 | 
			
		||||
    A context object for a Jabber-RPC session.
 | 
			
		||||
    '''
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def __init__(self, client, session_close_callback):
 | 
			
		||||
        '''
 | 
			
		||||
        Initializes a new RPC session.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            client -- The SleekXMPP client associated with this session.
 | 
			
		||||
            session_close_callback -- A callback called when the
 | 
			
		||||
                session is closed.
 | 
			
		||||
        '''
 | 
			
		||||
        self._client = client
 | 
			
		||||
        self._session_close_callback = session_close_callback
 | 
			
		||||
        self._event = threading.Event()
 | 
			
		||||
        self._entries = {}
 | 
			
		||||
        self._callbacks = {}
 | 
			
		||||
        self._acls = {}
 | 
			
		||||
        self._lock = RLock()
 | 
			
		||||
 | 
			
		||||
    def _wait(self):
 | 
			
		||||
        self._event.wait()
 | 
			
		||||
 | 
			
		||||
    def _notify(self, event):
 | 
			
		||||
        log.debug("RPC Session as %s started." % self._client.boundjid.full)
 | 
			
		||||
        self._client.sendPresence()
 | 
			
		||||
        self._event.set()
 | 
			
		||||
        pass
 | 
			
		||||
 | 
			
		||||
    def _register_call(self, endpoint, method, name=None):
 | 
			
		||||
        '''
 | 
			
		||||
        Registers a method from an endpoint as remotely callable.
 | 
			
		||||
        '''
 | 
			
		||||
        if name is None:
 | 
			
		||||
            name = method.__name__
 | 
			
		||||
        key = "%s.%s" % (endpoint, name)
 | 
			
		||||
        log.debug("Registering call handler for %s (%s)." % (key, method))
 | 
			
		||||
        with self._lock:
 | 
			
		||||
            if self._entries.has_key(key):
 | 
			
		||||
                raise KeyError("A handler for %s has already been regisered!" % endpoint)
 | 
			
		||||
            self._entries[key] = JabberRPCEntry(endpoint, method)
 | 
			
		||||
        return key
 | 
			
		||||
 | 
			
		||||
    def _register_acl(self, endpoint, acl):
 | 
			
		||||
        log.debug("Registering ACL %s for endpoint %s." % (repr(acl), endpoint))
 | 
			
		||||
        with self._lock:
 | 
			
		||||
            self._acls[endpoint] = acl
 | 
			
		||||
 | 
			
		||||
    def _register_callback(self, pid, callback):
 | 
			
		||||
        with self._lock:
 | 
			
		||||
            self._callbacks[pid] = callback
 | 
			
		||||
 | 
			
		||||
    def forget_callback(self, callback):
 | 
			
		||||
        with self._lock:
 | 
			
		||||
            pid = self._find_key(self._callbacks, callback)
 | 
			
		||||
            if pid is not None:
 | 
			
		||||
                del self._callback[pid]
 | 
			
		||||
            else:
 | 
			
		||||
                raise ValueError("Unknown callback!")
 | 
			
		||||
        pass
 | 
			
		||||
 | 
			
		||||
    def _find_key(self, dict, value):
 | 
			
		||||
        """return the key of dictionary dic given the value"""
 | 
			
		||||
        search = [k for k, v in dict.iteritems() if v == value]
 | 
			
		||||
        if len(search) == 0:
 | 
			
		||||
            return None
 | 
			
		||||
        else:
 | 
			
		||||
            return search[0]
 | 
			
		||||
 | 
			
		||||
    def _unregister_call(self, key):
 | 
			
		||||
        #removes the registered call
 | 
			
		||||
        with self._lock:
 | 
			
		||||
            if self._entries[key]:
 | 
			
		||||
                del self._entries[key]
 | 
			
		||||
            else:
 | 
			
		||||
                raise ValueError()
 | 
			
		||||
 | 
			
		||||
    def new_proxy(self, target_jid, endpoint_cls):
 | 
			
		||||
        '''
 | 
			
		||||
        Instantiates a new proxy object, which proxies to a remote
 | 
			
		||||
        endpoint. This method uses a class reference without
 | 
			
		||||
        constructor arguments to instantiate the proxy.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            target_jid -- the XMPP entity ID hosting the endpoint.
 | 
			
		||||
            endpoint_cls -- The remote (duck) type.
 | 
			
		||||
        '''
 | 
			
		||||
        try:
 | 
			
		||||
            argspec = inspect.getargspec(endpoint_cls.__init__)
 | 
			
		||||
            args = [None] * (len(argspec[0]) - 1)
 | 
			
		||||
            result = endpoint_cls(*args)
 | 
			
		||||
            Endpoint.__init__(result, self, target_jid)
 | 
			
		||||
            return Proxy(result)
 | 
			
		||||
        except:
 | 
			
		||||
            traceback.print_exc(file=sys.stdout)
 | 
			
		||||
 | 
			
		||||
    def new_handler(self, acl, handler_cls, *args, **kwargs):
 | 
			
		||||
        '''
 | 
			
		||||
        Instantiates a new handler object, which is called remotely
 | 
			
		||||
        by others. The user can control the effect of the call by
 | 
			
		||||
        implementing the remote method in the local endpoint class. The
 | 
			
		||||
        returned reference can be called locally and will behave as a
 | 
			
		||||
        regular instance.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            acl -- Access control list (see ACL class)
 | 
			
		||||
            handler_clss -- The local (duck) type.
 | 
			
		||||
            *args -- Constructor arguments for the local type.
 | 
			
		||||
            **kwargs -- Constructor keyworded arguments for the local
 | 
			
		||||
                type.
 | 
			
		||||
        '''
 | 
			
		||||
        argspec = inspect.getargspec(handler_cls.__init__)
 | 
			
		||||
        base_argspec = inspect.getargspec(Endpoint.__init__)
 | 
			
		||||
        if(argspec == base_argspec):
 | 
			
		||||
            result = handler_cls(self, self._client.boundjid.full)
 | 
			
		||||
        else:
 | 
			
		||||
            result = handler_cls(*args, **kwargs)
 | 
			
		||||
            Endpoint.__init__(result, self, self._client.boundjid.full)
 | 
			
		||||
        method_dict = result.get_methods()
 | 
			
		||||
        for method_name, method in method_dict.iteritems():
 | 
			
		||||
            #!!! self._client.plugin['xep_0009'].register_call(result.FQN(), method, method_name)
 | 
			
		||||
            self._register_call(result.FQN(), method, method_name)
 | 
			
		||||
        self._register_acl(result.FQN(), acl)
 | 
			
		||||
        return result
 | 
			
		||||
 | 
			
		||||
#    def is_available(self, targetCls, pto):
 | 
			
		||||
#        return self._client.is_available(pto)
 | 
			
		||||
 | 
			
		||||
    def _call_remote(self, pto, pmethod, callback, *arguments):
 | 
			
		||||
        iq = self._client.plugin['xep_0009'].make_iq_method_call(pto, pmethod, py2xml(*arguments))
 | 
			
		||||
        pid = iq['id']
 | 
			
		||||
        if callback is None:
 | 
			
		||||
            future = Future()
 | 
			
		||||
            self._register_callback(pid, future)
 | 
			
		||||
            iq.send()
 | 
			
		||||
            return future.get_value(30)
 | 
			
		||||
        else:
 | 
			
		||||
            log.debug("[RemoteSession] _call_remote %s" % callback)
 | 
			
		||||
            self._register_callback(pid, callback)
 | 
			
		||||
            iq.send()
 | 
			
		||||
 | 
			
		||||
    def close(self):
 | 
			
		||||
        '''
 | 
			
		||||
        Closes this session.
 | 
			
		||||
        '''
 | 
			
		||||
        self._client.disconnect(False)
 | 
			
		||||
        self._session_close_callback()
 | 
			
		||||
 | 
			
		||||
    def _on_jabber_rpc_method_call(self, iq):
 | 
			
		||||
        iq.enable('rpc_query')
 | 
			
		||||
        params = iq['rpc_query']['method_call']['params']
 | 
			
		||||
        args = xml2py(params)
 | 
			
		||||
        pmethod = iq['rpc_query']['method_call']['method_name']
 | 
			
		||||
        try:
 | 
			
		||||
            with self._lock:
 | 
			
		||||
                entry = self._entries[pmethod]
 | 
			
		||||
                rules = self._acls[entry.get_endpoint_FQN()]
 | 
			
		||||
            if ACL.check(rules, iq['from'], pmethod):
 | 
			
		||||
                return_value = entry.call_method(args)
 | 
			
		||||
            else:
 | 
			
		||||
                raise AuthorizationException("Unauthorized access to %s from %s!" % (pmethod, iq['from']))
 | 
			
		||||
            if return_value is None:
 | 
			
		||||
                return_value = ()
 | 
			
		||||
            response = self._client.plugin['xep_0009'].make_iq_method_response(iq['id'], iq['from'], py2xml(*return_value))
 | 
			
		||||
            response.send()
 | 
			
		||||
        except InvocationException as ie:
 | 
			
		||||
            fault = dict()
 | 
			
		||||
            fault['code'] = 500
 | 
			
		||||
            fault['string'] = ie.get_message()
 | 
			
		||||
            self._client.plugin['xep_0009']._send_fault(iq, fault2xml(fault))
 | 
			
		||||
        except AuthorizationException as ae:
 | 
			
		||||
            log.error(ae.get_message())
 | 
			
		||||
            error = self._client.plugin['xep_0009']._forbidden(iq)
 | 
			
		||||
            error.send()
 | 
			
		||||
        except Exception as e:
 | 
			
		||||
            if isinstance(e, KeyError):
 | 
			
		||||
                log.error("No handler available for %s!" % pmethod)
 | 
			
		||||
                error = self._client.plugin['xep_0009']._item_not_found(iq)
 | 
			
		||||
            else:
 | 
			
		||||
                traceback.print_exc(file=sys.stderr)
 | 
			
		||||
                log.error("An unexpected problem occurred invoking method %s!" % pmethod)
 | 
			
		||||
                error = self._client.plugin['xep_0009']._undefined_condition(iq)
 | 
			
		||||
            #! print "[REMOTE.PY] _handle_remote_procedure_call AN ERROR SHOULD BE SENT NOW %s " % e
 | 
			
		||||
            error.send()
 | 
			
		||||
 | 
			
		||||
    def _on_jabber_rpc_method_response(self, iq):
 | 
			
		||||
        iq.enable('rpc_query')
 | 
			
		||||
        args = xml2py(iq['rpc_query']['method_response']['params'])
 | 
			
		||||
        pid = iq['id']
 | 
			
		||||
        with self._lock:
 | 
			
		||||
            callback = self._callbacks[pid]
 | 
			
		||||
            del self._callbacks[pid]
 | 
			
		||||
        if(len(args) > 0):
 | 
			
		||||
            callback.set_value(args[0])
 | 
			
		||||
        else:
 | 
			
		||||
            callback.set_value(None)
 | 
			
		||||
        pass
 | 
			
		||||
 | 
			
		||||
    def _on_jabber_rpc_method_response2(self, iq):
 | 
			
		||||
        iq.enable('rpc_query')
 | 
			
		||||
        if iq['rpc_query']['method_response']['fault'] is not None:
 | 
			
		||||
            self._on_jabber_rpc_method_fault(iq)
 | 
			
		||||
        else:
 | 
			
		||||
            args = xml2py(iq['rpc_query']['method_response']['params'])
 | 
			
		||||
            pid = iq['id']
 | 
			
		||||
            with self._lock:
 | 
			
		||||
                callback = self._callbacks[pid]
 | 
			
		||||
                del self._callbacks[pid]
 | 
			
		||||
            if(len(args) > 0):
 | 
			
		||||
                callback.set_value(args[0])
 | 
			
		||||
            else:
 | 
			
		||||
                callback.set_value(None)
 | 
			
		||||
            pass
 | 
			
		||||
 | 
			
		||||
    def _on_jabber_rpc_method_fault(self, iq):
 | 
			
		||||
        iq.enable('rpc_query')
 | 
			
		||||
        fault = xml2fault(iq['rpc_query']['method_response']['fault'])
 | 
			
		||||
        pid = iq['id']
 | 
			
		||||
        with self._lock:
 | 
			
		||||
            callback = self._callbacks[pid]
 | 
			
		||||
            del self._callbacks[pid]
 | 
			
		||||
        e = {
 | 
			
		||||
             500: InvocationException
 | 
			
		||||
        }[fault['code']](fault['string'])
 | 
			
		||||
        callback.cancel_with_error(e)
 | 
			
		||||
 | 
			
		||||
    def _on_jabber_rpc_error(self, iq):
 | 
			
		||||
        pid = iq['id']
 | 
			
		||||
        pmethod = self._client.plugin['xep_0009']._extract_method(iq['rpc_query'])
 | 
			
		||||
        code = iq['error']['code']
 | 
			
		||||
        type = iq['error']['type']
 | 
			
		||||
        condition = iq['error']['condition']
 | 
			
		||||
        #! print("['REMOTE.PY']._BINDING_handle_remote_procedure_error -> ERROR! ERROR! ERROR! Condition is '%s'" % condition)
 | 
			
		||||
        with self._lock:
 | 
			
		||||
            callback = self._callbacks[pid]
 | 
			
		||||
            del self._callbacks[pid]
 | 
			
		||||
        e = {
 | 
			
		||||
            'item-not-found': RemoteException("No remote handler available for %s at %s!" % (pmethod, iq['from'])),
 | 
			
		||||
            'forbidden': AuthorizationException("Forbidden to invoke remote handler for %s at %s!" % (pmethod, iq['from'])),
 | 
			
		||||
            'undefined-condition': RemoteException("An unexpected problem occured trying to invoke %s at %s!" % (pmethod, iq['from'])),
 | 
			
		||||
        }[condition]
 | 
			
		||||
        if e is None:
 | 
			
		||||
            RemoteException("An unexpected exception occurred at %s!" % iq['from'])
 | 
			
		||||
        callback.cancel_with_error(e)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Remote(object):
 | 
			
		||||
    '''
 | 
			
		||||
    Bootstrap class for Jabber-RPC sessions. New sessions are openend
 | 
			
		||||
    with an existing XMPP client, or one is instantiated on demand.
 | 
			
		||||
    '''
 | 
			
		||||
    _instance = None
 | 
			
		||||
    _sessions = dict()
 | 
			
		||||
    _lock = threading.RLock()
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def new_session_with_client(cls, client, callback=None):
 | 
			
		||||
        '''
 | 
			
		||||
        Opens a new session with a given client.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            client -- An XMPP client.
 | 
			
		||||
            callback -- An optional callback which can be used to track
 | 
			
		||||
                the starting state of the session.
 | 
			
		||||
        '''
 | 
			
		||||
        with Remote._lock:
 | 
			
		||||
            if(client.boundjid.bare in cls._sessions):
 | 
			
		||||
                raise RemoteException("There already is a session associated with these credentials!")
 | 
			
		||||
            else:
 | 
			
		||||
                cls._sessions[client.boundjid.bare] = client;
 | 
			
		||||
        def _session_close_callback():
 | 
			
		||||
            with Remote._lock:
 | 
			
		||||
                del cls._sessions[client.boundjid.bare]
 | 
			
		||||
        result = RemoteSession(client, _session_close_callback)
 | 
			
		||||
        client.plugin['xep_0009'].xmpp.add_event_handler('jabber_rpc_method_call', result._on_jabber_rpc_method_call)
 | 
			
		||||
        client.plugin['xep_0009'].xmpp.add_event_handler('jabber_rpc_method_response', result._on_jabber_rpc_method_response)
 | 
			
		||||
        client.plugin['xep_0009'].xmpp.add_event_handler('jabber_rpc_method_fault', result._on_jabber_rpc_method_fault)
 | 
			
		||||
        client.plugin['xep_0009'].xmpp.add_event_handler('jabber_rpc_error', result._on_jabber_rpc_error)
 | 
			
		||||
        if callback is None:
 | 
			
		||||
            start_event_handler = result._notify
 | 
			
		||||
        else:
 | 
			
		||||
            start_event_handler = callback
 | 
			
		||||
        client.add_event_handler("session_start", start_event_handler)
 | 
			
		||||
        if client.connect():
 | 
			
		||||
            client.process(threaded=True)
 | 
			
		||||
        else:
 | 
			
		||||
            raise RemoteException("Could not connect to XMPP server!")
 | 
			
		||||
        pass
 | 
			
		||||
        if callback is None:
 | 
			
		||||
            result._wait()
 | 
			
		||||
        return result
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def new_session(cls, jid, password, callback=None):
 | 
			
		||||
        '''
 | 
			
		||||
        Opens a new session and instantiates a new XMPP client.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            jid -- The XMPP JID for logging in.
 | 
			
		||||
            password -- The password for logging in.
 | 
			
		||||
            callback -- An optional callback which can be used to track
 | 
			
		||||
                the starting state of the session.
 | 
			
		||||
        '''
 | 
			
		||||
        client = sleekxmpp.ClientXMPP(jid, password)
 | 
			
		||||
        #? Register plug-ins.
 | 
			
		||||
        client.registerPlugin('xep_0004') # Data Forms
 | 
			
		||||
        client.registerPlugin('xep_0009') # Jabber-RPC
 | 
			
		||||
        client.registerPlugin('xep_0030') # Service Discovery
 | 
			
		||||
        client.registerPlugin('xep_0060') # PubSub
 | 
			
		||||
        client.registerPlugin('xep_0199') # XMPP Ping
 | 
			
		||||
        return cls.new_session_with_client(client, callback)
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										221
									
								
								sleekxmpp/plugins/xep_0009/rpc.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										221
									
								
								sleekxmpp/plugins/xep_0009/rpc.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,221 @@
 | 
			
		||||
"""
 | 
			
		||||
    SleekXMPP: The Sleek XMPP Library
 | 
			
		||||
    Copyright (C) 2011 Nathanael C. Fritz, Dann Martens (TOMOTON).
 | 
			
		||||
    This file is part of SleekXMPP.
 | 
			
		||||
 | 
			
		||||
    See the file LICENSE for copying permission.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
from sleekxmpp.plugins import base
 | 
			
		||||
from sleekxmpp.plugins.xep_0009.stanza.RPC import RPCQuery, MethodCall, MethodResponse
 | 
			
		||||
from sleekxmpp.stanza.iq import Iq
 | 
			
		||||
from sleekxmpp.xmlstream.handler.callback import Callback
 | 
			
		||||
from sleekxmpp.xmlstream.matcher.xpath import MatchXPath
 | 
			
		||||
from sleekxmpp.xmlstream.stanzabase import register_stanza_plugin
 | 
			
		||||
from xml.etree import cElementTree as ET
 | 
			
		||||
import logging
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
log = logging.getLogger(__name__)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class xep_0009(base.base_plugin):
 | 
			
		||||
 | 
			
		||||
    def plugin_init(self):
 | 
			
		||||
        self.xep = '0009'
 | 
			
		||||
        self.description = 'Jabber-RPC'
 | 
			
		||||
        #self.stanza = sleekxmpp.plugins.xep_0009.stanza
 | 
			
		||||
 | 
			
		||||
        register_stanza_plugin(Iq, RPCQuery)
 | 
			
		||||
        register_stanza_plugin(RPCQuery, MethodCall)
 | 
			
		||||
        register_stanza_plugin(RPCQuery, MethodResponse)
 | 
			
		||||
 | 
			
		||||
        self.xmpp.registerHandler(
 | 
			
		||||
            Callback('RPC Call', MatchXPath('{%s}iq/{%s}query/{%s}methodCall' % (self.xmpp.default_ns, RPCQuery.namespace, RPCQuery.namespace)),
 | 
			
		||||
            self._handle_method_call)
 | 
			
		||||
        )
 | 
			
		||||
        self.xmpp.registerHandler(
 | 
			
		||||
            Callback('RPC Call', MatchXPath('{%s}iq/{%s}query/{%s}methodResponse' % (self.xmpp.default_ns, RPCQuery.namespace, RPCQuery.namespace)),
 | 
			
		||||
            self._handle_method_response)
 | 
			
		||||
        )
 | 
			
		||||
        self.xmpp.registerHandler(
 | 
			
		||||
            Callback('RPC Call', MatchXPath('{%s}iq/{%s}error' % (self.xmpp.default_ns, self.xmpp.default_ns)),
 | 
			
		||||
            self._handle_error)
 | 
			
		||||
        )
 | 
			
		||||
        self.xmpp.add_event_handler('jabber_rpc_method_call', self._on_jabber_rpc_method_call)
 | 
			
		||||
        self.xmpp.add_event_handler('jabber_rpc_method_response', self._on_jabber_rpc_method_response)
 | 
			
		||||
        self.xmpp.add_event_handler('jabber_rpc_method_fault', self._on_jabber_rpc_method_fault)
 | 
			
		||||
        self.xmpp.add_event_handler('jabber_rpc_error', self._on_jabber_rpc_error)
 | 
			
		||||
        self.xmpp.add_event_handler('error', self._handle_error)
 | 
			
		||||
        #self.activeCalls = []
 | 
			
		||||
 | 
			
		||||
    def post_init(self):
 | 
			
		||||
        base.base_plugin.post_init(self)
 | 
			
		||||
        self.xmpp.plugin['xep_0030'].add_feature('jabber:iq:rpc')
 | 
			
		||||
        self.xmpp.plugin['xep_0030'].add_identity('automation','rpc')
 | 
			
		||||
 | 
			
		||||
    def make_iq_method_call(self, pto, pmethod, params):
 | 
			
		||||
        iq = self.xmpp.makeIqSet()
 | 
			
		||||
        iq.attrib['to'] = pto
 | 
			
		||||
        iq.attrib['from'] = self.xmpp.boundjid.full
 | 
			
		||||
        iq.enable('rpc_query')
 | 
			
		||||
        iq['rpc_query']['method_call']['method_name'] = pmethod
 | 
			
		||||
        iq['rpc_query']['method_call']['params'] = params
 | 
			
		||||
        return iq;
 | 
			
		||||
 | 
			
		||||
    def make_iq_method_response(self, pid, pto, params):
 | 
			
		||||
        iq = self.xmpp.makeIqResult(pid)
 | 
			
		||||
        iq.attrib['to'] = pto
 | 
			
		||||
        iq.attrib['from'] = self.xmpp.boundjid.full
 | 
			
		||||
        iq.enable('rpc_query')
 | 
			
		||||
        iq['rpc_query']['method_response']['params'] = params
 | 
			
		||||
        return iq
 | 
			
		||||
 | 
			
		||||
    def make_iq_method_response_fault(self, pid, pto, params):
 | 
			
		||||
        iq = self.xmpp.makeIqResult(pid)
 | 
			
		||||
        iq.attrib['to'] = pto
 | 
			
		||||
        iq.attrib['from'] = self.xmpp.boundjid.full
 | 
			
		||||
        iq.enable('rpc_query')
 | 
			
		||||
        iq['rpc_query']['method_response']['params'] = None
 | 
			
		||||
        iq['rpc_query']['method_response']['fault'] = params
 | 
			
		||||
        return iq
 | 
			
		||||
 | 
			
		||||
#    def make_iq_method_error(self, pto, pid, pmethod, params, code, type, condition):
 | 
			
		||||
#        iq = self.xmpp.makeIqError(pid)
 | 
			
		||||
#        iq.attrib['to'] = pto
 | 
			
		||||
#        iq.attrib['from'] = self.xmpp.boundjid.full
 | 
			
		||||
#        iq['error']['code'] = code
 | 
			
		||||
#        iq['error']['type'] = type
 | 
			
		||||
#        iq['error']['condition'] = condition
 | 
			
		||||
#        iq['rpc_query']['method_call']['method_name'] = pmethod
 | 
			
		||||
#        iq['rpc_query']['method_call']['params'] = params
 | 
			
		||||
#        return iq
 | 
			
		||||
 | 
			
		||||
    def _item_not_found(self, iq):
 | 
			
		||||
        payload = iq.get_payload()
 | 
			
		||||
        iq.reply().error().set_payload(payload);
 | 
			
		||||
        iq['error']['code'] = '404'
 | 
			
		||||
        iq['error']['type'] = 'cancel'
 | 
			
		||||
        iq['error']['condition'] = 'item-not-found'
 | 
			
		||||
        return iq
 | 
			
		||||
 | 
			
		||||
    def _undefined_condition(self, iq):
 | 
			
		||||
        payload = iq.get_payload()
 | 
			
		||||
        iq.reply().error().set_payload(payload)
 | 
			
		||||
        iq['error']['code'] = '500'
 | 
			
		||||
        iq['error']['type'] = 'cancel'
 | 
			
		||||
        iq['error']['condition'] = 'undefined-condition'
 | 
			
		||||
        return iq
 | 
			
		||||
 | 
			
		||||
    def _forbidden(self, iq):
 | 
			
		||||
        payload = iq.get_payload()
 | 
			
		||||
        iq.reply().error().set_payload(payload)
 | 
			
		||||
        iq['error']['code'] = '403'
 | 
			
		||||
        iq['error']['type'] = 'auth'
 | 
			
		||||
        iq['error']['condition'] = 'forbidden'
 | 
			
		||||
        return iq
 | 
			
		||||
 | 
			
		||||
    def _recipient_unvailable(self, iq):
 | 
			
		||||
        payload = iq.get_payload()
 | 
			
		||||
        iq.reply().error().set_payload(payload)
 | 
			
		||||
        iq['error']['code'] = '404'
 | 
			
		||||
        iq['error']['type'] = 'wait'
 | 
			
		||||
        iq['error']['condition'] = 'recipient-unavailable'
 | 
			
		||||
        return iq
 | 
			
		||||
 | 
			
		||||
    def _handle_method_call(self, iq):
 | 
			
		||||
        type = iq['type']
 | 
			
		||||
        if type == 'set':
 | 
			
		||||
            log.debug("Incoming Jabber-RPC call from %s" % iq['from'])
 | 
			
		||||
            self.xmpp.event('jabber_rpc_method_call', iq)
 | 
			
		||||
        else:
 | 
			
		||||
            if type == 'error' and ['rpc_query'] is None:
 | 
			
		||||
                self.handle_error(iq)
 | 
			
		||||
            else:
 | 
			
		||||
                log.debug("Incoming Jabber-RPC error from %s" % iq['from'])
 | 
			
		||||
                self.xmpp.event('jabber_rpc_error', iq)
 | 
			
		||||
 | 
			
		||||
    def _handle_method_response(self, iq):
 | 
			
		||||
        if iq['rpc_query']['method_response']['fault'] is not None:
 | 
			
		||||
            log.debug("Incoming Jabber-RPC fault from %s" % iq['from'])
 | 
			
		||||
            #self._on_jabber_rpc_method_fault(iq)
 | 
			
		||||
            self.xmpp.event('jabber_rpc_method_fault', iq)
 | 
			
		||||
        else:
 | 
			
		||||
            log.debug("Incoming Jabber-RPC response from %s" % iq['from'])
 | 
			
		||||
            self.xmpp.event('jabber_rpc_method_response', iq)
 | 
			
		||||
 | 
			
		||||
    def _handle_error(self, iq):
 | 
			
		||||
        print("['XEP-0009']._handle_error -> ERROR! Iq is '%s'" % iq)
 | 
			
		||||
        print("#######################")
 | 
			
		||||
        print("### NOT IMPLEMENTED ###")
 | 
			
		||||
        print("#######################")
 | 
			
		||||
 | 
			
		||||
    def _on_jabber_rpc_method_call(self, iq, forwarded=False):
 | 
			
		||||
        """
 | 
			
		||||
        A default handler for Jabber-RPC method call. If another
 | 
			
		||||
        handler is registered, this one will defer and not run.
 | 
			
		||||
 | 
			
		||||
        If this handler is called by your own custom handler with
 | 
			
		||||
        forwarded set to True, then it will run as normal.
 | 
			
		||||
        """
 | 
			
		||||
        if not forwarded and self.xmpp.event_handled('jabber_rpc_method_call') > 1:
 | 
			
		||||
            return
 | 
			
		||||
        # Reply with error by default
 | 
			
		||||
        error = self.client.plugin['xep_0009']._item_not_found(iq)
 | 
			
		||||
        error.send()
 | 
			
		||||
 | 
			
		||||
    def _on_jabber_rpc_method_response(self, iq, forwarded=False):
 | 
			
		||||
        """
 | 
			
		||||
        A default handler for Jabber-RPC method response. If another
 | 
			
		||||
        handler is registered, this one will defer and not run.
 | 
			
		||||
 | 
			
		||||
        If this handler is called by your own custom handler with
 | 
			
		||||
        forwarded set to True, then it will run as normal.
 | 
			
		||||
        """
 | 
			
		||||
        if not forwarded and self.xmpp.event_handled('jabber_rpc_method_response') > 1:
 | 
			
		||||
            return
 | 
			
		||||
        error = self.client.plugin['xep_0009']._recpient_unavailable(iq)
 | 
			
		||||
        error.send()
 | 
			
		||||
 | 
			
		||||
    def _on_jabber_rpc_method_fault(self, iq, forwarded=False):
 | 
			
		||||
        """
 | 
			
		||||
        A default handler for Jabber-RPC fault response. If another
 | 
			
		||||
        handler is registered, this one will defer and not run.
 | 
			
		||||
 | 
			
		||||
        If this handler is called by your own custom handler with
 | 
			
		||||
        forwarded set to True, then it will run as normal.
 | 
			
		||||
        """
 | 
			
		||||
        if not forwarded and self.xmpp.event_handled('jabber_rpc_method_fault') > 1:
 | 
			
		||||
            return
 | 
			
		||||
        error = self.client.plugin['xep_0009']._recpient_unavailable(iq)
 | 
			
		||||
        error.send()
 | 
			
		||||
 | 
			
		||||
    def _on_jabber_rpc_error(self, iq, forwarded=False):
 | 
			
		||||
        """
 | 
			
		||||
        A default handler for Jabber-RPC error response. If another
 | 
			
		||||
        handler is registered, this one will defer and not run.
 | 
			
		||||
 | 
			
		||||
        If this handler is called by your own custom handler with
 | 
			
		||||
        forwarded set to True, then it will run as normal.
 | 
			
		||||
        """
 | 
			
		||||
        if not forwarded and self.xmpp.event_handled('jabber_rpc_error') > 1:
 | 
			
		||||
            return
 | 
			
		||||
        error = self.client.plugin['xep_0009']._recpient_unavailable(iq, iq.get_payload())
 | 
			
		||||
        error.send()
 | 
			
		||||
 | 
			
		||||
    def _send_fault(self, iq, fault_xml): #
 | 
			
		||||
        fault = self.make_iq_method_response_fault(iq['id'], iq['from'], fault_xml)
 | 
			
		||||
        fault.send()
 | 
			
		||||
 | 
			
		||||
    def _send_error(self, iq):
 | 
			
		||||
        print("['XEP-0009']._send_error -> ERROR! Iq is '%s'" % iq)
 | 
			
		||||
        print("#######################")
 | 
			
		||||
        print("### NOT IMPLEMENTED ###")
 | 
			
		||||
        print("#######################")
 | 
			
		||||
 | 
			
		||||
    def _extract_method(self, stanza):
 | 
			
		||||
        xml = ET.fromstring("%s" % stanza)
 | 
			
		||||
        return xml.find("./methodCall/methodName").text
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										64
									
								
								sleekxmpp/plugins/xep_0009/stanza/RPC.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								sleekxmpp/plugins/xep_0009/stanza/RPC.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,64 @@
 | 
			
		||||
"""
 | 
			
		||||
    SleekXMPP: The Sleek XMPP Library
 | 
			
		||||
    Copyright (C) 2011 Nathanael C. Fritz, Dann Martens (TOMOTON).
 | 
			
		||||
    This file is part of SleekXMPP.
 | 
			
		||||
 | 
			
		||||
    See the file LICENSE for copying permission.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
from sleekxmpp.xmlstream.stanzabase import ElementBase
 | 
			
		||||
from xml.etree import cElementTree as ET
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class RPCQuery(ElementBase):
 | 
			
		||||
    name = 'query'
 | 
			
		||||
    namespace = 'jabber:iq:rpc'
 | 
			
		||||
    plugin_attrib = 'rpc_query'
 | 
			
		||||
    interfaces = set(())
 | 
			
		||||
    subinterfaces = set(())
 | 
			
		||||
    plugin_attrib_map = {}
 | 
			
		||||
    plugin_tag_map = {}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class MethodCall(ElementBase):
 | 
			
		||||
    name = 'methodCall'
 | 
			
		||||
    namespace = 'jabber:iq:rpc'
 | 
			
		||||
    plugin_attrib = 'method_call'
 | 
			
		||||
    interfaces = set(('method_name', 'params'))
 | 
			
		||||
    subinterfaces = set(())
 | 
			
		||||
    plugin_attrib_map = {}
 | 
			
		||||
    plugin_tag_map = {}
 | 
			
		||||
 | 
			
		||||
    def get_method_name(self):
 | 
			
		||||
        return self._get_sub_text('methodName')
 | 
			
		||||
 | 
			
		||||
    def set_method_name(self, value):
 | 
			
		||||
        return self._set_sub_text('methodName', value)
 | 
			
		||||
 | 
			
		||||
    def get_params(self):
 | 
			
		||||
        return self.xml.find('{%s}params' % self.namespace)
 | 
			
		||||
 | 
			
		||||
    def set_params(self, params):
 | 
			
		||||
        self.append(params)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class MethodResponse(ElementBase):
 | 
			
		||||
    name = 'methodResponse'
 | 
			
		||||
    namespace = 'jabber:iq:rpc'
 | 
			
		||||
    plugin_attrib = 'method_response'
 | 
			
		||||
    interfaces = set(('params', 'fault'))
 | 
			
		||||
    subinterfaces = set(())
 | 
			
		||||
    plugin_attrib_map = {}
 | 
			
		||||
    plugin_tag_map = {}
 | 
			
		||||
 | 
			
		||||
    def get_params(self):
 | 
			
		||||
        return self.xml.find('{%s}params' % self.namespace)
 | 
			
		||||
 | 
			
		||||
    def set_params(self, params):
 | 
			
		||||
        self.append(params)
 | 
			
		||||
 | 
			
		||||
    def get_fault(self):
 | 
			
		||||
        return self.xml.find('{%s}fault' % self.namespace)
 | 
			
		||||
 | 
			
		||||
    def set_fault(self, fault):
 | 
			
		||||
        self.append(fault)
 | 
			
		||||
							
								
								
									
										9
									
								
								sleekxmpp/plugins/xep_0009/stanza/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								sleekxmpp/plugins/xep_0009/stanza/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,9 @@
 | 
			
		||||
"""
 | 
			
		||||
    SleekXMPP: The Sleek XMPP Library
 | 
			
		||||
    Copyright (C) 2011 Nathanael C. Fritz, Dann Martens (TOMOTON).
 | 
			
		||||
    This file is part of SleekXMPP.
 | 
			
		||||
 | 
			
		||||
    See the file LICENSE for copying permission.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
from sleekxmpp.plugins.xep_0009.stanza.RPC import RPCQuery, MethodCall, MethodResponse
 | 
			
		||||
@@ -1,329 +0,0 @@
 | 
			
		||||
"""
 | 
			
		||||
    SleekXMPP: The Sleek XMPP Library
 | 
			
		||||
    Copyright (C) 2010 Nathanael C. Fritz, Lance J.T. Stout
 | 
			
		||||
    This file is part of SleekXMPP.
 | 
			
		||||
 | 
			
		||||
    See the file LICENSE for copying permission.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
import logging
 | 
			
		||||
from . import base
 | 
			
		||||
from .. xmlstream.handler.callback import Callback
 | 
			
		||||
from .. xmlstream.matcher.xpath import MatchXPath
 | 
			
		||||
from .. xmlstream.stanzabase import registerStanzaPlugin, ElementBase, ET, JID
 | 
			
		||||
from .. stanza.iq import Iq
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
log = logging.getLogger(__name__)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class DiscoInfo(ElementBase):
 | 
			
		||||
    namespace = 'http://jabber.org/protocol/disco#info'
 | 
			
		||||
    name = 'query'
 | 
			
		||||
    plugin_attrib = 'disco_info'
 | 
			
		||||
    interfaces = set(('node', 'features', 'identities'))
 | 
			
		||||
 | 
			
		||||
    def getFeatures(self):
 | 
			
		||||
        features = []
 | 
			
		||||
        featuresXML = self.xml.findall('{%s}feature' % self.namespace)
 | 
			
		||||
        for feature in featuresXML:
 | 
			
		||||
            features.append(feature.attrib['var'])
 | 
			
		||||
        return features
 | 
			
		||||
 | 
			
		||||
    def setFeatures(self, features):
 | 
			
		||||
        self.delFeatures()
 | 
			
		||||
        for name in features:
 | 
			
		||||
            self.addFeature(name)
 | 
			
		||||
 | 
			
		||||
    def delFeatures(self):
 | 
			
		||||
        featuresXML = self.xml.findall('{%s}feature' % self.namespace)
 | 
			
		||||
        for feature in featuresXML:
 | 
			
		||||
            self.xml.remove(feature)
 | 
			
		||||
 | 
			
		||||
    def addFeature(self, feature):
 | 
			
		||||
        featureXML = ET.Element('{%s}feature' % self.namespace,
 | 
			
		||||
                    {'var': feature})
 | 
			
		||||
        self.xml.append(featureXML)
 | 
			
		||||
 | 
			
		||||
    def delFeature(self, feature):
 | 
			
		||||
        featuresXML = self.xml.findall('{%s}feature' % self.namespace)
 | 
			
		||||
        for featureXML in featuresXML:
 | 
			
		||||
            if featureXML.attrib['var'] == feature:
 | 
			
		||||
                self.xml.remove(featureXML)
 | 
			
		||||
 | 
			
		||||
    def getIdentities(self):
 | 
			
		||||
        ids = []
 | 
			
		||||
        idsXML = self.xml.findall('{%s}identity' % self.namespace)
 | 
			
		||||
        for idXML in idsXML:
 | 
			
		||||
            idData = (idXML.attrib['category'],
 | 
			
		||||
                  idXML.attrib['type'],
 | 
			
		||||
                  idXML.attrib.get('name', ''))
 | 
			
		||||
            ids.append(idData)
 | 
			
		||||
        return ids
 | 
			
		||||
 | 
			
		||||
    def setIdentities(self, ids):
 | 
			
		||||
        self.delIdentities()
 | 
			
		||||
        for idData in ids:
 | 
			
		||||
            self.addIdentity(*idData)
 | 
			
		||||
 | 
			
		||||
    def delIdentities(self):
 | 
			
		||||
        idsXML = self.xml.findall('{%s}identity' % self.namespace)
 | 
			
		||||
        for idXML in idsXML:
 | 
			
		||||
            self.xml.remove(idXML)
 | 
			
		||||
 | 
			
		||||
    def addIdentity(self, category, id_type, name=''):
 | 
			
		||||
        idXML = ET.Element('{%s}identity' % self.namespace,
 | 
			
		||||
                   {'category': category,
 | 
			
		||||
                    'type': id_type,
 | 
			
		||||
                    'name': name})
 | 
			
		||||
        self.xml.append(idXML)
 | 
			
		||||
 | 
			
		||||
    def delIdentity(self, category, id_type, name=''):
 | 
			
		||||
        idsXML = self.xml.findall('{%s}identity' % self.namespace)
 | 
			
		||||
        for idXML in idsXML:
 | 
			
		||||
            idData = (idXML.attrib['category'],
 | 
			
		||||
                  idXML.attrib['type'])
 | 
			
		||||
            delId = (category, id_type)
 | 
			
		||||
            if idData == delId:
 | 
			
		||||
                self.xml.remove(idXML)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class DiscoItems(ElementBase):
 | 
			
		||||
    namespace = 'http://jabber.org/protocol/disco#items'
 | 
			
		||||
    name = 'query'
 | 
			
		||||
    plugin_attrib = 'disco_items'
 | 
			
		||||
    interfaces = set(('node', 'items'))
 | 
			
		||||
 | 
			
		||||
    def getItems(self):
 | 
			
		||||
        items = []
 | 
			
		||||
        itemsXML = self.xml.findall('{%s}item' % self.namespace)
 | 
			
		||||
        for item in itemsXML:
 | 
			
		||||
            itemData = (item.attrib['jid'],
 | 
			
		||||
                    item.attrib.get('node'),
 | 
			
		||||
                    item.attrib.get('name'))
 | 
			
		||||
            items.append(itemData)
 | 
			
		||||
        return items
 | 
			
		||||
 | 
			
		||||
    def setItems(self, items):
 | 
			
		||||
        self.delItems()
 | 
			
		||||
        for item in items:
 | 
			
		||||
            self.addItem(*item)
 | 
			
		||||
 | 
			
		||||
    def delItems(self):
 | 
			
		||||
        itemsXML = self.xml.findall('{%s}item' % self.namespace)
 | 
			
		||||
        for item in itemsXML:
 | 
			
		||||
            self.xml.remove(item)
 | 
			
		||||
 | 
			
		||||
    def addItem(self, jid, node='', name=''):
 | 
			
		||||
        itemXML = ET.Element('{%s}item' % self.namespace, {'jid': jid})
 | 
			
		||||
        if name:
 | 
			
		||||
            itemXML.attrib['name'] = name
 | 
			
		||||
        if node:
 | 
			
		||||
            itemXML.attrib['node'] = node
 | 
			
		||||
        self.xml.append(itemXML)
 | 
			
		||||
 | 
			
		||||
    def delItem(self, jid, node=''):
 | 
			
		||||
        itemsXML = self.xml.findall('{%s}item' % self.namespace)
 | 
			
		||||
        for itemXML in itemsXML:
 | 
			
		||||
            itemData = (itemXML.attrib['jid'],
 | 
			
		||||
                    itemXML.attrib.get('node', ''))
 | 
			
		||||
            itemDel = (jid, node)
 | 
			
		||||
            if itemData == itemDel:
 | 
			
		||||
                self.xml.remove(itemXML)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class DiscoNode(object):
 | 
			
		||||
    """
 | 
			
		||||
    Collection object for grouping info and item information
 | 
			
		||||
    into nodes.
 | 
			
		||||
    """
 | 
			
		||||
    def __init__(self, name):
 | 
			
		||||
        self.name = name
 | 
			
		||||
        self.info = DiscoInfo()
 | 
			
		||||
        self.items = DiscoItems()
 | 
			
		||||
 | 
			
		||||
        self.info['node'] = name
 | 
			
		||||
        self.items['node'] = name
 | 
			
		||||
 | 
			
		||||
        # This is a bit like poor man's inheritance, but
 | 
			
		||||
        # to simplify adding information to the node we
 | 
			
		||||
        # map node functions to either the info or items
 | 
			
		||||
        # stanza objects.
 | 
			
		||||
        #
 | 
			
		||||
        # We don't want to make DiscoNode inherit from
 | 
			
		||||
        # DiscoInfo and DiscoItems because DiscoNode is
 | 
			
		||||
        # not an actual stanza, and doing so would create
 | 
			
		||||
        # confusion and potential bugs.
 | 
			
		||||
 | 
			
		||||
        self._map(self.items, 'items', ['get', 'set', 'del'])
 | 
			
		||||
        self._map(self.items, 'item', ['add', 'del'])
 | 
			
		||||
        self._map(self.info, 'identities', ['get', 'set', 'del'])
 | 
			
		||||
        self._map(self.info, 'identity', ['add', 'del'])
 | 
			
		||||
        self._map(self.info, 'features', ['get', 'set', 'del'])
 | 
			
		||||
        self._map(self.info, 'feature', ['add', 'del'])
 | 
			
		||||
 | 
			
		||||
    def isEmpty(self):
 | 
			
		||||
        """
 | 
			
		||||
        Test if the node contains any information. Useful for
 | 
			
		||||
        determining if a node can be deleted.
 | 
			
		||||
        """
 | 
			
		||||
        ids = self.getIdentities()
 | 
			
		||||
        features = self.getFeatures()
 | 
			
		||||
        items = self.getItems()
 | 
			
		||||
 | 
			
		||||
        if not ids and not features and not items:
 | 
			
		||||
            return True
 | 
			
		||||
        return False
 | 
			
		||||
 | 
			
		||||
    def _map(self, obj, interface, access):
 | 
			
		||||
        """
 | 
			
		||||
        Map functions of the form obj.accessInterface
 | 
			
		||||
        to self.accessInterface for each given access type.
 | 
			
		||||
        """
 | 
			
		||||
        interface = interface.title()
 | 
			
		||||
        for access_type in access:
 | 
			
		||||
            method = access_type + interface
 | 
			
		||||
            if hasattr(obj, method):
 | 
			
		||||
                setattr(self, method, getattr(obj, method))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class xep_0030(base.base_plugin):
 | 
			
		||||
    """
 | 
			
		||||
    XEP-0030 Service Discovery
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    def plugin_init(self):
 | 
			
		||||
        self.xep = '0030'
 | 
			
		||||
        self.description = 'Service Discovery'
 | 
			
		||||
 | 
			
		||||
        self.xmpp.registerHandler(
 | 
			
		||||
            Callback('Disco Items',
 | 
			
		||||
                 MatchXPath('{%s}iq/{%s}query' % (self.xmpp.default_ns,
 | 
			
		||||
                                  DiscoItems.namespace)),
 | 
			
		||||
                 self.handle_item_query))
 | 
			
		||||
 | 
			
		||||
        self.xmpp.registerHandler(
 | 
			
		||||
            Callback('Disco Info',
 | 
			
		||||
                 MatchXPath('{%s}iq/{%s}query' % (self.xmpp.default_ns,
 | 
			
		||||
                                  DiscoInfo.namespace)),
 | 
			
		||||
                 self.handle_info_query))
 | 
			
		||||
 | 
			
		||||
        registerStanzaPlugin(Iq, DiscoInfo)
 | 
			
		||||
        registerStanzaPlugin(Iq, DiscoItems)
 | 
			
		||||
 | 
			
		||||
        self.xmpp.add_event_handler('disco_items_request', self.handle_disco_items)
 | 
			
		||||
        self.xmpp.add_event_handler('disco_info_request', self.handle_disco_info)
 | 
			
		||||
 | 
			
		||||
        self.nodes = {'main': DiscoNode('main')}
 | 
			
		||||
 | 
			
		||||
    def add_node(self, node):
 | 
			
		||||
        if node not in self.nodes:
 | 
			
		||||
            self.nodes[node] = DiscoNode(node)
 | 
			
		||||
 | 
			
		||||
    def del_node(self, node):
 | 
			
		||||
        if node in self.nodes:
 | 
			
		||||
            del self.nodes[node]
 | 
			
		||||
 | 
			
		||||
    def handle_item_query(self, iq):
 | 
			
		||||
        if iq['type'] == 'get':
 | 
			
		||||
            log.debug("Items requested by %s" % iq['from'])
 | 
			
		||||
            self.xmpp.event('disco_items_request', iq)
 | 
			
		||||
        elif iq['type'] == 'result':
 | 
			
		||||
            log.debug("Items result from %s" % iq['from'])
 | 
			
		||||
            self.xmpp.event('disco_items', iq)
 | 
			
		||||
 | 
			
		||||
    def handle_info_query(self, iq):
 | 
			
		||||
        if iq['type'] == 'get':
 | 
			
		||||
            log.debug("Info requested by %s" % iq['from'])
 | 
			
		||||
            self.xmpp.event('disco_info_request', iq)
 | 
			
		||||
        elif iq['type'] == 'result':
 | 
			
		||||
            log.debug("Info result from %s" % iq['from'])
 | 
			
		||||
            self.xmpp.event('disco_info', iq)
 | 
			
		||||
 | 
			
		||||
    def handle_disco_info(self, iq, forwarded=False):
 | 
			
		||||
        """
 | 
			
		||||
        A default handler for disco#info requests. If another
 | 
			
		||||
        handler is registered, this one will defer and not run.
 | 
			
		||||
        """
 | 
			
		||||
        if not forwarded and self.xmpp.event_handled('disco_info_request'):
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
        node_name = iq['disco_info']['node']
 | 
			
		||||
        if not node_name:
 | 
			
		||||
            node_name = 'main'
 | 
			
		||||
 | 
			
		||||
        log.debug("Using default handler for disco#info on node '%s'." % node_name)
 | 
			
		||||
 | 
			
		||||
        if node_name in self.nodes:
 | 
			
		||||
            node = self.nodes[node_name]
 | 
			
		||||
            iq.reply().setPayload(node.info.xml).send()
 | 
			
		||||
        else:
 | 
			
		||||
            log.debug("Node %s requested, but does not exist." % node_name)
 | 
			
		||||
            iq.reply().error().setPayload(iq['disco_info'].xml)
 | 
			
		||||
            iq['error']['code'] = '404'
 | 
			
		||||
            iq['error']['type'] = 'cancel'
 | 
			
		||||
            iq['error']['condition'] = 'item-not-found'
 | 
			
		||||
            iq.send()
 | 
			
		||||
 | 
			
		||||
    def handle_disco_items(self, iq, forwarded=False):
 | 
			
		||||
        """
 | 
			
		||||
        A default handler for disco#items requests. If another
 | 
			
		||||
        handler is registered, this one will defer and not run.
 | 
			
		||||
 | 
			
		||||
        If this handler is called by your own custom handler with
 | 
			
		||||
        forwarded set to True, then it will run as normal.
 | 
			
		||||
        """
 | 
			
		||||
        if not forwarded and self.xmpp.event_handled('disco_items_request'):
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
        node_name = iq['disco_items']['node']
 | 
			
		||||
        if not node_name:
 | 
			
		||||
            node_name = 'main'
 | 
			
		||||
 | 
			
		||||
        log.debug("Using default handler for disco#items on node '%s'." % node_name)
 | 
			
		||||
 | 
			
		||||
        if node_name in self.nodes:
 | 
			
		||||
            node = self.nodes[node_name]
 | 
			
		||||
            iq.reply().setPayload(node.items.xml).send()
 | 
			
		||||
        else:
 | 
			
		||||
            log.debug("Node %s requested, but does not exist." % node_name)
 | 
			
		||||
            iq.reply().error().setPayload(iq['disco_items'].xml)
 | 
			
		||||
            iq['error']['code'] = '404'
 | 
			
		||||
            iq['error']['type'] = 'cancel'
 | 
			
		||||
            iq['error']['condition'] = 'item-not-found'
 | 
			
		||||
            iq.send()
 | 
			
		||||
 | 
			
		||||
    # Older interface methods for backwards compatibility
 | 
			
		||||
 | 
			
		||||
    def getInfo(self, jid, node='', dfrom=None):
 | 
			
		||||
        iq = self.xmpp.Iq()
 | 
			
		||||
        iq['type'] = 'get'
 | 
			
		||||
        iq['to'] = jid
 | 
			
		||||
        iq['from'] = dfrom
 | 
			
		||||
        iq['disco_info']['node'] = node
 | 
			
		||||
        return iq.send()
 | 
			
		||||
 | 
			
		||||
    def getItems(self, jid, node='', dfrom=None):
 | 
			
		||||
        iq = self.xmpp.Iq()
 | 
			
		||||
        iq['type'] = 'get'
 | 
			
		||||
        iq['to'] = jid
 | 
			
		||||
        iq['from'] = dfrom
 | 
			
		||||
        iq['disco_items']['node'] = node
 | 
			
		||||
        return iq.send()
 | 
			
		||||
 | 
			
		||||
    def add_feature(self, feature, node='main'):
 | 
			
		||||
        self.add_node(node)
 | 
			
		||||
        self.nodes[node].addFeature(feature)
 | 
			
		||||
 | 
			
		||||
    def add_identity(self, category='', itype='', name='', node='main'):
 | 
			
		||||
        self.add_node(node)
 | 
			
		||||
        self.nodes[node].addIdentity(category=category,
 | 
			
		||||
                         id_type=itype,
 | 
			
		||||
                         name=name)
 | 
			
		||||
 | 
			
		||||
    def add_item(self, jid=None, name='', node='main', subnode=''):
 | 
			
		||||
        self.add_node(node)
 | 
			
		||||
        self.add_node(subnode)
 | 
			
		||||
        if jid is None:
 | 
			
		||||
            jid = self.xmpp.fulljid
 | 
			
		||||
        self.nodes[node].addItem(jid=jid, name=name, node=subnode)
 | 
			
		||||
							
								
								
									
										12
									
								
								sleekxmpp/plugins/xep_0030/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								sleekxmpp/plugins/xep_0030/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,12 @@
 | 
			
		||||
"""
 | 
			
		||||
    SleekXMPP: The Sleek XMPP Library
 | 
			
		||||
    Copyright (C) 2010 Nathanael C. Fritz, Lance J.T. Stout
 | 
			
		||||
    This file is part of SleekXMPP.
 | 
			
		||||
 | 
			
		||||
    See the file LICENSE for copying permission.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
from sleekxmpp.plugins.xep_0030 import stanza
 | 
			
		||||
from sleekxmpp.plugins.xep_0030.stanza import DiscoInfo, DiscoItems
 | 
			
		||||
from sleekxmpp.plugins.xep_0030.static import StaticDisco
 | 
			
		||||
from sleekxmpp.plugins.xep_0030.disco import xep_0030
 | 
			
		||||
							
								
								
									
										623
									
								
								sleekxmpp/plugins/xep_0030/disco.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										623
									
								
								sleekxmpp/plugins/xep_0030/disco.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,623 @@
 | 
			
		||||
"""
 | 
			
		||||
    SleekXMPP: The Sleek XMPP Library
 | 
			
		||||
    Copyright (C) 2010 Nathanael C. Fritz, Lance J.T. Stout
 | 
			
		||||
    This file is part of SleekXMPP.
 | 
			
		||||
 | 
			
		||||
    See the file LICENSE for copying permission.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
import logging
 | 
			
		||||
 | 
			
		||||
import sleekxmpp
 | 
			
		||||
from sleekxmpp import Iq
 | 
			
		||||
from sleekxmpp.exceptions import XMPPError
 | 
			
		||||
from sleekxmpp.plugins.base import base_plugin
 | 
			
		||||
from sleekxmpp.xmlstream.handler import Callback
 | 
			
		||||
from sleekxmpp.xmlstream.matcher import StanzaPath
 | 
			
		||||
from sleekxmpp.xmlstream import register_stanza_plugin, ElementBase, ET, JID
 | 
			
		||||
from sleekxmpp.plugins.xep_0030 import DiscoInfo, DiscoItems, StaticDisco
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
log = logging.getLogger(__name__)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class xep_0030(base_plugin):
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    XEP-0030: Service Discovery
 | 
			
		||||
 | 
			
		||||
    Service discovery in XMPP allows entities to discover information about
 | 
			
		||||
    other agents in the network, such as the feature sets supported by a
 | 
			
		||||
    client, or signposts to other, related entities.
 | 
			
		||||
 | 
			
		||||
    Also see <http://www.xmpp.org/extensions/xep-0030.html>.
 | 
			
		||||
 | 
			
		||||
    The XEP-0030 plugin works using a hierarchy of dynamic
 | 
			
		||||
    node handlers, ranging from global handlers to specific
 | 
			
		||||
    JID+node handlers. The default set of handlers operate
 | 
			
		||||
    in a static manner, storing disco information in memory.
 | 
			
		||||
    However, custom handlers may use any available backend
 | 
			
		||||
    storage mechanism desired, such as SQLite or Redis.
 | 
			
		||||
 | 
			
		||||
    Node handler hierarchy:
 | 
			
		||||
        JID   | Node  | Level
 | 
			
		||||
        ---------------------
 | 
			
		||||
        None  | None  | Global
 | 
			
		||||
        Given | None  | All nodes for the JID
 | 
			
		||||
        None  | Given | Node on self.xmpp.boundjid
 | 
			
		||||
        Given | Given | A single node
 | 
			
		||||
 | 
			
		||||
    Stream Handlers:
 | 
			
		||||
        Disco Info  -- Any Iq stanze that includes a query with the
 | 
			
		||||
                       namespace http://jabber.org/protocol/disco#info.
 | 
			
		||||
        Disco Items -- Any Iq stanze that includes a query with the
 | 
			
		||||
                       namespace http://jabber.org/protocol/disco#items.
 | 
			
		||||
 | 
			
		||||
    Events:
 | 
			
		||||
        disco_info         -- Received a disco#info Iq query result.
 | 
			
		||||
        disco_items        -- Received a disco#items Iq query result.
 | 
			
		||||
        disco_info_query   -- Received a disco#info Iq query request.
 | 
			
		||||
        disco_items_query  -- Received a disco#items Iq query request.
 | 
			
		||||
 | 
			
		||||
    Attributes:
 | 
			
		||||
        stanza           -- A reference to the module containing the
 | 
			
		||||
                            stanza classes provided by this plugin.
 | 
			
		||||
        static           -- Object containing the default set of
 | 
			
		||||
                            static node handlers.
 | 
			
		||||
        default_handlers -- A dictionary mapping operations to the default
 | 
			
		||||
                            global handler (by default, the static handlers).
 | 
			
		||||
        xmpp             -- The main SleekXMPP object.
 | 
			
		||||
 | 
			
		||||
    Methods:
 | 
			
		||||
        set_node_handler -- Assign a handler to a JID/node combination.
 | 
			
		||||
        del_node_handler -- Remove a handler from a JID/node combination.
 | 
			
		||||
        get_info         -- Retrieve disco#info data, locally or remote.
 | 
			
		||||
        get_items        -- Retrieve disco#items data, locally or remote.
 | 
			
		||||
        set_identities   --
 | 
			
		||||
        set_features     --
 | 
			
		||||
        set_items        --
 | 
			
		||||
        del_items        --
 | 
			
		||||
        del_identity     --
 | 
			
		||||
        del_feature      --
 | 
			
		||||
        del_item         --
 | 
			
		||||
        add_identity     --
 | 
			
		||||
        add_feature      --
 | 
			
		||||
        add_item         --
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    def plugin_init(self):
 | 
			
		||||
        """
 | 
			
		||||
        Start the XEP-0030 plugin.
 | 
			
		||||
        """
 | 
			
		||||
        self.xep = '0030'
 | 
			
		||||
        self.description = 'Service Discovery'
 | 
			
		||||
        self.stanza = sleekxmpp.plugins.xep_0030.stanza
 | 
			
		||||
 | 
			
		||||
        self.xmpp.register_handler(
 | 
			
		||||
                Callback('Disco Info',
 | 
			
		||||
                         StanzaPath('iq/disco_info'),
 | 
			
		||||
                         self._handle_disco_info))
 | 
			
		||||
 | 
			
		||||
        self.xmpp.register_handler(
 | 
			
		||||
                Callback('Disco Items',
 | 
			
		||||
                         StanzaPath('iq/disco_items'),
 | 
			
		||||
                         self._handle_disco_items))
 | 
			
		||||
 | 
			
		||||
        register_stanza_plugin(Iq, DiscoInfo)
 | 
			
		||||
        register_stanza_plugin(Iq, DiscoItems)
 | 
			
		||||
 | 
			
		||||
        self.static = StaticDisco(self.xmpp)
 | 
			
		||||
 | 
			
		||||
        self._disco_ops = ['get_info', 'set_identities', 'set_features',
 | 
			
		||||
                           'get_items', 'set_items', 'del_items',
 | 
			
		||||
                           'add_identity', 'del_identity', 'add_feature',
 | 
			
		||||
                           'del_feature', 'add_item', 'del_item',
 | 
			
		||||
                           'del_identities', 'del_features']
 | 
			
		||||
        self.default_handlers = {}
 | 
			
		||||
        self._handlers = {}
 | 
			
		||||
        for op in self._disco_ops:
 | 
			
		||||
            self._add_disco_op(op, getattr(self.static, op))
 | 
			
		||||
 | 
			
		||||
    def post_init(self):
 | 
			
		||||
        """Handle cross-plugin dependencies."""
 | 
			
		||||
        base_plugin.post_init(self)
 | 
			
		||||
        if 'xep_0059' in self.xmpp.plugin:
 | 
			
		||||
            register_stanza_plugin(DiscoItems,
 | 
			
		||||
                                   self.xmpp['xep_0059'].stanza.Set)
 | 
			
		||||
 | 
			
		||||
    def _add_disco_op(self, op, default_handler):
 | 
			
		||||
        self.default_handlers[op] = default_handler
 | 
			
		||||
        self._handlers[op] = {'global': default_handler,
 | 
			
		||||
                              'jid': {},
 | 
			
		||||
                              'node': {}}
 | 
			
		||||
 | 
			
		||||
    def set_node_handler(self, htype, jid=None, node=None, handler=None):
 | 
			
		||||
        """
 | 
			
		||||
        Add a node handler for the given hierarchy level and
 | 
			
		||||
        handler type.
 | 
			
		||||
 | 
			
		||||
        Node handlers are ordered in a hierarchy where the
 | 
			
		||||
        most specific handler is executed. Thus, a fallback,
 | 
			
		||||
        global handler can be used for the majority of cases
 | 
			
		||||
        with a few node specific handler that override the
 | 
			
		||||
        global behavior.
 | 
			
		||||
 | 
			
		||||
        Node handler hierarchy:
 | 
			
		||||
            JID   | Node  | Level
 | 
			
		||||
            ---------------------
 | 
			
		||||
            None  | None  | Global
 | 
			
		||||
            Given | None  | All nodes for the JID
 | 
			
		||||
            None  | Given | Node on self.xmpp.boundjid
 | 
			
		||||
            Given | Given | A single node
 | 
			
		||||
 | 
			
		||||
        Handler types:
 | 
			
		||||
            get_info
 | 
			
		||||
            get_items
 | 
			
		||||
            set_identities
 | 
			
		||||
            set_features
 | 
			
		||||
            set_items
 | 
			
		||||
            del_items
 | 
			
		||||
            del_identities
 | 
			
		||||
            del_identity
 | 
			
		||||
            del_feature
 | 
			
		||||
            del_features
 | 
			
		||||
            del_item
 | 
			
		||||
            add_identity
 | 
			
		||||
            add_feature
 | 
			
		||||
            add_item
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            htype   -- The operation provided by the handler.
 | 
			
		||||
            jid     -- The JID the handler applies to. May be narrowed
 | 
			
		||||
                       further if a node is given.
 | 
			
		||||
            node    -- The particular node the handler is for. If no JID
 | 
			
		||||
                       is given, then the self.xmpp.boundjid.full is
 | 
			
		||||
                       assumed.
 | 
			
		||||
            handler -- The handler function to use.
 | 
			
		||||
        """
 | 
			
		||||
        if htype not in self._disco_ops:
 | 
			
		||||
            return
 | 
			
		||||
        if jid is None and node is None:
 | 
			
		||||
            self._handlers[htype]['global'] = handler
 | 
			
		||||
        elif node is None:
 | 
			
		||||
            self._handlers[htype]['jid'][jid] = handler
 | 
			
		||||
        elif jid is None:
 | 
			
		||||
            if self.xmpp.is_component:
 | 
			
		||||
                jid = self.xmpp.boundjid.full
 | 
			
		||||
            else:
 | 
			
		||||
                jid = self.xmpp.boundjid.bare
 | 
			
		||||
            self._handlers[htype]['node'][(jid, node)] = handler
 | 
			
		||||
        else:
 | 
			
		||||
            self._handlers[htype]['node'][(jid, node)] = handler
 | 
			
		||||
 | 
			
		||||
    def del_node_handler(self, htype, jid, node):
 | 
			
		||||
        """
 | 
			
		||||
        Remove a handler type for a JID and node combination.
 | 
			
		||||
 | 
			
		||||
        The next handler in the hierarchy will be used if one
 | 
			
		||||
        exists. If removing the global handler, make sure that
 | 
			
		||||
        other handlers exist to process existing nodes.
 | 
			
		||||
 | 
			
		||||
        Node handler hierarchy:
 | 
			
		||||
            JID   | Node  | Level
 | 
			
		||||
            ---------------------
 | 
			
		||||
            None  | None  | Global
 | 
			
		||||
            Given | None  | All nodes for the JID
 | 
			
		||||
            None  | Given | Node on self.xmpp.boundjid
 | 
			
		||||
            Given | Given | A single node
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            htype -- The type of handler to remove.
 | 
			
		||||
            jid   -- The JID from which to remove the handler.
 | 
			
		||||
            node  -- The node from which to remove the handler.
 | 
			
		||||
        """
 | 
			
		||||
        self.set_node_handler(htype, jid, node, None)
 | 
			
		||||
 | 
			
		||||
    def restore_defaults(self, jid=None, node=None, handlers=None):
 | 
			
		||||
        """
 | 
			
		||||
        Change all or some of a node's handlers to the default
 | 
			
		||||
        handlers. Useful for manually overriding the contents
 | 
			
		||||
        of a node that would otherwise be handled by a JID level
 | 
			
		||||
        or global level dynamic handler.
 | 
			
		||||
 | 
			
		||||
        The default is to use the built-in static handlers, but that
 | 
			
		||||
        may be changed by modifying self.default_handlers.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            jid      -- The JID owning the node to modify.
 | 
			
		||||
            node     -- The node to change to using static handlers.
 | 
			
		||||
            handlers -- Optional list of handlers to change to the
 | 
			
		||||
                        default version. If provided, only these
 | 
			
		||||
                        handlers will be changed. Otherwise, all
 | 
			
		||||
                        handlers will use the default version.
 | 
			
		||||
        """
 | 
			
		||||
        if handlers is None:
 | 
			
		||||
            handlers = self._disco_ops
 | 
			
		||||
        for op in handlers:
 | 
			
		||||
            self.del_node_handler(op, jid, node)
 | 
			
		||||
            self.set_node_handler(op, jid, node, self.default_handlers[op])
 | 
			
		||||
 | 
			
		||||
    def get_info(self, jid=None, node=None, local=False, **kwargs):
 | 
			
		||||
        """
 | 
			
		||||
        Retrieve the disco#info results from a given JID/node combination.
 | 
			
		||||
 | 
			
		||||
        Info may be retrieved from both local resources and remote agents;
 | 
			
		||||
        the local parameter indicates if the information should be gathered
 | 
			
		||||
        by executing the local node handlers, or if a disco#info stanza
 | 
			
		||||
        must be generated and sent.
 | 
			
		||||
 | 
			
		||||
        If requesting items from a local JID/node, then only a DiscoInfo
 | 
			
		||||
        stanza will be returned. Otherwise, an Iq stanza will be returned.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            jid      -- Request info from this JID.
 | 
			
		||||
            node     -- The particular node to query.
 | 
			
		||||
            local    -- If true, then the query is for a JID/node
 | 
			
		||||
                        combination handled by this Sleek instance and
 | 
			
		||||
                        no stanzas need to be sent.
 | 
			
		||||
                        Otherwise, a disco stanza must be sent to the
 | 
			
		||||
                        remove JID to retrieve the info.
 | 
			
		||||
            ifrom    -- Specifiy the sender's JID.
 | 
			
		||||
            block    -- If true, block and wait for the stanzas' reply.
 | 
			
		||||
            timeout  -- The time in seconds to block while waiting for
 | 
			
		||||
                        a reply. If None, then wait indefinitely. The
 | 
			
		||||
                        timeout value is only used when block=True.
 | 
			
		||||
            callback -- Optional callback to execute when a reply is
 | 
			
		||||
                        received instead of blocking and waiting for
 | 
			
		||||
                        the reply.
 | 
			
		||||
        """
 | 
			
		||||
        if local or jid is None:
 | 
			
		||||
            log.debug("Looking up local disco#info data " + \
 | 
			
		||||
                      "for %s, node %s." % (jid, node))
 | 
			
		||||
            info = self._run_node_handler('get_info', jid, node, kwargs)
 | 
			
		||||
            return self._fix_default_info(info)
 | 
			
		||||
 | 
			
		||||
        iq = self.xmpp.Iq()
 | 
			
		||||
        # Check dfrom parameter for backwards compatibility
 | 
			
		||||
        iq['from'] = kwargs.get('ifrom', kwargs.get('dfrom', ''))
 | 
			
		||||
        iq['to'] = jid
 | 
			
		||||
        iq['type'] = 'get'
 | 
			
		||||
        iq['disco_info']['node'] = node if node else ''
 | 
			
		||||
        return iq.send(timeout=kwargs.get('timeout', None),
 | 
			
		||||
                       block=kwargs.get('block', True),
 | 
			
		||||
                       callback=kwargs.get('callback', None))
 | 
			
		||||
 | 
			
		||||
    def get_items(self, jid=None, node=None, local=False, **kwargs):
 | 
			
		||||
        """
 | 
			
		||||
        Retrieve the disco#items results from a given JID/node combination.
 | 
			
		||||
 | 
			
		||||
        Items may be retrieved from both local resources and remote agents;
 | 
			
		||||
        the local parameter indicates if the items should be gathered by
 | 
			
		||||
        executing the local node handlers, or if a disco#items stanza must
 | 
			
		||||
        be generated and sent.
 | 
			
		||||
 | 
			
		||||
        If requesting items from a local JID/node, then only a DiscoItems
 | 
			
		||||
        stanza will be returned. Otherwise, an Iq stanza will be returned.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            jid      -- Request info from this JID.
 | 
			
		||||
            node     -- The particular node to query.
 | 
			
		||||
            local    -- If true, then the query is for a JID/node
 | 
			
		||||
                        combination handled by this Sleek instance and
 | 
			
		||||
                        no stanzas need to be sent.
 | 
			
		||||
                        Otherwise, a disco stanza must be sent to the
 | 
			
		||||
                        remove JID to retrieve the items.
 | 
			
		||||
            ifrom    -- Specifiy the sender's JID.
 | 
			
		||||
            block    -- If true, block and wait for the stanzas' reply.
 | 
			
		||||
            timeout  -- The time in seconds to block while waiting for
 | 
			
		||||
                        a reply. If None, then wait indefinitely.
 | 
			
		||||
            callback -- Optional callback to execute when a reply is
 | 
			
		||||
                        received instead of blocking and waiting for
 | 
			
		||||
                        the reply.
 | 
			
		||||
            iterator -- If True, return a result set iterator using
 | 
			
		||||
                        the XEP-0059 plugin, if the plugin is loaded.
 | 
			
		||||
                        Otherwise the parameter is ignored.
 | 
			
		||||
        """
 | 
			
		||||
        if local or jid is None:
 | 
			
		||||
            return self._run_node_handler('get_items', jid, node, kwargs)
 | 
			
		||||
 | 
			
		||||
        iq = self.xmpp.Iq()
 | 
			
		||||
        # Check dfrom parameter for backwards compatibility
 | 
			
		||||
        iq['from'] = kwargs.get('ifrom', kwargs.get('dfrom', ''))
 | 
			
		||||
        iq['to'] = jid
 | 
			
		||||
        iq['type'] = 'get'
 | 
			
		||||
        iq['disco_items']['node'] = node if node else ''
 | 
			
		||||
        if kwargs.get('iterator', False) and self.xmpp['xep_0059']:
 | 
			
		||||
            return self.xmpp['xep_0059'].iterate(iq, 'disco_items')
 | 
			
		||||
        else:
 | 
			
		||||
            return iq.send(timeout=kwargs.get('timeout', None),
 | 
			
		||||
                           block=kwargs.get('block', True),
 | 
			
		||||
                           callback=kwargs.get('callback', None))
 | 
			
		||||
 | 
			
		||||
    def set_items(self, jid=None, node=None, **kwargs):
 | 
			
		||||
        """
 | 
			
		||||
        Set or replace all items for the specified JID/node combination.
 | 
			
		||||
 | 
			
		||||
        The given items must be in a list or set where each item is a
 | 
			
		||||
        tuple of the form: (jid, node, name).
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            jid   -- The JID to modify.
 | 
			
		||||
            node  -- Optional node to modify.
 | 
			
		||||
            items -- A series of items in tuple format.
 | 
			
		||||
        """
 | 
			
		||||
        self._run_node_handler('set_items', jid, node, kwargs)
 | 
			
		||||
 | 
			
		||||
    def del_items(self, jid=None, node=None, **kwargs):
 | 
			
		||||
        """
 | 
			
		||||
        Remove all items from the given JID/node combination.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            jid  -- The JID to modify.
 | 
			
		||||
            node -- Optional node to modify.
 | 
			
		||||
        """
 | 
			
		||||
        self._run_node_handler('del_items', jid, node, kwargs)
 | 
			
		||||
 | 
			
		||||
    def add_item(self, jid='', name='', node=None, subnode='', ijid=None):
 | 
			
		||||
        """
 | 
			
		||||
        Add a new item element to the given JID/node combination.
 | 
			
		||||
 | 
			
		||||
        Each item is required to have a JID, but may also specify
 | 
			
		||||
        a node value to reference non-addressable entities.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            jid  -- The JID for the item.
 | 
			
		||||
            name  -- Optional name for the item.
 | 
			
		||||
            node  -- The node to modify.
 | 
			
		||||
            subnode -- Optional node for the item.
 | 
			
		||||
            ijid   -- The JID to modify.
 | 
			
		||||
        """
 | 
			
		||||
        if not jid:
 | 
			
		||||
            jid = self.xmpp.boundjid.full
 | 
			
		||||
        kwargs = {'ijid': jid,
 | 
			
		||||
                  'name': name,
 | 
			
		||||
                  'inode': subnode}
 | 
			
		||||
        self._run_node_handler('add_item', ijid, node, kwargs)
 | 
			
		||||
 | 
			
		||||
    def del_item(self, jid=None, node=None, **kwargs):
 | 
			
		||||
        """
 | 
			
		||||
        Remove a single item from the given JID/node combination.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            jid   -- The JID to modify.
 | 
			
		||||
            node  -- The node to modify.
 | 
			
		||||
            ijid  -- The item's JID.
 | 
			
		||||
            inode -- The item's node.
 | 
			
		||||
        """
 | 
			
		||||
        self._run_node_handler('del_item', jid, node, kwargs)
 | 
			
		||||
 | 
			
		||||
    def add_identity(self, category='', itype='', name='',
 | 
			
		||||
                     node=None, jid=None, lang=None):
 | 
			
		||||
        """
 | 
			
		||||
        Add a new identity to the given JID/node combination.
 | 
			
		||||
 | 
			
		||||
        Each identity must be unique in terms of all four identity
 | 
			
		||||
        components: category, type, name, and language.
 | 
			
		||||
 | 
			
		||||
        Multiple, identical category/type pairs are allowed only
 | 
			
		||||
        if the xml:lang values are different. Likewise, multiple
 | 
			
		||||
        category/type/xml:lang pairs are allowed so long as the
 | 
			
		||||
        names are different. A category and type is always required.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            category -- The identity's category.
 | 
			
		||||
            itype    -- The identity's type.
 | 
			
		||||
            name     -- Optional name for the identity.
 | 
			
		||||
            lang     -- Optional two-letter language code.
 | 
			
		||||
            node     -- The node to modify.
 | 
			
		||||
            jid      -- The JID to modify.
 | 
			
		||||
        """
 | 
			
		||||
        kwargs = {'category': category,
 | 
			
		||||
                  'itype': itype,
 | 
			
		||||
                  'name': name,
 | 
			
		||||
                  'lang': lang}
 | 
			
		||||
        self._run_node_handler('add_identity', jid, node, kwargs)
 | 
			
		||||
 | 
			
		||||
    def add_feature(self, feature, node=None, jid=None):
 | 
			
		||||
        """
 | 
			
		||||
        Add a feature to a JID/node combination.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            feature -- The namespace of the supported feature.
 | 
			
		||||
            node    -- The node to modify.
 | 
			
		||||
            jid     -- The JID to modify.
 | 
			
		||||
        """
 | 
			
		||||
        kwargs = {'feature': feature}
 | 
			
		||||
        self._run_node_handler('add_feature', jid, node, kwargs)
 | 
			
		||||
 | 
			
		||||
    def del_identity(self, jid=None, node=None, **kwargs):
 | 
			
		||||
        """
 | 
			
		||||
        Remove an identity from the given JID/node combination.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            jid      -- The JID to modify.
 | 
			
		||||
            node     -- The node to modify.
 | 
			
		||||
            category -- The identity's category.
 | 
			
		||||
            itype    -- The identity's type value.
 | 
			
		||||
            name     -- Optional, human readable name for the identity.
 | 
			
		||||
            lang     -- Optional, the identity's xml:lang value.
 | 
			
		||||
        """
 | 
			
		||||
        self._run_node_handler('del_identity', jid, node, kwargs)
 | 
			
		||||
 | 
			
		||||
    def del_feature(self, jid=None, node=None, **kwargs):
 | 
			
		||||
        """
 | 
			
		||||
        Remove a feature from a given JID/node combination.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            jid     -- The JID to modify.
 | 
			
		||||
            node    -- The node to modify.
 | 
			
		||||
            feature -- The feature's namespace.
 | 
			
		||||
        """
 | 
			
		||||
        self._run_node_handler('del_feature', jid, node, kwargs)
 | 
			
		||||
 | 
			
		||||
    def set_identities(self, jid=None, node=None, **kwargs):
 | 
			
		||||
        """
 | 
			
		||||
        Add or replace all identities for the given JID/node combination.
 | 
			
		||||
 | 
			
		||||
        The identities must be in a set where each identity is a tuple
 | 
			
		||||
        of the form: (category, type, lang, name)
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            jid        -- The JID to modify.
 | 
			
		||||
            node       -- The node to modify.
 | 
			
		||||
            identities -- A set of identities in tuple form.
 | 
			
		||||
            lang       -- Optional, xml:lang value.
 | 
			
		||||
        """
 | 
			
		||||
        self._run_node_handler('set_identities', jid, node, kwargs)
 | 
			
		||||
 | 
			
		||||
    def del_identities(self, jid=None, node=None, **kwargs):
 | 
			
		||||
        """
 | 
			
		||||
        Remove all identities for a JID/node combination.
 | 
			
		||||
 | 
			
		||||
        If a language is specified, only identities using that
 | 
			
		||||
        language will be removed.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            jid  -- The JID to modify.
 | 
			
		||||
            node -- The node to modify.
 | 
			
		||||
            lang -- Optional. If given, only remove identities
 | 
			
		||||
                    using this xml:lang value.
 | 
			
		||||
        """
 | 
			
		||||
        self._run_node_handler('del_identities', jid, node, kwargs)
 | 
			
		||||
 | 
			
		||||
    def set_features(self, jid=None, node=None, **kwargs):
 | 
			
		||||
        """
 | 
			
		||||
        Add or replace the set of supported features
 | 
			
		||||
        for a JID/node combination.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            jid      -- The JID to modify.
 | 
			
		||||
            node     -- The node to modify.
 | 
			
		||||
            features -- The new set of supported features.
 | 
			
		||||
        """
 | 
			
		||||
        self._run_node_handler('set_features', jid, node, kwargs)
 | 
			
		||||
 | 
			
		||||
    def del_features(self, jid=None, node=None, **kwargs):
 | 
			
		||||
        """
 | 
			
		||||
        Remove all features from a JID/node combination.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            jid  -- The JID to modify.
 | 
			
		||||
            node -- The node to modify.
 | 
			
		||||
        """
 | 
			
		||||
        self._run_node_handler('del_features', jid, node, kwargs)
 | 
			
		||||
 | 
			
		||||
    def _run_node_handler(self, htype, jid, node, data={}):
 | 
			
		||||
        """
 | 
			
		||||
        Execute the most specific node handler for the given
 | 
			
		||||
        JID/node combination.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            htype -- The handler type to execute.
 | 
			
		||||
            jid   -- The JID requested.
 | 
			
		||||
            node  -- The node requested.
 | 
			
		||||
            data  -- Optional, custom data to pass to the handler.
 | 
			
		||||
        """
 | 
			
		||||
        if jid is None:
 | 
			
		||||
            if self.xmpp.is_component:
 | 
			
		||||
                jid = self.xmpp.boundjid.full
 | 
			
		||||
            else:
 | 
			
		||||
                jid = self.xmpp.boundjid.bare
 | 
			
		||||
        if node is None:
 | 
			
		||||
            node = ''
 | 
			
		||||
 | 
			
		||||
        if self._handlers[htype]['node'].get((jid, node), False):
 | 
			
		||||
            return self._handlers[htype]['node'][(jid, node)](jid, node, data)
 | 
			
		||||
        elif self._handlers[htype]['jid'].get(jid, False):
 | 
			
		||||
            return self._handlers[htype]['jid'][jid](jid, node, data)
 | 
			
		||||
        elif self._handlers[htype]['global']:
 | 
			
		||||
            return self._handlers[htype]['global'](jid, node, data)
 | 
			
		||||
        else:
 | 
			
		||||
            return None
 | 
			
		||||
 | 
			
		||||
    def _handle_disco_info(self, iq):
 | 
			
		||||
        """
 | 
			
		||||
        Process an incoming disco#info stanza. If it is a get
 | 
			
		||||
        request, find and return the appropriate identities
 | 
			
		||||
        and features. If it is an info result, fire the
 | 
			
		||||
        disco_info event.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            iq -- The incoming disco#items stanza.
 | 
			
		||||
        """
 | 
			
		||||
        if iq['type'] == 'get':
 | 
			
		||||
            log.debug("Received disco info query from " + \
 | 
			
		||||
                      "<%s> to <%s>." % (iq['from'], iq['to']))
 | 
			
		||||
            if self.xmpp.is_component:
 | 
			
		||||
                jid = iq['to'].full
 | 
			
		||||
            else:
 | 
			
		||||
                jid = iq['to'].bare
 | 
			
		||||
            info = self._run_node_handler('get_info',
 | 
			
		||||
                                          jid,
 | 
			
		||||
                                          iq['disco_info']['node'],
 | 
			
		||||
                                          iq)
 | 
			
		||||
            iq.reply()
 | 
			
		||||
            if info:
 | 
			
		||||
                info = self._fix_default_info(info)
 | 
			
		||||
                iq.set_payload(info.xml)
 | 
			
		||||
            iq.send()
 | 
			
		||||
        elif iq['type'] == 'result':
 | 
			
		||||
            log.debug("Received disco info result from" + \
 | 
			
		||||
                      "%s to %s." % (iq['from'], iq['to']))
 | 
			
		||||
            self.xmpp.event('disco_info', iq)
 | 
			
		||||
 | 
			
		||||
    def _handle_disco_items(self, iq):
 | 
			
		||||
        """
 | 
			
		||||
        Process an incoming disco#items stanza. If it is a get
 | 
			
		||||
        request, find and return the appropriate items. If it
 | 
			
		||||
        is an items result, fire the disco_items event.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            iq -- The incoming disco#items stanza.
 | 
			
		||||
        """
 | 
			
		||||
        if iq['type'] == 'get':
 | 
			
		||||
            log.debug("Received disco items query from " + \
 | 
			
		||||
                      "<%s> to <%s>." % (iq['from'], iq['to']))
 | 
			
		||||
            if self.xmpp.is_component:
 | 
			
		||||
                jid = iq['to'].full
 | 
			
		||||
            else:
 | 
			
		||||
                jid = iq['to'].bare
 | 
			
		||||
            items = self._run_node_handler('get_items',
 | 
			
		||||
                                          jid,
 | 
			
		||||
                                          iq['disco_items']['node'])
 | 
			
		||||
            iq.reply()
 | 
			
		||||
            if items:
 | 
			
		||||
                iq.set_payload(items.xml)
 | 
			
		||||
            iq.send()
 | 
			
		||||
        elif iq['type'] == 'result':
 | 
			
		||||
            log.debug("Received disco items result from" + \
 | 
			
		||||
                      "%s to %s." % (iq['from'], iq['to']))
 | 
			
		||||
            self.xmpp.event('disco_items', iq)
 | 
			
		||||
 | 
			
		||||
    def _fix_default_info(self, info):
 | 
			
		||||
        """
 | 
			
		||||
        Disco#info results for a JID are required to include at least
 | 
			
		||||
        one identity and feature. As a default, if no other identity is
 | 
			
		||||
        provided, SleekXMPP will use either the generic component or the
 | 
			
		||||
        bot client identity. A the standard disco#info feature will also be
 | 
			
		||||
        added if no features are provided.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            info -- The disco#info quest (not the full Iq stanza) to modify.
 | 
			
		||||
        """
 | 
			
		||||
        if not info['node']:
 | 
			
		||||
            if not info['identities']:
 | 
			
		||||
                if self.xmpp.is_component:
 | 
			
		||||
                    log.debug("No identity found for this entity." + \
 | 
			
		||||
                              "Using default component identity.")
 | 
			
		||||
                    info.add_identity('component', 'generic')
 | 
			
		||||
                else:
 | 
			
		||||
                    log.debug("No identity found for this entity." + \
 | 
			
		||||
                              "Using default client identity.")
 | 
			
		||||
                    info.add_identity('client', 'bot')
 | 
			
		||||
            if not info['features']:
 | 
			
		||||
                log.debug("No features found for this entity." + \
 | 
			
		||||
                          "Using default disco#info feature.")
 | 
			
		||||
                info.add_feature(info.namespace)
 | 
			
		||||
        return info
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# Retain some backwards compatibility
 | 
			
		||||
xep_0030.getInfo = xep_0030.get_info
 | 
			
		||||
xep_0030.getItems = xep_0030.get_items
 | 
			
		||||
xep_0030.make_static = xep_0030.restore_defaults
 | 
			
		||||
							
								
								
									
										10
									
								
								sleekxmpp/plugins/xep_0030/stanza/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								sleekxmpp/plugins/xep_0030/stanza/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,10 @@
 | 
			
		||||
"""
 | 
			
		||||
    SleekXMPP: The Sleek XMPP Library
 | 
			
		||||
    Copyright (C) 2010 Nathanael C. Fritz, Lance J.T. Stout
 | 
			
		||||
    This file is part of SleekXMPP.
 | 
			
		||||
 | 
			
		||||
    See the file LICENSE for copying permission.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
from sleekxmpp.plugins.xep_0030.stanza.info import DiscoInfo
 | 
			
		||||
from sleekxmpp.plugins.xep_0030.stanza.items import DiscoItems
 | 
			
		||||
							
								
								
									
										262
									
								
								sleekxmpp/plugins/xep_0030/stanza/info.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										262
									
								
								sleekxmpp/plugins/xep_0030/stanza/info.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,262 @@
 | 
			
		||||
"""
 | 
			
		||||
    SleekXMPP: The Sleek XMPP Library
 | 
			
		||||
    Copyright (C) 2010 Nathanael C. Fritz, Lance J.T. Stout
 | 
			
		||||
    This file is part of SleekXMPP.
 | 
			
		||||
 | 
			
		||||
    See the file LICENSE for copying permission.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
from sleekxmpp.xmlstream import ElementBase, ET
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class DiscoInfo(ElementBase):
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    XMPP allows for users and agents to find the identities and features
 | 
			
		||||
    supported by other entities in the XMPP network through service discovery,
 | 
			
		||||
    or "disco". In particular, the "disco#info" query type for <iq> stanzas is
 | 
			
		||||
    used to request the list of identities and features offered by a JID.
 | 
			
		||||
 | 
			
		||||
    An identity is a combination of a category and type, such as the 'client'
 | 
			
		||||
    category with a type of 'pc' to indicate the agent is a human operated
 | 
			
		||||
    client with a GUI, or a category of 'gateway' with a type of 'aim' to
 | 
			
		||||
    identify the agent as a gateway for the legacy AIM protocol. See
 | 
			
		||||
    <http://xmpp.org/registrar/disco-categories.html> for a full list of
 | 
			
		||||
    accepted category and type combinations.
 | 
			
		||||
 | 
			
		||||
    Features are simply a set of the namespaces that identify the supported
 | 
			
		||||
    features. For example, a client that supports service discovery will
 | 
			
		||||
    include the feature 'http://jabber.org/protocol/disco#info'.
 | 
			
		||||
 | 
			
		||||
    Since clients and components may operate in several roles at once, identity
 | 
			
		||||
    and feature information may be grouped into "nodes". If one were to write
 | 
			
		||||
    all of the identities and features used by a client, then node names would
 | 
			
		||||
    be like section headings.
 | 
			
		||||
 | 
			
		||||
    Example disco#info stanzas:
 | 
			
		||||
        <iq type="get">
 | 
			
		||||
          <query xmlns="http://jabber.org/protocol/disco#info" />
 | 
			
		||||
        </iq>
 | 
			
		||||
 | 
			
		||||
        <iq type="result">
 | 
			
		||||
          <query xmlns="http://jabber.org/protocol/disco#info">
 | 
			
		||||
            <identity category="client" type="bot" name="SleekXMPP Bot" />
 | 
			
		||||
            <feature var="http://jabber.org/protocol/disco#info" />
 | 
			
		||||
            <feature var="jabber:x:data" />
 | 
			
		||||
            <feature var="urn:xmpp:ping" />
 | 
			
		||||
          </query>
 | 
			
		||||
        </iq>
 | 
			
		||||
 | 
			
		||||
    Stanza Interface:
 | 
			
		||||
        node       -- The name of the node to either
 | 
			
		||||
                      query or return info from.
 | 
			
		||||
        identities -- A set of 4-tuples, where each tuple contains
 | 
			
		||||
                      the category, type, xml:lang, and name
 | 
			
		||||
                      of an identity.
 | 
			
		||||
        features   -- A set of namespaces for features.
 | 
			
		||||
 | 
			
		||||
    Methods:
 | 
			
		||||
        add_identity   -- Add a new, single identity.
 | 
			
		||||
        del_identity   -- Remove a single identity.
 | 
			
		||||
        get_identities -- Return all identities in tuple form.
 | 
			
		||||
        set_identities -- Use multiple identities, each given in tuple form.
 | 
			
		||||
        del_identities -- Remove all identities.
 | 
			
		||||
        add_feature    -- Add a single feature.
 | 
			
		||||
        del_feature    -- Remove a single feature.
 | 
			
		||||
        get_features   -- Return a list of all features.
 | 
			
		||||
        set_features   -- Use a given list of features.
 | 
			
		||||
        del_features   -- Remove all features.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    name = 'query'
 | 
			
		||||
    namespace = 'http://jabber.org/protocol/disco#info'
 | 
			
		||||
    plugin_attrib = 'disco_info'
 | 
			
		||||
    interfaces = set(('node', 'features', 'identities'))
 | 
			
		||||
    lang_interfaces = set(('identities',))
 | 
			
		||||
 | 
			
		||||
    # Cache identities and features
 | 
			
		||||
    _identities = set()
 | 
			
		||||
    _features = set()
 | 
			
		||||
 | 
			
		||||
    def setup(self, xml=None):
 | 
			
		||||
        """
 | 
			
		||||
        Populate the stanza object using an optional XML object.
 | 
			
		||||
 | 
			
		||||
        Overrides ElementBase.setup
 | 
			
		||||
 | 
			
		||||
        Caches identity and feature information.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            xml -- Use an existing XML object for the stanza's values.
 | 
			
		||||
        """
 | 
			
		||||
        ElementBase.setup(self, xml)
 | 
			
		||||
 | 
			
		||||
        self._identities = set([id[0:3] for id in self['identities']])
 | 
			
		||||
        self._features = self['features']
 | 
			
		||||
 | 
			
		||||
    def add_identity(self, category, itype, name=None, lang=None):
 | 
			
		||||
        """
 | 
			
		||||
        Add a new identity element. Each identity must be unique
 | 
			
		||||
        in terms of all four identity components.
 | 
			
		||||
 | 
			
		||||
        Multiple, identical category/type pairs are allowed only
 | 
			
		||||
        if the xml:lang values are different. Likewise, multiple
 | 
			
		||||
        category/type/xml:lang pairs are allowed so long as the names
 | 
			
		||||
        are different. In any case, a category and type are required.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            category -- The general category to which the agent belongs.
 | 
			
		||||
            itype    -- A more specific designation with the category.
 | 
			
		||||
            name     -- Optional human readable name for this identity.
 | 
			
		||||
            lang     -- Optional standard xml:lang value.
 | 
			
		||||
        """
 | 
			
		||||
        identity = (category, itype, lang)
 | 
			
		||||
        if identity not in self._identities:
 | 
			
		||||
            self._identities.add(identity)
 | 
			
		||||
            id_xml = ET.Element('{%s}identity' % self.namespace)
 | 
			
		||||
            id_xml.attrib['category'] = category
 | 
			
		||||
            id_xml.attrib['type'] = itype
 | 
			
		||||
            if lang:
 | 
			
		||||
                id_xml.attrib['{%s}lang' % self.xml_ns] = lang
 | 
			
		||||
            if name:
 | 
			
		||||
                id_xml.attrib['name'] = name
 | 
			
		||||
            self.xml.append(id_xml)
 | 
			
		||||
            return True
 | 
			
		||||
        return False
 | 
			
		||||
 | 
			
		||||
    def del_identity(self, category, itype, name=None, lang=None):
 | 
			
		||||
        """
 | 
			
		||||
        Remove a given identity.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            category -- The general category to which the agent belonged.
 | 
			
		||||
            itype    -- A more specific designation with the category.
 | 
			
		||||
            name     -- Optional human readable name for this identity.
 | 
			
		||||
            lang     -- Optional, standard xml:lang value.
 | 
			
		||||
        """
 | 
			
		||||
        identity = (category, itype, lang)
 | 
			
		||||
        if identity in self._identities:
 | 
			
		||||
            self._identities.remove(identity)
 | 
			
		||||
            for id_xml in self.findall('{%s}identity' % self.namespace):
 | 
			
		||||
                id = (id_xml.attrib['category'],
 | 
			
		||||
                      id_xml.attrib['type'],
 | 
			
		||||
                      id_xml.attrib.get('{%s}lang' % self.xml_ns, None))
 | 
			
		||||
                if id == identity:
 | 
			
		||||
                    self.xml.remove(id_xml)
 | 
			
		||||
                    return True
 | 
			
		||||
        return False
 | 
			
		||||
 | 
			
		||||
    def get_identities(self, lang=None):
 | 
			
		||||
        """
 | 
			
		||||
        Return a set of all identities in tuple form as so:
 | 
			
		||||
            (category, type, lang, name)
 | 
			
		||||
 | 
			
		||||
        If a language was specified, only return identities using
 | 
			
		||||
        that language.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            lang -- Optional, standard xml:lang value.
 | 
			
		||||
        """
 | 
			
		||||
        identities = set()
 | 
			
		||||
        for id_xml in self.findall('{%s}identity' % self.namespace):
 | 
			
		||||
            xml_lang = id_xml.attrib.get('{%s}lang' % self.xml_ns, None)
 | 
			
		||||
            if lang is None or xml_lang == lang:
 | 
			
		||||
                identities.add((
 | 
			
		||||
                    id_xml.attrib['category'],
 | 
			
		||||
                    id_xml.attrib['type'],
 | 
			
		||||
                    id_xml.attrib.get('{%s}lang' % self.xml_ns, None),
 | 
			
		||||
                    id_xml.attrib.get('name', None)))
 | 
			
		||||
        return identities
 | 
			
		||||
 | 
			
		||||
    def set_identities(self, identities, lang=None):
 | 
			
		||||
        """
 | 
			
		||||
        Add or replace all identities. The identities must be a in set
 | 
			
		||||
        where each identity is a tuple of the form:
 | 
			
		||||
            (category, type, lang, name)
 | 
			
		||||
 | 
			
		||||
        If a language is specifified, any identities using that language
 | 
			
		||||
        will be removed to be replaced with the given identities.
 | 
			
		||||
 | 
			
		||||
        NOTE: An identity's language will not be changed regardless of
 | 
			
		||||
              the value of lang.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            identities -- A set of identities in tuple form.
 | 
			
		||||
            lang       -- Optional, standard xml:lang value.
 | 
			
		||||
        """
 | 
			
		||||
        self.del_identities(lang)
 | 
			
		||||
        for identity in identities:
 | 
			
		||||
            category, itype, lang, name = identity
 | 
			
		||||
            self.add_identity(category, itype, name, lang)
 | 
			
		||||
 | 
			
		||||
    def del_identities(self, lang=None):
 | 
			
		||||
        """
 | 
			
		||||
        Remove all identities. If a language was specified, only
 | 
			
		||||
        remove identities using that language.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            lang -- Optional, standard xml:lang value.
 | 
			
		||||
        """
 | 
			
		||||
        for id_xml in self.findall('{%s}identity' % self.namespace):
 | 
			
		||||
            if lang is None:
 | 
			
		||||
                self.xml.remove(id_xml)
 | 
			
		||||
            elif id_xml.attrib.get('{%s}lang' % self.xml_ns, None) == lang:
 | 
			
		||||
                self._identities.remove((
 | 
			
		||||
                    id_xml.attrib['category'],
 | 
			
		||||
                    id_xml.attrib['type'],
 | 
			
		||||
                    id_xml.attrib.get('{%s}lang' % self.xml_ns, None)))
 | 
			
		||||
                self.xml.remove(id_xml)
 | 
			
		||||
 | 
			
		||||
    def add_feature(self, feature):
 | 
			
		||||
        """
 | 
			
		||||
        Add a single, new feature.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            feature -- The namespace of the supported feature.
 | 
			
		||||
        """
 | 
			
		||||
        if feature not in self._features:
 | 
			
		||||
            self._features.add(feature)
 | 
			
		||||
            feature_xml = ET.Element('{%s}feature' % self.namespace)
 | 
			
		||||
            feature_xml.attrib['var'] = feature
 | 
			
		||||
            self.xml.append(feature_xml)
 | 
			
		||||
            return True
 | 
			
		||||
        return False
 | 
			
		||||
 | 
			
		||||
    def del_feature(self, feature):
 | 
			
		||||
        """
 | 
			
		||||
        Remove a single feature.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            feature -- The namespace of the removed feature.
 | 
			
		||||
        """
 | 
			
		||||
        if feature in self._features:
 | 
			
		||||
            self._features.remove(feature)
 | 
			
		||||
            for feature_xml in self.findall('{%s}feature' % self.namespace):
 | 
			
		||||
                if feature_xml.attrib['var'] == feature:
 | 
			
		||||
                    self.xml.remove(feature_xml)
 | 
			
		||||
                    return True
 | 
			
		||||
        return False
 | 
			
		||||
 | 
			
		||||
    def get_features(self):
 | 
			
		||||
        """Return the set of all supported features."""
 | 
			
		||||
        features = set()
 | 
			
		||||
        for feature_xml in self.findall('{%s}feature' % self.namespace):
 | 
			
		||||
            features.add(feature_xml.attrib['var'])
 | 
			
		||||
        return features
 | 
			
		||||
 | 
			
		||||
    def set_features(self, features):
 | 
			
		||||
        """
 | 
			
		||||
        Add or replace the set of supported features.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            features -- The new set of supported features.
 | 
			
		||||
        """
 | 
			
		||||
        self.del_features()
 | 
			
		||||
        for feature in features:
 | 
			
		||||
            self.add_feature(feature)
 | 
			
		||||
 | 
			
		||||
    def del_features(self):
 | 
			
		||||
        """Remove all features."""
 | 
			
		||||
        self._features = set()
 | 
			
		||||
        for feature_xml in self.findall('{%s}feature' % self.namespace):
 | 
			
		||||
            self.xml.remove(feature_xml)
 | 
			
		||||
							
								
								
									
										136
									
								
								sleekxmpp/plugins/xep_0030/stanza/items.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										136
									
								
								sleekxmpp/plugins/xep_0030/stanza/items.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,136 @@
 | 
			
		||||
"""
 | 
			
		||||
    SleekXMPP: The Sleek XMPP Library
 | 
			
		||||
    Copyright (C) 2010 Nathanael C. Fritz, Lance J.T. Stout
 | 
			
		||||
    This file is part of SleekXMPP.
 | 
			
		||||
 | 
			
		||||
    See the file LICENSE for copying permission.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
from sleekxmpp.xmlstream import ElementBase, ET
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class DiscoItems(ElementBase):
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    Example disco#items stanzas:
 | 
			
		||||
        <iq type="get">
 | 
			
		||||
          <query xmlns="http://jabber.org/protocol/disco#items" />
 | 
			
		||||
        </iq>
 | 
			
		||||
 | 
			
		||||
        <iq type="result">
 | 
			
		||||
          <query xmlns="http://jabber.org/protocol/disco#items">
 | 
			
		||||
            <item jid="chat.example.com"
 | 
			
		||||
                  node="xmppdev"
 | 
			
		||||
                  name="XMPP Dev" />
 | 
			
		||||
            <item jid="chat.example.com"
 | 
			
		||||
                  node="sleekdev"
 | 
			
		||||
                  name="SleekXMPP Dev" />
 | 
			
		||||
          </query>
 | 
			
		||||
        </iq>
 | 
			
		||||
 | 
			
		||||
    Stanza Interface:
 | 
			
		||||
        node  -- The name of the node to either
 | 
			
		||||
                 query or return info from.
 | 
			
		||||
        items -- A list of 3-tuples, where each tuple contains
 | 
			
		||||
                 the JID, node, and name of an item.
 | 
			
		||||
 | 
			
		||||
    Methods:
 | 
			
		||||
        add_item  -- Add a single new item.
 | 
			
		||||
        del_item  -- Remove a single item.
 | 
			
		||||
        get_items -- Return all items.
 | 
			
		||||
        set_items -- Set or replace all items.
 | 
			
		||||
        del_items -- Remove all items.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    name = 'query'
 | 
			
		||||
    namespace = 'http://jabber.org/protocol/disco#items'
 | 
			
		||||
    plugin_attrib = 'disco_items'
 | 
			
		||||
    interfaces = set(('node', 'items'))
 | 
			
		||||
 | 
			
		||||
    # Cache items
 | 
			
		||||
    _items = set()
 | 
			
		||||
 | 
			
		||||
    def setup(self, xml=None):
 | 
			
		||||
        """
 | 
			
		||||
        Populate the stanza object using an optional XML object.
 | 
			
		||||
 | 
			
		||||
        Overrides ElementBase.setup
 | 
			
		||||
 | 
			
		||||
        Caches item information.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            xml -- Use an existing XML object for the stanza's values.
 | 
			
		||||
        """
 | 
			
		||||
        ElementBase.setup(self, xml)
 | 
			
		||||
        self._items = set([item[0:2] for item in self['items']])
 | 
			
		||||
 | 
			
		||||
    def add_item(self, jid, node=None, name=None):
 | 
			
		||||
        """
 | 
			
		||||
        Add a new item element. Each item is required to have a
 | 
			
		||||
        JID, but may also specify a node value to reference
 | 
			
		||||
        non-addressable entitities.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            jid  -- The JID for the item.
 | 
			
		||||
            node -- Optional additional information to reference
 | 
			
		||||
                    non-addressable items.
 | 
			
		||||
            name -- Optional human readable name for the item.
 | 
			
		||||
        """
 | 
			
		||||
        if (jid, node) not in self._items:
 | 
			
		||||
            self._items.add((jid, node))
 | 
			
		||||
            item_xml = ET.Element('{%s}item' % self.namespace)
 | 
			
		||||
            item_xml.attrib['jid'] = jid
 | 
			
		||||
            if name:
 | 
			
		||||
                item_xml.attrib['name'] = name
 | 
			
		||||
            if node:
 | 
			
		||||
                item_xml.attrib['node'] = node
 | 
			
		||||
            self.xml.append(item_xml)
 | 
			
		||||
            return True
 | 
			
		||||
        return False
 | 
			
		||||
 | 
			
		||||
    def del_item(self, jid, node=None):
 | 
			
		||||
        """
 | 
			
		||||
        Remove a single item.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            jid  -- JID of the item to remove.
 | 
			
		||||
            node -- Optional extra identifying information.
 | 
			
		||||
        """
 | 
			
		||||
        if (jid, node) in self._items:
 | 
			
		||||
            for item_xml in self.findall('{%s}item' % self.namespace):
 | 
			
		||||
                item = (item_xml.attrib['jid'],
 | 
			
		||||
                        item_xml.attrib.get('node', None))
 | 
			
		||||
                if item == (jid, node):
 | 
			
		||||
                    self.xml.remove(item_xml)
 | 
			
		||||
                    return True
 | 
			
		||||
        return False
 | 
			
		||||
 | 
			
		||||
    def get_items(self):
 | 
			
		||||
        """Return all items."""
 | 
			
		||||
        items = set()
 | 
			
		||||
        for item_xml in self.findall('{%s}item' % self.namespace):
 | 
			
		||||
            item = (item_xml.attrib['jid'],
 | 
			
		||||
                    item_xml.attrib.get('node'),
 | 
			
		||||
                    item_xml.attrib.get('name'))
 | 
			
		||||
            items.add(item)
 | 
			
		||||
        return items
 | 
			
		||||
 | 
			
		||||
    def set_items(self, items):
 | 
			
		||||
        """
 | 
			
		||||
        Set or replace all items. The given items must be in a
 | 
			
		||||
        list or set where each item is a tuple of the form:
 | 
			
		||||
            (jid, node, name)
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            items -- A series of items in tuple format.
 | 
			
		||||
        """
 | 
			
		||||
        self.del_items()
 | 
			
		||||
        for item in items:
 | 
			
		||||
            jid, node, name = item
 | 
			
		||||
            self.add_item(jid, node, name)
 | 
			
		||||
 | 
			
		||||
    def del_items(self):
 | 
			
		||||
        """Remove all items."""
 | 
			
		||||
        self._items = set()
 | 
			
		||||
        for item_xml in self.findall('{%s}item' % self.namespace):
 | 
			
		||||
            self.xml.remove(item_xml)
 | 
			
		||||
							
								
								
									
										265
									
								
								sleekxmpp/plugins/xep_0030/static.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										265
									
								
								sleekxmpp/plugins/xep_0030/static.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,265 @@
 | 
			
		||||
"""
 | 
			
		||||
    SleekXMPP: The Sleek XMPP Library
 | 
			
		||||
    Copyright (C) 2010 Nathanael C. Fritz, Lance J.T. Stout
 | 
			
		||||
    This file is part of SleekXMPP.
 | 
			
		||||
 | 
			
		||||
    See the file LICENSE for copying permission.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
import logging
 | 
			
		||||
 | 
			
		||||
import sleekxmpp
 | 
			
		||||
from sleekxmpp import Iq
 | 
			
		||||
from sleekxmpp.exceptions import XMPPError
 | 
			
		||||
from sleekxmpp.plugins.base import base_plugin
 | 
			
		||||
from sleekxmpp.xmlstream.handler import Callback
 | 
			
		||||
from sleekxmpp.xmlstream.matcher import StanzaPath
 | 
			
		||||
from sleekxmpp.xmlstream import register_stanza_plugin, ElementBase, ET, JID
 | 
			
		||||
from sleekxmpp.plugins.xep_0030 import DiscoInfo, DiscoItems
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
log = logging.getLogger(__name__)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class StaticDisco(object):
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    While components will likely require fully dynamic handling
 | 
			
		||||
    of service discovery information, most clients and simple bots
 | 
			
		||||
    only need to manage a few disco nodes that will remain mostly
 | 
			
		||||
    static.
 | 
			
		||||
 | 
			
		||||
    StaticDisco provides a set of node handlers that will store
 | 
			
		||||
    static sets of disco info and items in memory.
 | 
			
		||||
 | 
			
		||||
    Attributes:
 | 
			
		||||
        nodes -- A dictionary mapping (JID, node) tuples to a dict
 | 
			
		||||
                 containing a disco#info and a disco#items stanza.
 | 
			
		||||
        xmpp  -- The main SleekXMPP object.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    def __init__(self, xmpp):
 | 
			
		||||
        """
 | 
			
		||||
        Create a static disco interface. Sets of disco#info and
 | 
			
		||||
        disco#items are maintained for every given JID and node
 | 
			
		||||
        combination. These stanzas are used to store disco
 | 
			
		||||
        information in memory without any additional processing.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            xmpp -- The main SleekXMPP object.
 | 
			
		||||
        """
 | 
			
		||||
        self.nodes = {}
 | 
			
		||||
        self.xmpp = xmpp
 | 
			
		||||
 | 
			
		||||
    def add_node(self, jid=None, node=None):
 | 
			
		||||
        """
 | 
			
		||||
        Create a new set of stanzas for the provided
 | 
			
		||||
        JID and node combination.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            jid  -- The JID that will own the new stanzas.
 | 
			
		||||
            node -- The node that will own the new stanzas.
 | 
			
		||||
        """
 | 
			
		||||
        if jid is None:
 | 
			
		||||
            jid = self.xmpp.boundjid.full
 | 
			
		||||
        if node is None:
 | 
			
		||||
            node = ''
 | 
			
		||||
        if (jid, node) not in self.nodes:
 | 
			
		||||
            self.nodes[(jid, node)] = {'info': DiscoInfo(),
 | 
			
		||||
                                       'items': DiscoItems()}
 | 
			
		||||
            self.nodes[(jid, node)]['info']['node'] = node
 | 
			
		||||
            self.nodes[(jid, node)]['items']['node'] = node
 | 
			
		||||
 | 
			
		||||
    # =================================================================
 | 
			
		||||
    # Node Handlers
 | 
			
		||||
    #
 | 
			
		||||
    # Each handler accepts three arguments: jid, node, and data.
 | 
			
		||||
    # The jid and node parameters together determine the set of
 | 
			
		||||
    # info and items stanzas that will be retrieved or added.
 | 
			
		||||
    # The data parameter is a dictionary with additional paramters
 | 
			
		||||
    # that will be passed to other calls.
 | 
			
		||||
 | 
			
		||||
    def get_info(self, jid, node, data):
 | 
			
		||||
        """
 | 
			
		||||
        Return the stored info data for the requested JID/node combination.
 | 
			
		||||
 | 
			
		||||
        The data parameter is not used.
 | 
			
		||||
        """
 | 
			
		||||
        if (jid, node) not in self.nodes:
 | 
			
		||||
            if not node:
 | 
			
		||||
                return DiscoInfo()
 | 
			
		||||
            else:
 | 
			
		||||
                raise XMPPError(condition='item-not-found')
 | 
			
		||||
        else:
 | 
			
		||||
            return self.nodes[(jid, node)]['info']
 | 
			
		||||
 | 
			
		||||
    def del_info(self, jid, node, data):
 | 
			
		||||
        """
 | 
			
		||||
        Reset the info stanza for a given JID/node combination.
 | 
			
		||||
 | 
			
		||||
        The data parameter is not used.
 | 
			
		||||
        """
 | 
			
		||||
        if (jid, node) in self.nodes:
 | 
			
		||||
            self.nodes[(jid, node)]['info'] = DiscoInfo()
 | 
			
		||||
 | 
			
		||||
    def get_items(self, jid, node, data):
 | 
			
		||||
        """
 | 
			
		||||
        Return the stored items data for the requested JID/node combination.
 | 
			
		||||
 | 
			
		||||
        The data parameter is not used.
 | 
			
		||||
        """
 | 
			
		||||
        if (jid, node) not in self.nodes:
 | 
			
		||||
            if not node:
 | 
			
		||||
                return DiscoInfo()
 | 
			
		||||
            else:
 | 
			
		||||
                raise XMPPError(condition='item-not-found')
 | 
			
		||||
        else:
 | 
			
		||||
            return self.nodes[(jid, node)]['items']
 | 
			
		||||
 | 
			
		||||
    def set_items(self, jid, node, data):
 | 
			
		||||
        """
 | 
			
		||||
        Replace the stored items data for a JID/node combination.
 | 
			
		||||
 | 
			
		||||
        The data parameter may provided:
 | 
			
		||||
            items -- A set of items in tuple format.
 | 
			
		||||
        """
 | 
			
		||||
        items = data.get('items', set())
 | 
			
		||||
        self.add_node(jid, node)
 | 
			
		||||
        self.nodes[(jid, node)]['items']['items'] = items
 | 
			
		||||
 | 
			
		||||
    def del_items(self, jid, node, data):
 | 
			
		||||
        """
 | 
			
		||||
        Reset the items stanza for a given JID/node combination.
 | 
			
		||||
 | 
			
		||||
        The data parameter is not used.
 | 
			
		||||
        """
 | 
			
		||||
        if (jid, node) in self.nodes:
 | 
			
		||||
            self.nodes[(jid, node)]['items'] = DiscoItems()
 | 
			
		||||
 | 
			
		||||
    def add_identity(self, jid, node, data):
 | 
			
		||||
        """
 | 
			
		||||
        Add a new identity to te JID/node combination.
 | 
			
		||||
 | 
			
		||||
        The data parameter may provide:
 | 
			
		||||
            category -- The general category to which the agent belongs.
 | 
			
		||||
            itype    -- A more specific designation with the category.
 | 
			
		||||
            name     -- Optional human readable name for this identity.
 | 
			
		||||
            lang     -- Optional standard xml:lang value.
 | 
			
		||||
        """
 | 
			
		||||
        self.add_node(jid, node)
 | 
			
		||||
        self.nodes[(jid, node)]['info'].add_identity(
 | 
			
		||||
                data.get('category', ''),
 | 
			
		||||
                data.get('itype', ''),
 | 
			
		||||
                data.get('name', None),
 | 
			
		||||
                data.get('lang', None))
 | 
			
		||||
 | 
			
		||||
    def set_identities(self, jid, node, data):
 | 
			
		||||
        """
 | 
			
		||||
        Add or replace all identities for a JID/node combination.
 | 
			
		||||
 | 
			
		||||
        The data parameter should include:
 | 
			
		||||
            identities -- A list of identities in tuple form:
 | 
			
		||||
                            (category, type, name, lang)
 | 
			
		||||
        """
 | 
			
		||||
        identities = data.get('identities', set())
 | 
			
		||||
        self.add_node(jid, node)
 | 
			
		||||
        self.nodes[(jid, node)]['info']['identities'] = identities
 | 
			
		||||
 | 
			
		||||
    def del_identity(self, jid, node, data):
 | 
			
		||||
        """
 | 
			
		||||
        Remove an identity from a JID/node combination.
 | 
			
		||||
 | 
			
		||||
        The data parameter may provide:
 | 
			
		||||
            category -- The general category to which the agent belonged.
 | 
			
		||||
            itype    -- A more specific designation with the category.
 | 
			
		||||
            name     -- Optional human readable name for this identity.
 | 
			
		||||
            lang     -- Optional, standard xml:lang value.
 | 
			
		||||
        """
 | 
			
		||||
        if (jid, node) not in self.nodes:
 | 
			
		||||
            return
 | 
			
		||||
        self.nodes[(jid, node)]['info'].del_identity(
 | 
			
		||||
                data.get('category', ''),
 | 
			
		||||
                data.get('itype', ''),
 | 
			
		||||
                data.get('name', None),
 | 
			
		||||
                data.get('lang', None))
 | 
			
		||||
 | 
			
		||||
    def del_identities(self, jid, node, data):
 | 
			
		||||
        """
 | 
			
		||||
        Remove all identities from a JID/node combination.
 | 
			
		||||
 | 
			
		||||
        The data parameter is not used.
 | 
			
		||||
        """
 | 
			
		||||
        if (jid, node) not in self.nodes:
 | 
			
		||||
            return
 | 
			
		||||
        del self.nodes[(jid, node)]['info']['identities']
 | 
			
		||||
 | 
			
		||||
    def add_feature(self, jid, node, data):
 | 
			
		||||
        """
 | 
			
		||||
        Add a feature to a JID/node combination.
 | 
			
		||||
 | 
			
		||||
        The data parameter should include:
 | 
			
		||||
            feature -- The namespace of the supported feature.
 | 
			
		||||
        """
 | 
			
		||||
        self.add_node(jid, node)
 | 
			
		||||
        self.nodes[(jid, node)]['info'].add_feature(data.get('feature', ''))
 | 
			
		||||
 | 
			
		||||
    def set_features(self, jid, node, data):
 | 
			
		||||
        """
 | 
			
		||||
        Add or replace all features for a JID/node combination.
 | 
			
		||||
 | 
			
		||||
        The data parameter should include:
 | 
			
		||||
            features -- The new set of supported features.
 | 
			
		||||
        """
 | 
			
		||||
        features = data.get('features', set())
 | 
			
		||||
        self.add_node(jid, node)
 | 
			
		||||
        self.nodes[(jid, node)]['info']['features'] = features
 | 
			
		||||
 | 
			
		||||
    def del_feature(self, jid, node, data):
 | 
			
		||||
        """
 | 
			
		||||
        Remove a feature from a JID/node combination.
 | 
			
		||||
 | 
			
		||||
        The data parameter should include:
 | 
			
		||||
            feature -- The namespace of the removed feature.
 | 
			
		||||
        """
 | 
			
		||||
        if (jid, node) not in self.nodes:
 | 
			
		||||
            return
 | 
			
		||||
        self.nodes[(jid, node)]['info'].del_feature(data.get('feature', ''))
 | 
			
		||||
 | 
			
		||||
    def del_features(self, jid, node, data):
 | 
			
		||||
        """
 | 
			
		||||
        Remove all features from a JID/node combination.
 | 
			
		||||
 | 
			
		||||
        The data parameter is not used.
 | 
			
		||||
        """
 | 
			
		||||
        if (jid, node) not in self.nodes:
 | 
			
		||||
            return
 | 
			
		||||
        del self.nodes[(jid, node)]['info']['features']
 | 
			
		||||
 | 
			
		||||
    def add_item(self, jid, node, data):
 | 
			
		||||
        """
 | 
			
		||||
        Add an item to a JID/node combination.
 | 
			
		||||
 | 
			
		||||
        The data parameter may include:
 | 
			
		||||
            ijid  -- The JID for the item.
 | 
			
		||||
            inode -- Optional additional information to reference
 | 
			
		||||
                     non-addressable items.
 | 
			
		||||
            name  -- Optional human readable name for the item.
 | 
			
		||||
        """
 | 
			
		||||
        self.add_node(jid, node)
 | 
			
		||||
        self.nodes[(jid, node)]['items'].add_item(
 | 
			
		||||
                data.get('ijid', ''),
 | 
			
		||||
                node=data.get('inode', ''),
 | 
			
		||||
                name=data.get('name', ''))
 | 
			
		||||
 | 
			
		||||
    def del_item(self, jid, node, data):
 | 
			
		||||
        """
 | 
			
		||||
        Remove an item from a JID/node combination.
 | 
			
		||||
 | 
			
		||||
        The data parameter may include:
 | 
			
		||||
            ijid  -- JID of the item to remove.
 | 
			
		||||
            inode -- Optional extra identifying information.
 | 
			
		||||
        """
 | 
			
		||||
        if (jid, node) in self.nodes:
 | 
			
		||||
            self.nodes[(jid, node)]['items'].del_item(
 | 
			
		||||
                    data.get('ijid', ''),
 | 
			
		||||
                    node=data.get('inode', None))
 | 
			
		||||
@@ -108,7 +108,7 @@ class MUCPresence(ElementBase):
 | 
			
		||||
 | 
			
		||||
class xep_0045(base.base_plugin):
 | 
			
		||||
    """
 | 
			
		||||
	Impliments XEP-0045 Multi User Chat
 | 
			
		||||
    Implements XEP-0045 Multi User Chat
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    def plugin_init(self):
 | 
			
		||||
@@ -121,6 +121,14 @@ class xep_0045(base.base_plugin):
 | 
			
		||||
        self.xmpp.registerHandler(Callback('MUCPresence', MatchXMLMask("<presence xmlns='%s' />" % self.xmpp.default_ns), self.handle_groupchat_presence))
 | 
			
		||||
        self.xmpp.registerHandler(Callback('MUCMessage', MatchXMLMask("<message xmlns='%s' type='groupchat'><body/></message>" % self.xmpp.default_ns), self.handle_groupchat_message))
 | 
			
		||||
        self.xmpp.registerHandler(Callback('MUCSubject', MatchXMLMask("<message xmlns='%s' type='groupchat'><subject/></message>" % self.xmpp.default_ns), self.handle_groupchat_subject))
 | 
			
		||||
        self.xmpp.registerHandler(Callback('MUCInvite', MatchXPath("{%s}message/{http://jabber.org/protocol/muc#user}x/invite" % self.xmpp.default_ns), self.handle_groupchat_invite))
 | 
			
		||||
 | 
			
		||||
    def handle_groupchat_invite(self, inv):
 | 
			
		||||
        """ Handle an invite into a muc.
 | 
			
		||||
        """
 | 
			
		||||
        logging.debug("MUC invite to %s from %s: %s" % (inv['from'], inv["from"], inv))
 | 
			
		||||
        if inv['from'] not in self.rooms.keys():
 | 
			
		||||
            self.xmpp.event("groupchat_invite", inv)
 | 
			
		||||
 | 
			
		||||
    def handle_groupchat_presence(self, pr):
 | 
			
		||||
        """ Handle a presence in a muc.
 | 
			
		||||
@@ -268,10 +276,10 @@ class xep_0045(base.base_plugin):
 | 
			
		||||
            raise ValueError
 | 
			
		||||
        return True
 | 
			
		||||
 | 
			
		||||
	def invite(self, room, jid, reason=''):
 | 
			
		||||
    def invite(self, room, jid, reason='', mfrom=''):
 | 
			
		||||
        """ Invite a jid to a room."""
 | 
			
		||||
        msg = self.xmpp.makeMessage(room)
 | 
			
		||||
		msg['from'] = self.xmpp.jid
 | 
			
		||||
        msg['from'] = mfrom
 | 
			
		||||
        x = ET.Element('{http://jabber.org/protocol/muc#user}x')
 | 
			
		||||
        invite = ET.Element('{http://jabber.org/protocol/muc#user}invite', {'to': jid})
 | 
			
		||||
        if reason:
 | 
			
		||||
@@ -291,10 +299,10 @@ class xep_0045(base.base_plugin):
 | 
			
		||||
            self.xmpp.sendPresence(pshow='unavailable', pto="%s/%s" % (room, nick))
 | 
			
		||||
        del self.rooms[room]
 | 
			
		||||
 | 
			
		||||
	def getRoomConfig(self, room):
 | 
			
		||||
    def getRoomConfig(self, room, ifrom=''):
 | 
			
		||||
        iq = self.xmpp.makeIqGet('http://jabber.org/protocol/muc#owner')
 | 
			
		||||
        iq['to'] = room
 | 
			
		||||
		iq['from'] = self.xmpp.jid
 | 
			
		||||
        iq['from'] = ifrom
 | 
			
		||||
        result = iq.send()
 | 
			
		||||
        if result is None or result['type'] != 'result':
 | 
			
		||||
            raise ValueError
 | 
			
		||||
@@ -308,15 +316,16 @@ class xep_0045(base.base_plugin):
 | 
			
		||||
        x = ET.Element('{jabber:x:data}x', type='cancel')
 | 
			
		||||
        query.append(x)
 | 
			
		||||
        iq = self.xmpp.makeIqSet(query)
 | 
			
		||||
        iq['to'] = room
 | 
			
		||||
        iq.send()
 | 
			
		||||
 | 
			
		||||
	def setRoomConfig(self, room, config):
 | 
			
		||||
    def setRoomConfig(self, room, config, ifrom=''):
 | 
			
		||||
        query = ET.Element('{http://jabber.org/protocol/muc#owner}query')
 | 
			
		||||
        x = config.getXML('submit')
 | 
			
		||||
        query.append(x)
 | 
			
		||||
        iq = self.xmpp.makeIqSet(query)
 | 
			
		||||
        iq['to'] = room
 | 
			
		||||
		iq['from'] = self.xmpp.jid
 | 
			
		||||
        iq['from'] = ifrom
 | 
			
		||||
        iq.send()
 | 
			
		||||
 | 
			
		||||
    def getJoinedRooms(self):
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										10
									
								
								sleekxmpp/plugins/xep_0050/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								sleekxmpp/plugins/xep_0050/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,10 @@
 | 
			
		||||
"""
 | 
			
		||||
    SleekXMPP: The Sleek XMPP Library
 | 
			
		||||
    Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout
 | 
			
		||||
    This file is part of SleekXMPP.
 | 
			
		||||
 | 
			
		||||
    See the file LICENSE for copying permission.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
from sleekxmpp.plugins.xep_0050.stanza import Command
 | 
			
		||||
from sleekxmpp.plugins.xep_0050.adhoc import xep_0050
 | 
			
		||||
							
								
								
									
										593
									
								
								sleekxmpp/plugins/xep_0050/adhoc.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										593
									
								
								sleekxmpp/plugins/xep_0050/adhoc.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,593 @@
 | 
			
		||||
"""
 | 
			
		||||
    SleekXMPP: The Sleek XMPP Library
 | 
			
		||||
    Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout
 | 
			
		||||
    This file is part of SleekXMPP.
 | 
			
		||||
 | 
			
		||||
    See the file LICENSE for copying permission.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
import logging
 | 
			
		||||
import time
 | 
			
		||||
 | 
			
		||||
from sleekxmpp import Iq
 | 
			
		||||
from sleekxmpp.xmlstream.handler import Callback
 | 
			
		||||
from sleekxmpp.xmlstream.matcher import StanzaPath
 | 
			
		||||
from sleekxmpp.xmlstream import register_stanza_plugin, JID
 | 
			
		||||
from sleekxmpp.plugins.base import base_plugin
 | 
			
		||||
from sleekxmpp.plugins.xep_0050 import stanza
 | 
			
		||||
from sleekxmpp.plugins.xep_0050 import Command
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
log = logging.getLogger(__name__)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class xep_0050(base_plugin):
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    XEP-0050: Ad-Hoc Commands
 | 
			
		||||
 | 
			
		||||
    XMPP's Adhoc Commands provides a generic workflow mechanism for
 | 
			
		||||
    interacting with applications. The result is similar to menu selections
 | 
			
		||||
    and multi-step dialogs in normal desktop applications. Clients do not
 | 
			
		||||
    need to know in advance what commands are provided by any particular
 | 
			
		||||
    application or agent. While adhoc commands provide similar functionality
 | 
			
		||||
    to Jabber-RPC, adhoc commands are used primarily for human interaction.
 | 
			
		||||
 | 
			
		||||
    Also see <http://xmpp.org/extensions/xep-0050.html>
 | 
			
		||||
 | 
			
		||||
    Configuration Values:
 | 
			
		||||
        threaded -- Indicates if command events should be threaded.
 | 
			
		||||
                    Defaults to True.
 | 
			
		||||
 | 
			
		||||
    Events:
 | 
			
		||||
        command_execute  -- Received a command with action="execute"
 | 
			
		||||
        command_next     -- Received a command with action="next"
 | 
			
		||||
        command_complete -- Received a command with action="complete"
 | 
			
		||||
        command_cancel   -- Received a command with action="cancel"
 | 
			
		||||
 | 
			
		||||
    Attributes:
 | 
			
		||||
        threaded -- Indicates if command events should be threaded.
 | 
			
		||||
                    Defaults to True.
 | 
			
		||||
        commands -- A dictionary mapping JID/node pairs to command
 | 
			
		||||
                    names and handlers.
 | 
			
		||||
        sessions -- A dictionary or equivalent backend mapping
 | 
			
		||||
                    session IDs to dictionaries containing data
 | 
			
		||||
                    relevant to a command's session.
 | 
			
		||||
 | 
			
		||||
    Methods:
 | 
			
		||||
        plugin_init       -- Overrides base_plugin.plugin_init
 | 
			
		||||
        post_init         -- Overrides base_plugin.post_init
 | 
			
		||||
        new_session       -- Return a new session ID.
 | 
			
		||||
        prep_handlers     -- Placeholder. May call with a list of handlers
 | 
			
		||||
                             to prepare them for use with the session storage
 | 
			
		||||
                             backend, if needed.
 | 
			
		||||
        set_backend       -- Replace the default session storage with some
 | 
			
		||||
                             external storage mechanism, such as a database.
 | 
			
		||||
                             The provided backend wrapper must be able to
 | 
			
		||||
                             act using the same syntax as a dictionary.
 | 
			
		||||
        add_command       -- Add a command for use by external entitites.
 | 
			
		||||
        get_commands      -- Retrieve a list of commands provided by a
 | 
			
		||||
                             remote agent.
 | 
			
		||||
        send_command      -- Send a command request to a remote agent.
 | 
			
		||||
        start_command     -- Command user API: initiate a command session
 | 
			
		||||
        continue_command  -- Command user API: proceed to the next step
 | 
			
		||||
        cancel_command    -- Command user API: cancel a command
 | 
			
		||||
        complete_command  -- Command user API: finish a command
 | 
			
		||||
        terminate_command -- Command user API: delete a command's session
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    def plugin_init(self):
 | 
			
		||||
        """Start the XEP-0050 plugin."""
 | 
			
		||||
        self.xep = '0050'
 | 
			
		||||
        self.description = 'Ad-Hoc Commands'
 | 
			
		||||
        self.stanza = stanza
 | 
			
		||||
 | 
			
		||||
        self.threaded = self.config.get('threaded', True)
 | 
			
		||||
        self.commands = {}
 | 
			
		||||
        self.sessions = self.config.get('session_db', {})
 | 
			
		||||
 | 
			
		||||
        self.xmpp.register_handler(
 | 
			
		||||
                Callback("Ad-Hoc Execute",
 | 
			
		||||
                         StanzaPath('iq@type=set/command'),
 | 
			
		||||
                         self._handle_command))
 | 
			
		||||
 | 
			
		||||
        self.xmpp.register_handler(
 | 
			
		||||
                Callback("Ad-Hoc Result",
 | 
			
		||||
                         StanzaPath('iq@type=result/command'),
 | 
			
		||||
                         self._handle_command_result))
 | 
			
		||||
 | 
			
		||||
        self.xmpp.register_handler(
 | 
			
		||||
                Callback("Ad-Hoc Error",
 | 
			
		||||
                         StanzaPath('iq@type=error/command'),
 | 
			
		||||
                         self._handle_command_result))
 | 
			
		||||
 | 
			
		||||
        register_stanza_plugin(Iq, stanza.Command)
 | 
			
		||||
 | 
			
		||||
        self.xmpp.add_event_handler('command_execute',
 | 
			
		||||
                                    self._handle_command_start,
 | 
			
		||||
                                    threaded=self.threaded)
 | 
			
		||||
        self.xmpp.add_event_handler('command_next',
 | 
			
		||||
                                    self._handle_command_next,
 | 
			
		||||
                                    threaded=self.threaded)
 | 
			
		||||
        self.xmpp.add_event_handler('command_cancel',
 | 
			
		||||
                                    self._handle_command_cancel,
 | 
			
		||||
                                    threaded=self.threaded)
 | 
			
		||||
        self.xmpp.add_event_handler('command_complete',
 | 
			
		||||
                                    self._handle_command_complete,
 | 
			
		||||
                                    threaded=self.threaded)
 | 
			
		||||
 | 
			
		||||
    def post_init(self):
 | 
			
		||||
        """Handle cross-plugin interactions."""
 | 
			
		||||
        base_plugin.post_init(self)
 | 
			
		||||
        self.xmpp['xep_0030'].add_feature(Command.namespace)
 | 
			
		||||
 | 
			
		||||
    def set_backend(self, db):
 | 
			
		||||
        """
 | 
			
		||||
        Replace the default session storage dictionary with
 | 
			
		||||
        a generic, external data storage mechanism.
 | 
			
		||||
 | 
			
		||||
        The replacement backend must be able to interact through
 | 
			
		||||
        the same syntax and interfaces as a normal dictionary.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            db -- The new session storage mechanism.
 | 
			
		||||
        """
 | 
			
		||||
        self.sessions = db
 | 
			
		||||
 | 
			
		||||
    def prep_handlers(self, handlers, **kwargs):
 | 
			
		||||
        """
 | 
			
		||||
        Prepare a list of functions for use by the backend service.
 | 
			
		||||
 | 
			
		||||
        Intended to be replaced by the backend service as needed.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            handlers -- A list of function pointers
 | 
			
		||||
            **kwargs -- Any additional parameters required by the backend.
 | 
			
		||||
        """
 | 
			
		||||
        pass
 | 
			
		||||
 | 
			
		||||
    # =================================================================
 | 
			
		||||
    # Server side (command provider) API
 | 
			
		||||
 | 
			
		||||
    def add_command(self, jid=None, node=None, name='', handler=None):
 | 
			
		||||
        """
 | 
			
		||||
        Make a new command available to external entities.
 | 
			
		||||
 | 
			
		||||
        Access control may be implemented in the provided handler.
 | 
			
		||||
 | 
			
		||||
        Command workflow is done across a sequence of command handlers. The
 | 
			
		||||
        first handler is given the intial Iq stanza of the request in order
 | 
			
		||||
        to support access control. Subsequent handlers are given only the
 | 
			
		||||
        payload items of the command. All handlers will receive the command's
 | 
			
		||||
        session data.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            jid     -- The JID that will expose the command.
 | 
			
		||||
            node    -- The node associated with the command.
 | 
			
		||||
            name    -- A human readable name for the command.
 | 
			
		||||
            handler -- A function that will generate the response to the
 | 
			
		||||
                       initial command request, as well as enforcing any
 | 
			
		||||
                       access control policies.
 | 
			
		||||
        """
 | 
			
		||||
        if jid is None:
 | 
			
		||||
            jid = self.xmpp.boundjid
 | 
			
		||||
        elif not isinstance(jid, JID):
 | 
			
		||||
            jid = JID(jid)
 | 
			
		||||
        item_jid = jid.full
 | 
			
		||||
 | 
			
		||||
        # Client disco uses only the bare JID
 | 
			
		||||
        if self.xmpp.is_component:
 | 
			
		||||
            jid = jid.full
 | 
			
		||||
        else:
 | 
			
		||||
            jid = jid.bare
 | 
			
		||||
 | 
			
		||||
        self.xmpp['xep_0030'].add_identity(category='automation',
 | 
			
		||||
                                           itype='command-list',
 | 
			
		||||
                                           name='Ad-Hoc commands',
 | 
			
		||||
                                           node=Command.namespace,
 | 
			
		||||
                                           jid=jid)
 | 
			
		||||
        self.xmpp['xep_0030'].add_item(jid=item_jid,
 | 
			
		||||
                                       name=name,
 | 
			
		||||
                                       node=Command.namespace,
 | 
			
		||||
                                       subnode=node,
 | 
			
		||||
                                       ijid=jid)
 | 
			
		||||
        self.xmpp['xep_0030'].add_identity(category='automation',
 | 
			
		||||
                                           itype='command-node',
 | 
			
		||||
                                           name=name,
 | 
			
		||||
                                           node=node,
 | 
			
		||||
                                           jid=jid)
 | 
			
		||||
        self.xmpp['xep_0030'].add_feature(Command.namespace, None, jid)
 | 
			
		||||
 | 
			
		||||
        self.commands[(item_jid, node)] = (name, handler)
 | 
			
		||||
 | 
			
		||||
    def new_session(self):
 | 
			
		||||
        """Return a new session ID."""
 | 
			
		||||
        return str(time.time()) + '-' + self.xmpp.new_id()
 | 
			
		||||
 | 
			
		||||
    def _handle_command(self, iq):
 | 
			
		||||
        """Raise command events based on the command action."""
 | 
			
		||||
        self.xmpp.event('command_%s' % iq['command']['action'], iq)
 | 
			
		||||
 | 
			
		||||
    def _handle_command_start(self, iq):
 | 
			
		||||
        """
 | 
			
		||||
        Process an initial request to execute a command.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            iq -- The command execution request.
 | 
			
		||||
        """
 | 
			
		||||
        sessionid = self.new_session()
 | 
			
		||||
        node = iq['command']['node']
 | 
			
		||||
        key = (iq['to'].full, node)
 | 
			
		||||
        name, handler = self.commands.get(key, ('Not found', None))
 | 
			
		||||
        if not handler:
 | 
			
		||||
            log.debug('Command not found: %s, %s' % (key, self.commands))
 | 
			
		||||
 | 
			
		||||
        initial_session = {'id': sessionid,
 | 
			
		||||
                           'from': iq['from'],
 | 
			
		||||
                           'to': iq['to'],
 | 
			
		||||
                           'node': node,
 | 
			
		||||
                           'payload': None,
 | 
			
		||||
                           'interfaces': '',
 | 
			
		||||
                           'payload_classes': None,
 | 
			
		||||
                           'notes': None,
 | 
			
		||||
                           'has_next': False,
 | 
			
		||||
                           'allow_complete': False,
 | 
			
		||||
                           'allow_prev': False,
 | 
			
		||||
                           'past': [],
 | 
			
		||||
                           'next': None,
 | 
			
		||||
                           'prev': None,
 | 
			
		||||
                           'cancel': None}
 | 
			
		||||
 | 
			
		||||
        session = handler(iq, initial_session)
 | 
			
		||||
 | 
			
		||||
        self._process_command_response(iq, session)
 | 
			
		||||
 | 
			
		||||
    def _handle_command_next(self, iq):
 | 
			
		||||
        """
 | 
			
		||||
        Process a request for the next step in the workflow
 | 
			
		||||
        for a command with multiple steps.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            iq -- The command continuation request.
 | 
			
		||||
        """
 | 
			
		||||
        sessionid = iq['command']['sessionid']
 | 
			
		||||
        session = self.sessions[sessionid]
 | 
			
		||||
 | 
			
		||||
        handler = session['next']
 | 
			
		||||
        interfaces = session['interfaces']
 | 
			
		||||
        results = []
 | 
			
		||||
        for stanza in iq['command']['substanzas']:
 | 
			
		||||
            if stanza.plugin_attrib in interfaces:
 | 
			
		||||
                results.append(stanza)
 | 
			
		||||
        if len(results) == 1:
 | 
			
		||||
            results = results[0]
 | 
			
		||||
 | 
			
		||||
        session = handler(results, session)
 | 
			
		||||
 | 
			
		||||
        self._process_command_response(iq, session)
 | 
			
		||||
 | 
			
		||||
    def _process_command_response(self, iq, session):
 | 
			
		||||
        """
 | 
			
		||||
        Generate a command reply stanza based on the
 | 
			
		||||
        provided session data.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            iq      -- The command request stanza.
 | 
			
		||||
            session -- A dictionary of relevant session data.
 | 
			
		||||
        """
 | 
			
		||||
        sessionid = session['id']
 | 
			
		||||
 | 
			
		||||
        payload = session['payload']
 | 
			
		||||
        if not isinstance(payload, list):
 | 
			
		||||
            payload = [payload]
 | 
			
		||||
 | 
			
		||||
        session['interfaces'] = [item.plugin_attrib for item in payload]
 | 
			
		||||
        session['payload_classes'] = [item.__class__ for item in payload]
 | 
			
		||||
 | 
			
		||||
        self.sessions[sessionid] = session
 | 
			
		||||
 | 
			
		||||
        for item in payload:
 | 
			
		||||
            register_stanza_plugin(Command, item.__class__, iterable=True)
 | 
			
		||||
 | 
			
		||||
        iq.reply()
 | 
			
		||||
        iq['command']['node'] = session['node']
 | 
			
		||||
        iq['command']['sessionid'] = session['id']
 | 
			
		||||
 | 
			
		||||
        if session['next'] is None:
 | 
			
		||||
            iq['command']['actions'] = []
 | 
			
		||||
            iq['command']['status'] = 'completed'
 | 
			
		||||
        elif session['has_next']:
 | 
			
		||||
            actions = ['next']
 | 
			
		||||
            if session['allow_complete']:
 | 
			
		||||
                actions.append('complete')
 | 
			
		||||
            if session['allow_prev']:
 | 
			
		||||
                actions.append('prev')
 | 
			
		||||
            iq['command']['actions'] = actions
 | 
			
		||||
            iq['command']['status'] = 'executing'
 | 
			
		||||
        else:
 | 
			
		||||
            iq['command']['actions'] = ['complete']
 | 
			
		||||
            iq['command']['status'] = 'executing'
 | 
			
		||||
 | 
			
		||||
        iq['command']['notes'] = session['notes']
 | 
			
		||||
 | 
			
		||||
        for item in payload:
 | 
			
		||||
            iq['command'].append(item)
 | 
			
		||||
 | 
			
		||||
        iq.send()
 | 
			
		||||
 | 
			
		||||
    def _handle_command_cancel(self, iq):
 | 
			
		||||
        """
 | 
			
		||||
        Process a request to cancel a command's execution.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            iq -- The command cancellation request.
 | 
			
		||||
        """
 | 
			
		||||
        node = iq['command']['node']
 | 
			
		||||
        sessionid = iq['command']['sessionid']
 | 
			
		||||
        session = self.sessions[sessionid]
 | 
			
		||||
        handler = session['cancel']
 | 
			
		||||
 | 
			
		||||
        if handler:
 | 
			
		||||
            handler(iq, session)
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            del self.sessions[sessionid]
 | 
			
		||||
        except:
 | 
			
		||||
            pass
 | 
			
		||||
 | 
			
		||||
        iq.reply()
 | 
			
		||||
        iq['command']['node'] = node
 | 
			
		||||
        iq['command']['sessionid'] = sessionid
 | 
			
		||||
        iq['command']['status'] = 'canceled'
 | 
			
		||||
        iq['command']['notes'] = session['notes']
 | 
			
		||||
        iq.send()
 | 
			
		||||
 | 
			
		||||
    def _handle_command_complete(self, iq):
 | 
			
		||||
        """
 | 
			
		||||
        Process a request to finish the execution of command
 | 
			
		||||
        and terminate the workflow.
 | 
			
		||||
 | 
			
		||||
        All data related to the command session will be removed.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            iq -- The command completion request.
 | 
			
		||||
        """
 | 
			
		||||
        node = iq['command']['node']
 | 
			
		||||
        sessionid = iq['command']['sessionid']
 | 
			
		||||
        session = self.sessions[sessionid]
 | 
			
		||||
        handler = session['next']
 | 
			
		||||
        interfaces = session['interfaces']
 | 
			
		||||
        results = []
 | 
			
		||||
        for stanza in iq['command']['substanzas']:
 | 
			
		||||
            if stanza.plugin_attrib in interfaces:
 | 
			
		||||
                results.append(stanza)
 | 
			
		||||
        if len(results) == 1:
 | 
			
		||||
            results = results[0]
 | 
			
		||||
 | 
			
		||||
        if handler:
 | 
			
		||||
            handler(results, session)
 | 
			
		||||
 | 
			
		||||
        iq.reply()
 | 
			
		||||
        iq['command']['node'] = node
 | 
			
		||||
        iq['command']['sessionid'] = sessionid
 | 
			
		||||
        iq['command']['actions'] = []
 | 
			
		||||
        iq['command']['status'] = 'completed'
 | 
			
		||||
        iq['command']['notes'] = session['notes']
 | 
			
		||||
        iq.send()
 | 
			
		||||
 | 
			
		||||
        del self.sessions[sessionid]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    # =================================================================
 | 
			
		||||
    # Client side (command user) API
 | 
			
		||||
 | 
			
		||||
    def get_commands(self, jid, **kwargs):
 | 
			
		||||
        """
 | 
			
		||||
        Return a list of commands provided by a given JID.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            jid      -- The JID to query for commands.
 | 
			
		||||
            local    -- If true, then the query is for a JID/node
 | 
			
		||||
                        combination handled by this Sleek instance and
 | 
			
		||||
                        no stanzas need to be sent.
 | 
			
		||||
                        Otherwise, a disco stanza must be sent to the
 | 
			
		||||
                        remove JID to retrieve the items.
 | 
			
		||||
            ifrom    -- Specifiy the sender's JID.
 | 
			
		||||
            block    -- If true, block and wait for the stanzas' reply.
 | 
			
		||||
            timeout  -- The time in seconds to block while waiting for
 | 
			
		||||
                        a reply. If None, then wait indefinitely.
 | 
			
		||||
            callback -- Optional callback to execute when a reply is
 | 
			
		||||
                        received instead of blocking and waiting for
 | 
			
		||||
                        the reply.
 | 
			
		||||
            iterator -- If True, return a result set iterator using
 | 
			
		||||
                        the XEP-0059 plugin, if the plugin is loaded.
 | 
			
		||||
                        Otherwise the parameter is ignored.
 | 
			
		||||
        """
 | 
			
		||||
        return self.xmpp['xep_0030'].get_items(jid=jid,
 | 
			
		||||
                                               node=Command.namespace,
 | 
			
		||||
                                               **kwargs)
 | 
			
		||||
 | 
			
		||||
    def send_command(self, jid, node, ifrom=None, action='execute',
 | 
			
		||||
                    payload=None, sessionid=None, **kwargs):
 | 
			
		||||
        """
 | 
			
		||||
        Create and send a command stanza, without using the provided
 | 
			
		||||
        workflow management APIs.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            jid       -- The JID to send the command request or result.
 | 
			
		||||
            node      -- The node for the command.
 | 
			
		||||
            ifrom     -- Specify the sender's JID.
 | 
			
		||||
            action    -- May be one of: execute, cancel, complete,
 | 
			
		||||
                         or cancel.
 | 
			
		||||
            payload   -- Either a list of payload items, or a single
 | 
			
		||||
                         payload item such as a data form.
 | 
			
		||||
            sessionid -- The current session's ID value.
 | 
			
		||||
            block     -- Specify if the send call will block until a
 | 
			
		||||
                         response is received, or a timeout occurs.
 | 
			
		||||
                         Defaults to True.
 | 
			
		||||
            timeout   -- The length of time (in seconds) to wait for a
 | 
			
		||||
                         response before exiting the send call
 | 
			
		||||
                         if blocking is used. Defaults to
 | 
			
		||||
                         sleekxmpp.xmlstream.RESPONSE_TIMEOUT
 | 
			
		||||
            callback  -- Optional reference to a stream handler
 | 
			
		||||
                         function. Will be executed when a reply
 | 
			
		||||
                         stanza is received.
 | 
			
		||||
        """
 | 
			
		||||
        iq = self.xmpp.Iq()
 | 
			
		||||
        iq['type'] = 'set'
 | 
			
		||||
        iq['to'] = jid
 | 
			
		||||
        if ifrom:
 | 
			
		||||
            iq['from'] = ifrom
 | 
			
		||||
        iq['command']['node'] = node
 | 
			
		||||
        iq['command']['action'] = action
 | 
			
		||||
        if sessionid is not None:
 | 
			
		||||
            iq['command']['sessionid'] = sessionid
 | 
			
		||||
        if payload is not None:
 | 
			
		||||
            if not isinstance(payload, list):
 | 
			
		||||
                payload = [payload]
 | 
			
		||||
            for item in payload:
 | 
			
		||||
                iq['command'].append(item)
 | 
			
		||||
        return iq.send(**kwargs)
 | 
			
		||||
 | 
			
		||||
    def start_command(self, jid, node, session, ifrom=None):
 | 
			
		||||
        """
 | 
			
		||||
        Initiate executing a command provided by a remote agent.
 | 
			
		||||
 | 
			
		||||
        The workflow provided is always non-blocking.
 | 
			
		||||
 | 
			
		||||
        The provided session dictionary should contain:
 | 
			
		||||
            next  -- A handler for processing the command result.
 | 
			
		||||
            error -- A handler for processing any error stanzas
 | 
			
		||||
                     generated by the request.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            jid     -- The JID to send the command request.
 | 
			
		||||
            node    -- The node for the desired command.
 | 
			
		||||
            session -- A dictionary of relevant session data.
 | 
			
		||||
            ifrom   -- Optionally specify the sender's JID.
 | 
			
		||||
        """
 | 
			
		||||
        session['jid'] = jid
 | 
			
		||||
        session['node'] = node
 | 
			
		||||
        session['timestamp'] = time.time()
 | 
			
		||||
        session['payload'] = None
 | 
			
		||||
        iq = self.xmpp.Iq()
 | 
			
		||||
        iq['type'] = 'set'
 | 
			
		||||
        iq['to'] = jid
 | 
			
		||||
        if ifrom:
 | 
			
		||||
            iq['from'] = ifrom
 | 
			
		||||
            session['from'] = ifrom
 | 
			
		||||
        iq['command']['node'] = node
 | 
			
		||||
        iq['command']['action'] = 'execute'
 | 
			
		||||
        sessionid = 'client:pending_' + iq['id']
 | 
			
		||||
        session['id'] = sessionid
 | 
			
		||||
        self.sessions[sessionid] = session
 | 
			
		||||
        iq.send(block=False)
 | 
			
		||||
 | 
			
		||||
    def continue_command(self, session):
 | 
			
		||||
        """
 | 
			
		||||
        Execute the next action of the command.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            session -- All stored data relevant to the current
 | 
			
		||||
                       command session.
 | 
			
		||||
        """
 | 
			
		||||
        sessionid = 'client:' + session['id']
 | 
			
		||||
        self.sessions[sessionid] = session
 | 
			
		||||
 | 
			
		||||
        self.send_command(session['jid'],
 | 
			
		||||
                          session['node'],
 | 
			
		||||
                          ifrom=session.get('from', None),
 | 
			
		||||
                          action='next',
 | 
			
		||||
                          payload=session.get('payload', None),
 | 
			
		||||
                          sessionid=session['id'])
 | 
			
		||||
 | 
			
		||||
    def cancel_command(self, session):
 | 
			
		||||
        """
 | 
			
		||||
        Cancel the execution of a command.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            session -- All stored data relevant to the current
 | 
			
		||||
                       command session.
 | 
			
		||||
        """
 | 
			
		||||
        sessionid = 'client:' + session['id']
 | 
			
		||||
        self.sessions[sessionid] = session
 | 
			
		||||
 | 
			
		||||
        self.send_command(session['jid'],
 | 
			
		||||
                          session['node'],
 | 
			
		||||
                          ifrom=session.get('from', None),
 | 
			
		||||
                          action='cancel',
 | 
			
		||||
                          payload=session.get('payload', None),
 | 
			
		||||
                          sessionid=session['id'])
 | 
			
		||||
 | 
			
		||||
    def complete_command(self, session):
 | 
			
		||||
        """
 | 
			
		||||
        Finish the execution of a command workflow.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            session -- All stored data relevant to the current
 | 
			
		||||
                       command session.
 | 
			
		||||
        """
 | 
			
		||||
        sessionid = 'client:' + session['id']
 | 
			
		||||
        self.sessions[sessionid] = session
 | 
			
		||||
 | 
			
		||||
        self.send_command(session['jid'],
 | 
			
		||||
                          session['node'],
 | 
			
		||||
                          ifrom=session.get('from', None),
 | 
			
		||||
                          action='complete',
 | 
			
		||||
                          payload=session.get('payload', None),
 | 
			
		||||
                          sessionid=session['id'])
 | 
			
		||||
 | 
			
		||||
    def terminate_command(self, session):
 | 
			
		||||
        """
 | 
			
		||||
        Delete a command's session after a command has completed
 | 
			
		||||
        or an error has occured.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            session -- All stored data relevant to the current
 | 
			
		||||
                       command session.
 | 
			
		||||
        """
 | 
			
		||||
        try:
 | 
			
		||||
            del self.sessions[session['id']]
 | 
			
		||||
        except:
 | 
			
		||||
            pass
 | 
			
		||||
 | 
			
		||||
    def _handle_command_result(self, iq):
 | 
			
		||||
        """
 | 
			
		||||
        Process the results of a command request.
 | 
			
		||||
 | 
			
		||||
        Will execute the 'next' handler stored in the session
 | 
			
		||||
        data, or the 'error' handler depending on the Iq's type.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            iq -- The command response.
 | 
			
		||||
        """
 | 
			
		||||
        sessionid = 'client:' + iq['command']['sessionid']
 | 
			
		||||
        pending = False
 | 
			
		||||
 | 
			
		||||
        if sessionid not in self.sessions:
 | 
			
		||||
            pending = True
 | 
			
		||||
            pendingid = 'client:pending_' + iq['id']
 | 
			
		||||
            if pendingid not in self.sessions:
 | 
			
		||||
                return
 | 
			
		||||
            sessionid = pendingid
 | 
			
		||||
 | 
			
		||||
        session = self.sessions[sessionid]
 | 
			
		||||
        sessionid = 'client:' + iq['command']['sessionid']
 | 
			
		||||
        session['id'] = iq['command']['sessionid']
 | 
			
		||||
 | 
			
		||||
        self.sessions[sessionid] = session
 | 
			
		||||
 | 
			
		||||
        if pending:
 | 
			
		||||
            del self.sessions[pendingid]
 | 
			
		||||
 | 
			
		||||
        handler_type = 'next'
 | 
			
		||||
        if iq['type'] == 'error':
 | 
			
		||||
            handler_type = 'error'
 | 
			
		||||
        handler = session.get(handler_type, None)
 | 
			
		||||
        if handler:
 | 
			
		||||
            handler(iq, session)
 | 
			
		||||
        elif iq['type'] == 'error':
 | 
			
		||||
            self.terminate_command(session)
 | 
			
		||||
 | 
			
		||||
        if iq['command']['status']  == 'completed':
 | 
			
		||||
            self.terminate_command(session)
 | 
			
		||||
							
								
								
									
										185
									
								
								sleekxmpp/plugins/xep_0050/stanza.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										185
									
								
								sleekxmpp/plugins/xep_0050/stanza.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,185 @@
 | 
			
		||||
"""
 | 
			
		||||
    SleekXMPP: The Sleek XMPP Library
 | 
			
		||||
    Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout
 | 
			
		||||
    This file is part of SleekXMPP.
 | 
			
		||||
 | 
			
		||||
    See the file LICENSE for copying permission.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
from sleekxmpp.xmlstream import ElementBase, ET
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Command(ElementBase):
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    XMPP's Adhoc Commands provides a generic workflow mechanism for
 | 
			
		||||
    interacting with applications. The result is similar to menu selections
 | 
			
		||||
    and multi-step dialogs in normal desktop applications. Clients do not
 | 
			
		||||
    need to know in advance what commands are provided by any particular
 | 
			
		||||
    application or agent. While adhoc commands provide similar functionality
 | 
			
		||||
    to Jabber-RPC, adhoc commands are used primarily for human interaction.
 | 
			
		||||
 | 
			
		||||
    Also see <http://xmpp.org/extensions/xep-0050.html>
 | 
			
		||||
 | 
			
		||||
    Example command stanzas:
 | 
			
		||||
      <iq type="set">
 | 
			
		||||
        <command xmlns="http://jabber.org/protocol/commands"
 | 
			
		||||
                 node="run_foo"
 | 
			
		||||
                 action="execute" />
 | 
			
		||||
      </iq>
 | 
			
		||||
 | 
			
		||||
      <iq type="result">
 | 
			
		||||
        <command xmlns="http://jabber.org/protocol/commands"
 | 
			
		||||
                 node="run_foo"
 | 
			
		||||
                 sessionid="12345"
 | 
			
		||||
                 status="executing">
 | 
			
		||||
          <actions>
 | 
			
		||||
            <complete />
 | 
			
		||||
          </actions>
 | 
			
		||||
          <note type="info">Information!</note>
 | 
			
		||||
          <x xmlns="jabber:x:data">
 | 
			
		||||
            <field var="greeting"
 | 
			
		||||
                   type="text-single"
 | 
			
		||||
                   label="Greeting" />
 | 
			
		||||
          </x>
 | 
			
		||||
        </command>
 | 
			
		||||
      </iq>
 | 
			
		||||
 | 
			
		||||
    Stanza Interface:
 | 
			
		||||
        action    -- The action to perform.
 | 
			
		||||
        actions   -- The set of allowable next actions.
 | 
			
		||||
        node      -- The node associated with the command.
 | 
			
		||||
        notes     -- A list of tuples for informative notes.
 | 
			
		||||
        sessionid -- A unique identifier for a command session.
 | 
			
		||||
        status    -- May be one of: canceled, completed, or executing.
 | 
			
		||||
 | 
			
		||||
    Attributes:
 | 
			
		||||
        actions      -- A set of allowed action values.
 | 
			
		||||
        statuses     -- A set of allowed status values.
 | 
			
		||||
        next_actions -- A set of allowed next action names.
 | 
			
		||||
 | 
			
		||||
    Methods:
 | 
			
		||||
        get_action  -- Return the requested action.
 | 
			
		||||
        get_actions -- Return the allowable next actions.
 | 
			
		||||
        set_actions -- Set the allowable next actions.
 | 
			
		||||
        del_actions -- Remove the current set of next actions.
 | 
			
		||||
        get_notes   -- Return a list of informative note data.
 | 
			
		||||
        set_notes   -- Set informative notes.
 | 
			
		||||
        del_notes   -- Remove any note data.
 | 
			
		||||
        add_note    -- Add a single note.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    name = 'command'
 | 
			
		||||
    namespace = 'http://jabber.org/protocol/commands'
 | 
			
		||||
    plugin_attrib = 'command'
 | 
			
		||||
    interfaces = set(('action', 'sessionid', 'node',
 | 
			
		||||
                      'status', 'actions', 'notes'))
 | 
			
		||||
    actions = set(('cancel', 'complete', 'execute', 'next', 'prev'))
 | 
			
		||||
    statuses = set(('canceled', 'completed', 'executing'))
 | 
			
		||||
    next_actions = set(('prev', 'next', 'complete'))
 | 
			
		||||
 | 
			
		||||
    def get_action(self):
 | 
			
		||||
        """
 | 
			
		||||
        Return the value of the action attribute.
 | 
			
		||||
 | 
			
		||||
        If the Iq stanza's type is "set" then use a default
 | 
			
		||||
        value of "execute".
 | 
			
		||||
        """
 | 
			
		||||
        if self.parent()['type'] == 'set':
 | 
			
		||||
            return self._get_attr('action', default='execute')
 | 
			
		||||
        return self._get_attr('action')
 | 
			
		||||
 | 
			
		||||
    def set_actions(self, values):
 | 
			
		||||
        """
 | 
			
		||||
        Assign the set of allowable next actions.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            values -- A list containing any combination of:
 | 
			
		||||
                        'prev', 'next', and 'complete'
 | 
			
		||||
        """
 | 
			
		||||
        self.del_actions()
 | 
			
		||||
        if values:
 | 
			
		||||
            self._set_sub_text('{%s}actions' % self.namespace, '', True)
 | 
			
		||||
            actions = self.find('{%s}actions' % self.namespace)
 | 
			
		||||
            for val in values:
 | 
			
		||||
                if val in self.next_actions:
 | 
			
		||||
                    action = ET.Element('{%s}%s' % (self.namespace, val))
 | 
			
		||||
                    actions.append(action)
 | 
			
		||||
 | 
			
		||||
    def get_actions(self):
 | 
			
		||||
        """
 | 
			
		||||
        Return the set of allowable next actions.
 | 
			
		||||
        """
 | 
			
		||||
        actions = []
 | 
			
		||||
        actions_xml = self.find('{%s}actions' % self.namespace)
 | 
			
		||||
        if actions_xml is not None:
 | 
			
		||||
            for action in self.next_actions:
 | 
			
		||||
                action_xml = actions_xml.find('{%s}%s' % (self.namespace,
 | 
			
		||||
                                                          action))
 | 
			
		||||
                if action_xml is not None:
 | 
			
		||||
                    actions.append(action)
 | 
			
		||||
        return actions
 | 
			
		||||
 | 
			
		||||
    def del_actions(self):
 | 
			
		||||
        """
 | 
			
		||||
        Remove all allowable next actions.
 | 
			
		||||
        """
 | 
			
		||||
        self._del_sub('{%s}actions' % self.namespace)
 | 
			
		||||
 | 
			
		||||
    def get_notes(self):
 | 
			
		||||
        """
 | 
			
		||||
        Return a list of note information.
 | 
			
		||||
 | 
			
		||||
        Example:
 | 
			
		||||
            [('info', 'Some informative data'),
 | 
			
		||||
             ('warning', 'Use caution'),
 | 
			
		||||
             ('error', 'The command ran, but had errors')]
 | 
			
		||||
        """
 | 
			
		||||
        notes = []
 | 
			
		||||
        notes_xml = self.findall('{%s}note' % self.namespace)
 | 
			
		||||
        for note in notes_xml:
 | 
			
		||||
            notes.append((note.attrib.get('type', 'info'),
 | 
			
		||||
                          note.text))
 | 
			
		||||
        return notes
 | 
			
		||||
 | 
			
		||||
    def set_notes(self, notes):
 | 
			
		||||
        """
 | 
			
		||||
        Add multiple notes to the command result.
 | 
			
		||||
 | 
			
		||||
        Each note is a tuple, with the first item being one of:
 | 
			
		||||
        'info', 'warning', or 'error', and the second item being
 | 
			
		||||
        any human readable message.
 | 
			
		||||
 | 
			
		||||
        Example:
 | 
			
		||||
            [('info', 'Some informative data'),
 | 
			
		||||
             ('warning', 'Use caution'),
 | 
			
		||||
             ('error', 'The command ran, but had errors')]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            notes -- A list of tuples of note information.
 | 
			
		||||
        """
 | 
			
		||||
        self.del_notes()
 | 
			
		||||
        for note in notes:
 | 
			
		||||
            self.add_note(note[1], note[0])
 | 
			
		||||
 | 
			
		||||
    def del_notes(self):
 | 
			
		||||
        """
 | 
			
		||||
        Remove all notes associated with the command result.
 | 
			
		||||
        """
 | 
			
		||||
        notes_xml = self.findall('{%s}note' % self.namespace)
 | 
			
		||||
        for note in notes_xml:
 | 
			
		||||
            self.xml.remove(note)
 | 
			
		||||
 | 
			
		||||
    def add_note(self, msg='', ntype='info'):
 | 
			
		||||
        """
 | 
			
		||||
        Add a single note annotation to the command.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            msg   -- A human readable message.
 | 
			
		||||
            ntype -- One of: 'info', 'warning', 'error'
 | 
			
		||||
        """
 | 
			
		||||
        xml = ET.Element('{%s}note' % self.namespace)
 | 
			
		||||
        xml.attrib['type'] = ntype
 | 
			
		||||
        xml.text = msg
 | 
			
		||||
        self.xml.append(xml)
 | 
			
		||||
							
								
								
									
										10
									
								
								sleekxmpp/plugins/xep_0059/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								sleekxmpp/plugins/xep_0059/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,10 @@
 | 
			
		||||
"""
 | 
			
		||||
    SleekXMPP: The Sleek XMPP Library
 | 
			
		||||
    Copyright (C) 2010 Nathanael C. Fritz, Erik Reuterborg Larsson
 | 
			
		||||
    This file is part of SleekXMPP.
 | 
			
		||||
 | 
			
		||||
    See the file LICENSE for copying permission.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
from sleekxmpp.plugins.xep_0059.stanza import Set
 | 
			
		||||
from sleekxmpp.plugins.xep_0059.rsm import ResultIterator, xep_0059
 | 
			
		||||
							
								
								
									
										119
									
								
								sleekxmpp/plugins/xep_0059/rsm.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										119
									
								
								sleekxmpp/plugins/xep_0059/rsm.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,119 @@
 | 
			
		||||
"""
 | 
			
		||||
    SleekXMPP: The Sleek XMPP Library
 | 
			
		||||
    Copyright (C) 2010 Nathanael C. Fritz, Erik Reuterborg Larsson
 | 
			
		||||
    This file is part of SleekXMPP.
 | 
			
		||||
 | 
			
		||||
    See the file LICENSE for copying permission.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
import logging
 | 
			
		||||
 | 
			
		||||
import sleekxmpp
 | 
			
		||||
from sleekxmpp import Iq
 | 
			
		||||
from sleekxmpp.plugins.base import base_plugin
 | 
			
		||||
from sleekxmpp.xmlstream import register_stanza_plugin
 | 
			
		||||
from sleekxmpp.plugins.xep_0059 import Set
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
log = logging.getLogger(__name__)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ResultIterator():
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    An iterator for Result Set Managment
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    def __init__(self, query, interface, amount=10, start=None, reverse=False):
 | 
			
		||||
        """
 | 
			
		||||
        Arguments:
 | 
			
		||||
           query     -- The template query
 | 
			
		||||
           interface -- The substanza of the query, for example disco_items
 | 
			
		||||
           amount    -- The max amounts of items to request per iteration
 | 
			
		||||
           start     -- From which item id to start
 | 
			
		||||
           reverse   -- If True, page backwards through the results
 | 
			
		||||
 | 
			
		||||
        Example:
 | 
			
		||||
           q = Iq()
 | 
			
		||||
           q['to'] = 'pubsub.example.com'
 | 
			
		||||
           q['disco_items']['node'] = 'blog'
 | 
			
		||||
           for i in ResultIterator(q, 'disco_items', '10'):
 | 
			
		||||
               print i['disco_items']['items']
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        self.query = query
 | 
			
		||||
        self.amount = amount
 | 
			
		||||
        self.start = start
 | 
			
		||||
        self.interface = interface
 | 
			
		||||
        self.reverse = reverse
 | 
			
		||||
 | 
			
		||||
    def __iter__(self):
 | 
			
		||||
        return self
 | 
			
		||||
 | 
			
		||||
    def __next__(self):
 | 
			
		||||
        return self.next()
 | 
			
		||||
 | 
			
		||||
    def next(self):
 | 
			
		||||
        """
 | 
			
		||||
        Return the next page of results from a query.
 | 
			
		||||
 | 
			
		||||
        Note: If using backwards paging, then the next page of
 | 
			
		||||
              results will be the items before the current page
 | 
			
		||||
              of items.
 | 
			
		||||
        """
 | 
			
		||||
        self.query[self.interface]['rsm']['before'] = self.reverse
 | 
			
		||||
        self.query['id'] = self.query.stream.new_id()
 | 
			
		||||
        self.query[self.interface]['rsm']['max'] = str(self.amount)
 | 
			
		||||
 | 
			
		||||
        if self.start and self.reverse:
 | 
			
		||||
            self.query[self.interface]['rsm']['before'] = self.start
 | 
			
		||||
        elif self.start:
 | 
			
		||||
            self.query[self.interface]['rsm']['after'] = self.start
 | 
			
		||||
 | 
			
		||||
        r = self.query.send(block=True)
 | 
			
		||||
 | 
			
		||||
        if not r or not r[self.interface]['rsm']['first'] and \
 | 
			
		||||
           not r[self.interface]['rsm']['last']:
 | 
			
		||||
            raise StopIteration
 | 
			
		||||
 | 
			
		||||
        if self.reverse:
 | 
			
		||||
            self.start = r[self.interface]['rsm']['first']
 | 
			
		||||
        else:
 | 
			
		||||
            self.start = r[self.interface]['rsm']['last']
 | 
			
		||||
 | 
			
		||||
        return r
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class xep_0059(base_plugin):
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    XEP-0050: Result Set Management
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    def plugin_init(self):
 | 
			
		||||
        """
 | 
			
		||||
        Start the XEP-0059 plugin.
 | 
			
		||||
        """
 | 
			
		||||
        self.xep = '0059'
 | 
			
		||||
        self.description = 'Result Set Management'
 | 
			
		||||
        self.stanza = sleekxmpp.plugins.xep_0059.stanza
 | 
			
		||||
 | 
			
		||||
    def post_init(self):
 | 
			
		||||
        """Handle inter-plugin dependencies."""
 | 
			
		||||
        base_plugin.post_init(self)
 | 
			
		||||
        self.xmpp['xep_0030'].add_feature(Set.namespace)
 | 
			
		||||
 | 
			
		||||
    def iterate(self, stanza, interface):
 | 
			
		||||
        """
 | 
			
		||||
        Create a new result set iterator for a given stanza query.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            stanza    -- A stanza object to serve as a template for
 | 
			
		||||
                         queries made each iteration. For example, a
 | 
			
		||||
                         basic disco#items query.
 | 
			
		||||
            interface -- The name of the substanza to which the
 | 
			
		||||
                         result set management stanza should be
 | 
			
		||||
                         appended. For example, for disco#items queries
 | 
			
		||||
                         the interface 'disco_items' should be used.
 | 
			
		||||
        """
 | 
			
		||||
        return ResultIterator(stanza, interface)
 | 
			
		||||
							
								
								
									
										108
									
								
								sleekxmpp/plugins/xep_0059/stanza.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										108
									
								
								sleekxmpp/plugins/xep_0059/stanza.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,108 @@
 | 
			
		||||
"""
 | 
			
		||||
    SleekXMPP: The Sleek XMPP Library
 | 
			
		||||
    Copyright (C) 2010 Nathanael C. Fritz, Erik Reuterborg Larsson
 | 
			
		||||
    This file is part of SleekXMPP.
 | 
			
		||||
 | 
			
		||||
    See the file LICENSE for copying permission.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
from sleekxmpp.xmlstream import ElementBase, ET
 | 
			
		||||
from sleekxmpp.plugins.xep_0030.stanza.items import DiscoItems
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Set(ElementBase):
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    XEP-0059 (Result Set Managment) can be used to manage the
 | 
			
		||||
    results of queries. For example, limiting the number of items
 | 
			
		||||
    per response or starting at certain positions.
 | 
			
		||||
 | 
			
		||||
    Example set stanzas:
 | 
			
		||||
    <iq type="get">
 | 
			
		||||
      <query xmlns="http://jabber.org/protocol/disco#items">
 | 
			
		||||
        <set xmlns="http://jabber.org/protocol/rsm">
 | 
			
		||||
          <max>2</max>
 | 
			
		||||
        </set>
 | 
			
		||||
      </query>
 | 
			
		||||
    </iq>
 | 
			
		||||
 | 
			
		||||
    <iq type="result">
 | 
			
		||||
      <query xmlns="http://jabber.org/protocol/disco#items">
 | 
			
		||||
        <item jid="conference.example.com" />
 | 
			
		||||
        <item jid="pubsub.example.com" />
 | 
			
		||||
        <set xmlns="http://jabber.org/protocol/rsm">
 | 
			
		||||
          <first>conference.example.com</first>
 | 
			
		||||
          <last>pubsub.example.com</last>
 | 
			
		||||
        </set>
 | 
			
		||||
      </query>
 | 
			
		||||
    </iq>
 | 
			
		||||
 | 
			
		||||
    Stanza Interface:
 | 
			
		||||
        first_index -- The index attribute of <first>
 | 
			
		||||
        after       -- The id defining from which item to start
 | 
			
		||||
        before      -- The id defining from which item to
 | 
			
		||||
                       start when browsing backwards
 | 
			
		||||
        max         -- Max amount per response
 | 
			
		||||
        first       -- Id for the first item in the response
 | 
			
		||||
        last        -- Id for the last item in the response
 | 
			
		||||
        index       -- Used to set an index to start from
 | 
			
		||||
        count       -- The number of remote items available
 | 
			
		||||
 | 
			
		||||
    Methods:
 | 
			
		||||
        set_first_index -- Sets the index attribute for <first> and
 | 
			
		||||
                           creates the element if it doesn't exist
 | 
			
		||||
        get_first_index -- Returns the value of the index
 | 
			
		||||
                           attribute for <first>
 | 
			
		||||
        del_first_index -- Removes the index attribute for <first>
 | 
			
		||||
                           but keeps the element
 | 
			
		||||
        set_before      -- Sets the value of <before>, if the value is True
 | 
			
		||||
                           then the element will be created without a value
 | 
			
		||||
        get_before      -- Returns the value of <before>, if it is
 | 
			
		||||
                           empty it will return True
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    namespace = 'http://jabber.org/protocol/rsm'
 | 
			
		||||
    name = 'set'
 | 
			
		||||
    plugin_attrib = 'rsm'
 | 
			
		||||
    sub_interfaces = set(('first', 'after', 'before', 'count',
 | 
			
		||||
                          'index', 'last', 'max'))
 | 
			
		||||
    interfaces = set(('first_index', 'first', 'after', 'before',
 | 
			
		||||
                      'count', 'index', 'last', 'max'))
 | 
			
		||||
 | 
			
		||||
    def set_first_index(self, val):
 | 
			
		||||
        fi = self.find("{%s}first" % (self.namespace))
 | 
			
		||||
        if fi is not None:
 | 
			
		||||
            if val:
 | 
			
		||||
                fi.attrib['index'] = val
 | 
			
		||||
            else:
 | 
			
		||||
                del fi.attrib['index']
 | 
			
		||||
        elif val:
 | 
			
		||||
            fi = ET.Element("{%s}first" % (self.namespace))
 | 
			
		||||
            fi.attrib['index'] = val
 | 
			
		||||
            self.xml.append(fi)
 | 
			
		||||
 | 
			
		||||
    def get_first_index(self):
 | 
			
		||||
        fi = self.find("{%s}first" % (self.namespace))
 | 
			
		||||
        if fi is not None:
 | 
			
		||||
            return fi.attrib.get('index', '')
 | 
			
		||||
 | 
			
		||||
    def del_first_index(self):
 | 
			
		||||
        fi = self.xml.find("{%s}first" % (self.namespace))
 | 
			
		||||
        if fi is not None:
 | 
			
		||||
            del fi.attrib['index']
 | 
			
		||||
 | 
			
		||||
    def set_before(self, val):
 | 
			
		||||
        b = self.xml.find("{%s}before" % (self.namespace))
 | 
			
		||||
        if b is None and val == True:
 | 
			
		||||
            self._set_sub_text('{%s}before' % self.namespace, '', True)
 | 
			
		||||
        else:
 | 
			
		||||
            self._set_sub_text('{%s}before' % self.namespace, val)
 | 
			
		||||
 | 
			
		||||
    def get_before(self):
 | 
			
		||||
        b = self.xml.find("{%s}before" % (self.namespace))
 | 
			
		||||
        if b is not None and not b.text:
 | 
			
		||||
            return True
 | 
			
		||||
        elif b is not None:
 | 
			
		||||
            return b.text
 | 
			
		||||
        else:
 | 
			
		||||
            return None
 | 
			
		||||
@@ -51,7 +51,7 @@ class xep_0060(base.base_plugin):
 | 
			
		||||
		pubsub.append(configure)
 | 
			
		||||
		iq = self.xmpp.makeIqSet(pubsub)
 | 
			
		||||
		iq.attrib['to'] = jid
 | 
			
		||||
		iq.attrib['from'] = self.xmpp.fulljid
 | 
			
		||||
		iq.attrib['from'] = self.xmpp.boundjid.full
 | 
			
		||||
		id = iq['id']
 | 
			
		||||
		result = iq.send()
 | 
			
		||||
		if result is False or result is None or result['type'] == 'error': return False
 | 
			
		||||
@@ -63,15 +63,15 @@ class xep_0060(base.base_plugin):
 | 
			
		||||
		subscribe.attrib['node'] = node
 | 
			
		||||
		if subscribee is None:
 | 
			
		||||
			if bare:
 | 
			
		||||
				subscribe.attrib['jid'] = self.xmpp.jid
 | 
			
		||||
				subscribe.attrib['jid'] = self.xmpp.boundjid.bare
 | 
			
		||||
			else:
 | 
			
		||||
				subscribe.attrib['jid'] = self.xmpp.fulljid
 | 
			
		||||
				subscribe.attrib['jid'] = self.xmpp.boundjid.full
 | 
			
		||||
		else:
 | 
			
		||||
			subscribe.attrib['jid'] = subscribee
 | 
			
		||||
		pubsub.append(subscribe)
 | 
			
		||||
		iq = self.xmpp.makeIqSet(pubsub)
 | 
			
		||||
		iq.attrib['to'] = jid
 | 
			
		||||
		iq.attrib['from'] = self.xmpp.fulljid
 | 
			
		||||
		iq.attrib['from'] = self.xmpp.boundjid.full
 | 
			
		||||
		id = iq['id']
 | 
			
		||||
		result = iq.send()
 | 
			
		||||
		if result is False or result is None or result['type'] == 'error': return False
 | 
			
		||||
@@ -83,15 +83,15 @@ class xep_0060(base.base_plugin):
 | 
			
		||||
		unsubscribe.attrib['node'] = node
 | 
			
		||||
		if subscribee is None:
 | 
			
		||||
			if bare:
 | 
			
		||||
				unsubscribe.attrib['jid'] = self.xmpp.jid
 | 
			
		||||
				unsubscribe.attrib['jid'] = self.xmpp.boundjid.bare
 | 
			
		||||
			else:
 | 
			
		||||
				unsubscribe.attrib['jid'] = self.xmpp.fulljid
 | 
			
		||||
				unsubscribe.attrib['jid'] = self.xmpp.boundjid.full
 | 
			
		||||
		else:
 | 
			
		||||
			unsubscribe.attrib['jid'] = subscribee
 | 
			
		||||
		pubsub.append(unsubscribe)
 | 
			
		||||
		iq = self.xmpp.makeIqSet(pubsub)
 | 
			
		||||
		iq.attrib['to'] = jid
 | 
			
		||||
		iq.attrib['from'] = self.xmpp.fulljid
 | 
			
		||||
		iq.attrib['from'] = self.xmpp.boundjid.full
 | 
			
		||||
		id = iq['id']
 | 
			
		||||
		result = iq.send()
 | 
			
		||||
		if result is False or result is None or result['type'] == 'error': return False
 | 
			
		||||
@@ -109,7 +109,7 @@ class xep_0060(base.base_plugin):
 | 
			
		||||
		iq = self.xmpp.makeIqGet()
 | 
			
		||||
		iq.append(pubsub)
 | 
			
		||||
		iq.attrib['to'] = jid
 | 
			
		||||
		iq.attrib['from'] = self.xmpp.fulljid
 | 
			
		||||
		iq.attrib['from'] = self.xmpp.boundjid.full
 | 
			
		||||
		id = iq['id']
 | 
			
		||||
		#self.xmpp.add_handler("<iq id='%s'/>" % id, self.handlerCreateNodeResponse)
 | 
			
		||||
		result = iq.send()
 | 
			
		||||
@@ -133,7 +133,7 @@ class xep_0060(base.base_plugin):
 | 
			
		||||
		iq = self.xmpp.makeIqGet()
 | 
			
		||||
		iq.append(pubsub)
 | 
			
		||||
		iq.attrib['to'] = jid
 | 
			
		||||
		iq.attrib['from'] = self.xmpp.fulljid
 | 
			
		||||
		iq.attrib['from'] = self.xmpp.boundjid.full
 | 
			
		||||
		id = iq['id']
 | 
			
		||||
		result = iq.send()
 | 
			
		||||
		if result is None or result == False or result['type'] == 'error':
 | 
			
		||||
@@ -156,7 +156,7 @@ class xep_0060(base.base_plugin):
 | 
			
		||||
		iq = self.xmpp.makeIqGet()
 | 
			
		||||
		iq.append(pubsub)
 | 
			
		||||
		iq.attrib['to'] = jid
 | 
			
		||||
		iq.attrib['from'] = self.xmpp.fulljid
 | 
			
		||||
		iq.attrib['from'] = self.xmpp.boundjid.full
 | 
			
		||||
		id = iq['id']
 | 
			
		||||
		result = iq.send()
 | 
			
		||||
		if result is None or result == False or result['type'] == 'error':
 | 
			
		||||
@@ -179,7 +179,7 @@ class xep_0060(base.base_plugin):
 | 
			
		||||
		pubsub.append(delete)
 | 
			
		||||
		iq.append(pubsub)
 | 
			
		||||
		iq.attrib['to'] = jid
 | 
			
		||||
		iq.attrib['from'] = self.xmpp.fulljid
 | 
			
		||||
		iq.attrib['from'] = self.xmpp.boundjid.full
 | 
			
		||||
		result = iq.send()
 | 
			
		||||
		if result is not None and result is not False and result['type'] != 'error':
 | 
			
		||||
			return True
 | 
			
		||||
@@ -196,7 +196,7 @@ class xep_0060(base.base_plugin):
 | 
			
		||||
		pubsub.append(configure)
 | 
			
		||||
		iq = self.xmpp.makeIqSet(pubsub)
 | 
			
		||||
		iq.attrib['to'] = jid
 | 
			
		||||
		iq.attrib['from'] = self.xmpp.fulljid
 | 
			
		||||
		iq.attrib['from'] = self.xmpp.boundjid.full
 | 
			
		||||
		id = iq['id']
 | 
			
		||||
		result = iq.send()
 | 
			
		||||
		if result is None or result['type'] == 'error':
 | 
			
		||||
@@ -217,7 +217,7 @@ class xep_0060(base.base_plugin):
 | 
			
		||||
		pubsub.append(publish)
 | 
			
		||||
		iq = self.xmpp.makeIqSet(pubsub)
 | 
			
		||||
		iq.attrib['to'] = jid
 | 
			
		||||
		iq.attrib['from'] = self.xmpp.fulljid
 | 
			
		||||
		iq.attrib['from'] = self.xmpp.boundjid.full
 | 
			
		||||
		id = iq['id']
 | 
			
		||||
		result = iq.send()
 | 
			
		||||
		if result is None or result is False or result['type'] == 'error': return False
 | 
			
		||||
@@ -236,7 +236,7 @@ class xep_0060(base.base_plugin):
 | 
			
		||||
		pubsub.append(retract)
 | 
			
		||||
		iq = self.xmpp.makeIqSet(pubsub)
 | 
			
		||||
		iq.attrib['to'] = jid
 | 
			
		||||
		iq.attrib['from'] = self.xmpp.fulljid
 | 
			
		||||
		iq.attrib['from'] = self.xmpp.boundjid.full
 | 
			
		||||
		id = iq['id']
 | 
			
		||||
		result = iq.send()
 | 
			
		||||
		if result is None or result is False or result['type'] == 'error': return False
 | 
			
		||||
@@ -287,7 +287,7 @@ class xep_0060(base.base_plugin):
 | 
			
		||||
		pubsub.append(affs)
 | 
			
		||||
		iq = self.xmpp.makeIqSet(pubsub)
 | 
			
		||||
		iq.attrib['to'] = ps_jid
 | 
			
		||||
		iq.attrib['from'] = self.xmpp.fulljid
 | 
			
		||||
		iq.attrib['from'] = self.xmpp.boundjid.full
 | 
			
		||||
		id = iq['id']
 | 
			
		||||
		result = iq.send()
 | 
			
		||||
		if result is None or result is False or result['type'] == 'error':
 | 
			
		||||
 
 | 
			
		||||
@@ -36,7 +36,7 @@ class xep_0078(base.base_plugin):
 | 
			
		||||
		log.debug("Starting jabber:iq:auth Authentication")
 | 
			
		||||
		auth_request = self.xmpp.makeIqGet()
 | 
			
		||||
		auth_request_query = ET.Element('{jabber:iq:auth}query')
 | 
			
		||||
		auth_request.attrib['to'] = self.xmpp.server
 | 
			
		||||
		auth_request.attrib['to'] = self.xmpp.boundjid.host
 | 
			
		||||
		username = ET.Element('username')
 | 
			
		||||
		username.text = self.xmpp.username
 | 
			
		||||
		auth_request_query.append(username)
 | 
			
		||||
 
 | 
			
		||||
@@ -1,104 +0,0 @@
 | 
			
		||||
"""
 | 
			
		||||
    SleekXMPP: The Sleek XMPP Library
 | 
			
		||||
    Copyright (C) 2010 Nathanael C. Fritz, Lance J.T. Stout
 | 
			
		||||
    This file is part of SleekXMPP.
 | 
			
		||||
 | 
			
		||||
    See the file LICENSE for copying permissio
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
import logging
 | 
			
		||||
from . import base
 | 
			
		||||
from .. xmlstream.handler.callback import Callback
 | 
			
		||||
from .. xmlstream.matcher.xpath import MatchXPath
 | 
			
		||||
from .. xmlstream.stanzabase import registerStanzaPlugin, ElementBase, ET, JID
 | 
			
		||||
from .. stanza.message import Message
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
log = logging.getLogger(__name__)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ChatState(ElementBase):
 | 
			
		||||
    namespace = 'http://jabber.org/protocol/chatstates'
 | 
			
		||||
    plugin_attrib = 'chat_state'
 | 
			
		||||
    interface = set(('state',))
 | 
			
		||||
    states = set(('active', 'composing', 'gone', 'inactive', 'paused'))
 | 
			
		||||
 | 
			
		||||
    def active(self):
 | 
			
		||||
        self.setState('active')
 | 
			
		||||
 | 
			
		||||
    def composing(self):
 | 
			
		||||
        self.setState('composing')
 | 
			
		||||
 | 
			
		||||
    def gone(self):
 | 
			
		||||
        self.setState('gone')
 | 
			
		||||
 | 
			
		||||
    def inactive(self):
 | 
			
		||||
        self.setState('inactive')
 | 
			
		||||
 | 
			
		||||
    def paused(self):
 | 
			
		||||
        self.setState('paused')
 | 
			
		||||
 | 
			
		||||
    def setState(self, state):
 | 
			
		||||
        if state in self.states:
 | 
			
		||||
            self.name = state
 | 
			
		||||
            self.xml.tag = '{%s}%s' % (self.namespace, state)
 | 
			
		||||
        else:
 | 
			
		||||
            raise ValueError('Invalid chat state')
 | 
			
		||||
 | 
			
		||||
    def getState(self):
 | 
			
		||||
        return self.name
 | 
			
		||||
 | 
			
		||||
# In order to match the various chat state elements,
 | 
			
		||||
# we need one stanza object per state, even though
 | 
			
		||||
# they are all the same except for the initial name
 | 
			
		||||
# value. Do not depend on the type of the chat state
 | 
			
		||||
# stanza object for the actual state.
 | 
			
		||||
 | 
			
		||||
class Active(ChatState):
 | 
			
		||||
    name = 'active'
 | 
			
		||||
class Composing(ChatState):
 | 
			
		||||
    name = 'composing'
 | 
			
		||||
class Gone(ChatState):
 | 
			
		||||
    name = 'gone'
 | 
			
		||||
class Inactive(ChatState):
 | 
			
		||||
    name = 'inactive'
 | 
			
		||||
class Paused(ChatState):
 | 
			
		||||
    name = 'paused'
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class xep_0085(base.base_plugin):
 | 
			
		||||
    """
 | 
			
		||||
    XEP-0085 Chat State Notifications
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    def plugin_init(self):
 | 
			
		||||
        self.xep = '0085'
 | 
			
		||||
        self.description = 'Chat State Notifications'
 | 
			
		||||
 | 
			
		||||
        handlers = [('Active Chat State', 'active'),
 | 
			
		||||
                    ('Composing Chat State', 'composing'),
 | 
			
		||||
                    ('Gone Chat State', 'gone'),
 | 
			
		||||
                    ('Inactive Chat State', 'inactive'),
 | 
			
		||||
                    ('Paused Chat State', 'paused')]
 | 
			
		||||
        for handler in handlers:
 | 
			
		||||
            self.xmpp.registerHandler(
 | 
			
		||||
                Callback(handler[0],
 | 
			
		||||
                         MatchXPath("{%s}message/{%s}%s" % (self.xmpp.default_ns,
 | 
			
		||||
                                                            ChatState.namespace,
 | 
			
		||||
                                                            handler[1])),
 | 
			
		||||
                         self._handleChatState))
 | 
			
		||||
 | 
			
		||||
        registerStanzaPlugin(Message, Active)
 | 
			
		||||
        registerStanzaPlugin(Message, Composing)
 | 
			
		||||
        registerStanzaPlugin(Message, Gone)
 | 
			
		||||
        registerStanzaPlugin(Message, Inactive)
 | 
			
		||||
        registerStanzaPlugin(Message, Paused)
 | 
			
		||||
 | 
			
		||||
    def post_init(self):
 | 
			
		||||
        base.base_plugin.post_init(self)
 | 
			
		||||
        self.xmpp.plugin['xep_0030'].add_feature('http://jabber.org/protocol/chatstates')
 | 
			
		||||
 | 
			
		||||
    def _handleChatState(self, msg):
 | 
			
		||||
        state = msg['chat_state'].name
 | 
			
		||||
        log.debug("Chat State: %s, %s" % (state, msg['from'].jid))
 | 
			
		||||
        self.xmpp.event('chatstate_%s' % state, msg)
 | 
			
		||||
							
								
								
									
										10
									
								
								sleekxmpp/plugins/xep_0085/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								sleekxmpp/plugins/xep_0085/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,10 @@
 | 
			
		||||
"""
 | 
			
		||||
    SleekXMPP: The Sleek XMPP Library
 | 
			
		||||
    Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout
 | 
			
		||||
    This file is part of SleekXMPP.
 | 
			
		||||
 | 
			
		||||
    See the file LICENSE for copying permissio
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
from sleekxmpp.plugins.xep_0085.stanza import ChatState
 | 
			
		||||
from sleekxmpp.plugins.xep_0085.chat_states import xep_0085
 | 
			
		||||
							
								
								
									
										49
									
								
								sleekxmpp/plugins/xep_0085/chat_states.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								sleekxmpp/plugins/xep_0085/chat_states.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,49 @@
 | 
			
		||||
"""
 | 
			
		||||
    SleekXMPP: The Sleek XMPP Library
 | 
			
		||||
    Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout
 | 
			
		||||
    This file is part of SleekXMPP.
 | 
			
		||||
 | 
			
		||||
    See the file LICENSE for copying permissio
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
import logging
 | 
			
		||||
 | 
			
		||||
import sleekxmpp
 | 
			
		||||
from sleekxmpp.stanza import Message
 | 
			
		||||
from sleekxmpp.xmlstream.handler import Callback
 | 
			
		||||
from sleekxmpp.xmlstream.matcher import StanzaPath
 | 
			
		||||
from sleekxmpp.xmlstream import register_stanza_plugin, ElementBase, ET
 | 
			
		||||
from sleekxmpp.plugins.base import base_plugin
 | 
			
		||||
from sleekxmpp.plugins.xep_0085 import stanza, ChatState
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
log = logging.getLogger(__name__)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class xep_0085(base_plugin):
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    XEP-0085 Chat State Notifications
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    def plugin_init(self):
 | 
			
		||||
        self.xep = '0085'
 | 
			
		||||
        self.description = 'Chat State Notifications'
 | 
			
		||||
        self.stanza = stanza
 | 
			
		||||
 | 
			
		||||
        for state in ChatState.states:
 | 
			
		||||
            self.xmpp.register_handler(
 | 
			
		||||
                Callback('Chat State: %s' % state,
 | 
			
		||||
                         StanzaPath('message@chat_state=%s' % state),
 | 
			
		||||
                         self._handle_chat_state))
 | 
			
		||||
 | 
			
		||||
        register_stanza_plugin(Message, ChatState)
 | 
			
		||||
 | 
			
		||||
    def post_init(self):
 | 
			
		||||
        base_plugin.post_init(self)
 | 
			
		||||
        self.xmpp.plugin['xep_0030'].add_feature(ChatState.namespace)
 | 
			
		||||
 | 
			
		||||
    def _handle_chat_state(self, msg):
 | 
			
		||||
        state = msg['chat_state']
 | 
			
		||||
        log.debug("Chat State: %s, %s" % (state, msg['from'].jid))
 | 
			
		||||
        self.xmpp.event('chatstate_%s' % state, msg)
 | 
			
		||||
							
								
								
									
										73
									
								
								sleekxmpp/plugins/xep_0085/stanza.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										73
									
								
								sleekxmpp/plugins/xep_0085/stanza.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,73 @@
 | 
			
		||||
"""
 | 
			
		||||
    SleekXMPP: The Sleek XMPP Library
 | 
			
		||||
    Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout
 | 
			
		||||
    This file is part of SleekXMPP.
 | 
			
		||||
 | 
			
		||||
    See the file LICENSE for copying permissio
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
import sleekxmpp
 | 
			
		||||
from sleekxmpp.xmlstream import ElementBase, ET
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ChatState(ElementBase):
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    Example chat state stanzas:
 | 
			
		||||
        <message>
 | 
			
		||||
          <active xmlns="http://jabber.org/protocol/chatstates" />
 | 
			
		||||
        </message>
 | 
			
		||||
 | 
			
		||||
        <message>
 | 
			
		||||
          <paused xmlns="http://jabber.org/protocol/chatstates" />
 | 
			
		||||
        </message>
 | 
			
		||||
 | 
			
		||||
    Stanza Interfaces:
 | 
			
		||||
        chat_state
 | 
			
		||||
 | 
			
		||||
    Attributes:
 | 
			
		||||
        states
 | 
			
		||||
 | 
			
		||||
    Methods:
 | 
			
		||||
        get_chat_state
 | 
			
		||||
        set_chat_state
 | 
			
		||||
        del_chat_state
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    name = ''
 | 
			
		||||
    namespace = 'http://jabber.org/protocol/chatstates'
 | 
			
		||||
    plugin_attrib = 'chat_state'
 | 
			
		||||
    interfaces = set(('chat_state',))
 | 
			
		||||
    is_extension = True
 | 
			
		||||
 | 
			
		||||
    states = set(('active', 'composing', 'gone', 'inactive', 'paused'))
 | 
			
		||||
 | 
			
		||||
    def setup(self, xml=None):
 | 
			
		||||
        self.xml = ET.Element('')
 | 
			
		||||
        return True
 | 
			
		||||
 | 
			
		||||
    def get_chat_state(self):
 | 
			
		||||
        parent = self.parent()
 | 
			
		||||
        for state in self.states:
 | 
			
		||||
            state_xml = parent.find('{%s}%s' % (self.namespace, state))
 | 
			
		||||
            if state_xml is not None:
 | 
			
		||||
                self.xml = state_xml
 | 
			
		||||
                return state
 | 
			
		||||
        return ''
 | 
			
		||||
 | 
			
		||||
    def set_chat_state(self, state):
 | 
			
		||||
        self.del_chat_state()
 | 
			
		||||
        parent = self.parent()
 | 
			
		||||
        if state in self.states:
 | 
			
		||||
            self.xml = ET.Element('{%s}%s' % (self.namespace, state))
 | 
			
		||||
            parent.append(self.xml)
 | 
			
		||||
        elif state not in [None, '']:
 | 
			
		||||
            raise ValueError('Invalid chat state')
 | 
			
		||||
 | 
			
		||||
    def del_chat_state(self):
 | 
			
		||||
        parent = self.parent()
 | 
			
		||||
        for state in self.states:
 | 
			
		||||
            state_xml = parent.find('{%s}%s' % (self.namespace, state))
 | 
			
		||||
            if state_xml is not None:
 | 
			
		||||
                self.xml = ET.Element('')
 | 
			
		||||
                parent.xml.remove(state_xml)
 | 
			
		||||
@@ -1,49 +0,0 @@
 | 
			
		||||
 | 
			
		||||
from __future__ import with_statement
 | 
			
		||||
from . import base
 | 
			
		||||
import logging
 | 
			
		||||
from xml.etree import cElementTree as ET
 | 
			
		||||
import copy
 | 
			
		||||
 | 
			
		||||
class xep_0086(base.base_plugin):
 | 
			
		||||
	"""
 | 
			
		||||
	XEP-0086 Error Condition Mappings
 | 
			
		||||
	"""
 | 
			
		||||
 | 
			
		||||
	def plugin_init(self):
 | 
			
		||||
		self.xep = '0086'
 | 
			
		||||
		self.description = 'Error Condition Mappings'
 | 
			
		||||
		self.error_map = {
 | 
			
		||||
			'bad-request':('modify','400'),
 | 
			
		||||
			'conflict':('cancel','409'),
 | 
			
		||||
			'feature-not-implemented':('cancel','501'),
 | 
			
		||||
			'forbidden':('auth','403'),
 | 
			
		||||
			'gone':('modify','302'),
 | 
			
		||||
			'internal-server-error':('wait','500'),
 | 
			
		||||
			'item-not-found':('cancel','404'),
 | 
			
		||||
			'jid-malformed':('modify','400'),
 | 
			
		||||
			'not-acceptable':('modify','406'),
 | 
			
		||||
			'not-allowed':('cancel','405'),
 | 
			
		||||
			'not-authorized':('auth','401'),
 | 
			
		||||
			'payment-required':('auth','402'),
 | 
			
		||||
			'recipient-unavailable':('wait','404'),
 | 
			
		||||
			'redirect':('modify','302'),
 | 
			
		||||
			'registration-required':('auth','407'),
 | 
			
		||||
			'remote-server-not-found':('cancel','404'),
 | 
			
		||||
			'remote-server-timeout':('wait','504'),
 | 
			
		||||
			'resource-constraint':('wait','500'),
 | 
			
		||||
			'service-unavailable':('cancel','503'),
 | 
			
		||||
			'subscription-required':('auth','407'),
 | 
			
		||||
			'undefined-condition':(None,'500'),
 | 
			
		||||
			'unexpected-request':('wait','400')
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
	def makeError(self, condition, cdata=None, errorType=None, text=None, customElem=None):
 | 
			
		||||
		conditionElem = self.xmpp.makeStanzaErrorCondition(condition, cdata)
 | 
			
		||||
		if errorType is None:
 | 
			
		||||
			error = self.xmpp.makeStanzaError(conditionElem, self.error_map[condition][0], self.error_map[condition][1], text, customElem)
 | 
			
		||||
		else:
 | 
			
		||||
			error = self.xmpp.makeStanzaError(conditionElem, errorType, self.error_map[condition][1], text, customElem)
 | 
			
		||||
		error.append(conditionElem)
 | 
			
		||||
		return error
 | 
			
		||||
							
								
								
									
										10
									
								
								sleekxmpp/plugins/xep_0086/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								sleekxmpp/plugins/xep_0086/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,10 @@
 | 
			
		||||
"""
 | 
			
		||||
    SleekXMPP: The Sleek XMPP Library
 | 
			
		||||
    Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout
 | 
			
		||||
    This file is part of SleekXMPP.
 | 
			
		||||
 | 
			
		||||
    See the file LICENSE for copying permission.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
from sleekxmpp.plugins.xep_0086.stanza import LegacyError
 | 
			
		||||
from sleekxmpp.plugins.xep_0086.legacy_error import xep_0086
 | 
			
		||||
							
								
								
									
										42
									
								
								sleekxmpp/plugins/xep_0086/legacy_error.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								sleekxmpp/plugins/xep_0086/legacy_error.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,42 @@
 | 
			
		||||
"""
 | 
			
		||||
    SleekXMPP: The Sleek XMPP Library
 | 
			
		||||
    Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout
 | 
			
		||||
    This file is part of SleekXMPP.
 | 
			
		||||
 | 
			
		||||
    See the file LICENSE for copying permission.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
from sleekxmpp.stanza import Error
 | 
			
		||||
from sleekxmpp.xmlstream import register_stanza_plugin
 | 
			
		||||
from sleekxmpp.plugins.base import base_plugin
 | 
			
		||||
from sleekxmpp.plugins.xep_0086 import stanza, LegacyError
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class xep_0086(base_plugin):
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    XEP-0086: Error Condition Mappings
 | 
			
		||||
 | 
			
		||||
    Older XMPP implementations used code based error messages, similar
 | 
			
		||||
    to HTTP response codes. Since then, error condition elements have
 | 
			
		||||
    been introduced. XEP-0086 provides a mapping between the new
 | 
			
		||||
    condition elements and a combination of error types and the older
 | 
			
		||||
    response codes.
 | 
			
		||||
 | 
			
		||||
    Also see <http://xmpp.org/extensions/xep-0086.html>.
 | 
			
		||||
 | 
			
		||||
    Configuration Values:
 | 
			
		||||
        override -- Indicates if applying legacy error codes should
 | 
			
		||||
                    be done automatically. Defaults to True.
 | 
			
		||||
                    If False, then inserting legacy error codes can
 | 
			
		||||
                    be done using:
 | 
			
		||||
                        iq['error']['legacy']['condition'] = ...
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    def plugin_init(self):
 | 
			
		||||
        self.xep = '0086'
 | 
			
		||||
        self.description = 'Error Condition Mappings'
 | 
			
		||||
        self.stanza = stanza
 | 
			
		||||
 | 
			
		||||
        register_stanza_plugin(Error, LegacyError,
 | 
			
		||||
                               overrides=self.config.get('override', True))
 | 
			
		||||
							
								
								
									
										91
									
								
								sleekxmpp/plugins/xep_0086/stanza.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										91
									
								
								sleekxmpp/plugins/xep_0086/stanza.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,91 @@
 | 
			
		||||
"""
 | 
			
		||||
    SleekXMPP: The Sleek XMPP Library
 | 
			
		||||
    Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout
 | 
			
		||||
    This file is part of SleekXMPP.
 | 
			
		||||
 | 
			
		||||
    See the file LICENSE for copying permission.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
from sleekxmpp.stanza import Error
 | 
			
		||||
from sleekxmpp.xmlstream import ElementBase, ET, register_stanza_plugin
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class LegacyError(ElementBase):
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    Older XMPP implementations used code based error messages, similar
 | 
			
		||||
    to HTTP response codes. Since then, error condition elements have
 | 
			
		||||
    been introduced. XEP-0086 provides a mapping between the new
 | 
			
		||||
    condition elements and a combination of error types and the older
 | 
			
		||||
    response codes.
 | 
			
		||||
 | 
			
		||||
    Also see <http://xmpp.org/extensions/xep-0086.html>.
 | 
			
		||||
 | 
			
		||||
    Example legacy error stanzas:
 | 
			
		||||
        <error xmlns="jabber:client" code="501" type="cancel">
 | 
			
		||||
          <feature-not-implemented
 | 
			
		||||
                xmlns="urn:ietf:params:xml:ns:xmpp-stanzas" />
 | 
			
		||||
        </error>
 | 
			
		||||
 | 
			
		||||
        <error code="402" type="auth">
 | 
			
		||||
          <payment-required
 | 
			
		||||
                xmlns="urn:ietf:params:xml:ns:xmpp-stanzas" />
 | 
			
		||||
        </error>
 | 
			
		||||
 | 
			
		||||
    Attributes:
 | 
			
		||||
        error_map -- A map of error conditions to error types and
 | 
			
		||||
                     code values.
 | 
			
		||||
    Methods:
 | 
			
		||||
        setup         -- Overrides ElementBase.setup
 | 
			
		||||
        set_condition -- Remap the type and code interfaces when a
 | 
			
		||||
                         condition is set.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    name = 'legacy'
 | 
			
		||||
    namespace = Error.namespace
 | 
			
		||||
    plugin_attrib = name
 | 
			
		||||
    interfaces = set(('condition',))
 | 
			
		||||
    overrides = ['set_condition']
 | 
			
		||||
 | 
			
		||||
    error_map = {'bad-request': ('modify','400'),
 | 
			
		||||
                 'conflict': ('cancel','409'),
 | 
			
		||||
                 'feature-not-implemented': ('cancel','501'),
 | 
			
		||||
                 'forbidden': ('auth','403'),
 | 
			
		||||
                 'gone': ('modify','302'),
 | 
			
		||||
                 'internal-server-error': ('wait','500'),
 | 
			
		||||
                 'item-not-found': ('cancel','404'),
 | 
			
		||||
                 'jid-malformed': ('modify','400'),
 | 
			
		||||
                 'not-acceptable': ('modify','406'),
 | 
			
		||||
                 'not-allowed': ('cancel','405'),
 | 
			
		||||
                 'not-authorized': ('auth','401'),
 | 
			
		||||
                 'payment-required': ('auth','402'),
 | 
			
		||||
                 'recipient-unavailable': ('wait','404'),
 | 
			
		||||
                 'redirect': ('modify','302'),
 | 
			
		||||
                 'registration-required': ('auth','407'),
 | 
			
		||||
                 'remote-server-not-found': ('cancel','404'),
 | 
			
		||||
                 'remote-server-timeout': ('wait','504'),
 | 
			
		||||
                 'resource-constraint': ('wait','500'),
 | 
			
		||||
                 'service-unavailable': ('cancel','503'),
 | 
			
		||||
                 'subscription-required': ('auth','407'),
 | 
			
		||||
                 'undefined-condition': (None,'500'),
 | 
			
		||||
                 'unexpected-request': ('wait','400')}
 | 
			
		||||
 | 
			
		||||
    def setup(self, xml):
 | 
			
		||||
        """Don't create XML for the plugin."""
 | 
			
		||||
        self.xml = ET.Element('')
 | 
			
		||||
 | 
			
		||||
    def set_condition(self, value):
 | 
			
		||||
        """
 | 
			
		||||
        Set the error type and code based on the given error
 | 
			
		||||
        condition value.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            value -- The new error condition.
 | 
			
		||||
        """
 | 
			
		||||
        self.parent().set_condition(value)
 | 
			
		||||
 | 
			
		||||
        error_data = self.error_map.get(value, None)
 | 
			
		||||
        if error_data is not None:
 | 
			
		||||
            if error_data[0] is not None:
 | 
			
		||||
                self.parent()['type'] = error_data[0]
 | 
			
		||||
            self.parent()['code'] = error_data[1]
 | 
			
		||||
@@ -1,56 +0,0 @@
 | 
			
		||||
"""
 | 
			
		||||
    SleekXMPP: The Sleek XMPP Library
 | 
			
		||||
    Copyright (C) 2010 Nathanael C. Fritz
 | 
			
		||||
    This file is part of SleekXMPP.
 | 
			
		||||
    
 | 
			
		||||
    See the file LICENSE for copying permission.
 | 
			
		||||
"""
 | 
			
		||||
from xml.etree import cElementTree as ET
 | 
			
		||||
from . import base
 | 
			
		||||
from .. xmlstream.handler.xmlwaiter import XMLWaiter
 | 
			
		||||
 | 
			
		||||
class xep_0092(base.base_plugin):
 | 
			
		||||
	"""
 | 
			
		||||
	XEP-0092 Software Version
 | 
			
		||||
	"""
 | 
			
		||||
	def plugin_init(self):
 | 
			
		||||
		self.description = "Software Version"
 | 
			
		||||
		self.xep = "0092"
 | 
			
		||||
		self.name = self.config.get('name', 'SleekXMPP')
 | 
			
		||||
		self.version = self.config.get('version', '0.1-dev')
 | 
			
		||||
		self.xmpp.add_handler("<iq type='get' xmlns='%s'><query xmlns='jabber:iq:version' /></iq>" % self.xmpp.default_ns, self.report_version, name='Sofware Version')
 | 
			
		||||
	
 | 
			
		||||
	def post_init(self):
 | 
			
		||||
		base.base_plugin.post_init(self)
 | 
			
		||||
		self.xmpp.plugin['xep_0030'].add_feature('jabber:iq:version')
 | 
			
		||||
	
 | 
			
		||||
	def report_version(self, xml):
 | 
			
		||||
		iq = self.xmpp.makeIqResult(xml.get('id', 'unknown'))
 | 
			
		||||
		iq.attrib['to'] = xml.get('from', self.xmpp.server)
 | 
			
		||||
		query = ET.Element('{jabber:iq:version}query')
 | 
			
		||||
		name = ET.Element('name')
 | 
			
		||||
		name.text = self.name
 | 
			
		||||
		version = ET.Element('version')
 | 
			
		||||
		version.text = self.version
 | 
			
		||||
		query.append(name)
 | 
			
		||||
		query.append(version)
 | 
			
		||||
		iq.append(query)
 | 
			
		||||
		self.xmpp.send(iq)
 | 
			
		||||
	
 | 
			
		||||
	def getVersion(self, jid):
 | 
			
		||||
		iq = self.xmpp.makeIqGet()
 | 
			
		||||
		query = ET.Element('{jabber:iq:version}query')
 | 
			
		||||
		iq.append(query)
 | 
			
		||||
		iq.attrib['to'] = jid
 | 
			
		||||
		iq.attrib['from'] = self.xmpp.fulljid
 | 
			
		||||
		id = iq.get('id')
 | 
			
		||||
		result = iq.send()
 | 
			
		||||
		if result and result is not None and result.get('type', 'error') != 'error':
 | 
			
		||||
			qry = result.find('{jabber:iq:version}query')
 | 
			
		||||
			version = {}
 | 
			
		||||
			for child in qry.getchildren():
 | 
			
		||||
				version[child.tag.split('}')[-1]] = child.text
 | 
			
		||||
			return version
 | 
			
		||||
		else:
 | 
			
		||||
			return False
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										11
									
								
								sleekxmpp/plugins/xep_0092/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								sleekxmpp/plugins/xep_0092/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,11 @@
 | 
			
		||||
"""
 | 
			
		||||
    SleekXMPP: The Sleek XMPP Library
 | 
			
		||||
    Copyright (C) 2010 Nathanael C. Fritz, Lance J.T. Stout
 | 
			
		||||
    This file is part of SleekXMPP.
 | 
			
		||||
 | 
			
		||||
    See the file LICENSE for copying permission.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
from sleekxmpp.plugins.xep_0092 import stanza
 | 
			
		||||
from sleekxmpp.plugins.xep_0092.stanza import Version
 | 
			
		||||
from sleekxmpp.plugins.xep_0092.version import xep_0092
 | 
			
		||||
							
								
								
									
										42
									
								
								sleekxmpp/plugins/xep_0092/stanza.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								sleekxmpp/plugins/xep_0092/stanza.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,42 @@
 | 
			
		||||
"""
 | 
			
		||||
    SleekXMPP: The Sleek XMPP Library
 | 
			
		||||
    Copyright (C) 2010 Nathanael C. Fritz
 | 
			
		||||
    This file is part of SleekXMPP.
 | 
			
		||||
 | 
			
		||||
    See the file LICENSE for copying permission.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
from sleekxmpp.xmlstream import ElementBase, ET
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Version(ElementBase):
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    XMPP allows for an agent to advertise the name and version of the
 | 
			
		||||
    underlying software libraries, as well as the operating system
 | 
			
		||||
    that the agent is running on.
 | 
			
		||||
 | 
			
		||||
    Example version stanzas:
 | 
			
		||||
      <iq type="get">
 | 
			
		||||
        <query xmlns="jabber:iq:version" />
 | 
			
		||||
      </iq>
 | 
			
		||||
 | 
			
		||||
      <iq type="result">
 | 
			
		||||
        <query xmlns="jabber:iq:version">
 | 
			
		||||
          <name>SleekXMPP</name>
 | 
			
		||||
          <version>1.0</version>
 | 
			
		||||
          <os>Linux</os>
 | 
			
		||||
        </query>
 | 
			
		||||
      </iq>
 | 
			
		||||
 | 
			
		||||
    Stanza Interface:
 | 
			
		||||
        name    -- The human readable name of the software.
 | 
			
		||||
        version -- The specific version of the software.
 | 
			
		||||
        os      -- The name of the operating system running the program.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    name = 'query'
 | 
			
		||||
    namespace = 'jabber:iq:version'
 | 
			
		||||
    plugin_attrib = 'software_version'
 | 
			
		||||
    interfaces = set(('name', 'version', 'os'))
 | 
			
		||||
    sub_interfaces = interfaces
 | 
			
		||||
							
								
								
									
										88
									
								
								sleekxmpp/plugins/xep_0092/version.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										88
									
								
								sleekxmpp/plugins/xep_0092/version.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,88 @@
 | 
			
		||||
"""
 | 
			
		||||
    SleekXMPP: The Sleek XMPP Library
 | 
			
		||||
    Copyright (C) 2010 Nathanael C. Fritz
 | 
			
		||||
    This file is part of SleekXMPP.
 | 
			
		||||
 | 
			
		||||
    See the file LICENSE for copying permission.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
import logging
 | 
			
		||||
 | 
			
		||||
import sleekxmpp
 | 
			
		||||
from sleekxmpp import Iq
 | 
			
		||||
from sleekxmpp.xmlstream import register_stanza_plugin
 | 
			
		||||
from sleekxmpp.xmlstream.handler import Callback
 | 
			
		||||
from sleekxmpp.xmlstream.matcher import StanzaPath
 | 
			
		||||
from sleekxmpp.plugins.base import base_plugin
 | 
			
		||||
from sleekxmpp.plugins.xep_0092 import Version
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
log = logging.getLogger(__name__)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class xep_0092(base_plugin):
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    XEP-0092: Software Version
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    def plugin_init(self):
 | 
			
		||||
        """
 | 
			
		||||
        Start the XEP-0092 plugin.
 | 
			
		||||
        """
 | 
			
		||||
        self.xep = "0092"
 | 
			
		||||
        self.description = "Software Version"
 | 
			
		||||
        self.stanza = sleekxmpp.plugins.xep_0092.stanza
 | 
			
		||||
 | 
			
		||||
        self.name = self.config.get('name', 'SleekXMPP')
 | 
			
		||||
        self.version = self.config.get('version', '0.1-dev')
 | 
			
		||||
        self.os = self.config.get('os', '')
 | 
			
		||||
 | 
			
		||||
        self.getVersion = self.get_version
 | 
			
		||||
 | 
			
		||||
        self.xmpp.register_handler(
 | 
			
		||||
                Callback('Software Version',
 | 
			
		||||
                         StanzaPath('iq@type=get/software_version'),
 | 
			
		||||
                         self._handle_version))
 | 
			
		||||
 | 
			
		||||
        register_stanza_plugin(Iq, Version)
 | 
			
		||||
 | 
			
		||||
    def post_init(self):
 | 
			
		||||
        """
 | 
			
		||||
        Handle cross-plugin dependencies.
 | 
			
		||||
        """
 | 
			
		||||
        base_plugin.post_init(self)
 | 
			
		||||
        self.xmpp.plugin['xep_0030'].add_feature('jabber:iq:version')
 | 
			
		||||
 | 
			
		||||
    def _handle_version(self, iq):
 | 
			
		||||
        """
 | 
			
		||||
        Respond to a software version query.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            iq -- The Iq stanza containing the software version query.
 | 
			
		||||
        """
 | 
			
		||||
        iq.reply()
 | 
			
		||||
        iq['software_version']['name'] = self.name
 | 
			
		||||
        iq['software_version']['version'] = self.version
 | 
			
		||||
        iq['software_version']['os'] = self.os
 | 
			
		||||
        iq.send()
 | 
			
		||||
 | 
			
		||||
    def get_version(self, jid, ifrom=None):
 | 
			
		||||
        """
 | 
			
		||||
        Retrieve the software version of a remote agent.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            jid -- The JID of the entity to query.
 | 
			
		||||
        """
 | 
			
		||||
        iq = self.xmpp.Iq()
 | 
			
		||||
        iq['to'] = jid
 | 
			
		||||
        if ifrom:
 | 
			
		||||
            iq['from'] = ifrom
 | 
			
		||||
        iq['type'] = 'get'
 | 
			
		||||
        iq['query'] = Version.namespace
 | 
			
		||||
 | 
			
		||||
        result = iq.send()
 | 
			
		||||
 | 
			
		||||
        if result and result['type'] != 'error':
 | 
			
		||||
            return result['software_version'].values
 | 
			
		||||
        return False
 | 
			
		||||
@@ -1,51 +0,0 @@
 | 
			
		||||
"""
 | 
			
		||||
    SleekXMPP: The Sleek XMPP Library
 | 
			
		||||
    Copyright (C) 2010 Nathanael C. Fritz, Lance J.T. Stout
 | 
			
		||||
    This file is part of SleekXMPP.
 | 
			
		||||
 | 
			
		||||
    See the file LICENSE for copying permission.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
import logging
 | 
			
		||||
from . import base
 | 
			
		||||
from .. xmlstream.handler.callback import Callback
 | 
			
		||||
from .. xmlstream.matcher.xpath import MatchXPath
 | 
			
		||||
from .. xmlstream.stanzabase import registerStanzaPlugin, ElementBase, ET, JID
 | 
			
		||||
from .. stanza.iq import Iq
 | 
			
		||||
from . xep_0030 import DiscoInfo, DiscoItems
 | 
			
		||||
from . xep_0004 import Form
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class xep_0128(base.base_plugin):
 | 
			
		||||
    """
 | 
			
		||||
    XEP-0128 Service Discovery Extensions
 | 
			
		||||
    """
 | 
			
		||||
	
 | 
			
		||||
    def plugin_init(self):
 | 
			
		||||
        self.xep = '0128'
 | 
			
		||||
        self.description = 'Service Discovery Extensions'
 | 
			
		||||
 | 
			
		||||
        registerStanzaPlugin(DiscoInfo, Form)
 | 
			
		||||
        registerStanzaPlugin(DiscoItems, Form)
 | 
			
		||||
 | 
			
		||||
    def extend_info(self, node, data=None):
 | 
			
		||||
        if data is None:
 | 
			
		||||
            data = {}
 | 
			
		||||
        node = self.xmpp['xep_0030'].nodes.get(node, None)
 | 
			
		||||
        if node is None:
 | 
			
		||||
            self.xmpp['xep_0030'].add_node(node)
 | 
			
		||||
        
 | 
			
		||||
        info = node.info
 | 
			
		||||
        info['form']['type'] = 'result'
 | 
			
		||||
        info['form'].setFields(data, default=None)
 | 
			
		||||
 | 
			
		||||
    def extend_items(self, node, data=None):
 | 
			
		||||
        if data is None:
 | 
			
		||||
            data = {}
 | 
			
		||||
        node = self.xmpp['xep_0030'].nodes.get(node, None)
 | 
			
		||||
        if node is None:
 | 
			
		||||
            self.xmpp['xep_0030'].add_node(node)
 | 
			
		||||
        
 | 
			
		||||
        items = node.items
 | 
			
		||||
        items['form']['type'] = 'result'
 | 
			
		||||
        items['form'].setFields(data, default=None)
 | 
			
		||||
							
								
								
									
										10
									
								
								sleekxmpp/plugins/xep_0128/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								sleekxmpp/plugins/xep_0128/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,10 @@
 | 
			
		||||
"""
 | 
			
		||||
    SleekXMPP: The Sleek XMPP Library
 | 
			
		||||
    Copyright (C) 2010 Nathanael C. Fritz, Lance J.T. Stout
 | 
			
		||||
    This file is part of SleekXMPP.
 | 
			
		||||
 | 
			
		||||
    See the file LICENSE for copying permission.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
from sleekxmpp.plugins.xep_0128.static import StaticExtendedDisco
 | 
			
		||||
from sleekxmpp.plugins.xep_0128.extended_disco import xep_0128
 | 
			
		||||
							
								
								
									
										101
									
								
								sleekxmpp/plugins/xep_0128/extended_disco.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										101
									
								
								sleekxmpp/plugins/xep_0128/extended_disco.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,101 @@
 | 
			
		||||
"""
 | 
			
		||||
    SleekXMPP: The Sleek XMPP Library
 | 
			
		||||
    Copyright (C) 2010 Nathanael C. Fritz, Lance J.T. Stout
 | 
			
		||||
    This file is part of SleekXMPP.
 | 
			
		||||
 | 
			
		||||
    See the file LICENSE for copying permission.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
import logging
 | 
			
		||||
 | 
			
		||||
import sleekxmpp
 | 
			
		||||
from sleekxmpp import Iq
 | 
			
		||||
from sleekxmpp.xmlstream import register_stanza_plugin
 | 
			
		||||
from sleekxmpp.plugins.base import base_plugin
 | 
			
		||||
from sleekxmpp.plugins.xep_0004 import Form
 | 
			
		||||
from sleekxmpp.plugins.xep_0030 import DiscoInfo
 | 
			
		||||
from sleekxmpp.plugins.xep_0128 import StaticExtendedDisco
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class xep_0128(base_plugin):
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    XEP-0128: Service Discovery Extensions
 | 
			
		||||
 | 
			
		||||
    Allow the use of data forms to add additional identity
 | 
			
		||||
    information to disco#info results.
 | 
			
		||||
 | 
			
		||||
    Also see <http://www.xmpp.org/extensions/xep-0128.html>.
 | 
			
		||||
 | 
			
		||||
    Attributes:
 | 
			
		||||
        disco  -- A reference to the XEP-0030 plugin.
 | 
			
		||||
        static -- Object containing the default set of static
 | 
			
		||||
                  node handlers.
 | 
			
		||||
        xmpp   -- The main SleekXMPP object.
 | 
			
		||||
 | 
			
		||||
    Methods:
 | 
			
		||||
        set_extended_info -- Set extensions to a disco#info result.
 | 
			
		||||
        add_extended_info -- Add an extension to a disco#info result.
 | 
			
		||||
        del_extended_info -- Remove all extensions from a disco#info result.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    def plugin_init(self):
 | 
			
		||||
        """Start the XEP-0128 plugin."""
 | 
			
		||||
        self.xep = '0128'
 | 
			
		||||
        self.description = 'Service Discovery Extensions'
 | 
			
		||||
 | 
			
		||||
        self._disco_ops = ['set_extended_info',
 | 
			
		||||
                           'add_extended_info',
 | 
			
		||||
                           'del_extended_info']
 | 
			
		||||
 | 
			
		||||
        register_stanza_plugin(DiscoInfo, Form, iterable=True)
 | 
			
		||||
 | 
			
		||||
    def post_init(self):
 | 
			
		||||
        """Handle cross-plugin dependencies."""
 | 
			
		||||
        base_plugin.post_init(self)
 | 
			
		||||
        self.disco = self.xmpp['xep_0030']
 | 
			
		||||
        self.static = StaticExtendedDisco(self.disco.static)
 | 
			
		||||
 | 
			
		||||
        self.disco.set_extended_info = self.set_extended_info
 | 
			
		||||
        self.disco.add_extended_info = self.add_extended_info
 | 
			
		||||
        self.disco.del_extended_info = self.del_extended_info
 | 
			
		||||
 | 
			
		||||
        for op in self._disco_ops:
 | 
			
		||||
            self.disco._add_disco_op(op, getattr(self.static, op))
 | 
			
		||||
 | 
			
		||||
    def set_extended_info(self, jid=None, node=None, **kwargs):
 | 
			
		||||
        """
 | 
			
		||||
        Set additional, extended identity information to a node.
 | 
			
		||||
 | 
			
		||||
        Replaces any existing extended information.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            jid  -- The JID to modify.
 | 
			
		||||
            node -- The node to modify.
 | 
			
		||||
            data -- Either a form, or a list of forms to use
 | 
			
		||||
                    as extended information, replacing any
 | 
			
		||||
                    existing extensions.
 | 
			
		||||
        """
 | 
			
		||||
        self.disco._run_node_handler('set_extended_info', jid, node, kwargs)
 | 
			
		||||
 | 
			
		||||
    def add_extended_info(self, jid=None, node=None, **kwargs):
 | 
			
		||||
        """
 | 
			
		||||
        Add additional, extended identity information to a node.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            jid  -- The JID to modify.
 | 
			
		||||
            node -- The node to modify.
 | 
			
		||||
            data -- Either a form, or a list of forms to add
 | 
			
		||||
                    as extended information.
 | 
			
		||||
        """
 | 
			
		||||
        self.disco._run_node_handler('add_extended_info', jid, node, kwargs)
 | 
			
		||||
 | 
			
		||||
    def del_extended_info(self, jid=None, node=None, **kwargs):
 | 
			
		||||
        """
 | 
			
		||||
        Remove all extended identity information to a node.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            jid  -- The JID to modify.
 | 
			
		||||
            node -- The node to modify.
 | 
			
		||||
        """
 | 
			
		||||
        self.disco._run_node_handler('del_extended_info', jid, node, kwargs)
 | 
			
		||||
							
								
								
									
										72
									
								
								sleekxmpp/plugins/xep_0128/static.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								sleekxmpp/plugins/xep_0128/static.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,72 @@
 | 
			
		||||
"""
 | 
			
		||||
    SleekXMPP: The Sleek XMPP Library
 | 
			
		||||
    Copyright (C) 2010 Nathanael C. Fritz, Lance J.T. Stout
 | 
			
		||||
    This file is part of SleekXMPP.
 | 
			
		||||
 | 
			
		||||
    See the file LICENSE for copying permission.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
import logging
 | 
			
		||||
 | 
			
		||||
import sleekxmpp
 | 
			
		||||
from sleekxmpp.plugins.xep_0030 import StaticDisco
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
log = logging.getLogger(__name__)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class StaticExtendedDisco(object):
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    Extend the default StaticDisco implementation to provide
 | 
			
		||||
    support for extended identity information.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    def __init__(self, static):
 | 
			
		||||
        """
 | 
			
		||||
        Augment the default XEP-0030 static handler object.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            static -- The default static XEP-0030 handler object.
 | 
			
		||||
        """
 | 
			
		||||
        self.static = static
 | 
			
		||||
 | 
			
		||||
    def set_extended_info(self, jid, node, data):
 | 
			
		||||
        """
 | 
			
		||||
        Replace the extended identity data for a JID/node combination.
 | 
			
		||||
 | 
			
		||||
        The data parameter may provide:
 | 
			
		||||
            data -- Either a single data form, or a list of data forms.
 | 
			
		||||
        """
 | 
			
		||||
        self.del_extended_info(jid, node, data)
 | 
			
		||||
        self.add_extended_info(jid, node, data)
 | 
			
		||||
 | 
			
		||||
    def add_extended_info(self, jid, node, data):
 | 
			
		||||
        """
 | 
			
		||||
        Add additional extended identity data for a JID/node combination.
 | 
			
		||||
 | 
			
		||||
        The data parameter may provide:
 | 
			
		||||
            data -- Either a single data form, or a list of data forms.
 | 
			
		||||
        """
 | 
			
		||||
        self.static.add_node(jid, node)
 | 
			
		||||
 | 
			
		||||
        forms = data.get('data', [])
 | 
			
		||||
        if not isinstance(forms, list):
 | 
			
		||||
            forms = [forms]
 | 
			
		||||
 | 
			
		||||
        for form in forms:
 | 
			
		||||
            self.static.nodes[(jid, node)]['info'].append(form)
 | 
			
		||||
 | 
			
		||||
    def del_extended_info(self, jid, node, data):
 | 
			
		||||
        """
 | 
			
		||||
        Replace the extended identity data for a JID/node combination.
 | 
			
		||||
 | 
			
		||||
        The data parameter is not used.
 | 
			
		||||
        """
 | 
			
		||||
        if (jid, node) not in self.static.nodes:
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
        info = self.static.nodes[(jid, node)]['info']
 | 
			
		||||
 | 
			
		||||
        for form in info['substanza']:
 | 
			
		||||
            info.xml.remove(form.xml)
 | 
			
		||||
@@ -1,63 +0,0 @@
 | 
			
		||||
"""
 | 
			
		||||
    SleekXMPP: The Sleek XMPP Library
 | 
			
		||||
    Copyright (C) 2010 Nathanael C. Fritz
 | 
			
		||||
    This file is part of SleekXMPP.
 | 
			
		||||
 | 
			
		||||
    See the file LICENSE for copying permission.
 | 
			
		||||
"""
 | 
			
		||||
from xml.etree import cElementTree as ET
 | 
			
		||||
from . import base
 | 
			
		||||
import time
 | 
			
		||||
import logging
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
log = logging.getLogger(__name__)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class xep_0199(base.base_plugin):
 | 
			
		||||
    """XEP-0199 XMPP Ping"""
 | 
			
		||||
 | 
			
		||||
    def plugin_init(self):
 | 
			
		||||
        self.description = "XMPP Ping"
 | 
			
		||||
        self.xep = "0199"
 | 
			
		||||
        self.xmpp.add_handler("<iq type='get' xmlns='%s'><ping xmlns='urn:xmpp:ping'/></iq>" % self.xmpp.default_ns, self.handler_ping, name='XMPP Ping')
 | 
			
		||||
        if self.config.get('keepalive', True):
 | 
			
		||||
            self.xmpp.add_event_handler('session_start', self.handler_pingserver, threaded=True)
 | 
			
		||||
 | 
			
		||||
    def post_init(self):
 | 
			
		||||
        base.base_plugin.post_init(self)
 | 
			
		||||
        self.xmpp.plugin['xep_0030'].add_feature('urn:xmpp:ping')
 | 
			
		||||
 | 
			
		||||
    def handler_pingserver(self, xml):
 | 
			
		||||
        self.xmpp.schedule("xep-0119 ping", float(self.config.get('frequency', 300)), self.scheduled_ping, repeat=True)
 | 
			
		||||
 | 
			
		||||
    def scheduled_ping(self):
 | 
			
		||||
        log.debug("pinging...")
 | 
			
		||||
        if self.sendPing(self.xmpp.server, self.config.get('timeout', 30)) is False:
 | 
			
		||||
            log.debug("Did not recieve ping back in time.  Requesting Reconnect.")
 | 
			
		||||
            self.xmpp.reconnect()
 | 
			
		||||
 | 
			
		||||
    def handler_ping(self, xml):
 | 
			
		||||
        iq = self.xmpp.makeIqResult(xml.get('id', 'unknown'))
 | 
			
		||||
        iq.attrib['to'] = xml.get('from', self.xmpp.boundjid.domain)
 | 
			
		||||
        self.xmpp.send(iq)
 | 
			
		||||
 | 
			
		||||
    def sendPing(self, jid, timeout = 30):
 | 
			
		||||
        """ sendPing(jid, timeout)
 | 
			
		||||
        Sends a ping to the specified jid, returning the time (in seconds)
 | 
			
		||||
        to receive a reply, or None if no reply is received in timeout seconds.
 | 
			
		||||
        """
 | 
			
		||||
        id = self.xmpp.getNewId()
 | 
			
		||||
        iq = self.xmpp.makeIq(id)
 | 
			
		||||
        iq.attrib['type'] = 'get'
 | 
			
		||||
        iq.attrib['to'] = jid
 | 
			
		||||
        ping = ET.Element('{urn:xmpp:ping}ping')
 | 
			
		||||
        iq.append(ping)
 | 
			
		||||
        startTime = time.clock()
 | 
			
		||||
        #pingresult = self.xmpp.send(iq, self.xmpp.makeIq(id), timeout)
 | 
			
		||||
        pingresult = iq.send()
 | 
			
		||||
        endTime = time.clock()
 | 
			
		||||
        if pingresult == False:
 | 
			
		||||
            #self.xmpp.disconnect(reconnect=True)
 | 
			
		||||
            return False
 | 
			
		||||
        return endTime - startTime
 | 
			
		||||
							
								
								
									
										10
									
								
								sleekxmpp/plugins/xep_0199/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								sleekxmpp/plugins/xep_0199/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,10 @@
 | 
			
		||||
"""
 | 
			
		||||
    SleekXMPP: The Sleek XMPP Library
 | 
			
		||||
    Copyright (C) 2010 Nathanael C. Fritz
 | 
			
		||||
    This file is part of SleekXMPP.
 | 
			
		||||
 | 
			
		||||
    See the file LICENSE for copying permission.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
from sleekxmpp.plugins.xep_0199.stanza import Ping
 | 
			
		||||
from sleekxmpp.plugins.xep_0199.ping import xep_0199
 | 
			
		||||
							
								
								
									
										163
									
								
								sleekxmpp/plugins/xep_0199/ping.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										163
									
								
								sleekxmpp/plugins/xep_0199/ping.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,163 @@
 | 
			
		||||
"""
 | 
			
		||||
    SleekXMPP: The Sleek XMPP Library
 | 
			
		||||
    Copyright (C) 2010 Nathanael C. Fritz
 | 
			
		||||
    This file is part of SleekXMPP.
 | 
			
		||||
 | 
			
		||||
    See the file LICENSE for copying permission.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
import time
 | 
			
		||||
import logging
 | 
			
		||||
 | 
			
		||||
import sleekxmpp
 | 
			
		||||
from sleekxmpp import Iq
 | 
			
		||||
from sleekxmpp.xmlstream import register_stanza_plugin
 | 
			
		||||
from sleekxmpp.xmlstream.matcher import StanzaPath
 | 
			
		||||
from sleekxmpp.xmlstream.handler import Callback
 | 
			
		||||
from sleekxmpp.plugins.base import base_plugin
 | 
			
		||||
from sleekxmpp.plugins.xep_0199 import stanza, Ping
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
log = logging.getLogger(__name__)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class xep_0199(base_plugin):
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    XEP-0199: XMPP Ping
 | 
			
		||||
 | 
			
		||||
    Given that XMPP is based on TCP connections, it is possible for the
 | 
			
		||||
    underlying connection to be terminated without the application's
 | 
			
		||||
    awareness. Ping stanzas provide an alternative to whitespace based
 | 
			
		||||
    keepalive methods for detecting lost connections.
 | 
			
		||||
 | 
			
		||||
    Also see <http://www.xmpp.org/extensions/xep-0199.html>.
 | 
			
		||||
 | 
			
		||||
    Attributes:
 | 
			
		||||
        keepalive -- If True, periodically send ping requests
 | 
			
		||||
                     to the server. If a ping is not answered,
 | 
			
		||||
                     the connection will be reset.
 | 
			
		||||
        frequency -- Time in seconds between keepalive pings.
 | 
			
		||||
                     Defaults to 300 seconds.
 | 
			
		||||
        timeout   -- Time in seconds to wait for a ping response.
 | 
			
		||||
                     Defaults to 30 seconds.
 | 
			
		||||
    Methods:
 | 
			
		||||
        send_ping -- Send a ping to a given JID, returning the
 | 
			
		||||
                     round trip time.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    def plugin_init(self):
 | 
			
		||||
        """
 | 
			
		||||
        Start the XEP-0199 plugin.
 | 
			
		||||
        """
 | 
			
		||||
        self.description = 'XMPP Ping'
 | 
			
		||||
        self.xep = '0199'
 | 
			
		||||
        self.stanza = stanza
 | 
			
		||||
 | 
			
		||||
        self.keepalive = self.config.get('keepalive', False)
 | 
			
		||||
        self.frequency = float(self.config.get('frequency', 300))
 | 
			
		||||
        self.timeout = self.config.get('timeout', 30)
 | 
			
		||||
 | 
			
		||||
        register_stanza_plugin(Iq, Ping)
 | 
			
		||||
 | 
			
		||||
        self.xmpp.register_handler(
 | 
			
		||||
                Callback('Ping',
 | 
			
		||||
                         StanzaPath('iq@type=get/ping'),
 | 
			
		||||
                         self._handle_ping))
 | 
			
		||||
 | 
			
		||||
        if self.keepalive:
 | 
			
		||||
            self.xmpp.add_event_handler('session_start',
 | 
			
		||||
                                        self._handle_keepalive,
 | 
			
		||||
                                        threaded=True)
 | 
			
		||||
 | 
			
		||||
    def post_init(self):
 | 
			
		||||
        """Handle cross-plugin dependencies."""
 | 
			
		||||
        base_plugin.post_init(self)
 | 
			
		||||
        self.xmpp['xep_0030'].add_feature(Ping.namespace)
 | 
			
		||||
 | 
			
		||||
    def _handle_keepalive(self, event):
 | 
			
		||||
        """
 | 
			
		||||
        Begin periodic pinging of the server. If a ping is not
 | 
			
		||||
        answered, the connection will be restarted.
 | 
			
		||||
 | 
			
		||||
        The pinging interval can be adjused using self.frequency
 | 
			
		||||
        before beginning processing.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            event -- The session_start event.
 | 
			
		||||
        """
 | 
			
		||||
        def scheduled_ping():
 | 
			
		||||
            """Send ping request to the server."""
 | 
			
		||||
            log.debug("Pinging...")
 | 
			
		||||
            resp = self.send_ping(self.xmpp.boundjid.host, self.timeout)
 | 
			
		||||
            if resp is None or resp is False:
 | 
			
		||||
                log.debug("Did not recieve ping back in time." + \
 | 
			
		||||
                          "Requesting Reconnect.")
 | 
			
		||||
                self.xmpp.reconnect()
 | 
			
		||||
 | 
			
		||||
        self.xmpp.schedule('Ping Keep Alive',
 | 
			
		||||
                           self.frequency,
 | 
			
		||||
                           scheduled_ping,
 | 
			
		||||
                           repeat=True)
 | 
			
		||||
 | 
			
		||||
    def _handle_ping(self, iq):
 | 
			
		||||
        """
 | 
			
		||||
        Automatically reply to ping requests.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            iq -- The ping request.
 | 
			
		||||
        """
 | 
			
		||||
        log.debug("Pinged by %s" % iq['from'])
 | 
			
		||||
        iq.reply().enable('ping').send()
 | 
			
		||||
 | 
			
		||||
    def send_ping(self, jid, timeout=None, errorfalse=False,
 | 
			
		||||
                  ifrom=None, block=True, callback=None):
 | 
			
		||||
        """
 | 
			
		||||
        Send a ping request and calculate the response time.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            jid        -- The JID that will receive the ping.
 | 
			
		||||
            timeout    -- Time in seconds to wait for a response.
 | 
			
		||||
                          Defaults to self.timeout.
 | 
			
		||||
            errorfalse -- Indicates if False should be returned
 | 
			
		||||
                          if an error stanza is received. Defaults
 | 
			
		||||
                          to False.
 | 
			
		||||
            ifrom      -- Specifiy the sender JID.
 | 
			
		||||
            block      -- Indicate if execution should block until
 | 
			
		||||
                          a pong response is received. Defaults
 | 
			
		||||
                          to True.
 | 
			
		||||
            callback   -- Optional handler to execute when a pong
 | 
			
		||||
                          is received. Useful in conjunction with
 | 
			
		||||
                          the option block=False.
 | 
			
		||||
        """
 | 
			
		||||
        log.debug("Pinging %s" % jid)
 | 
			
		||||
        if timeout is None:
 | 
			
		||||
            timeout = self.timeout
 | 
			
		||||
 | 
			
		||||
        iq = self.xmpp.Iq()
 | 
			
		||||
        iq['type'] = 'get'
 | 
			
		||||
        iq['to'] = jid
 | 
			
		||||
        if ifrom:
 | 
			
		||||
            iq['from'] = ifrom
 | 
			
		||||
        iq.enable('ping')
 | 
			
		||||
 | 
			
		||||
        start_time = time.clock()
 | 
			
		||||
        resp = iq.send(block=block,
 | 
			
		||||
                       timeout=timeout,
 | 
			
		||||
                       callback=callback)
 | 
			
		||||
        end_time = time.clock()
 | 
			
		||||
 | 
			
		||||
        delay = end_time - start_time
 | 
			
		||||
 | 
			
		||||
        if not block:
 | 
			
		||||
            return None
 | 
			
		||||
 | 
			
		||||
        if not resp or resp['type'] == 'error':
 | 
			
		||||
            return False
 | 
			
		||||
 | 
			
		||||
        log.debug("Pong: %s %f" % (jid, delay))
 | 
			
		||||
        return delay
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# Backwards compatibility for names
 | 
			
		||||
xep_0199.sendPing = xep_0199.send_ping
 | 
			
		||||
							
								
								
									
										36
									
								
								sleekxmpp/plugins/xep_0199/stanza.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								sleekxmpp/plugins/xep_0199/stanza.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,36 @@
 | 
			
		||||
"""
 | 
			
		||||
    SleekXMPP: The Sleek XMPP Library
 | 
			
		||||
    Copyright (C) 2010 Nathanael C. Fritz
 | 
			
		||||
    This file is part of SleekXMPP.
 | 
			
		||||
 | 
			
		||||
    See the file LICENSE for copying permission.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
import sleekxmpp
 | 
			
		||||
from sleekxmpp.xmlstream import ElementBase
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Ping(ElementBase):
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    Given that XMPP is based on TCP connections, it is possible for the
 | 
			
		||||
    underlying connection to be terminated without the application's
 | 
			
		||||
    awareness. Ping stanzas provide an alternative to whitespace based
 | 
			
		||||
    keepalive methods for detecting lost connections.
 | 
			
		||||
 | 
			
		||||
    Example ping stanza:
 | 
			
		||||
        <iq type="get">
 | 
			
		||||
          <ping xmlns="urn:xmpp:ping" />
 | 
			
		||||
        </iq>
 | 
			
		||||
 | 
			
		||||
    Stanza Interface:
 | 
			
		||||
        None
 | 
			
		||||
 | 
			
		||||
    Methods:
 | 
			
		||||
        None
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    name = 'ping'
 | 
			
		||||
    namespace = 'urn:xmpp:ping'
 | 
			
		||||
    plugin_attrib = 'ping'
 | 
			
		||||
    interfaces = set()
 | 
			
		||||
@@ -27,10 +27,12 @@ class EntityTime(ElementBase):
 | 
			
		||||
    interfaces = set(('tzo', 'utc'))
 | 
			
		||||
    sub_interfaces = set(('tzo', 'utc'))
 | 
			
		||||
 | 
			
		||||
    #def get_utc(self): # TODO: return a datetime.tzinfo object?
 | 
			
		||||
    #def get_tzo(self):
 | 
			
		||||
        # TODO: Right now it returns a string but maybe it should
 | 
			
		||||
        # return a datetime.tzinfo object or maybe a datetime.timedelta?
 | 
			
		||||
        #pass
 | 
			
		||||
 | 
			
		||||
    def set_tzo(self, tzo): # TODO: support datetime.tzinfo objects?
 | 
			
		||||
    def set_tzo(self, tzo):
 | 
			
		||||
        if isinstance(tzo, tzinfo):
 | 
			
		||||
            td = datetime.now(tzo).utcoffset() # What if we are faking the time? datetime.now() shouldn't be used here'
 | 
			
		||||
            seconds = td.seconds + td.days * 24 * 3600
 | 
			
		||||
@@ -45,7 +47,7 @@ class EntityTime(ElementBase):
 | 
			
		||||
        # Returns a datetime object instead the string. Is this a good idea?
 | 
			
		||||
        value = self._get_sub_text('utc')
 | 
			
		||||
        if '.' in value:
 | 
			
		||||
            return datetime.strptime(value, '%Y-%m-%d.%fT%H:%M:%SZ')
 | 
			
		||||
            return datetime.strptime(value, '%Y-%m-%dT%H:%M:%S.%fZ')
 | 
			
		||||
        else:
 | 
			
		||||
            return datetime.strptime(value, '%Y-%m-%dT%H:%M:%SZ')
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										10
									
								
								sleekxmpp/plugins/xep_0249/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								sleekxmpp/plugins/xep_0249/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,10 @@
 | 
			
		||||
"""
 | 
			
		||||
    SleekXMPP: The Sleek XMPP Library
 | 
			
		||||
    Copyright (C) 2011 Nathanael C. Fritz, Dalek
 | 
			
		||||
    This file is part of SleekXMPP.
 | 
			
		||||
 | 
			
		||||
    See the file LICENSE for copying permission.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
from sleekxmpp.plugins.xep_0249.stanza import Invite
 | 
			
		||||
from sleekxmpp.plugins.xep_0249.invite import xep_0249
 | 
			
		||||
							
								
								
									
										79
									
								
								sleekxmpp/plugins/xep_0249/invite.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								sleekxmpp/plugins/xep_0249/invite.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,79 @@
 | 
			
		||||
"""
 | 
			
		||||
    SleekXMPP: The Sleek XMPP Library
 | 
			
		||||
    Copyright (C) 2011 Nathanael C. Fritz, Dalek
 | 
			
		||||
    This file is part of SleekXMPP.
 | 
			
		||||
 | 
			
		||||
    See the file LICENSE for copying permission.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
import logging
 | 
			
		||||
 | 
			
		||||
import sleekxmpp
 | 
			
		||||
from sleekxmpp import Message
 | 
			
		||||
from sleekxmpp.plugins.base import base_plugin
 | 
			
		||||
from sleekxmpp.xmlstream import register_stanza_plugin
 | 
			
		||||
from sleekxmpp.xmlstream.handler import Callback
 | 
			
		||||
from sleekxmpp.xmlstream.matcher import StanzaPath
 | 
			
		||||
from sleekxmpp.plugins.xep_0249 import Invite
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
log = logging.getLogger(__name__)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class xep_0249(base_plugin):
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    XEP-0249: Direct MUC Invitations
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    def plugin_init(self):
 | 
			
		||||
        self.xep = "0249"
 | 
			
		||||
        self.description = "Direct MUC Invitations"
 | 
			
		||||
        self.stanza = sleekxmpp.plugins.xep_0249.stanza
 | 
			
		||||
 | 
			
		||||
        self.xmpp.register_handler(
 | 
			
		||||
                Callback('Direct MUC Invitations',
 | 
			
		||||
                         StanzaPath('message/groupchat_invite'),
 | 
			
		||||
                         self._handle_invite))
 | 
			
		||||
 | 
			
		||||
        register_stanza_plugin(Message, Invite)
 | 
			
		||||
 | 
			
		||||
    def post_init(self):
 | 
			
		||||
        base_plugin.post_init(self)
 | 
			
		||||
        self.xmpp['xep_0030'].add_feature(Invite.namespace)
 | 
			
		||||
 | 
			
		||||
    def _handle_invite(self, msg):
 | 
			
		||||
        """
 | 
			
		||||
        Raise an event for all invitations received.
 | 
			
		||||
        """
 | 
			
		||||
        log.debug("Received direct muc invitation from %s to room %s",
 | 
			
		||||
                  msg['from'], msg['groupchat_invite']['jid'])
 | 
			
		||||
 | 
			
		||||
        self.xmpp.event('groupchat_direct_invite', msg)
 | 
			
		||||
 | 
			
		||||
    def send_invitation(self, jid, roomjid, password=None,
 | 
			
		||||
                        reason=None, ifrom=None):
 | 
			
		||||
        """
 | 
			
		||||
        Send a direct MUC invitation to an XMPP entity.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            jid      -- The JID of the entity that will receive
 | 
			
		||||
                        the invitation
 | 
			
		||||
            roomjid  -- the address of the groupchat room to be joined
 | 
			
		||||
            password -- a password needed for entry into a
 | 
			
		||||
                        password-protected room (OPTIONAL).
 | 
			
		||||
            reason   -- a human-readable purpose for the invitation
 | 
			
		||||
                        (OPTIONAL).
 | 
			
		||||
        """
 | 
			
		||||
 | 
			
		||||
        msg = self.xmpp.Message()
 | 
			
		||||
        msg['to'] = jid
 | 
			
		||||
        if ifrom is not None:
 | 
			
		||||
            msg['from'] = ifrom
 | 
			
		||||
        msg['groupchat_invite']['jid'] = roomjid
 | 
			
		||||
        if password is not None:
 | 
			
		||||
            msg['groupchat_invite']['password'] = password
 | 
			
		||||
        if reason is not None:
 | 
			
		||||
            msg['groupchat_invite']['reason'] = reason
 | 
			
		||||
 | 
			
		||||
        return msg.send()
 | 
			
		||||
							
								
								
									
										39
									
								
								sleekxmpp/plugins/xep_0249/stanza.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								sleekxmpp/plugins/xep_0249/stanza.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,39 @@
 | 
			
		||||
"""
 | 
			
		||||
    SleekXMPP: The Sleek XMPP Library
 | 
			
		||||
    Copyright (C) 2011 Nathanael C. Fritz, Dalek
 | 
			
		||||
    This file is part of SleekXMPP.
 | 
			
		||||
 | 
			
		||||
    See the file LICENSE for copying permission.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
from sleekxmpp.xmlstream import ElementBase
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Invite(ElementBase):
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    XMPP allows for an agent in an MUC room to directly invite another
 | 
			
		||||
    user to join the chat room (as opposed to a mediated invitation
 | 
			
		||||
    done through the server).
 | 
			
		||||
 | 
			
		||||
    Example invite stanza:
 | 
			
		||||
      <message from='crone1@shakespeare.lit/desktop'
 | 
			
		||||
          to='hecate@shakespeare.lit'>
 | 
			
		||||
        <x xmlns='jabber:x:conference'
 | 
			
		||||
           jid='darkcave@macbeth.shakespeare.lit'
 | 
			
		||||
           password='cauldronburn'
 | 
			
		||||
           reason='Hey Hecate, this is the place for all good witches!'/>
 | 
			
		||||
      </message>
 | 
			
		||||
 | 
			
		||||
    Stanza Interface:
 | 
			
		||||
        jid      -- The JID of the groupchat room
 | 
			
		||||
        password -- The password used to gain entry in the room
 | 
			
		||||
                    (optional)
 | 
			
		||||
        reason   -- The reason for the invitation (optional)
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    name = "x"
 | 
			
		||||
    namespace = "jabber:x:conference"
 | 
			
		||||
    plugin_attrib = "groupchat_invite"
 | 
			
		||||
    interfaces = ("jid", "password", "reason")
 | 
			
		||||
@@ -8,6 +8,7 @@
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
from sleekxmpp.stanza.error import Error
 | 
			
		||||
from sleekxmpp.stanza.stream_error import StreamError
 | 
			
		||||
from sleekxmpp.stanza.iq import Iq
 | 
			
		||||
from sleekxmpp.stanza.message import Message
 | 
			
		||||
from sleekxmpp.stanza.presence import Presence
 | 
			
		||||
 
 | 
			
		||||
@@ -77,15 +77,6 @@ class Error(ElementBase):
 | 
			
		||||
        Arguments:
 | 
			
		||||
            xml -- Use an existing XML object for the stanza's values.
 | 
			
		||||
        """
 | 
			
		||||
        # To comply with PEP8, method names now use underscores.
 | 
			
		||||
        # Deprecated method names are re-mapped for backwards compatibility.
 | 
			
		||||
        self.getCondition = self.get_condition
 | 
			
		||||
        self.setCondition = self.set_condition
 | 
			
		||||
        self.delCondition = self.del_condition
 | 
			
		||||
        self.getText = self.get_text
 | 
			
		||||
        self.setText = self.set_text
 | 
			
		||||
        self.delText = self.del_text
 | 
			
		||||
 | 
			
		||||
        if ElementBase.setup(self, xml):
 | 
			
		||||
            #If we had to generate XML then set default values.
 | 
			
		||||
            self['type'] = 'cancel'
 | 
			
		||||
@@ -139,3 +130,13 @@ class Error(ElementBase):
 | 
			
		||||
        """Remove the <text> element."""
 | 
			
		||||
        self._del_sub('{%s}text' % self.condition_ns)
 | 
			
		||||
        return self
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# To comply with PEP8, method names now use underscores.
 | 
			
		||||
# Deprecated method names are re-mapped for backwards compatibility.
 | 
			
		||||
Error.getCondition = Error.get_condition
 | 
			
		||||
Error.setCondition = Error.set_condition
 | 
			
		||||
Error.delCondition = Error.del_condition
 | 
			
		||||
Error.getText = Error.get_text
 | 
			
		||||
Error.setText = Error.set_text
 | 
			
		||||
Error.delText = Error.del_text
 | 
			
		||||
 
 | 
			
		||||
@@ -46,23 +46,6 @@ class HTMLIM(ElementBase):
 | 
			
		||||
    interfaces = set(('body',))
 | 
			
		||||
    plugin_attrib = name
 | 
			
		||||
 | 
			
		||||
    def setup(self, xml=None):
 | 
			
		||||
        """
 | 
			
		||||
        Populate the stanza object using an optional XML object.
 | 
			
		||||
 | 
			
		||||
        Overrides StanzaBase.setup.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            xml -- Use an existing XML object for the stanza's values.
 | 
			
		||||
        """
 | 
			
		||||
        # To comply with PEP8, method names now use underscores.
 | 
			
		||||
        # Deprecated method names are re-mapped for backwards compatibility.
 | 
			
		||||
        self.setBody = self.set_body
 | 
			
		||||
        self.getBody = self.get_body
 | 
			
		||||
        self.delBody = self.del_body
 | 
			
		||||
 | 
			
		||||
        return ElementBase.setup(self, xml)
 | 
			
		||||
 | 
			
		||||
    def set_body(self, html):
 | 
			
		||||
        """
 | 
			
		||||
        Set the contents of the HTML body.
 | 
			
		||||
@@ -95,3 +78,9 @@ class HTMLIM(ElementBase):
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
register_stanza_plugin(Message, HTMLIM)
 | 
			
		||||
 | 
			
		||||
# To comply with PEP8, method names now use underscores.
 | 
			
		||||
# Deprecated method names are re-mapped for backwards compatibility.
 | 
			
		||||
HTMLIM.setBody = HTMLIM.set_body
 | 
			
		||||
HTMLIM.getBody = HTMLIM.get_body
 | 
			
		||||
HTMLIM.delBody = HTMLIM.del_body
 | 
			
		||||
 
 | 
			
		||||
@@ -8,8 +8,8 @@
 | 
			
		||||
 | 
			
		||||
from sleekxmpp.stanza import Error
 | 
			
		||||
from sleekxmpp.stanza.rootstanza import RootStanza
 | 
			
		||||
from sleekxmpp.xmlstream import RESPONSE_TIMEOUT, StanzaBase, ET
 | 
			
		||||
from sleekxmpp.xmlstream.handler import Waiter
 | 
			
		||||
from sleekxmpp.xmlstream import StanzaBase, ET
 | 
			
		||||
from sleekxmpp.xmlstream.handler import Waiter, Callback
 | 
			
		||||
from sleekxmpp.xmlstream.matcher import MatcherId
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -75,16 +75,9 @@ class Iq(RootStanza):
 | 
			
		||||
        Overrides StanzaBase.__init__.
 | 
			
		||||
        """
 | 
			
		||||
        StanzaBase.__init__(self, *args, **kwargs)
 | 
			
		||||
        # To comply with PEP8, method names now use underscores.
 | 
			
		||||
        # Deprecated method names are re-mapped for backwards compatibility.
 | 
			
		||||
        self.setPayload = self.set_payload
 | 
			
		||||
        self.getQuery = self.get_query
 | 
			
		||||
        self.setQuery = self.set_query
 | 
			
		||||
        self.delQuery = self.del_query
 | 
			
		||||
 | 
			
		||||
        if self['id'] == '':
 | 
			
		||||
            if self.stream is not None:
 | 
			
		||||
                self['id'] = self.stream.getNewId()
 | 
			
		||||
                self['id'] = self.stream.new_id()
 | 
			
		||||
            else:
 | 
			
		||||
                self['id'] = '0'
 | 
			
		||||
 | 
			
		||||
@@ -144,7 +137,7 @@ class Iq(RootStanza):
 | 
			
		||||
                self.xml.remove(child)
 | 
			
		||||
        return self
 | 
			
		||||
 | 
			
		||||
    def reply(self):
 | 
			
		||||
    def reply(self, clear=True):
 | 
			
		||||
        """
 | 
			
		||||
        Send a reply <iq> stanza.
 | 
			
		||||
 | 
			
		||||
@@ -152,18 +145,28 @@ class Iq(RootStanza):
 | 
			
		||||
 | 
			
		||||
        Sets the 'type' to 'result' in addition to the default
 | 
			
		||||
        StanzaBase.reply behavior.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            clear -- Indicates if existing content should be
 | 
			
		||||
                     removed before replying. Defaults to True.
 | 
			
		||||
        """
 | 
			
		||||
        self['type'] = 'result'
 | 
			
		||||
        StanzaBase.reply(self)
 | 
			
		||||
        StanzaBase.reply(self, clear)
 | 
			
		||||
        return self
 | 
			
		||||
 | 
			
		||||
    def send(self, block=True, timeout=RESPONSE_TIMEOUT):
 | 
			
		||||
    def send(self, block=True, timeout=None, callback=None, now=False):
 | 
			
		||||
        """
 | 
			
		||||
        Send an <iq> stanza over the XML stream.
 | 
			
		||||
 | 
			
		||||
        The send call can optionally block until a response is received or
 | 
			
		||||
        a timeout occurs. Be aware that using blocking in non-threaded event
 | 
			
		||||
        handlers can drastically impact performance.
 | 
			
		||||
        handlers can drastically impact performance. Otherwise, a callback
 | 
			
		||||
        handler can be provided that will be executed when the Iq stanza's
 | 
			
		||||
        result reply is received. Be aware though that that the callback
 | 
			
		||||
        handler will not be executed in its own thread.
 | 
			
		||||
 | 
			
		||||
        Using both block and callback is not recommended, and only the
 | 
			
		||||
        callback argument will be used in that case.
 | 
			
		||||
 | 
			
		||||
        Overrides StanzaBase.send
 | 
			
		||||
 | 
			
		||||
@@ -173,11 +176,60 @@ class Iq(RootStanza):
 | 
			
		||||
            timeout  -- The length of time (in seconds) to wait for a response
 | 
			
		||||
                        before exiting the send call if blocking is used.
 | 
			
		||||
                        Defaults to sleekxmpp.xmlstream.RESPONSE_TIMEOUT
 | 
			
		||||
            callback -- Optional reference to a stream handler function. Will
 | 
			
		||||
                        be executed when a reply stanza is received.
 | 
			
		||||
            now      -- Indicates if the send queue should be skipped and send
 | 
			
		||||
                        the stanza immediately. Used during stream
 | 
			
		||||
                        initialization. Defaults to False.
 | 
			
		||||
        """
 | 
			
		||||
        if block and self['type'] in ('get', 'set'):
 | 
			
		||||
        if timeout is None:
 | 
			
		||||
            timeout = self.stream.response_timeout
 | 
			
		||||
        if callback is not None and self['type'] in ('get', 'set'):
 | 
			
		||||
            handler_name = 'IqCallback_%s' % self['id']
 | 
			
		||||
            handler = Callback(handler_name,
 | 
			
		||||
                               MatcherId(self['id']),
 | 
			
		||||
                               callback,
 | 
			
		||||
                               once=True)
 | 
			
		||||
            self.stream.register_handler(handler)
 | 
			
		||||
            StanzaBase.send(self, now=now)
 | 
			
		||||
            return handler_name
 | 
			
		||||
        elif block and self['type'] in ('get', 'set'):
 | 
			
		||||
            waitfor = Waiter('IqWait_%s' % self['id'], MatcherId(self['id']))
 | 
			
		||||
            self.stream.registerHandler(waitfor)
 | 
			
		||||
            StanzaBase.send(self)
 | 
			
		||||
            self.stream.register_handler(waitfor)
 | 
			
		||||
            StanzaBase.send(self, now=now)
 | 
			
		||||
            return waitfor.wait(timeout)
 | 
			
		||||
        else:
 | 
			
		||||
            return StanzaBase.send(self)
 | 
			
		||||
            return StanzaBase.send(self, now=now)
 | 
			
		||||
 | 
			
		||||
    def _set_stanza_values(self, values):
 | 
			
		||||
        """
 | 
			
		||||
        Set multiple stanza interface values using a dictionary.
 | 
			
		||||
 | 
			
		||||
        Stanza plugin values may be set usind nested dictionaries.
 | 
			
		||||
 | 
			
		||||
        If the interface 'query' is given, then it will be set
 | 
			
		||||
        last to avoid duplication of the <query /> element.
 | 
			
		||||
 | 
			
		||||
        Overrides ElementBase._set_stanza_values.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            values -- A dictionary mapping stanza interface with values.
 | 
			
		||||
                      Plugin interfaces may accept a nested dictionary that
 | 
			
		||||
                      will be used recursively.
 | 
			
		||||
        """
 | 
			
		||||
        query = values.get('query', '')
 | 
			
		||||
        if query:
 | 
			
		||||
            del values['query']
 | 
			
		||||
            StanzaBase._set_stanza_values(self, values)
 | 
			
		||||
            self['query'] = query
 | 
			
		||||
        else:
 | 
			
		||||
            StanzaBase._set_stanza_values(self, values)
 | 
			
		||||
        return self
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# To comply with PEP8, method names now use underscores.
 | 
			
		||||
# Deprecated method names are re-mapped for backwards compatibility.
 | 
			
		||||
Iq.setPayload = Iq.set_payload
 | 
			
		||||
Iq.getQuery = Iq.get_query
 | 
			
		||||
Iq.setQuery = Iq.set_query
 | 
			
		||||
Iq.delQuery = Iq.del_query
 | 
			
		||||
 
 | 
			
		||||
@@ -63,27 +63,6 @@ class Message(RootStanza):
 | 
			
		||||
    plugin_attrib = name
 | 
			
		||||
    types = set((None, 'normal', 'chat', 'headline', 'error', 'groupchat'))
 | 
			
		||||
 | 
			
		||||
    def setup(self, xml=None):
 | 
			
		||||
        """
 | 
			
		||||
        Populate the stanza object using an optional XML object.
 | 
			
		||||
 | 
			
		||||
        Overrides StanzaBase.setup.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            xml -- Use an existing XML object for the stanza's values.
 | 
			
		||||
        """
 | 
			
		||||
        # To comply with PEP8, method names now use underscores.
 | 
			
		||||
        # Deprecated method names are re-mapped for backwards compatibility.
 | 
			
		||||
        self.getType = self.get_type
 | 
			
		||||
        self.getMucroom = self.get_mucroom
 | 
			
		||||
        self.setMucroom = self.set_mucroom
 | 
			
		||||
        self.delMucroom = self.del_mucroom
 | 
			
		||||
        self.getMucnick = self.get_mucnick
 | 
			
		||||
        self.setMucnick = self.set_mucnick
 | 
			
		||||
        self.delMucnick = self.del_mucnick
 | 
			
		||||
 | 
			
		||||
        return StanzaBase.setup(self, xml)
 | 
			
		||||
 | 
			
		||||
    def get_type(self):
 | 
			
		||||
        """
 | 
			
		||||
        Return the message type.
 | 
			
		||||
@@ -104,7 +83,7 @@ class Message(RootStanza):
 | 
			
		||||
        self['type'] = 'normal'
 | 
			
		||||
        return self
 | 
			
		||||
 | 
			
		||||
    def reply(self, body=None):
 | 
			
		||||
    def reply(self, body=None, clear=True):
 | 
			
		||||
        """
 | 
			
		||||
        Create a message reply.
 | 
			
		||||
 | 
			
		||||
@@ -115,8 +94,10 @@ class Message(RootStanza):
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            body  -- Optional text content for the message.
 | 
			
		||||
            clear -- Indicates if existing content should be removed
 | 
			
		||||
                     before replying. Defaults to True.
 | 
			
		||||
        """
 | 
			
		||||
        StanzaBase.reply(self)
 | 
			
		||||
        StanzaBase.reply(self, clear)
 | 
			
		||||
        if self['type'] == 'groupchat':
 | 
			
		||||
            self['to'] = self['to'].bare
 | 
			
		||||
 | 
			
		||||
@@ -163,3 +144,14 @@ class Message(RootStanza):
 | 
			
		||||
    def del_mucnick(self):
 | 
			
		||||
        """Dummy method to prevent deletion."""
 | 
			
		||||
        pass
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# To comply with PEP8, method names now use underscores.
 | 
			
		||||
# Deprecated method names are re-mapped for backwards compatibility.
 | 
			
		||||
Message.getType = Message.get_type
 | 
			
		||||
Message.getMucroom = Message.get_mucroom
 | 
			
		||||
Message.setMucroom = Message.set_mucroom
 | 
			
		||||
Message.delMucroom = Message.del_mucroom
 | 
			
		||||
Message.getMucnick = Message.get_mucnick
 | 
			
		||||
Message.setMucnick = Message.set_mucnick
 | 
			
		||||
Message.delMucnick = Message.del_mucnick
 | 
			
		||||
 
 | 
			
		||||
@@ -44,28 +44,11 @@ class Nick(ElementBase):
 | 
			
		||||
        del_nick -- Remove the <nick> element.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    namespace = 'http://jabber.org/nick/nick'
 | 
			
		||||
    namespace = 'http://jabber.org/protocol/nick'
 | 
			
		||||
    name = 'nick'
 | 
			
		||||
    plugin_attrib = name
 | 
			
		||||
    interfaces = set(('nick',))
 | 
			
		||||
 | 
			
		||||
    def setup(self, xml=None):
 | 
			
		||||
        """
 | 
			
		||||
        Populate the stanza object using an optional XML object.
 | 
			
		||||
 | 
			
		||||
        Overrides StanzaBase.setup.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            xml -- Use an existing XML object for the stanza's values.
 | 
			
		||||
        """
 | 
			
		||||
        # To comply with PEP8, method names now use underscores.
 | 
			
		||||
        # Deprecated method names are re-mapped for backwards compatibility.
 | 
			
		||||
        self.setNick = self.set_nick
 | 
			
		||||
        self.getNick = self.get_nick
 | 
			
		||||
        self.delNick = self.del_nick
 | 
			
		||||
 | 
			
		||||
        return ElementBase.setup(self, xml)
 | 
			
		||||
 | 
			
		||||
    def set_nick(self, nick):
 | 
			
		||||
        """
 | 
			
		||||
        Add a <nick> element with the given nickname.
 | 
			
		||||
@@ -87,3 +70,9 @@ class Nick(ElementBase):
 | 
			
		||||
 | 
			
		||||
register_stanza_plugin(Message, Nick)
 | 
			
		||||
register_stanza_plugin(Presence, Nick)
 | 
			
		||||
 | 
			
		||||
# To comply with PEP8, method names now use underscores.
 | 
			
		||||
# Deprecated method names are re-mapped for backwards compatibility.
 | 
			
		||||
Nick.setNick = Nick.set_nick
 | 
			
		||||
Nick.getNick = Nick.get_nick
 | 
			
		||||
Nick.delNick = Nick.del_nick
 | 
			
		||||
 
 | 
			
		||||
@@ -72,26 +72,6 @@ class Presence(RootStanza):
 | 
			
		||||
                 'subscribed', 'unsubscribe', 'unsubscribed'))
 | 
			
		||||
    showtypes = set(('dnd', 'chat', 'xa', 'away'))
 | 
			
		||||
 | 
			
		||||
    def setup(self, xml=None):
 | 
			
		||||
        """
 | 
			
		||||
        Populate the stanza object using an optional XML object.
 | 
			
		||||
 | 
			
		||||
        Overrides ElementBase.setup.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            xml -- Use an existing XML object for the stanza's values.
 | 
			
		||||
        """
 | 
			
		||||
        # To comply with PEP8, method names now use underscores.
 | 
			
		||||
        # Deprecated method names are re-mapped for backwards compatibility.
 | 
			
		||||
        self.setShow = self.set_show
 | 
			
		||||
        self.getType = self.get_type
 | 
			
		||||
        self.setType = self.set_type
 | 
			
		||||
        self.delType = self.get_type
 | 
			
		||||
        self.getPriority = self.get_priority
 | 
			
		||||
        self.setPriority = self.set_priority
 | 
			
		||||
 | 
			
		||||
        return StanzaBase.setup(self, xml)
 | 
			
		||||
 | 
			
		||||
    def exception(self, e):
 | 
			
		||||
        """
 | 
			
		||||
        Override exception passback for presence.
 | 
			
		||||
@@ -173,14 +153,28 @@ class Presence(RootStanza):
 | 
			
		||||
            # The priority is not a number: we consider it 0 as a default
 | 
			
		||||
            return 0
 | 
			
		||||
 | 
			
		||||
    def reply(self):
 | 
			
		||||
    def reply(self, clear=True):
 | 
			
		||||
        """
 | 
			
		||||
        Set the appropriate presence reply type.
 | 
			
		||||
 | 
			
		||||
        Overrides StanzaBase.reply.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            clear -- Indicates if the stanza contents should be removed
 | 
			
		||||
                     before replying. Defaults to True.
 | 
			
		||||
        """
 | 
			
		||||
        if self['type'] == 'unsubscribe':
 | 
			
		||||
            self['type'] = 'unsubscribed'
 | 
			
		||||
        elif self['type'] == 'subscribe':
 | 
			
		||||
            self['type'] = 'subscribed'
 | 
			
		||||
        return StanzaBase.reply(self)
 | 
			
		||||
        return StanzaBase.reply(self, clear)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# To comply with PEP8, method names now use underscores.
 | 
			
		||||
# Deprecated method names are re-mapped for backwards compatibility.
 | 
			
		||||
Presence.setShow = Presence.set_show
 | 
			
		||||
Presence.getType = Presence.get_type
 | 
			
		||||
Presence.setType = Presence.set_type
 | 
			
		||||
Presence.delType = Presence.get_type
 | 
			
		||||
Presence.getPriority = Presence.get_priority
 | 
			
		||||
Presence.setPriority = Presence.set_priority
 | 
			
		||||
 
 | 
			
		||||
@@ -43,8 +43,8 @@ class RootStanza(StanzaBase):
 | 
			
		||||
        Arguments:
 | 
			
		||||
            e -- Exception object
 | 
			
		||||
        """
 | 
			
		||||
        self.reply()
 | 
			
		||||
        if isinstance(e, XMPPError):
 | 
			
		||||
            self.reply(clear=e.clear)
 | 
			
		||||
            # We raised this deliberately
 | 
			
		||||
            self['error']['condition'] = e.condition
 | 
			
		||||
            self['error']['text'] = e.text
 | 
			
		||||
@@ -54,16 +54,18 @@ class RootStanza(StanzaBase):
 | 
			
		||||
                                    e.extension_args)
 | 
			
		||||
                self['error'].append(extxml)
 | 
			
		||||
                self['error']['type'] = e.etype
 | 
			
		||||
            self.send()
 | 
			
		||||
        else:
 | 
			
		||||
            # We probably didn't raise this on purpose, so send a traceback
 | 
			
		||||
            self.reply()
 | 
			
		||||
            # We probably didn't raise this on purpose, so send an error stanza
 | 
			
		||||
            self['error']['condition'] = 'undefined-condition'
 | 
			
		||||
            if sys.version_info < (3, 0):
 | 
			
		||||
            self['error']['text'] = "SleekXMPP got into trouble."
 | 
			
		||||
            else:
 | 
			
		||||
                self['error']['text'] = traceback.format_tb(e.__traceback__)
 | 
			
		||||
            self.send()
 | 
			
		||||
            # log the error
 | 
			
		||||
            log.exception('Error handling {%s}%s stanza' %
 | 
			
		||||
                          (self.namespace, self.name))
 | 
			
		||||
        self.send()
 | 
			
		||||
 | 
			
		||||
            # Finally raise the exception, so it can be handled (or not)
 | 
			
		||||
            # at a higher level by using sys.excepthook.
 | 
			
		||||
            raise e
 | 
			
		||||
 | 
			
		||||
register_stanza_plugin(RootStanza, Error)
 | 
			
		||||
 
 | 
			
		||||
@@ -38,23 +38,6 @@ class Roster(ElementBase):
 | 
			
		||||
    plugin_attrib = 'roster'
 | 
			
		||||
    interfaces = set(('items',))
 | 
			
		||||
 | 
			
		||||
    def setup(self, xml=None):
 | 
			
		||||
        """
 | 
			
		||||
        Populate the stanza object using an optional XML object.
 | 
			
		||||
 | 
			
		||||
        Overrides StanzaBase.setup.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            xml -- Use an existing XML object for the stanza's values.
 | 
			
		||||
        """
 | 
			
		||||
        # To comply with PEP8, method names now use underscores.
 | 
			
		||||
        # Deprecated method names are re-mapped for backwards compatibility.
 | 
			
		||||
        self.setItems = self.set_items
 | 
			
		||||
        self.getItems = self.get_items
 | 
			
		||||
        self.delItems = self.del_items
 | 
			
		||||
 | 
			
		||||
        return ElementBase.setup(self, xml)
 | 
			
		||||
 | 
			
		||||
    def set_items(self, items):
 | 
			
		||||
        """
 | 
			
		||||
        Set the roster entries in the <roster> stanza.
 | 
			
		||||
@@ -123,3 +106,9 @@ class Roster(ElementBase):
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
register_stanza_plugin(Iq, Roster)
 | 
			
		||||
 | 
			
		||||
# To comply with PEP8, method names now use underscores.
 | 
			
		||||
# Deprecated method names are re-mapped for backwards compatibility.
 | 
			
		||||
Roster.setItems = Roster.set_items
 | 
			
		||||
Roster.getItems = Roster.get_items
 | 
			
		||||
Roster.delItems = Roster.del_items
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										69
									
								
								sleekxmpp/stanza/stream_error.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										69
									
								
								sleekxmpp/stanza/stream_error.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,69 @@
 | 
			
		||||
"""
 | 
			
		||||
    SleekXMPP: The Sleek XMPP Library
 | 
			
		||||
    Copyright (C) 2010  Nathanael C. Fritz
 | 
			
		||||
    This file is part of SleekXMPP.
 | 
			
		||||
 | 
			
		||||
    See the file LICENSE for copying permission.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
from sleekxmpp.stanza.error import Error
 | 
			
		||||
from sleekxmpp.xmlstream import StanzaBase, ElementBase, ET
 | 
			
		||||
from sleekxmpp.xmlstream import register_stanza_plugin
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class StreamError(Error, StanzaBase):
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    XMPP stanzas of type 'error' should include an <error> stanza that
 | 
			
		||||
    describes the nature of the error and how it should be handled.
 | 
			
		||||
 | 
			
		||||
    Use the 'XEP-0086: Error Condition Mappings' plugin to include error
 | 
			
		||||
    codes used in older XMPP versions.
 | 
			
		||||
 | 
			
		||||
    The stream:error stanza is used to provide more information for
 | 
			
		||||
    error that occur with the underlying XML stream itself, and not
 | 
			
		||||
    a particular stanza.
 | 
			
		||||
 | 
			
		||||
    Note: The StreamError stanza is mostly the same as the normal
 | 
			
		||||
          Error stanza, but with different namespaces and
 | 
			
		||||
          condition names.
 | 
			
		||||
 | 
			
		||||
    Example error stanza:
 | 
			
		||||
        <stream:error>
 | 
			
		||||
          <not-well-formed xmlns="urn:ietf:params:xml:ns:xmpp-streams" />
 | 
			
		||||
          <text xmlns="urn:ietf:params:xml:ns:xmpp-streams">
 | 
			
		||||
            XML was not well-formed.
 | 
			
		||||
          </text>
 | 
			
		||||
        </stream:error>
 | 
			
		||||
 | 
			
		||||
    Stanza Interface:
 | 
			
		||||
        condition -- The name of the condition element.
 | 
			
		||||
        text      -- Human readable description of the error.
 | 
			
		||||
 | 
			
		||||
    Attributes:
 | 
			
		||||
        conditions   -- The set of allowable error condition elements.
 | 
			
		||||
        condition_ns -- The namespace for the condition element.
 | 
			
		||||
 | 
			
		||||
    Methods:
 | 
			
		||||
        setup         -- Overrides ElementBase.setup.
 | 
			
		||||
        get_condition -- Retrieve the name of the condition element.
 | 
			
		||||
        set_condition -- Add a condition element.
 | 
			
		||||
        del_condition -- Remove the condition element.
 | 
			
		||||
        get_text      -- Retrieve the contents of the <text> element.
 | 
			
		||||
        set_text      -- Set the contents of the <text> element.
 | 
			
		||||
        del_text      -- Remove the <text> element.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    namespace = 'http://etherx.jabber.org/streams'
 | 
			
		||||
    interfaces = set(('condition', 'text'))
 | 
			
		||||
    conditions = set((
 | 
			
		||||
        'bad-format', 'bad-namespace-prefix', 'conflict',
 | 
			
		||||
        'connection-timeout', 'host-gone', 'host-unknown',
 | 
			
		||||
        'improper-addressing', 'internal-server-error', 'invalid-from',
 | 
			
		||||
        'invalid-namespace', 'invalid-xml', 'not-authorized',
 | 
			
		||||
        'not-well-formed', 'policy-violation', 'remote-connection-failed',
 | 
			
		||||
        'reset', 'resource-constraint', 'restricted-xml', 'see-other-host',
 | 
			
		||||
        'system-shutdown', 'undefined-condition', 'unsupported-encoding',
 | 
			
		||||
        'unsupported-feature', 'unsupported-stanza-type',
 | 
			
		||||
        'unsupported-version'))
 | 
			
		||||
    condition_ns = 'urn:ietf:params:xml:ns:xmpp-streams'
 | 
			
		||||
@@ -7,6 +7,7 @@
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
import socket
 | 
			
		||||
import threading
 | 
			
		||||
try:
 | 
			
		||||
    import queue
 | 
			
		||||
except ImportError:
 | 
			
		||||
@@ -40,6 +41,8 @@ class TestLiveSocket(object):
 | 
			
		||||
        self.recv_buffer = []
 | 
			
		||||
        self.recv_queue = queue.Queue()
 | 
			
		||||
        self.send_queue = queue.Queue()
 | 
			
		||||
        self.send_queue_lock = threading.Lock()
 | 
			
		||||
        self.recv_queue_lock = threading.Lock()
 | 
			
		||||
        self.is_live = True
 | 
			
		||||
 | 
			
		||||
    def __getattr__(self, name):
 | 
			
		||||
@@ -55,6 +58,18 @@ class TestLiveSocket(object):
 | 
			
		||||
    # ------------------------------------------------------------------
 | 
			
		||||
    # Testing Interface
 | 
			
		||||
 | 
			
		||||
    def disconnect_errror(self):
 | 
			
		||||
        """
 | 
			
		||||
        Used to simulate a socket disconnection error.
 | 
			
		||||
 | 
			
		||||
        Not used by live sockets.
 | 
			
		||||
        """
 | 
			
		||||
        try:
 | 
			
		||||
            self.socket.shutdown()
 | 
			
		||||
            self.socket.close()
 | 
			
		||||
        except:
 | 
			
		||||
            pass
 | 
			
		||||
 | 
			
		||||
    def next_sent(self, timeout=None):
 | 
			
		||||
        """
 | 
			
		||||
        Get the next stanza that has been sent.
 | 
			
		||||
@@ -108,6 +123,7 @@ class TestLiveSocket(object):
 | 
			
		||||
            Placeholders. Same as for socket.recv.
 | 
			
		||||
        """
 | 
			
		||||
        data = self.socket.recv(*args, **kwargs)
 | 
			
		||||
        with self.recv_queue_lock:
 | 
			
		||||
            self.recv_queue.put(data)
 | 
			
		||||
        return data
 | 
			
		||||
 | 
			
		||||
@@ -120,6 +136,7 @@ class TestLiveSocket(object):
 | 
			
		||||
        Arguments:
 | 
			
		||||
            data -- String value to write.
 | 
			
		||||
        """
 | 
			
		||||
        with self.send_queue_lock:
 | 
			
		||||
            self.send_queue.put(data)
 | 
			
		||||
        self.socket.send(data)
 | 
			
		||||
 | 
			
		||||
@@ -143,3 +160,15 @@ class TestLiveSocket(object):
 | 
			
		||||
            Placeholders, same as socket.recv()
 | 
			
		||||
        """
 | 
			
		||||
        return self.recv(*args, **kwargs)
 | 
			
		||||
 | 
			
		||||
    def clear(self):
 | 
			
		||||
        """
 | 
			
		||||
        Empty the send queue, typically done once the session has started to
 | 
			
		||||
        remove the feature negotiation and log in stanzas.
 | 
			
		||||
        """
 | 
			
		||||
        with self.send_queue_lock:
 | 
			
		||||
            for i in range(0, self.send_queue.qsize()):
 | 
			
		||||
                self.send_queue.get(block=False)
 | 
			
		||||
        with self.recv_queue_lock:
 | 
			
		||||
            for i in range(0, self.recv_queue.qsize()):
 | 
			
		||||
                self.recv_queue.get(block=False)
 | 
			
		||||
 
 | 
			
		||||
@@ -39,6 +39,7 @@ class TestSocket(object):
 | 
			
		||||
        self.recv_queue = queue.Queue()
 | 
			
		||||
        self.send_queue = queue.Queue()
 | 
			
		||||
        self.is_live = False
 | 
			
		||||
        self.disconnected = False
 | 
			
		||||
 | 
			
		||||
    def __getattr__(self, name):
 | 
			
		||||
        """
 | 
			
		||||
@@ -89,6 +90,13 @@ class TestSocket(object):
 | 
			
		||||
        """
 | 
			
		||||
        self.recv_queue.put(data)
 | 
			
		||||
 | 
			
		||||
    def disconnect_error(self):
 | 
			
		||||
        """
 | 
			
		||||
        Simulate a disconnect error by raising a socket.error exception
 | 
			
		||||
        for any current or further socket operations.
 | 
			
		||||
        """
 | 
			
		||||
        self.disconnected = True
 | 
			
		||||
 | 
			
		||||
    # ------------------------------------------------------------------
 | 
			
		||||
    # Socket Interface
 | 
			
		||||
 | 
			
		||||
@@ -99,6 +107,8 @@ class TestSocket(object):
 | 
			
		||||
        Arguments:
 | 
			
		||||
            Placeholders. Same as for socket.Socket.recv.
 | 
			
		||||
        """
 | 
			
		||||
        if self.disconnected:
 | 
			
		||||
            raise socket.error
 | 
			
		||||
        return self.read(block=True)
 | 
			
		||||
 | 
			
		||||
    def send(self, data):
 | 
			
		||||
@@ -108,6 +118,8 @@ class TestSocket(object):
 | 
			
		||||
        Arguments:
 | 
			
		||||
            data -- String value to write.
 | 
			
		||||
        """
 | 
			
		||||
        if self.disconnected:
 | 
			
		||||
            raise socket.error
 | 
			
		||||
        self.send_queue.put(data)
 | 
			
		||||
 | 
			
		||||
    # ------------------------------------------------------------------
 | 
			
		||||
@@ -132,6 +144,8 @@ class TestSocket(object):
 | 
			
		||||
            timeout -- Time in seconds a block should last before
 | 
			
		||||
                       returning None.
 | 
			
		||||
        """
 | 
			
		||||
        if self.disconnected:
 | 
			
		||||
            raise socket.error
 | 
			
		||||
        if timeout is not None:
 | 
			
		||||
            block = True
 | 
			
		||||
        try:
 | 
			
		||||
 
 | 
			
		||||
@@ -7,13 +7,20 @@
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
import unittest
 | 
			
		||||
try:
 | 
			
		||||
    import Queue as queue
 | 
			
		||||
except:
 | 
			
		||||
    import queue
 | 
			
		||||
 | 
			
		||||
import sleekxmpp
 | 
			
		||||
from sleekxmpp import ClientXMPP, ComponentXMPP
 | 
			
		||||
from sleekxmpp.stanza import Message, Iq, Presence
 | 
			
		||||
from sleekxmpp.test import TestSocket, TestLiveSocket
 | 
			
		||||
from sleekxmpp.xmlstream import StanzaBase, ET, register_stanza_plugin
 | 
			
		||||
from sleekxmpp.xmlstream import ET, register_stanza_plugin
 | 
			
		||||
from sleekxmpp.xmlstream import ElementBase, StanzaBase
 | 
			
		||||
from sleekxmpp.xmlstream.tostring import tostring
 | 
			
		||||
from sleekxmpp.xmlstream.matcher import StanzaPath, MatcherId
 | 
			
		||||
from sleekxmpp.xmlstream.matcher import MatchXMLMask, MatchXPath
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class SleekTest(unittest.TestCase):
 | 
			
		||||
@@ -46,6 +53,10 @@ class SleekTest(unittest.TestCase):
 | 
			
		||||
        compare              -- Compare XML objects against each other.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    def __init__(self, *args, **kwargs):
 | 
			
		||||
        unittest.TestCase.__init__(self, *args, **kwargs)
 | 
			
		||||
        self.xmpp = None
 | 
			
		||||
 | 
			
		||||
    def runTest(self):
 | 
			
		||||
        pass
 | 
			
		||||
 | 
			
		||||
@@ -67,6 +78,8 @@ class SleekTest(unittest.TestCase):
 | 
			
		||||
                xml = self.parse_xml(xml_string)
 | 
			
		||||
                xml = xml.getchildren()[0]
 | 
			
		||||
                return xml
 | 
			
		||||
            else:
 | 
			
		||||
                self.fail("XML data was mal-formed:\n%s" % xml_string)
 | 
			
		||||
 | 
			
		||||
    # ------------------------------------------------------------------
 | 
			
		||||
    # Shortcut methods for creating stanza objects
 | 
			
		||||
@@ -80,7 +93,7 @@ class SleekTest(unittest.TestCase):
 | 
			
		||||
        Arguments:
 | 
			
		||||
            xml -- An XML object to use for the Message's values.
 | 
			
		||||
        """
 | 
			
		||||
        return Message(None, *args, **kwargs)
 | 
			
		||||
        return Message(self.xmpp, *args, **kwargs)
 | 
			
		||||
 | 
			
		||||
    def Iq(self, *args, **kwargs):
 | 
			
		||||
        """
 | 
			
		||||
@@ -91,7 +104,7 @@ class SleekTest(unittest.TestCase):
 | 
			
		||||
        Arguments:
 | 
			
		||||
            xml -- An XML object to use for the Iq's values.
 | 
			
		||||
        """
 | 
			
		||||
        return Iq(None, *args, **kwargs)
 | 
			
		||||
        return Iq(self.xmpp, *args, **kwargs)
 | 
			
		||||
 | 
			
		||||
    def Presence(self, *args, **kwargs):
 | 
			
		||||
        """
 | 
			
		||||
@@ -102,7 +115,7 @@ class SleekTest(unittest.TestCase):
 | 
			
		||||
        Arguments:
 | 
			
		||||
            xml -- An XML object to use for the Iq's values.
 | 
			
		||||
        """
 | 
			
		||||
        return Presence(None, *args, **kwargs)
 | 
			
		||||
        return Presence(self.xmpp, *args, **kwargs)
 | 
			
		||||
 | 
			
		||||
    def check_jid(self, jid, user=None, domain=None, resource=None,
 | 
			
		||||
                  bare=None, full=None, string=None):
 | 
			
		||||
@@ -140,13 +153,12 @@ class SleekTest(unittest.TestCase):
 | 
			
		||||
    # ------------------------------------------------------------------
 | 
			
		||||
    # Methods for comparing stanza objects to XML strings
 | 
			
		||||
 | 
			
		||||
    def check(self, stanza, xml_string,
 | 
			
		||||
    def check(self, stanza, criteria, method='exact',
 | 
			
		||||
              defaults=None, use_values=True):
 | 
			
		||||
        """
 | 
			
		||||
        Create and compare several stanza objects to a correct XML string.
 | 
			
		||||
 | 
			
		||||
        If use_values is False, test using getStanzaValues() and
 | 
			
		||||
        setStanzaValues() will not be used.
 | 
			
		||||
        If use_values is False, tests using stanza.values will not be used.
 | 
			
		||||
 | 
			
		||||
        Some stanzas provide default values for some interfaces, but
 | 
			
		||||
        these defaults can be problematic for testing since they can easily
 | 
			
		||||
@@ -161,17 +173,39 @@ class SleekTest(unittest.TestCase):
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            stanza       -- The stanza object to test.
 | 
			
		||||
            xml_string   -- A string version of the correct XML expected.
 | 
			
		||||
            criteria     -- An expression the stanza must match against.
 | 
			
		||||
            method       -- The type of matching to use; one of:
 | 
			
		||||
                            'exact', 'mask', 'id', 'xpath', and 'stanzapath'.
 | 
			
		||||
                            Defaults to the value of self.match_method.
 | 
			
		||||
            defaults     -- A list of stanza interfaces that have default
 | 
			
		||||
                            values. These interfaces will be set to their
 | 
			
		||||
                            defaults for the given and generated stanzas to
 | 
			
		||||
                            prevent unexpected test failures.
 | 
			
		||||
            use_values   -- Indicates if testing using getStanzaValues() and
 | 
			
		||||
                            setStanzaValues() should be used. Defaults to
 | 
			
		||||
                            True.
 | 
			
		||||
            use_values   -- Indicates if testing using stanza.values should
 | 
			
		||||
                            be used. Defaults to True.
 | 
			
		||||
        """
 | 
			
		||||
        if method is None and hasattr(self, 'match_method'):
 | 
			
		||||
            method = getattr(self, 'match_method')
 | 
			
		||||
 | 
			
		||||
        if method != 'exact':
 | 
			
		||||
            matchers = {'stanzapath': StanzaPath,
 | 
			
		||||
                        'xpath': MatchXPath,
 | 
			
		||||
                        'mask': MatchXMLMask,
 | 
			
		||||
                        'id': MatcherId}
 | 
			
		||||
            Matcher = matchers.get(method, None)
 | 
			
		||||
            if Matcher is None:
 | 
			
		||||
                raise ValueError("Unknown matching method.")
 | 
			
		||||
            test = Matcher(criteria)
 | 
			
		||||
            self.failUnless(test.match(stanza),
 | 
			
		||||
                    "Stanza did not match using %s method:\n" % method + \
 | 
			
		||||
                    "Criteria:\n%s\n" % str(criteria) + \
 | 
			
		||||
                    "Stanza:\n%s" % str(stanza))
 | 
			
		||||
        else:
 | 
			
		||||
            stanza_class = stanza.__class__
 | 
			
		||||
        xml = self.parse_xml(xml_string)
 | 
			
		||||
            if not isinstance(criteria, ElementBase):
 | 
			
		||||
                xml = self.parse_xml(criteria)
 | 
			
		||||
            else:
 | 
			
		||||
                xml = criteria.xml
 | 
			
		||||
 | 
			
		||||
            # Ensure that top level namespaces are used, even if they
 | 
			
		||||
            # were not provided.
 | 
			
		||||
@@ -181,10 +215,10 @@ class SleekTest(unittest.TestCase):
 | 
			
		||||
            stanza2 = stanza_class(xml=xml)
 | 
			
		||||
 | 
			
		||||
            if use_values:
 | 
			
		||||
            # Using getStanzaValues() and setStanzaValues() will add
 | 
			
		||||
            # XML for any interface that has a default value. We need
 | 
			
		||||
            # to set those defaults on the existing stanzas and XML
 | 
			
		||||
            # so that they will compare correctly.
 | 
			
		||||
                # Using stanza.values will add XML for any interface that
 | 
			
		||||
                # has a default value. We need to set those defaults on
 | 
			
		||||
                # the existing stanzas and XML so that they will compare
 | 
			
		||||
                # correctly.
 | 
			
		||||
                default_stanza = stanza_class()
 | 
			
		||||
                if defaults is None:
 | 
			
		||||
                    known_defaults = {
 | 
			
		||||
@@ -203,9 +237,9 @@ class SleekTest(unittest.TestCase):
 | 
			
		||||
                            value = default_stanza.xml.attrib[interface]
 | 
			
		||||
                            xml.attrib[interface] = value
 | 
			
		||||
 | 
			
		||||
            values = stanza2.getStanzaValues()
 | 
			
		||||
                values = stanza2.values
 | 
			
		||||
                stanza3 = stanza_class()
 | 
			
		||||
            stanza3.setStanzaValues(values)
 | 
			
		||||
                stanza3.values = values
 | 
			
		||||
 | 
			
		||||
                debug = "Three methods for creating stanzas do not match.\n"
 | 
			
		||||
                debug += "Given XML:\n%s\n" % tostring(xml)
 | 
			
		||||
@@ -225,10 +259,17 @@ class SleekTest(unittest.TestCase):
 | 
			
		||||
    # ------------------------------------------------------------------
 | 
			
		||||
    # Methods for simulating stanza streams.
 | 
			
		||||
 | 
			
		||||
    def stream_disconnect(self):
 | 
			
		||||
        """
 | 
			
		||||
        Simulate a stream disconnection.
 | 
			
		||||
        """
 | 
			
		||||
        if self.xmpp:
 | 
			
		||||
            self.xmpp.socket.disconnect_error()
 | 
			
		||||
 | 
			
		||||
    def stream_start(self, mode='client', skip=True, header=None,
 | 
			
		||||
                           socket='mock', jid='tester@localhost',
 | 
			
		||||
                           password='test', server='localhost',
 | 
			
		||||
                           port=5222):
 | 
			
		||||
                           port=5222, plugins=None):
 | 
			
		||||
        """
 | 
			
		||||
        Initialize an XMPP client or component using a dummy XML stream.
 | 
			
		||||
 | 
			
		||||
@@ -248,6 +289,8 @@ class SleekTest(unittest.TestCase):
 | 
			
		||||
            server   -- The name of the XMPP server. Defaults to 'localhost'.
 | 
			
		||||
            port     -- The port to use when connecting to the server.
 | 
			
		||||
                        Defaults to 5222.
 | 
			
		||||
            plugins  -- List of plugins to register. By default, all plugins
 | 
			
		||||
                        are loaded.
 | 
			
		||||
        """
 | 
			
		||||
        if mode == 'client':
 | 
			
		||||
            self.xmpp = ClientXMPP(jid, password)
 | 
			
		||||
@@ -257,6 +300,10 @@ class SleekTest(unittest.TestCase):
 | 
			
		||||
        else:
 | 
			
		||||
            raise ValueError("Unknown XMPP connection mode.")
 | 
			
		||||
 | 
			
		||||
        # We will use this to wait for the session_start event
 | 
			
		||||
        # for live connections.
 | 
			
		||||
        skip_queue = queue.Queue()
 | 
			
		||||
 | 
			
		||||
        if socket == 'mock':
 | 
			
		||||
            self.xmpp.set_socket(TestSocket())
 | 
			
		||||
 | 
			
		||||
@@ -271,17 +318,30 @@ class SleekTest(unittest.TestCase):
 | 
			
		||||
            self.xmpp.socket.recv_data(header)
 | 
			
		||||
        elif socket == 'live':
 | 
			
		||||
            self.xmpp.socket_class = TestLiveSocket
 | 
			
		||||
            def wait_for_session(x):
 | 
			
		||||
                self.xmpp.socket.clear()
 | 
			
		||||
                skip_queue.put('started')
 | 
			
		||||
            self.xmpp.add_event_handler('session_start', wait_for_session)
 | 
			
		||||
            self.xmpp.connect()
 | 
			
		||||
        else:
 | 
			
		||||
            raise ValueError("Unknown socket type.")
 | 
			
		||||
 | 
			
		||||
        if plugins is None:
 | 
			
		||||
            self.xmpp.register_plugins()
 | 
			
		||||
        else:
 | 
			
		||||
            for plugin in plugins:
 | 
			
		||||
                self.xmpp.register_plugin(plugin)
 | 
			
		||||
        self.xmpp.process(threaded=True)
 | 
			
		||||
        if skip:
 | 
			
		||||
            if socket != 'live':
 | 
			
		||||
                # Mark send queue as usable
 | 
			
		||||
                self.xmpp.session_started_event.set()
 | 
			
		||||
                # Clear startup stanzas
 | 
			
		||||
                self.xmpp.socket.next_sent(timeout=1)
 | 
			
		||||
                if mode == 'component':
 | 
			
		||||
                    self.xmpp.socket.next_sent(timeout=1)
 | 
			
		||||
            else:
 | 
			
		||||
                skip_queue.get(block=True, timeout=10)
 | 
			
		||||
 | 
			
		||||
    def make_header(self, sto='',
 | 
			
		||||
                          sfrom='',
 | 
			
		||||
@@ -320,7 +380,7 @@ class SleekTest(unittest.TestCase):
 | 
			
		||||
        parts.append('xmlns="%s"' % default_ns)
 | 
			
		||||
        return header % ' '.join(parts)
 | 
			
		||||
 | 
			
		||||
    def recv(self, data, stanza_class=StanzaBase, defaults=[],
 | 
			
		||||
    def recv(self, data, defaults=[], method='exact',
 | 
			
		||||
             use_values=True, timeout=1):
 | 
			
		||||
        """
 | 
			
		||||
        Pass data to the dummy XMPP client as if it came from an XMPP server.
 | 
			
		||||
@@ -328,15 +388,17 @@ class SleekTest(unittest.TestCase):
 | 
			
		||||
        If using a live connection, verify what the server has sent.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            data         -- String stanza XML to be received and processed by
 | 
			
		||||
                            the XMPP client or component.
 | 
			
		||||
            stanza_class -- The stanza object class for verifying data received
 | 
			
		||||
                            by a live connection. Defaults to StanzaBase.
 | 
			
		||||
            data         -- If a dummy socket is being used, the XML that is to
 | 
			
		||||
                            be received next. Otherwise it is the criteria used
 | 
			
		||||
                            to match against live data that is received.
 | 
			
		||||
            defaults     -- A list of stanza interfaces with default values that
 | 
			
		||||
                            may interfere with comparisons.
 | 
			
		||||
            method       -- Select the type of comparison to use for
 | 
			
		||||
                            verifying the received stanza. Options are 'exact',
 | 
			
		||||
                            'id', 'stanzapath', 'xpath', and 'mask'.
 | 
			
		||||
                            Defaults to the value of self.match_method.
 | 
			
		||||
            use_values   -- Indicates if stanza comparisons should test using
 | 
			
		||||
                            getStanzaValues() and setStanzaValues().
 | 
			
		||||
                            Defaults to True.
 | 
			
		||||
                            stanza.values. Defaults to True.
 | 
			
		||||
            timeout      -- Time to wait in seconds for data to be received by
 | 
			
		||||
                            a live connection.
 | 
			
		||||
        """
 | 
			
		||||
@@ -346,9 +408,12 @@ class SleekTest(unittest.TestCase):
 | 
			
		||||
            # receiving data.
 | 
			
		||||
            recv_data = self.xmpp.socket.next_recv(timeout)
 | 
			
		||||
            if recv_data is None:
 | 
			
		||||
                return False
 | 
			
		||||
            stanza = stanza_class(xml=self.parse_xml(recv_data))
 | 
			
		||||
            return self.check(stanza_class, stanza, data,
 | 
			
		||||
                self.fail("No stanza was received.")
 | 
			
		||||
            xml = self.parse_xml(recv_data)
 | 
			
		||||
            self.fix_namespaces(xml, 'jabber:client')
 | 
			
		||||
            stanza = self.xmpp._build_stanza(xml, 'jabber:client')
 | 
			
		||||
            self.check(stanza, data,
 | 
			
		||||
                       method=method,
 | 
			
		||||
                       defaults=defaults,
 | 
			
		||||
                       use_values=use_values)
 | 
			
		||||
        else:
 | 
			
		||||
@@ -424,21 +489,33 @@ class SleekTest(unittest.TestCase):
 | 
			
		||||
                '%s %s' % (xml.tag, xml.attrib),
 | 
			
		||||
                '%s %s' % (recv_xml.tag, recv_xml.attrib)))
 | 
			
		||||
 | 
			
		||||
    def recv_feature(self, data, use_values=True, timeout=1):
 | 
			
		||||
    def recv_feature(self, data, method='mask', use_values=True, timeout=1):
 | 
			
		||||
        """
 | 
			
		||||
        """
 | 
			
		||||
        if method is None and hasattr(self, 'match_method'):
 | 
			
		||||
            method = getattr(self, 'match_method')
 | 
			
		||||
 | 
			
		||||
        if self.xmpp.socket.is_live:
 | 
			
		||||
            # we are working with a live connection, so we should
 | 
			
		||||
            # verify what has been received instead of simulating
 | 
			
		||||
            # receiving data.
 | 
			
		||||
            recv_data = self.xmpp.socket.next_recv(timeout)
 | 
			
		||||
            if recv_data is None:
 | 
			
		||||
                return False
 | 
			
		||||
            xml = self.parse_xml(data)
 | 
			
		||||
            recv_xml = self.parse_xml(recv_data)
 | 
			
		||||
            if recv_data is None:
 | 
			
		||||
                self.fail("No stanza was received.")
 | 
			
		||||
            if method == 'exact':
 | 
			
		||||
                self.failUnless(self.compare(xml, recv_xml),
 | 
			
		||||
                    "Features do not match.\nDesired:\n%s\nReceived:\n%s" % (
 | 
			
		||||
                        tostring(xml), tostring(recv_xml)))
 | 
			
		||||
            elif method == 'mask':
 | 
			
		||||
                matcher = MatchXMLMask(xml)
 | 
			
		||||
                self.failUnless(matcher.match(recv_xml),
 | 
			
		||||
                    "Stanza did not match using %s method:\n" % method + \
 | 
			
		||||
                    "Criteria:\n%s\n" % tostring(xml) + \
 | 
			
		||||
                    "Stanza:\n%s" % tostring(recv_xml))
 | 
			
		||||
            else:
 | 
			
		||||
                raise ValueError("Uknown matching method: %s" % method)
 | 
			
		||||
        else:
 | 
			
		||||
            # place the data in the dummy socket receiving queue.
 | 
			
		||||
            data = str(data)
 | 
			
		||||
@@ -489,20 +566,29 @@ class SleekTest(unittest.TestCase):
 | 
			
		||||
            "Stream headers do not match:\nDesired:\n%s\nSent:\n%s" % (
 | 
			
		||||
                header, sent_header))
 | 
			
		||||
 | 
			
		||||
    def send_feature(self, data, use_values=True, timeout=1):
 | 
			
		||||
    def send_feature(self, data, method='mask', use_values=True, timeout=1):
 | 
			
		||||
        """
 | 
			
		||||
        """
 | 
			
		||||
        sent_data = self.xmpp.socket.next_sent(timeout)
 | 
			
		||||
        if sent_data is None:
 | 
			
		||||
            return False
 | 
			
		||||
        xml = self.parse_xml(data)
 | 
			
		||||
        sent_xml = self.parse_xml(sent_data)
 | 
			
		||||
        if sent_data is None:
 | 
			
		||||
            self.fail("No stanza was sent.")
 | 
			
		||||
        if method == 'exact':
 | 
			
		||||
            self.failUnless(self.compare(xml, sent_xml),
 | 
			
		||||
            "Features do not match.\nDesired:\n%s\nSent:\n%s" % (
 | 
			
		||||
                "Features do not match.\nDesired:\n%s\nReceived:\n%s" % (
 | 
			
		||||
                    tostring(xml), tostring(sent_xml)))
 | 
			
		||||
        elif method == 'mask':
 | 
			
		||||
            matcher = MatchXMLMask(xml)
 | 
			
		||||
            self.failUnless(matcher.match(sent_xml),
 | 
			
		||||
                "Stanza did not match using %s method:\n" % method + \
 | 
			
		||||
                "Criteria:\n%s\n" % tostring(xml) + \
 | 
			
		||||
                "Stanza:\n%s" % tostring(sent_xml))
 | 
			
		||||
        else:
 | 
			
		||||
            raise ValueError("Uknown matching method: %s" % method)
 | 
			
		||||
 | 
			
		||||
    def send(self, data, defaults=None,
 | 
			
		||||
             use_values=True, timeout=.1):
 | 
			
		||||
    def send(self, data, defaults=None, use_values=True,
 | 
			
		||||
             timeout=.5, method='exact'):
 | 
			
		||||
        """
 | 
			
		||||
        Check that the XMPP client sent the given stanza XML.
 | 
			
		||||
 | 
			
		||||
@@ -518,13 +604,24 @@ class SleekTest(unittest.TestCase):
 | 
			
		||||
                            values which may interfere with comparisons.
 | 
			
		||||
            timeout      -- Time in seconds to wait for a stanza before
 | 
			
		||||
                            failing the check.
 | 
			
		||||
            method       -- Select the type of comparison to use for
 | 
			
		||||
                            verifying the sent stanza. Options are 'exact',
 | 
			
		||||
                            'id', 'stanzapath', 'xpath', and 'mask'.
 | 
			
		||||
                            Defaults to the value of self.match_method.
 | 
			
		||||
        """
 | 
			
		||||
        if isinstance(data, str):
 | 
			
		||||
            xml = self.parse_xml(data)
 | 
			
		||||
            self.fix_namespaces(xml, 'jabber:client')
 | 
			
		||||
            data = self.xmpp._build_stanza(xml, 'jabber:client')
 | 
			
		||||
        sent = self.xmpp.socket.next_sent(timeout)
 | 
			
		||||
        self.check(data, sent,
 | 
			
		||||
        if data is None and sent is None:
 | 
			
		||||
            return
 | 
			
		||||
        if data is None and sent is not None:
 | 
			
		||||
            self.fail("Stanza data was sent: %s" % sent)
 | 
			
		||||
        if sent is None:
 | 
			
		||||
            self.fail("No stanza was sent.")
 | 
			
		||||
 | 
			
		||||
        xml = self.parse_xml(sent)
 | 
			
		||||
        self.fix_namespaces(xml, 'jabber:client')
 | 
			
		||||
        sent = self.xmpp._build_stanza(xml, 'jabber:client')
 | 
			
		||||
        self.check(sent, data,
 | 
			
		||||
                   method=method,
 | 
			
		||||
                   defaults=defaults,
 | 
			
		||||
                   use_values=use_values)
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										4
									
								
								sleekxmpp/thirdparty/__init__.py
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								sleekxmpp/thirdparty/__init__.py
									
									
									
									
										vendored
									
									
								
							@@ -0,0 +1,4 @@
 | 
			
		||||
try:
 | 
			
		||||
    from collections import OrderedDict
 | 
			
		||||
except:
 | 
			
		||||
    from sleekxmpp.thirdparty.ordereddict import OrderedDict
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										127
									
								
								sleekxmpp/thirdparty/ordereddict.py
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										127
									
								
								sleekxmpp/thirdparty/ordereddict.py
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,127 @@
 | 
			
		||||
# Copyright (c) 2009 Raymond Hettinger
 | 
			
		||||
#
 | 
			
		||||
# 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.
 | 
			
		||||
 | 
			
		||||
from UserDict import DictMixin
 | 
			
		||||
 | 
			
		||||
class OrderedDict(dict, DictMixin):
 | 
			
		||||
 | 
			
		||||
    def __init__(self, *args, **kwds):
 | 
			
		||||
        if len(args) > 1:
 | 
			
		||||
            raise TypeError('expected at most 1 arguments, got %d' % len(args))
 | 
			
		||||
        try:
 | 
			
		||||
            self.__end
 | 
			
		||||
        except AttributeError:
 | 
			
		||||
            self.clear()
 | 
			
		||||
        self.update(*args, **kwds)
 | 
			
		||||
 | 
			
		||||
    def clear(self):
 | 
			
		||||
        self.__end = end = []
 | 
			
		||||
        end += [None, end, end]         # sentinel node for doubly linked list
 | 
			
		||||
        self.__map = {}                 # key --> [key, prev, next]
 | 
			
		||||
        dict.clear(self)
 | 
			
		||||
 | 
			
		||||
    def __setitem__(self, key, value):
 | 
			
		||||
        if key not in self:
 | 
			
		||||
            end = self.__end
 | 
			
		||||
            curr = end[1]
 | 
			
		||||
            curr[2] = end[1] = self.__map[key] = [key, curr, end]
 | 
			
		||||
        dict.__setitem__(self, key, value)
 | 
			
		||||
 | 
			
		||||
    def __delitem__(self, key):
 | 
			
		||||
        dict.__delitem__(self, key)
 | 
			
		||||
        key, prev, next = self.__map.pop(key)
 | 
			
		||||
        prev[2] = next
 | 
			
		||||
        next[1] = prev
 | 
			
		||||
 | 
			
		||||
    def __iter__(self):
 | 
			
		||||
        end = self.__end
 | 
			
		||||
        curr = end[2]
 | 
			
		||||
        while curr is not end:
 | 
			
		||||
            yield curr[0]
 | 
			
		||||
            curr = curr[2]
 | 
			
		||||
 | 
			
		||||
    def __reversed__(self):
 | 
			
		||||
        end = self.__end
 | 
			
		||||
        curr = end[1]
 | 
			
		||||
        while curr is not end:
 | 
			
		||||
            yield curr[0]
 | 
			
		||||
            curr = curr[1]
 | 
			
		||||
 | 
			
		||||
    def popitem(self, last=True):
 | 
			
		||||
        if not self:
 | 
			
		||||
            raise KeyError('dictionary is empty')
 | 
			
		||||
        if last:
 | 
			
		||||
            key = reversed(self).next()
 | 
			
		||||
        else:
 | 
			
		||||
            key = iter(self).next()
 | 
			
		||||
        value = self.pop(key)
 | 
			
		||||
        return key, value
 | 
			
		||||
 | 
			
		||||
    def __reduce__(self):
 | 
			
		||||
        items = [[k, self[k]] for k in self]
 | 
			
		||||
        tmp = self.__map, self.__end
 | 
			
		||||
        del self.__map, self.__end
 | 
			
		||||
        inst_dict = vars(self).copy()
 | 
			
		||||
        self.__map, self.__end = tmp
 | 
			
		||||
        if inst_dict:
 | 
			
		||||
            return (self.__class__, (items,), inst_dict)
 | 
			
		||||
        return self.__class__, (items,)
 | 
			
		||||
 | 
			
		||||
    def keys(self):
 | 
			
		||||
        return list(self)
 | 
			
		||||
 | 
			
		||||
    setdefault = DictMixin.setdefault
 | 
			
		||||
    update = DictMixin.update
 | 
			
		||||
    pop = DictMixin.pop
 | 
			
		||||
    values = DictMixin.values
 | 
			
		||||
    items = DictMixin.items
 | 
			
		||||
    iterkeys = DictMixin.iterkeys
 | 
			
		||||
    itervalues = DictMixin.itervalues
 | 
			
		||||
    iteritems = DictMixin.iteritems
 | 
			
		||||
 | 
			
		||||
    def __repr__(self):
 | 
			
		||||
        if not self:
 | 
			
		||||
            return '%s()' % (self.__class__.__name__,)
 | 
			
		||||
        return '%s(%r)' % (self.__class__.__name__, self.items())
 | 
			
		||||
 | 
			
		||||
    def copy(self):
 | 
			
		||||
        return self.__class__(self)
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def fromkeys(cls, iterable, value=None):
 | 
			
		||||
        d = cls()
 | 
			
		||||
        for key in iterable:
 | 
			
		||||
            d[key] = value
 | 
			
		||||
        return d
 | 
			
		||||
 | 
			
		||||
    def __eq__(self, other):
 | 
			
		||||
        if isinstance(other, OrderedDict):
 | 
			
		||||
            if len(self) != len(other):
 | 
			
		||||
                return False
 | 
			
		||||
            for p, q in  zip(self.items(), other.items()):
 | 
			
		||||
                if p != q:
 | 
			
		||||
                    return False
 | 
			
		||||
            return True
 | 
			
		||||
        return dict.__eq__(self, other)
 | 
			
		||||
 | 
			
		||||
    def __ne__(self, other):
 | 
			
		||||
        return not self == other
 | 
			
		||||
@@ -22,6 +22,8 @@ class FileSocket(_fileobject):
 | 
			
		||||
 | 
			
		||||
    def read(self, size=4096):
 | 
			
		||||
        """Read data from the socket as if it were a file."""
 | 
			
		||||
        if self._sock is None:
 | 
			
		||||
            return None
 | 
			
		||||
        data = self._sock.recv(size)
 | 
			
		||||
        if data is not None:
 | 
			
		||||
            return data
 | 
			
		||||
 
 | 
			
		||||
@@ -42,8 +42,6 @@ class BaseHandler(object):
 | 
			
		||||
                       this handler.
 | 
			
		||||
            stream  -- The XMLStream instance the handler should monitor.
 | 
			
		||||
        """
 | 
			
		||||
        self.checkDelete = self.check_delete
 | 
			
		||||
 | 
			
		||||
        self.name = name
 | 
			
		||||
        self.stream = stream
 | 
			
		||||
        self._destroy = False
 | 
			
		||||
@@ -87,3 +85,8 @@ class BaseHandler(object):
 | 
			
		||||
        handlers.
 | 
			
		||||
        """
 | 
			
		||||
        return self._destroy
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# To comply with PEP8, method names now use underscores.
 | 
			
		||||
# Deprecated method names are re-mapped for backwards compatibility.
 | 
			
		||||
BaseHandler.checkDelete = BaseHandler.check_delete
 | 
			
		||||
 
 | 
			
		||||
@@ -61,7 +61,8 @@ class Callback(BaseHandler):
 | 
			
		||||
        Arguments:
 | 
			
		||||
            payload -- The matched stanza object.
 | 
			
		||||
        """
 | 
			
		||||
        BaseHandler.prerun(self, payload)
 | 
			
		||||
        if self._once:
 | 
			
		||||
            self._destroy = True
 | 
			
		||||
        if self._instream:
 | 
			
		||||
            self.run(payload, True)
 | 
			
		||||
 | 
			
		||||
@@ -78,7 +79,7 @@ class Callback(BaseHandler):
 | 
			
		||||
                        Defaults to False.
 | 
			
		||||
        """
 | 
			
		||||
        if not self._instream or instream:
 | 
			
		||||
            BaseHandler.run(self, payload)
 | 
			
		||||
            self._pointer(payload)
 | 
			
		||||
            if self._once:
 | 
			
		||||
                self._destroy = True
 | 
			
		||||
                del self._pointer
 | 
			
		||||
 
 | 
			
		||||
@@ -12,7 +12,7 @@ try:
 | 
			
		||||
except ImportError:
 | 
			
		||||
    import Queue as queue
 | 
			
		||||
 | 
			
		||||
from sleekxmpp.xmlstream import StanzaBase, RESPONSE_TIMEOUT
 | 
			
		||||
from sleekxmpp.xmlstream import StanzaBase
 | 
			
		||||
from sleekxmpp.xmlstream.handler.base import BaseHandler
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -69,7 +69,7 @@ class Waiter(BaseHandler):
 | 
			
		||||
        """
 | 
			
		||||
        pass
 | 
			
		||||
 | 
			
		||||
    def wait(self, timeout=RESPONSE_TIMEOUT):
 | 
			
		||||
    def wait(self, timeout=None):
 | 
			
		||||
        """
 | 
			
		||||
        Block an event handler while waiting for a stanza to arrive.
 | 
			
		||||
 | 
			
		||||
@@ -84,6 +84,9 @@ class Waiter(BaseHandler):
 | 
			
		||||
                       arrive. Defaults to the global default timeout
 | 
			
		||||
                       value sleekxmpp.xmlstream.RESPONSE_TIMEOUT.
 | 
			
		||||
        """
 | 
			
		||||
        if timeout is None:
 | 
			
		||||
            timeout = self.stream.response_timeout
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            stanza = self._payload.get(True, timeout)
 | 
			
		||||
        except queue.Empty:
 | 
			
		||||
 
 | 
			
		||||
@@ -6,6 +6,8 @@
 | 
			
		||||
    See the file LICENSE for copying permission.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
from __future__ import unicode_literals
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class JID(object):
 | 
			
		||||
    """
 | 
			
		||||
@@ -42,7 +44,9 @@ class JID(object):
 | 
			
		||||
        Arguments:
 | 
			
		||||
            jid - The new JID value.
 | 
			
		||||
        """
 | 
			
		||||
        self._full = self._jid = str(jid)
 | 
			
		||||
        if isinstance(jid, JID):
 | 
			
		||||
            jid = jid.full
 | 
			
		||||
        self._full = self._jid = jid
 | 
			
		||||
        self._domain = None
 | 
			
		||||
        self._resource = None
 | 
			
		||||
        self._user = None
 | 
			
		||||
@@ -71,7 +75,7 @@ class JID(object):
 | 
			
		||||
            if self._domain is None:
 | 
			
		||||
                self._domain = self._jid.split('@', 1)[-1].split('/', 1)[0]
 | 
			
		||||
            return self._domain or ""
 | 
			
		||||
        elif name == 'full':
 | 
			
		||||
        elif name in ('full', 'jid'):
 | 
			
		||||
            return self._jid or ""
 | 
			
		||||
        elif name == 'bare':
 | 
			
		||||
            if self._bare is None:
 | 
			
		||||
@@ -121,3 +125,13 @@ class JID(object):
 | 
			
		||||
    def __str__(self):
 | 
			
		||||
        """Use the full JID as the string value."""
 | 
			
		||||
        return self.full
 | 
			
		||||
 | 
			
		||||
    def __repr__(self):
 | 
			
		||||
        return self.full
 | 
			
		||||
 | 
			
		||||
    def __eq__(self, other):
 | 
			
		||||
        """
 | 
			
		||||
        Two JIDs are considered equal if they have the same full JID value.
 | 
			
		||||
        """
 | 
			
		||||
        other = JID(other)
 | 
			
		||||
        return self.full == other.full
 | 
			
		||||
 
 | 
			
		||||
@@ -117,7 +117,8 @@ class MatchXMLMask(MatcherBase):
 | 
			
		||||
                return False
 | 
			
		||||
 | 
			
		||||
        # If the mask includes text, compare it.
 | 
			
		||||
        if mask.text and source.text != mask.text:
 | 
			
		||||
        if mask.text and source.text and \
 | 
			
		||||
           source.text.strip() != mask.text.strip():
 | 
			
		||||
            return False
 | 
			
		||||
 | 
			
		||||
        # Compare attributes. The stanza must include the attributes
 | 
			
		||||
@@ -127,10 +128,17 @@ class MatchXMLMask(MatcherBase):
 | 
			
		||||
                return False
 | 
			
		||||
 | 
			
		||||
        # Recursively check subelements.
 | 
			
		||||
        matched_elements = {}
 | 
			
		||||
        for subelement in mask:
 | 
			
		||||
            if use_ns:
 | 
			
		||||
                if not self._mask_cmp(source.find(subelement.tag),
 | 
			
		||||
                                      subelement, use_ns):
 | 
			
		||||
                matched = False
 | 
			
		||||
                for other in source.findall(subelement.tag):
 | 
			
		||||
                    matched_elements[other] = False
 | 
			
		||||
                    if self._mask_cmp(other, subelement, use_ns):
 | 
			
		||||
                        if not matched_elements.get(other, False):
 | 
			
		||||
                            matched_elements[other] = True
 | 
			
		||||
                            matched = True
 | 
			
		||||
                if not matched:
 | 
			
		||||
                    return False
 | 
			
		||||
            else:
 | 
			
		||||
                if not self._mask_cmp(self._get_child(source, subelement.tag),
 | 
			
		||||
 
 | 
			
		||||
@@ -132,6 +132,7 @@ class Scheduler(object):
 | 
			
		||||
        if threaded:
 | 
			
		||||
            self.thread = threading.Thread(name='sheduler_process',
 | 
			
		||||
                                           target=self._process)
 | 
			
		||||
            self.thread.daemon = True
 | 
			
		||||
            self.thread.start()
 | 
			
		||||
        else:
 | 
			
		||||
            self._process()
 | 
			
		||||
@@ -140,7 +141,8 @@ class Scheduler(object):
 | 
			
		||||
        """Process scheduled tasks."""
 | 
			
		||||
        self.run = True
 | 
			
		||||
        try:
 | 
			
		||||
            while self.run and (self.parentstop is None or not self.parentstop.isSet()):
 | 
			
		||||
            while self.run and (self.parentstop is None or \
 | 
			
		||||
                                not self.parentstop.isSet()):
 | 
			
		||||
                    wait = 1
 | 
			
		||||
                    updated = False
 | 
			
		||||
                    if self.schedule:
 | 
			
		||||
@@ -149,6 +151,8 @@ class Scheduler(object):
 | 
			
		||||
                        if wait <= 0.0:
 | 
			
		||||
                            newtask = self.addq.get(False)
 | 
			
		||||
                        else:
 | 
			
		||||
                            if wait >= 3.0:
 | 
			
		||||
                                wait = 3.0
 | 
			
		||||
                            newtask = self.addq.get(True, wait)
 | 
			
		||||
                    except queue.Empty:
 | 
			
		||||
                        cleanup = []
 | 
			
		||||
 
 | 
			
		||||
@@ -14,6 +14,7 @@ from xml.etree import cElementTree as ET
 | 
			
		||||
 | 
			
		||||
from sleekxmpp.xmlstream import JID
 | 
			
		||||
from sleekxmpp.xmlstream.tostring import tostring
 | 
			
		||||
from sleekxmpp.thirdparty import OrderedDict
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
log = logging.getLogger(__name__)
 | 
			
		||||
@@ -23,17 +24,32 @@ log = logging.getLogger(__name__)
 | 
			
		||||
XML_TYPE = type(ET.Element('xml'))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def register_stanza_plugin(stanza, plugin):
 | 
			
		||||
def register_stanza_plugin(stanza, plugin, iterable=False, overrides=False):
 | 
			
		||||
    """
 | 
			
		||||
    Associate a stanza object as a plugin for another stanza.
 | 
			
		||||
 | 
			
		||||
    Arguments:
 | 
			
		||||
        stanza    -- The class of the parent stanza.
 | 
			
		||||
        plugin    -- The class of the plugin stanza.
 | 
			
		||||
        iterable  -- Indicates if the plugin stanza should be
 | 
			
		||||
                     included in the parent stanza's iterable
 | 
			
		||||
                     'substanzas' interface results.
 | 
			
		||||
        overrides -- Indicates if the plugin should be allowed
 | 
			
		||||
                     to override the interface handlers for
 | 
			
		||||
                     the parent stanza.
 | 
			
		||||
    """
 | 
			
		||||
    tag = "{%s}%s" % (plugin.namespace, plugin.name)
 | 
			
		||||
    stanza.plugin_attrib_map[plugin.plugin_attrib] = plugin
 | 
			
		||||
    stanza.plugin_tag_map[tag] = plugin
 | 
			
		||||
    if iterable:
 | 
			
		||||
        # Prevent weird memory reference gotchas.
 | 
			
		||||
        stanza.plugin_iterables = stanza.plugin_iterables.copy()
 | 
			
		||||
        stanza.plugin_iterables.add(plugin)
 | 
			
		||||
    if overrides:
 | 
			
		||||
        # Prevent weird memory reference gotchas.
 | 
			
		||||
        stanza.plugin_overrides = stanza.plugin_overrides.copy()
 | 
			
		||||
        for interface in plugin.overrides:
 | 
			
		||||
            stanza.plugin_overrides[interface] = plugin.plugin_attrib
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# To maintain backwards compatibility for now, preserve the camel case name.
 | 
			
		||||
@@ -95,10 +111,22 @@ class ElementBase(object):
 | 
			
		||||
    >>> message['custom']['useful_thing'] = 'foo'
 | 
			
		||||
 | 
			
		||||
    If a plugin provides an interface that is the same as the plugin's
 | 
			
		||||
    plugin_attrib value, then the plugin's interface may be accessed
 | 
			
		||||
    directly from the parent stanza, as so:
 | 
			
		||||
    plugin_attrib value, then the plugin's interface may be assigned
 | 
			
		||||
    directly from the parent stanza, as shown below, but retrieving
 | 
			
		||||
    information will require all interfaces to be used, as so:
 | 
			
		||||
 | 
			
		||||
    >>> message['custom'] = 'bar' # Same as using message['custom']['custom']
 | 
			
		||||
    >>> message['custom']['custom'] # Must use all interfaces
 | 
			
		||||
    'bar'
 | 
			
		||||
 | 
			
		||||
    If the plugin sets the value is_extension = True, then both setting
 | 
			
		||||
    and getting an interface value that is the same as the plugin's
 | 
			
		||||
    plugin_attrib value will work, as so:
 | 
			
		||||
 | 
			
		||||
    >>> message['custom'] = 'bar'  # Using is_extension=True
 | 
			
		||||
    >>> message['custom']
 | 
			
		||||
    'bar'
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    Class Attributes:
 | 
			
		||||
        name              -- The name of the stanza's main element.
 | 
			
		||||
@@ -108,14 +136,35 @@ class ElementBase(object):
 | 
			
		||||
        sub_interfaces    -- A subset of the set of interfaces which map
 | 
			
		||||
                             to subelements instead of attributes.
 | 
			
		||||
        subitem           -- A set of stanza classes which are allowed to
 | 
			
		||||
                             be added as substanzas.
 | 
			
		||||
                             be added as substanzas. Deprecated version
 | 
			
		||||
                             of plugin_iterables.
 | 
			
		||||
        overrides         -- A list of interfaces prepended with 'get_',
 | 
			
		||||
                             'set_', or 'del_'. If the stanza is registered
 | 
			
		||||
                             as a plugin with overrides=True, then the
 | 
			
		||||
                             parent's interface handlers will be
 | 
			
		||||
                             overridden by the plugin's matching handler.
 | 
			
		||||
        types             -- A set of generic type attribute values.
 | 
			
		||||
        tag               -- The namespaced name of the stanza's root
 | 
			
		||||
                             element. Example: "{foo_ns}bar"
 | 
			
		||||
        plugin_attrib     -- The interface name that the stanza uses to be
 | 
			
		||||
                             accessed as a plugin from another stanza.
 | 
			
		||||
        plugin_attrib_map -- A mapping of plugin attribute names with the
 | 
			
		||||
                             associated plugin stanza classes.
 | 
			
		||||
        plugin_iterables  -- A set of stanza classes which are allowed to
 | 
			
		||||
                             be added as substanzas.
 | 
			
		||||
        plugin_overrides  -- A mapping of interfaces prepended with 'get_',
 | 
			
		||||
                             'set_' or 'del_' to plugin attrib names. Allows
 | 
			
		||||
                             a plugin to override the behaviour of a parent
 | 
			
		||||
                             stanza's interface handlers.
 | 
			
		||||
        plugin_tag_map    -- A mapping of plugin stanza tag names with
 | 
			
		||||
                             the associated plugin stanza classes.
 | 
			
		||||
        is_extension      -- When True, allows the stanza to provide one
 | 
			
		||||
                             additional interface to the parent stanza,
 | 
			
		||||
                             extending the interfaces supported by the
 | 
			
		||||
                             parent. Defaults to False.
 | 
			
		||||
        xml_ns            -- The XML namespace,
 | 
			
		||||
                             http://www.w3.org/XML/1998/namespace,
 | 
			
		||||
                             for use with xml:lang values.
 | 
			
		||||
 | 
			
		||||
    Instance Attributes:
 | 
			
		||||
        xml               -- The stanza's XML contents.
 | 
			
		||||
@@ -125,6 +174,10 @@ class ElementBase(object):
 | 
			
		||||
        values            -- A dictionary of the stanza's interfaces
 | 
			
		||||
                             and interface values, including plugins.
 | 
			
		||||
 | 
			
		||||
    Class Methods
 | 
			
		||||
        tag_name -- Return the namespaced version of the stanza's
 | 
			
		||||
                    root element's name.
 | 
			
		||||
 | 
			
		||||
    Methods:
 | 
			
		||||
        setup              -- Initialize the stanza's XML contents.
 | 
			
		||||
        enable             -- Instantiate a stanza plugin.
 | 
			
		||||
@@ -144,7 +197,7 @@ class ElementBase(object):
 | 
			
		||||
        _get_attr          -- Return an attribute's value from the main
 | 
			
		||||
                              stanza element.
 | 
			
		||||
        _get_sub_text      -- Return the text contents of a subelement.
 | 
			
		||||
        _set_sub_ext       -- Set the text contents of a subelement.
 | 
			
		||||
        _set_sub_text      -- Set the text contents of a subelement.
 | 
			
		||||
        _del_sub           -- Remove a subelement.
 | 
			
		||||
        match              -- Compare the stanza against an XPath expression.
 | 
			
		||||
        find               -- Return subelement matching an XPath expression.
 | 
			
		||||
@@ -157,6 +210,7 @@ class ElementBase(object):
 | 
			
		||||
        appendxml          -- Add XML content to the stanza.
 | 
			
		||||
        pop                -- Remove a substanza.
 | 
			
		||||
        next               -- Return the next iterable substanza.
 | 
			
		||||
        clear              -- Reset the stanza's XML contents.
 | 
			
		||||
        _fix_ns            -- Apply the stanza's namespace to non-namespaced
 | 
			
		||||
                              elements in an XPath expression.
 | 
			
		||||
    """
 | 
			
		||||
@@ -167,9 +221,14 @@ class ElementBase(object):
 | 
			
		||||
    interfaces = set(('type', 'to', 'from', 'id', 'payload'))
 | 
			
		||||
    types = set(('get', 'set', 'error', None, 'unavailable', 'normal', 'chat'))
 | 
			
		||||
    sub_interfaces = tuple()
 | 
			
		||||
    overrides = {}
 | 
			
		||||
    plugin_attrib_map = {}
 | 
			
		||||
    plugin_overrides = {}
 | 
			
		||||
    plugin_iterables = set()
 | 
			
		||||
    plugin_tag_map = {}
 | 
			
		||||
    subitem = None
 | 
			
		||||
    subitem = set()
 | 
			
		||||
    is_extension = False
 | 
			
		||||
    xml_ns = 'http://www.w3.org/XML/1998/namespace'
 | 
			
		||||
 | 
			
		||||
    def __init__(self, xml=None, parent=None):
 | 
			
		||||
        """
 | 
			
		||||
@@ -179,22 +238,11 @@ class ElementBase(object):
 | 
			
		||||
            xml    -- Initialize the stanza with optional existing XML.
 | 
			
		||||
            parent -- Optional stanza object that contains this stanza.
 | 
			
		||||
        """
 | 
			
		||||
        # To comply with PEP8, method names now use underscores.
 | 
			
		||||
        # Deprecated method names are re-mapped for backwards compatibility.
 | 
			
		||||
        self.initPlugin = self.init_plugin
 | 
			
		||||
        self._getAttr = self._get_attr
 | 
			
		||||
        self._setAttr = self._set_attr
 | 
			
		||||
        self._delAttr = self._del_attr
 | 
			
		||||
        self._getSubText = self._get_sub_text
 | 
			
		||||
        self._setSubText = self._set_sub_text
 | 
			
		||||
        self._delSub = self._del_sub
 | 
			
		||||
        self.getStanzaValues = self._get_stanza_values
 | 
			
		||||
        self.setStanzaValues = self._set_stanza_values
 | 
			
		||||
 | 
			
		||||
        self.xml = xml
 | 
			
		||||
        self.plugins = {}
 | 
			
		||||
        self.plugins = OrderedDict()
 | 
			
		||||
        self.iterables = []
 | 
			
		||||
        self._index = 0
 | 
			
		||||
        self.tag = self.tag_name()
 | 
			
		||||
        if parent is None:
 | 
			
		||||
            self.parent = None
 | 
			
		||||
        else:
 | 
			
		||||
@@ -203,6 +251,10 @@ class ElementBase(object):
 | 
			
		||||
        ElementBase.values = property(ElementBase._get_stanza_values,
 | 
			
		||||
                                      ElementBase._set_stanza_values)
 | 
			
		||||
 | 
			
		||||
        if self.subitem is not None:
 | 
			
		||||
            for sub in self.subitem:
 | 
			
		||||
                self.plugin_iterables.add(sub)
 | 
			
		||||
 | 
			
		||||
        if self.setup(xml):
 | 
			
		||||
            # If we generated our own XML, then everything is ready.
 | 
			
		||||
            return
 | 
			
		||||
@@ -212,8 +264,7 @@ class ElementBase(object):
 | 
			
		||||
            if child.tag in self.plugin_tag_map:
 | 
			
		||||
                plugin = self.plugin_tag_map[child.tag]
 | 
			
		||||
                self.plugins[plugin.plugin_attrib] = plugin(child, self)
 | 
			
		||||
            if self.subitem is not None:
 | 
			
		||||
                for sub in self.subitem:
 | 
			
		||||
            for sub in self.plugin_iterables:
 | 
			
		||||
                if child.tag == "{%s}%s" % (sub.namespace, sub.name):
 | 
			
		||||
                    self.iterables.append(sub(child, self))
 | 
			
		||||
                    break
 | 
			
		||||
@@ -283,14 +334,12 @@ class ElementBase(object):
 | 
			
		||||
        for interface in self.interfaces:
 | 
			
		||||
            values[interface] = self[interface]
 | 
			
		||||
        for plugin, stanza in self.plugins.items():
 | 
			
		||||
            values[plugin] = stanza._get_stanza_values()
 | 
			
		||||
            values[plugin] = stanza.values
 | 
			
		||||
        if self.iterables:
 | 
			
		||||
            iterables = []
 | 
			
		||||
            for stanza in self.iterables:
 | 
			
		||||
                iterables.append(stanza._get_stanza_values())
 | 
			
		||||
                iterables[-1].update({
 | 
			
		||||
                    '__childtag__': "{%s}%s" % (stanza.namespace,
 | 
			
		||||
                                                stanza.name)})
 | 
			
		||||
                iterables.append(stanza.values)
 | 
			
		||||
                iterables[-1]['__childtag__'] = stanza.tag
 | 
			
		||||
            values['substanzas'] = iterables
 | 
			
		||||
        return values
 | 
			
		||||
 | 
			
		||||
@@ -305,24 +354,34 @@ class ElementBase(object):
 | 
			
		||||
                      Plugin interfaces may accept a nested dictionary that
 | 
			
		||||
                      will be used recursively.
 | 
			
		||||
        """
 | 
			
		||||
        iterable_interfaces = [p.plugin_attrib for \
 | 
			
		||||
                                    p in self.plugin_iterables]
 | 
			
		||||
 | 
			
		||||
        for interface, value in values.items():
 | 
			
		||||
            if interface == 'substanzas':
 | 
			
		||||
                # Remove existing substanzas
 | 
			
		||||
                for stanza in self.iterables:
 | 
			
		||||
                    self.xml.remove(stanza.xml)
 | 
			
		||||
                self.iterables = []
 | 
			
		||||
 | 
			
		||||
                # Add new substanzas
 | 
			
		||||
                for subdict in value:
 | 
			
		||||
                    if '__childtag__' in subdict:
 | 
			
		||||
                        for subclass in self.subitem:
 | 
			
		||||
                        for subclass in self.plugin_iterables:
 | 
			
		||||
                            child_tag = "{%s}%s" % (subclass.namespace,
 | 
			
		||||
                                                    subclass.name)
 | 
			
		||||
                            if subdict['__childtag__'] == child_tag:
 | 
			
		||||
                                sub = subclass(parent=self)
 | 
			
		||||
                                sub._set_stanza_values(subdict)
 | 
			
		||||
                                sub.values = subdict
 | 
			
		||||
                                self.iterables.append(sub)
 | 
			
		||||
                                break
 | 
			
		||||
            elif interface in self.interfaces:
 | 
			
		||||
                self[interface] = value
 | 
			
		||||
            elif interface in self.plugin_attrib_map:
 | 
			
		||||
                if interface not in iterable_interfaces:
 | 
			
		||||
                    if interface not in self.plugins:
 | 
			
		||||
                        self.init_plugin(interface)
 | 
			
		||||
                self.plugins[interface]._set_stanza_values(value)
 | 
			
		||||
                    self.plugins[interface].values = value
 | 
			
		||||
        return self
 | 
			
		||||
 | 
			
		||||
    def __getitem__(self, attrib):
 | 
			
		||||
@@ -340,12 +399,13 @@ class ElementBase(object):
 | 
			
		||||
        The search order for interface value retrieval for an interface
 | 
			
		||||
        named 'foo' is:
 | 
			
		||||
            1. The list of substanzas.
 | 
			
		||||
            2. The result of calling get_foo.
 | 
			
		||||
            3. The result of calling getFoo.
 | 
			
		||||
            4. The contents of the foo subelement, if foo is a sub interface.
 | 
			
		||||
            5. The value of the foo attribute of the XML object.
 | 
			
		||||
            6. The plugin named 'foo'
 | 
			
		||||
            7. An empty string.
 | 
			
		||||
            2. The result of calling the get_foo override handler.
 | 
			
		||||
            3. The result of calling get_foo.
 | 
			
		||||
            4. The result of calling getFoo.
 | 
			
		||||
            5. The contents of the foo subelement, if foo is a sub interface.
 | 
			
		||||
            6. The value of the foo attribute of the XML object.
 | 
			
		||||
            7. The plugin named 'foo'
 | 
			
		||||
            8. An empty string.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            attrib -- The name of the requested stanza interface.
 | 
			
		||||
@@ -355,6 +415,16 @@ class ElementBase(object):
 | 
			
		||||
        elif attrib in self.interfaces:
 | 
			
		||||
            get_method = "get_%s" % attrib.lower()
 | 
			
		||||
            get_method2 = "get%s" % attrib.title()
 | 
			
		||||
 | 
			
		||||
            if self.plugin_overrides:
 | 
			
		||||
                plugin = self.plugin_overrides.get(get_method, None)
 | 
			
		||||
                if plugin:
 | 
			
		||||
                    if plugin not in self.plugins:
 | 
			
		||||
                        self.init_plugin(plugin)
 | 
			
		||||
                    handler = getattr(self.plugins[plugin], get_method, None)
 | 
			
		||||
                    if handler:
 | 
			
		||||
                        return handler()
 | 
			
		||||
 | 
			
		||||
            if hasattr(self, get_method):
 | 
			
		||||
                return getattr(self, get_method)()
 | 
			
		||||
            elif hasattr(self, get_method2):
 | 
			
		||||
@@ -367,6 +437,8 @@ class ElementBase(object):
 | 
			
		||||
        elif attrib in self.plugin_attrib_map:
 | 
			
		||||
            if attrib not in self.plugins:
 | 
			
		||||
                self.init_plugin(attrib)
 | 
			
		||||
            if self.plugins[attrib].is_extension:
 | 
			
		||||
                return self.plugins[attrib][attrib]
 | 
			
		||||
            return self.plugins[attrib]
 | 
			
		||||
        else:
 | 
			
		||||
            return ''
 | 
			
		||||
@@ -387,13 +459,14 @@ class ElementBase(object):
 | 
			
		||||
        The effect of interface value assignment for an interface
 | 
			
		||||
        named 'foo' will be one of:
 | 
			
		||||
            1. Delete the interface's contents if the value is None.
 | 
			
		||||
            2. Call set_foo, if it exists.
 | 
			
		||||
            3. Call setFoo, if it exists.
 | 
			
		||||
            4. Set the text of a foo element, if foo is in sub_interfaces.
 | 
			
		||||
            5. Set the value of a top level XML attribute name foo.
 | 
			
		||||
            6. Attempt to pass value to a plugin named foo using the plugin's
 | 
			
		||||
            2. Call the set_foo override handler, if it exists.
 | 
			
		||||
            3. Call set_foo, if it exists.
 | 
			
		||||
            4. Call setFoo, if it exists.
 | 
			
		||||
            5. Set the text of a foo element, if foo is in sub_interfaces.
 | 
			
		||||
            6. Set the value of a top level XML attribute name foo.
 | 
			
		||||
            7. Attempt to pass value to a plugin named foo using the plugin's
 | 
			
		||||
               foo interface.
 | 
			
		||||
            7. Do nothing.
 | 
			
		||||
            8. Do nothing.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            attrib -- The name of the stanza interface to modify.
 | 
			
		||||
@@ -403,6 +476,16 @@ class ElementBase(object):
 | 
			
		||||
            if value is not None:
 | 
			
		||||
                set_method = "set_%s" % attrib.lower()
 | 
			
		||||
                set_method2 = "set%s" % attrib.title()
 | 
			
		||||
 | 
			
		||||
                if self.plugin_overrides:
 | 
			
		||||
                    plugin = self.plugin_overrides.get(set_method, None)
 | 
			
		||||
                    if plugin:
 | 
			
		||||
                        if plugin not in self.plugins:
 | 
			
		||||
                            self.init_plugin(plugin)
 | 
			
		||||
                        handler = getattr(self.plugins[plugin], set_method, None)
 | 
			
		||||
                        if handler:
 | 
			
		||||
                            return handler(value)
 | 
			
		||||
 | 
			
		||||
                if hasattr(self, set_method):
 | 
			
		||||
                    getattr(self, set_method)(value,)
 | 
			
		||||
                elif hasattr(self, set_method2):
 | 
			
		||||
@@ -438,12 +521,13 @@ class ElementBase(object):
 | 
			
		||||
 | 
			
		||||
        The effect of deleting a stanza interface value named foo will be
 | 
			
		||||
        one of:
 | 
			
		||||
            1. Call del_foo, if it exists.
 | 
			
		||||
            2. Call delFoo, if it exists.
 | 
			
		||||
            3. Delete foo element, if foo is in sub_interfaces.
 | 
			
		||||
            4. Delete top level XML attribute named foo.
 | 
			
		||||
            5. Remove the foo plugin, if it was loaded.
 | 
			
		||||
            6. Do nothing.
 | 
			
		||||
            1. Call del_foo override handler, if it exists.
 | 
			
		||||
            2. Call del_foo, if it exists.
 | 
			
		||||
            3. Call delFoo, if it exists.
 | 
			
		||||
            4. Delete foo element, if foo is in sub_interfaces.
 | 
			
		||||
            5. Delete top level XML attribute named foo.
 | 
			
		||||
            6. Remove the foo plugin, if it was loaded.
 | 
			
		||||
            7. Do nothing.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            attrib -- The name of the affected stanza interface.
 | 
			
		||||
@@ -451,6 +535,16 @@ class ElementBase(object):
 | 
			
		||||
        if attrib in self.interfaces:
 | 
			
		||||
            del_method = "del_%s" % attrib.lower()
 | 
			
		||||
            del_method2 = "del%s" % attrib.title()
 | 
			
		||||
 | 
			
		||||
            if self.plugin_overrides:
 | 
			
		||||
                plugin = self.plugin_overrides.get(del_method, None)
 | 
			
		||||
                if plugin:
 | 
			
		||||
                    if plugin not in self.plugins:
 | 
			
		||||
                        self.init_plugin(plugin)
 | 
			
		||||
                    handler = getattr(self.plugins[plugin], del_method, None)
 | 
			
		||||
                    if handler:
 | 
			
		||||
                        return handler()
 | 
			
		||||
 | 
			
		||||
            if hasattr(self, del_method):
 | 
			
		||||
                getattr(self, del_method)()
 | 
			
		||||
            elif hasattr(self, del_method2):
 | 
			
		||||
@@ -463,8 +557,13 @@ class ElementBase(object):
 | 
			
		||||
        elif attrib in self.plugin_attrib_map:
 | 
			
		||||
            if attrib in self.plugins:
 | 
			
		||||
                xml = self.plugins[attrib].xml
 | 
			
		||||
                if self.plugins[attrib].is_extension:
 | 
			
		||||
                    del self.plugins[attrib][attrib]
 | 
			
		||||
                del self.plugins[attrib]
 | 
			
		||||
                try:
 | 
			
		||||
                    self.xml.remove(xml)
 | 
			
		||||
                except:
 | 
			
		||||
                    pass
 | 
			
		||||
        return self
 | 
			
		||||
 | 
			
		||||
    def _set_attr(self, name, value):
 | 
			
		||||
@@ -786,6 +885,28 @@ class ElementBase(object):
 | 
			
		||||
        """
 | 
			
		||||
        return self.__next__()
 | 
			
		||||
 | 
			
		||||
    def clear(self):
 | 
			
		||||
        """
 | 
			
		||||
        Remove all XML element contents and plugins.
 | 
			
		||||
 | 
			
		||||
        Any attribute values will be preserved.
 | 
			
		||||
        """
 | 
			
		||||
        for child in self.xml.getchildren():
 | 
			
		||||
            self.xml.remove(child)
 | 
			
		||||
        for plugin in list(self.plugins.keys()):
 | 
			
		||||
            del self.plugins[plugin]
 | 
			
		||||
        return self
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def tag_name(cls):
 | 
			
		||||
        """
 | 
			
		||||
        Return the namespaced name of the stanza's root element.
 | 
			
		||||
 | 
			
		||||
        For example, for the stanza <foo xmlns="bar" />,
 | 
			
		||||
        stanza.tag would return "{bar}foo".
 | 
			
		||||
        """
 | 
			
		||||
        return "{%s}%s" % (cls.namespace, cls.name)
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def attrib(self):
 | 
			
		||||
        """
 | 
			
		||||
@@ -858,13 +979,13 @@ class ElementBase(object):
 | 
			
		||||
            return False
 | 
			
		||||
 | 
			
		||||
        # Check that this stanza is a superset of the other stanza.
 | 
			
		||||
        values = self._get_stanza_values()
 | 
			
		||||
        values = self.values
 | 
			
		||||
        for key in other.keys():
 | 
			
		||||
            if key not in values or values[key] != other[key]:
 | 
			
		||||
                return False
 | 
			
		||||
 | 
			
		||||
        # Check that the other stanza is a superset of this stanza.
 | 
			
		||||
        values = other._get_stanza_values()
 | 
			
		||||
        values = other.values
 | 
			
		||||
        for key in self.keys():
 | 
			
		||||
            if key not in values or values[key] != self[key]:
 | 
			
		||||
                return False
 | 
			
		||||
@@ -934,11 +1055,16 @@ class ElementBase(object):
 | 
			
		||||
        """
 | 
			
		||||
        return self.__class__(xml=copy.deepcopy(self.xml), parent=self.parent)
 | 
			
		||||
 | 
			
		||||
    def __str__(self):
 | 
			
		||||
    def __str__(self, top_level_ns=True):
 | 
			
		||||
        """
 | 
			
		||||
        Return a string serialization of the underlying XML object.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            top_level_ns -- Display the top-most namespace.
 | 
			
		||||
                            Defaults to True.
 | 
			
		||||
        """
 | 
			
		||||
        return tostring(self.xml, xmlns='', stanza_ns=self.namespace)
 | 
			
		||||
        stanza_ns = '' if top_level_ns else self.namespace
 | 
			
		||||
        return tostring(self.xml, xmlns='', stanza_ns=stanza_ns)
 | 
			
		||||
 | 
			
		||||
    def __repr__(self):
 | 
			
		||||
        """
 | 
			
		||||
@@ -968,7 +1094,6 @@ class StanzaBase(ElementBase):
 | 
			
		||||
 | 
			
		||||
    Attributes:
 | 
			
		||||
        stream -- The XMLStream instance that will handle sending this stanza.
 | 
			
		||||
        tag    -- The namespaced version of the stanza's name.
 | 
			
		||||
 | 
			
		||||
    Methods:
 | 
			
		||||
        set_type    -- Set the type of the stanza.
 | 
			
		||||
@@ -979,7 +1104,6 @@ class StanzaBase(ElementBase):
 | 
			
		||||
        get_payload -- Return the stanza's XML contents.
 | 
			
		||||
        set_payload -- Append to the stanza's XML contents.
 | 
			
		||||
        del_payload -- Remove the stanza's XML contents.
 | 
			
		||||
        clear       -- Reset the stanza's XML contents.
 | 
			
		||||
        reply       -- Reset the stanza and modify the 'to' and 'from'
 | 
			
		||||
                       attributes to prepare for sending a reply.
 | 
			
		||||
        error       -- Set the stanza's type to 'error'.
 | 
			
		||||
@@ -1009,17 +1133,6 @@ class StanzaBase(ElementBase):
 | 
			
		||||
            sfrom  -- Optional string or JID object of the sender's JID.
 | 
			
		||||
            sid    -- Optional ID value for the stanza.
 | 
			
		||||
        """
 | 
			
		||||
        # To comply with PEP8, method names now use underscores.
 | 
			
		||||
        # Deprecated method names are re-mapped for backwards compatibility.
 | 
			
		||||
        self.setType = self.set_type
 | 
			
		||||
        self.getTo = self.get_to
 | 
			
		||||
        self.setTo = self.set_to
 | 
			
		||||
        self.getFrom = self.get_from
 | 
			
		||||
        self.setFrom = self.set_from
 | 
			
		||||
        self.getPayload = self.get_payload
 | 
			
		||||
        self.setPayload = self.set_payload
 | 
			
		||||
        self.delPayload = self.del_payload
 | 
			
		||||
 | 
			
		||||
        self.stream = stream
 | 
			
		||||
        if stream is not None:
 | 
			
		||||
            self.namespace = stream.default_ns
 | 
			
		||||
@@ -1094,24 +1207,17 @@ class StanzaBase(ElementBase):
 | 
			
		||||
        self.clear()
 | 
			
		||||
        return self
 | 
			
		||||
 | 
			
		||||
    def clear(self):
 | 
			
		||||
    def reply(self, clear=True):
 | 
			
		||||
        """
 | 
			
		||||
        Remove all XML element contents and plugins.
 | 
			
		||||
 | 
			
		||||
        Any attribute values will be preserved.
 | 
			
		||||
        """
 | 
			
		||||
        for child in self.xml.getchildren():
 | 
			
		||||
            self.xml.remove(child)
 | 
			
		||||
        for plugin in list(self.plugins.keys()):
 | 
			
		||||
            del self.plugins[plugin]
 | 
			
		||||
        return self
 | 
			
		||||
 | 
			
		||||
    def reply(self):
 | 
			
		||||
        """
 | 
			
		||||
        Reset the stanza and swap its 'from' and 'to' attributes to prepare
 | 
			
		||||
        for sending a reply stanza.
 | 
			
		||||
        Swap the 'from' and 'to' attributes to prepare the stanza for
 | 
			
		||||
        sending a reply. If clear=True, then also remove the stanza's
 | 
			
		||||
        contents to make room for the reply content.
 | 
			
		||||
 | 
			
		||||
        For client streams, the 'from' attribute is removed.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            clear -- Indicates if the stanza's contents should be
 | 
			
		||||
                     removed. Defaults to True
 | 
			
		||||
        """
 | 
			
		||||
        # if it's a component, use from
 | 
			
		||||
        if self.stream and hasattr(self.stream, "is_component") and \
 | 
			
		||||
@@ -1120,6 +1226,7 @@ class StanzaBase(ElementBase):
 | 
			
		||||
        else:
 | 
			
		||||
            self['to'] = self['from']
 | 
			
		||||
            del self['from']
 | 
			
		||||
        if clear:
 | 
			
		||||
            self.clear()
 | 
			
		||||
        return self
 | 
			
		||||
 | 
			
		||||
@@ -1146,9 +1253,15 @@ class StanzaBase(ElementBase):
 | 
			
		||||
        log.exception('Error handling {%s}%s stanza' % (self.namespace,
 | 
			
		||||
                                                            self.name))
 | 
			
		||||
 | 
			
		||||
    def send(self):
 | 
			
		||||
        """Queue the stanza to be sent on the XML stream."""
 | 
			
		||||
        self.stream.sendRaw(self.__str__())
 | 
			
		||||
    def send(self, now=False):
 | 
			
		||||
        """
 | 
			
		||||
        Queue the stanza to be sent on the XML stream.
 | 
			
		||||
        Arguments:
 | 
			
		||||
            now -- Indicates if the queue should be skipped and the
 | 
			
		||||
                   stanza sent immediately. Useful for stream
 | 
			
		||||
                   initialization. Defaults to False.
 | 
			
		||||
        """
 | 
			
		||||
        self.stream.send_raw(self.__str__(), now=now)
 | 
			
		||||
 | 
			
		||||
    def __copy__(self):
 | 
			
		||||
        """
 | 
			
		||||
@@ -1158,8 +1271,37 @@ class StanzaBase(ElementBase):
 | 
			
		||||
        return self.__class__(xml=copy.deepcopy(self.xml),
 | 
			
		||||
                              stream=self.stream)
 | 
			
		||||
 | 
			
		||||
    def __str__(self):
 | 
			
		||||
        """Serialize the stanza's XML to a string."""
 | 
			
		||||
    def __str__(self, top_level_ns=False):
 | 
			
		||||
        """
 | 
			
		||||
        Serialize the stanza's XML to a string.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            top_level_ns -- Display the top-most namespace.
 | 
			
		||||
                            Defaults to False.
 | 
			
		||||
        """
 | 
			
		||||
        stanza_ns = '' if top_level_ns else self.namespace
 | 
			
		||||
        return tostring(self.xml, xmlns='',
 | 
			
		||||
                        stanza_ns=self.namespace,
 | 
			
		||||
                        stanza_ns=stanza_ns,
 | 
			
		||||
                        stream=self.stream)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# To comply with PEP8, method names now use underscores.
 | 
			
		||||
# Deprecated method names are re-mapped for backwards compatibility.
 | 
			
		||||
ElementBase.initPlugin = ElementBase.init_plugin
 | 
			
		||||
ElementBase._getAttr = ElementBase._get_attr
 | 
			
		||||
ElementBase._setAttr = ElementBase._set_attr
 | 
			
		||||
ElementBase._delAttr = ElementBase._del_attr
 | 
			
		||||
ElementBase._getSubText = ElementBase._get_sub_text
 | 
			
		||||
ElementBase._setSubText = ElementBase._set_sub_text
 | 
			
		||||
ElementBase._delSub = ElementBase._del_sub
 | 
			
		||||
ElementBase.getStanzaValues = ElementBase._get_stanza_values
 | 
			
		||||
ElementBase.setStanzaValues = ElementBase._set_stanza_values
 | 
			
		||||
 | 
			
		||||
StanzaBase.setType = StanzaBase.set_type
 | 
			
		||||
StanzaBase.getTo = StanzaBase.get_to
 | 
			
		||||
StanzaBase.setTo = StanzaBase.set_to
 | 
			
		||||
StanzaBase.getFrom = StanzaBase.get_from
 | 
			
		||||
StanzaBase.setFrom = StanzaBase.set_from
 | 
			
		||||
StanzaBase.getPayload = StanzaBase.get_payload
 | 
			
		||||
StanzaBase.setPayload = StanzaBase.set_payload
 | 
			
		||||
StanzaBase.delPayload = StanzaBase.del_payload
 | 
			
		||||
 
 | 
			
		||||
@@ -52,9 +52,18 @@ def tostring(xml=None, xmlns='', stanza_ns='', stream=None, outbuffer=''):
 | 
			
		||||
 | 
			
		||||
    # Output escaped attribute values.
 | 
			
		||||
    for attrib, value in xml.attrib.items():
 | 
			
		||||
        if '{' not in attrib:
 | 
			
		||||
        value = xml_escape(value)
 | 
			
		||||
        if '}' not in attrib:
 | 
			
		||||
            output.append(' %s="%s"' % (attrib, value))
 | 
			
		||||
        else:
 | 
			
		||||
            attrib_ns = attrib.split('}')[0][1:]
 | 
			
		||||
            attrib = attrib.split('}')[1]
 | 
			
		||||
            if stream and attrib_ns in stream.namespace_map:
 | 
			
		||||
                mapped_ns = stream.namespace_map[attrib_ns]
 | 
			
		||||
                if mapped_ns:
 | 
			
		||||
                    output.append(' %s:%s="%s"' % (mapped_ns,
 | 
			
		||||
                                                   attrib,
 | 
			
		||||
                                                   value))
 | 
			
		||||
 | 
			
		||||
    if len(xml) or xml.text:
 | 
			
		||||
        # If there are additional child elements to serialize.
 | 
			
		||||
 
 | 
			
		||||
@@ -55,9 +55,18 @@ def tostring(xml=None, xmlns='', stanza_ns='', stream=None, outbuffer=''):
 | 
			
		||||
 | 
			
		||||
    # Output escaped attribute values.
 | 
			
		||||
    for attrib, value in xml.attrib.items():
 | 
			
		||||
        if '{' not in attrib:
 | 
			
		||||
        value = xml_escape(value)
 | 
			
		||||
            output.append(u' %s="%s"' % (attrib, value))
 | 
			
		||||
        if '}' not in attrib:
 | 
			
		||||
            output.append(' %s="%s"' % (attrib, value))
 | 
			
		||||
        else:
 | 
			
		||||
            attrib_ns = attrib.split('}')[0][1:]
 | 
			
		||||
            attrib = attrib.split('}')[1]
 | 
			
		||||
            if stream and attrib_ns in stream.namespace_map:
 | 
			
		||||
                mapped_ns = stream.namespace_map[attrib_ns]
 | 
			
		||||
                if mapped_ns:
 | 
			
		||||
                    output.append(' %s:%s="%s"' % (mapped_ns,
 | 
			
		||||
                                                   attrib,
 | 
			
		||||
                                                   value))
 | 
			
		||||
 | 
			
		||||
    if len(xml) or xml.text:
 | 
			
		||||
        # If there are additional child elements to serialize.
 | 
			
		||||
 
 | 
			
		||||
@@ -10,13 +10,14 @@ from __future__ import with_statement, unicode_literals
 | 
			
		||||
 | 
			
		||||
import copy
 | 
			
		||||
import logging
 | 
			
		||||
import signal
 | 
			
		||||
import socket as Socket
 | 
			
		||||
import ssl
 | 
			
		||||
import sys
 | 
			
		||||
import threading
 | 
			
		||||
import time
 | 
			
		||||
import types
 | 
			
		||||
import signal
 | 
			
		||||
import random
 | 
			
		||||
try:
 | 
			
		||||
    import queue
 | 
			
		||||
except ImportError:
 | 
			
		||||
@@ -25,6 +26,8 @@ except ImportError:
 | 
			
		||||
from sleekxmpp.thirdparty.statemachine import StateMachine
 | 
			
		||||
from sleekxmpp.xmlstream import Scheduler, tostring
 | 
			
		||||
from sleekxmpp.xmlstream.stanzabase import StanzaBase, ET
 | 
			
		||||
from sleekxmpp.xmlstream.handler import Waiter, XMLCallback
 | 
			
		||||
from sleekxmpp.xmlstream.matcher import MatchXMLMask
 | 
			
		||||
 | 
			
		||||
# In Python 2.x, file socket objects are broken. A patched socket
 | 
			
		||||
# wrapper is provided for this case in filesocket.py.
 | 
			
		||||
@@ -43,6 +46,9 @@ HANDLER_THREADS = 1
 | 
			
		||||
# Flag indicating if the SSL library is available for use.
 | 
			
		||||
SSL_SUPPORT = True
 | 
			
		||||
 | 
			
		||||
# Maximum time to delay between connection attempts is one hour.
 | 
			
		||||
RECONNECT_MAX_DELAY = 3600
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
log = logging.getLogger(__name__)
 | 
			
		||||
 | 
			
		||||
@@ -92,6 +98,8 @@ class XMLStream(object):
 | 
			
		||||
        ssl_support   -- Indicates if a SSL library is available for use.
 | 
			
		||||
        ssl_version   -- The version of the SSL protocol to use.
 | 
			
		||||
                         Defaults to ssl.PROTOCOL_TLSv1.
 | 
			
		||||
        ca_certs      -- File path to a CA certificate to verify the
 | 
			
		||||
                         server's identity.
 | 
			
		||||
        state         -- A state machine for managing the stream's
 | 
			
		||||
                         connection state.
 | 
			
		||||
        stream_footer -- The start tag and any attributes for the stream's
 | 
			
		||||
@@ -100,7 +108,11 @@ class XMLStream(object):
 | 
			
		||||
        use_ssl       -- Flag indicating if SSL should be used.
 | 
			
		||||
        use_tls       -- Flag indicating if TLS should be used.
 | 
			
		||||
        stop          -- threading Event used to stop all threads.
 | 
			
		||||
 | 
			
		||||
        auto_reconnect      -- Flag to determine whether we auto reconnect.
 | 
			
		||||
        reconnect_max_delay -- Maximum time to delay between connection
 | 
			
		||||
                               attempts. Defaults to RECONNECT_MAX_DELAY,
 | 
			
		||||
                               which is one hour.
 | 
			
		||||
 | 
			
		||||
    Methods:
 | 
			
		||||
        add_event_handler    -- Add a handler for a custom event.
 | 
			
		||||
@@ -146,21 +158,13 @@ class XMLStream(object):
 | 
			
		||||
            port   -- The port to use for the connection.
 | 
			
		||||
                      Defaults to 0.
 | 
			
		||||
        """
 | 
			
		||||
        # To comply with PEP8, method names now use underscores.
 | 
			
		||||
        # Deprecated method names are re-mapped for backwards compatibility.
 | 
			
		||||
        self.startTLS = self.start_tls
 | 
			
		||||
        self.registerStanza = self.register_stanza
 | 
			
		||||
        self.removeStanza = self.remove_stanza
 | 
			
		||||
        self.registerHandler = self.register_handler
 | 
			
		||||
        self.removeHandler = self.remove_handler
 | 
			
		||||
        self.setSocket = self.set_socket
 | 
			
		||||
        self.sendRaw = self.send_raw
 | 
			
		||||
        self.getId = self.get_id
 | 
			
		||||
        self.getNewId = self.new_id
 | 
			
		||||
        self.sendXML = self.send_xml
 | 
			
		||||
 | 
			
		||||
        self.ssl_support = SSL_SUPPORT
 | 
			
		||||
        self.ssl_version = ssl.PROTOCOL_TLSv1
 | 
			
		||||
        self.ca_certs = None
 | 
			
		||||
 | 
			
		||||
        self.response_timeout = RESPONSE_TIMEOUT
 | 
			
		||||
        self.reconnect_delay = None
 | 
			
		||||
        self.reconnect_max_delay = RECONNECT_MAX_DELAY
 | 
			
		||||
 | 
			
		||||
        self.state = StateMachine(('disconnected', 'connected'))
 | 
			
		||||
        self.state._set_state('disconnected')
 | 
			
		||||
@@ -184,11 +188,14 @@ class XMLStream(object):
 | 
			
		||||
        self.stop = threading.Event()
 | 
			
		||||
        self.stream_end_event = threading.Event()
 | 
			
		||||
        self.stream_end_event.set()
 | 
			
		||||
        self.session_started_event = threading.Event()
 | 
			
		||||
 | 
			
		||||
        self.event_queue = queue.Queue()
 | 
			
		||||
        self.send_queue = queue.Queue()
 | 
			
		||||
        self.__failed_send_stanza = None
 | 
			
		||||
        self.scheduler = Scheduler(self.event_queue, self.stop)
 | 
			
		||||
 | 
			
		||||
        self.namespace_map = {}
 | 
			
		||||
        self.namespace_map = {StanzaBase.xml_ns: 'xml'}
 | 
			
		||||
 | 
			
		||||
        self.__thread = {}
 | 
			
		||||
        self.__root_stanza = []
 | 
			
		||||
@@ -202,24 +209,53 @@ class XMLStream(object):
 | 
			
		||||
        self.auto_reconnect = True
 | 
			
		||||
        self.is_client = False
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            if hasattr(signal, 'SIGHUP'):
 | 
			
		||||
                signal.signal(signal.SIGHUP, self._handle_kill)
 | 
			
		||||
            if hasattr(signal, 'SIGTERM'):
 | 
			
		||||
                # Used in Windows
 | 
			
		||||
                signal.signal(signal.SIGTERM, self._handle_kill)
 | 
			
		||||
        except:
 | 
			
		||||
            log.debug("Can not set interrupt signal handlers. " + \
 | 
			
		||||
                          "SleekXMPP is not running from a main thread.")
 | 
			
		||||
    def use_signals(self, signals=None):
 | 
			
		||||
        """
 | 
			
		||||
        Register signal handlers for SIGHUP and SIGTERM, if possible,
 | 
			
		||||
        which will raise a "killed" event when the application is
 | 
			
		||||
        terminated.
 | 
			
		||||
 | 
			
		||||
    def _handle_kill(self, signum, frame):
 | 
			
		||||
        If a signal handler already existed, it will be executed first,
 | 
			
		||||
        before the "killed" event is raised.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            signals -- A list of signal names to be monitored.
 | 
			
		||||
                       Defaults to ['SIGHUP', 'SIGTERM'].
 | 
			
		||||
        """
 | 
			
		||||
        if signals is None:
 | 
			
		||||
            signals = ['SIGHUP', 'SIGTERM']
 | 
			
		||||
 | 
			
		||||
        existing_handlers = {}
 | 
			
		||||
        for sig_name in signals:
 | 
			
		||||
            if hasattr(signal, sig_name):
 | 
			
		||||
                sig = getattr(signal, sig_name)
 | 
			
		||||
                handler = signal.getsignal(sig)
 | 
			
		||||
                if handler:
 | 
			
		||||
                    existing_handlers[sig] = handler
 | 
			
		||||
 | 
			
		||||
        def handle_kill(signum, frame):
 | 
			
		||||
            """
 | 
			
		||||
            Capture kill event and disconnect cleanly after first
 | 
			
		||||
            spawning the "killed" event.
 | 
			
		||||
            """
 | 
			
		||||
 | 
			
		||||
            if signum in existing_handlers and \
 | 
			
		||||
                   existing_handlers[signum] != handle_kill:
 | 
			
		||||
                existing_handlers[signum](signum, frame)
 | 
			
		||||
 | 
			
		||||
            self.event("killed", direct=True)
 | 
			
		||||
            self.disconnect()
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            for sig_name in signals:
 | 
			
		||||
                if hasattr(signal, sig_name):
 | 
			
		||||
                    sig = getattr(signal, sig_name)
 | 
			
		||||
                    signal.signal(sig, handle_kill)
 | 
			
		||||
            self.__signals_installed = True
 | 
			
		||||
        except:
 | 
			
		||||
            log.debug("Can not set interrupt signal handlers. " + \
 | 
			
		||||
                      "SleekXMPP is not running from a main thread.")
 | 
			
		||||
 | 
			
		||||
    def new_id(self):
 | 
			
		||||
        """
 | 
			
		||||
        Generate and return a new stream ID in hexadecimal form.
 | 
			
		||||
@@ -277,9 +313,26 @@ class XMLStream(object):
 | 
			
		||||
        self.stop.clear()
 | 
			
		||||
        self.socket = self.socket_class(Socket.AF_INET, Socket.SOCK_STREAM)
 | 
			
		||||
        self.socket.settimeout(None)
 | 
			
		||||
 | 
			
		||||
        if self.reconnect_delay is None:
 | 
			
		||||
            delay = 1.0
 | 
			
		||||
        else:
 | 
			
		||||
            delay = min(self.reconnect_delay * 2, self.reconnect_max_delay)
 | 
			
		||||
            delay = random.normalvariate(delay, delay * 0.1)
 | 
			
		||||
            log.debug('Waiting %s seconds before connecting.' % delay)
 | 
			
		||||
            time.sleep(delay)
 | 
			
		||||
 | 
			
		||||
        if self.use_ssl and self.ssl_support:
 | 
			
		||||
            log.debug("Socket Wrapped for SSL")
 | 
			
		||||
            ssl_socket = ssl.wrap_socket(self.socket)
 | 
			
		||||
            if self.ca_certs is None:
 | 
			
		||||
                cert_policy = ssl.CERT_NONE
 | 
			
		||||
            else:
 | 
			
		||||
                cert_policy = ssl.CERT_REQUIRED
 | 
			
		||||
 | 
			
		||||
            ssl_socket = ssl.wrap_socket(self.socket,
 | 
			
		||||
                                         ca_certs=self.ca_certs,
 | 
			
		||||
                                         cert_reqs=cert_policy)
 | 
			
		||||
 | 
			
		||||
            if hasattr(self.socket, 'socket'):
 | 
			
		||||
                # We are using a testing socket, so preserve the top
 | 
			
		||||
                # layer of wrapping.
 | 
			
		||||
@@ -293,12 +346,14 @@ class XMLStream(object):
 | 
			
		||||
            self.set_socket(self.socket, ignore=True)
 | 
			
		||||
            #this event is where you should set your application state
 | 
			
		||||
            self.event("connected", direct=True)
 | 
			
		||||
            self.reconnect_delay = 1.0
 | 
			
		||||
            return True
 | 
			
		||||
        except Socket.error as serr:
 | 
			
		||||
            error_msg = "Could not connect to %s:%s. Socket Error #%s: %s"
 | 
			
		||||
            self.event('socket_error', serr)
 | 
			
		||||
            log.error(error_msg % (self.address[0], self.address[1],
 | 
			
		||||
                                       serr.errno, serr.strerror))
 | 
			
		||||
            time.sleep(1)
 | 
			
		||||
            self.reconnect_delay = delay
 | 
			
		||||
            return False
 | 
			
		||||
 | 
			
		||||
    def disconnect(self, reconnect=False):
 | 
			
		||||
@@ -318,11 +373,11 @@ class XMLStream(object):
 | 
			
		||||
 | 
			
		||||
    def _disconnect(self, reconnect=False):
 | 
			
		||||
        # Send the end of stream marker.
 | 
			
		||||
        self.send_raw(self.stream_footer)
 | 
			
		||||
        self.send_raw(self.stream_footer, now=True)
 | 
			
		||||
        self.session_started_event.clear()
 | 
			
		||||
        # Wait for confirmation that the stream was
 | 
			
		||||
        # closed in the other direction.
 | 
			
		||||
        if not reconnect:
 | 
			
		||||
            self.auto_reconnect = False
 | 
			
		||||
        self.auto_reconnect = reconnect
 | 
			
		||||
        self.stream_end_event.wait(4)
 | 
			
		||||
        if not self.auto_reconnect:
 | 
			
		||||
            self.stop.set()
 | 
			
		||||
@@ -331,9 +386,10 @@ class XMLStream(object):
 | 
			
		||||
            self.filesocket.close()
 | 
			
		||||
            self.socket.shutdown(Socket.SHUT_RDWR)
 | 
			
		||||
        except Socket.error as serr:
 | 
			
		||||
            pass
 | 
			
		||||
            self.event('socket_error', serr)
 | 
			
		||||
        finally:
 | 
			
		||||
            #clear your application state
 | 
			
		||||
            self.event('session_end', direct=True)
 | 
			
		||||
            self.event("disconnected", direct=True)
 | 
			
		||||
            return True
 | 
			
		||||
 | 
			
		||||
@@ -383,9 +439,17 @@ class XMLStream(object):
 | 
			
		||||
        if self.ssl_support:
 | 
			
		||||
            log.info("Negotiating TLS")
 | 
			
		||||
            log.info("Using SSL version: %s" % str(self.ssl_version))
 | 
			
		||||
            if self.ca_certs is None:
 | 
			
		||||
                cert_policy = ssl.CERT_NONE
 | 
			
		||||
            else:
 | 
			
		||||
                cert_policy = ssl.CERT_REQUIRED
 | 
			
		||||
 | 
			
		||||
            ssl_socket = ssl.wrap_socket(self.socket,
 | 
			
		||||
                                         ssl_version=self.ssl_version,
 | 
			
		||||
                                         do_handshake_on_connect=False)
 | 
			
		||||
                                         do_handshake_on_connect=False,
 | 
			
		||||
                                         ca_certs=self.ca_certs,
 | 
			
		||||
                                         cert_reqs=cert_policy)
 | 
			
		||||
 | 
			
		||||
            if hasattr(self.socket, 'socket'):
 | 
			
		||||
                # We are using a testing socket, so preserve the top
 | 
			
		||||
                # layer of wrapping.
 | 
			
		||||
@@ -458,8 +522,6 @@ class XMLStream(object):
 | 
			
		||||
        """
 | 
			
		||||
        # To prevent circular dependencies, we must load the matcher
 | 
			
		||||
        # and handler classes here.
 | 
			
		||||
        from sleekxmpp.xmlstream.matcher import MatchXMLMask
 | 
			
		||||
        from sleekxmpp.xmlstream.handler import XMLCallback
 | 
			
		||||
 | 
			
		||||
        if name is None:
 | 
			
		||||
            name = 'add_handler_%s' % self.getNewId()
 | 
			
		||||
@@ -606,7 +668,7 @@ class XMLStream(object):
 | 
			
		||||
        """
 | 
			
		||||
        return xml
 | 
			
		||||
 | 
			
		||||
    def send(self, data, mask=None, timeout=RESPONSE_TIMEOUT):
 | 
			
		||||
    def send(self, data, mask=None, timeout=None, now=False):
 | 
			
		||||
        """
 | 
			
		||||
        A wrapper for send_raw for sending stanza objects.
 | 
			
		||||
 | 
			
		||||
@@ -620,7 +682,13 @@ class XMLStream(object):
 | 
			
		||||
                       or a timeout occurs.
 | 
			
		||||
            timeout -- Time in seconds to wait for a response before
 | 
			
		||||
                       continuing. Defaults to RESPONSE_TIMEOUT.
 | 
			
		||||
            now     -- Indicates if the send queue should be skipped,
 | 
			
		||||
                       sending the stanza immediately. Useful mainly
 | 
			
		||||
                       for stream initialization stanzas.
 | 
			
		||||
                       Defaults to False.
 | 
			
		||||
        """
 | 
			
		||||
        if timeout is None:
 | 
			
		||||
            timeout = self.response_timeout
 | 
			
		||||
        if hasattr(mask, 'xml'):
 | 
			
		||||
            mask = mask.xml
 | 
			
		||||
        data = str(data)
 | 
			
		||||
@@ -629,21 +697,11 @@ class XMLStream(object):
 | 
			
		||||
            wait_for = Waiter("SendWait_%s" % self.new_id(),
 | 
			
		||||
                              MatchXMLMask(mask))
 | 
			
		||||
            self.register_handler(wait_for)
 | 
			
		||||
        self.send_raw(data)
 | 
			
		||||
        self.send_raw(data, now)
 | 
			
		||||
        if mask is not None:
 | 
			
		||||
            return wait_for.wait(timeout)
 | 
			
		||||
 | 
			
		||||
    def send_raw(self, data):
 | 
			
		||||
        """
 | 
			
		||||
        Send raw data across the stream.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            data -- Any string value.
 | 
			
		||||
        """
 | 
			
		||||
        self.send_queue.put(data)
 | 
			
		||||
        return True
 | 
			
		||||
 | 
			
		||||
    def send_xml(self, data, mask=None, timeout=RESPONSE_TIMEOUT):
 | 
			
		||||
    def send_xml(self, data, mask=None, timeout=None, now=False):
 | 
			
		||||
        """
 | 
			
		||||
        Send an XML object on the stream, and optionally wait
 | 
			
		||||
        for a response.
 | 
			
		||||
@@ -656,8 +714,39 @@ class XMLStream(object):
 | 
			
		||||
                       or a timeout occurs.
 | 
			
		||||
            timeout -- Time in seconds to wait for a response before
 | 
			
		||||
                       continuing. Defaults to RESPONSE_TIMEOUT.
 | 
			
		||||
            now     -- Indicates if the send queue should be skipped,
 | 
			
		||||
                       sending the stanza immediately. Useful mainly
 | 
			
		||||
                       for stream initialization stanzas.
 | 
			
		||||
                       Defaults to False.
 | 
			
		||||
        """
 | 
			
		||||
        return self.send(tostring(data), mask, timeout)
 | 
			
		||||
        if timeout is None:
 | 
			
		||||
            timeout = self.response_timeout
 | 
			
		||||
        return self.send(tostring(data), mask, timeout, now)
 | 
			
		||||
 | 
			
		||||
    def send_raw(self, data, now=False, reconnect=None):
 | 
			
		||||
        """
 | 
			
		||||
        Send raw data across the stream.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            data      -- Any string value.
 | 
			
		||||
            reconnect -- Indicates if the stream should be
 | 
			
		||||
                         restarted if there is an error sending
 | 
			
		||||
                         the stanza. Used mainly for testing.
 | 
			
		||||
                         Defaults to self.auto_reconnect.
 | 
			
		||||
        """
 | 
			
		||||
        if now:
 | 
			
		||||
            log.debug("SEND (IMMED): %s" % data)
 | 
			
		||||
            try:
 | 
			
		||||
                self.socket.send(data.encode('utf-8'))
 | 
			
		||||
            except Socket.error as serr:
 | 
			
		||||
                self.event('socket_error', serr)
 | 
			
		||||
                log.warning("Failed to send %s" % data)
 | 
			
		||||
                if reconnect is None:
 | 
			
		||||
                    reconnect = self.auto_reconnect
 | 
			
		||||
                self.disconnect(reconnect)
 | 
			
		||||
        else:
 | 
			
		||||
            self.send_queue.put(data)
 | 
			
		||||
        return True
 | 
			
		||||
 | 
			
		||||
    def process(self, threaded=True):
 | 
			
		||||
        """
 | 
			
		||||
@@ -675,10 +764,12 @@ class XMLStream(object):
 | 
			
		||||
                        Event handlers and the send queue will be threaded
 | 
			
		||||
                        regardless of this parameter's value.
 | 
			
		||||
        """
 | 
			
		||||
        self._thread_excepthook()
 | 
			
		||||
        self.scheduler.process(threaded=True)
 | 
			
		||||
 | 
			
		||||
        def start_thread(name, target):
 | 
			
		||||
            self.__thread[name] = threading.Thread(name=name, target=target)
 | 
			
		||||
            self.__thread[name].daemon = True
 | 
			
		||||
            self.__thread[name].start()
 | 
			
		||||
 | 
			
		||||
        for t in range(0, HANDLER_THREADS):
 | 
			
		||||
@@ -709,7 +800,7 @@ class XMLStream(object):
 | 
			
		||||
            firstrun = False
 | 
			
		||||
            try:
 | 
			
		||||
                if self.is_client:
 | 
			
		||||
                    self.send_raw(self.stream_header)
 | 
			
		||||
                    self.send_raw(self.stream_header, now=True)
 | 
			
		||||
                # The call to self.__read_xml will block and prevent
 | 
			
		||||
                # the body of the loop from running until a disconnect
 | 
			
		||||
                # occurs. After any reconnection, the stream header will
 | 
			
		||||
@@ -718,14 +809,15 @@ class XMLStream(object):
 | 
			
		||||
                    # Ensure the stream header is sent for any
 | 
			
		||||
                    # new connections.
 | 
			
		||||
                    if self.is_client:
 | 
			
		||||
                        self.send_raw(self.stream_header)
 | 
			
		||||
                        self.send_raw(self.stream_header, now=True)
 | 
			
		||||
            except KeyboardInterrupt:
 | 
			
		||||
                log.debug("Keyboard Escape Detected in _process")
 | 
			
		||||
                self.stop.set()
 | 
			
		||||
            except SystemExit:
 | 
			
		||||
                log.debug("SystemExit in _process")
 | 
			
		||||
                self.stop.set()
 | 
			
		||||
            except Socket.error:
 | 
			
		||||
            except Socket.error as serr:
 | 
			
		||||
                self.event('socket_error', serr)
 | 
			
		||||
                log.exception('Socket Error')
 | 
			
		||||
            except:
 | 
			
		||||
                if not self.stop.isSet():
 | 
			
		||||
@@ -733,6 +825,7 @@ class XMLStream(object):
 | 
			
		||||
            if not self.stop.isSet() and self.auto_reconnect:
 | 
			
		||||
                self.reconnect()
 | 
			
		||||
            else:
 | 
			
		||||
                self.event('killed', direct=True)
 | 
			
		||||
                self.disconnect()
 | 
			
		||||
                self.event_queue.put(('quit', None, None))
 | 
			
		||||
        self.scheduler.run = False
 | 
			
		||||
@@ -744,7 +837,9 @@ class XMLStream(object):
 | 
			
		||||
        """
 | 
			
		||||
        depth = 0
 | 
			
		||||
        root = None
 | 
			
		||||
        for (event, xml) in ET.iterparse(self.filesocket, (b'end', b'start')):
 | 
			
		||||
        try:
 | 
			
		||||
            for (event, xml) in ET.iterparse(self.filesocket,
 | 
			
		||||
                                             (b'end', b'start')):
 | 
			
		||||
                if event == b'start':
 | 
			
		||||
                    if depth == 0:
 | 
			
		||||
                        # We have received the start of the root element.
 | 
			
		||||
@@ -773,6 +868,8 @@ class XMLStream(object):
 | 
			
		||||
                            # Keep the root element empty of children to
 | 
			
		||||
                            # save on memory use.
 | 
			
		||||
                            root.clear()
 | 
			
		||||
        except SyntaxError:
 | 
			
		||||
            log.error("Error reading from XML stream.")
 | 
			
		||||
        log.debug("Ending read XML loop")
 | 
			
		||||
 | 
			
		||||
    def _build_stanza(self, xml, default_ns=None):
 | 
			
		||||
@@ -791,7 +888,8 @@ class XMLStream(object):
 | 
			
		||||
            default_ns = self.default_ns
 | 
			
		||||
        stanza_type = StanzaBase
 | 
			
		||||
        for stanza_class in self.__root_stanza:
 | 
			
		||||
            if xml.tag == "{%s}%s" % (default_ns, stanza_class.name):
 | 
			
		||||
            if xml.tag == "{%s}%s" % (default_ns, stanza_class.name) or \
 | 
			
		||||
               xml.tag == stanza_class.tag_name():
 | 
			
		||||
                stanza_type = stanza_class
 | 
			
		||||
                break
 | 
			
		||||
        stanza = stanza_type(self, xml)
 | 
			
		||||
@@ -814,12 +912,7 @@ class XMLStream(object):
 | 
			
		||||
 | 
			
		||||
        # Convert the raw XML object into a stanza object. If no registered
 | 
			
		||||
        # stanza type applies, a generic StanzaBase stanza will be used.
 | 
			
		||||
        stanza_type = StanzaBase
 | 
			
		||||
        for stanza_class in self.__root_stanza:
 | 
			
		||||
            if xml.tag == "{%s}%s" % (self.default_ns, stanza_class.name):
 | 
			
		||||
                stanza_type = stanza_class
 | 
			
		||||
                break
 | 
			
		||||
        stanza = stanza_type(self, xml)
 | 
			
		||||
        stanza = self._build_stanza(xml)
 | 
			
		||||
 | 
			
		||||
        # Match the stanza against registered handlers. Handlers marked
 | 
			
		||||
        # to run "in stream" will be executed immediately; the rest will
 | 
			
		||||
@@ -827,12 +920,12 @@ class XMLStream(object):
 | 
			
		||||
        unhandled = True
 | 
			
		||||
        for handler in self.__handlers:
 | 
			
		||||
            if handler.match(stanza):
 | 
			
		||||
                stanza_copy = stanza_type(self, copy.deepcopy(xml))
 | 
			
		||||
                stanza_copy = copy.copy(stanza)
 | 
			
		||||
                handler.prerun(stanza_copy)
 | 
			
		||||
                self.event_queue.put(('stanza', handler, stanza_copy))
 | 
			
		||||
                try:
 | 
			
		||||
                    if handler.check_delete():
 | 
			
		||||
                        self.__handlers.pop(self.__handlers.index(handler))
 | 
			
		||||
                        self.__handlers.remove(handler)
 | 
			
		||||
                except:
 | 
			
		||||
                    pass  # not thread safe
 | 
			
		||||
                unhandled = False
 | 
			
		||||
@@ -851,13 +944,14 @@ class XMLStream(object):
 | 
			
		||||
            func -- The event handler to execute.
 | 
			
		||||
            args -- Arguments to the event handler.
 | 
			
		||||
        """
 | 
			
		||||
        orig = copy.copy(args[0])
 | 
			
		||||
        try:
 | 
			
		||||
            func(*args)
 | 
			
		||||
        except Exception as e:
 | 
			
		||||
            error_msg = 'Error processing event handler: %s'
 | 
			
		||||
            log.exception(error_msg % str(func))
 | 
			
		||||
            if hasattr(args[0], 'exception'):
 | 
			
		||||
                args[0].exception(e)
 | 
			
		||||
            if hasattr(orig, 'exception'):
 | 
			
		||||
                orig.exception(e)
 | 
			
		||||
 | 
			
		||||
    def _event_runner(self):
 | 
			
		||||
        """
 | 
			
		||||
@@ -880,6 +974,7 @@ class XMLStream(object):
 | 
			
		||||
 | 
			
		||||
                etype, handler = event[0:2]
 | 
			
		||||
                args = event[2:]
 | 
			
		||||
                orig = copy.copy(args[0])
 | 
			
		||||
 | 
			
		||||
                if etype == 'stanza':
 | 
			
		||||
                    try:
 | 
			
		||||
@@ -887,15 +982,16 @@ class XMLStream(object):
 | 
			
		||||
                    except Exception as e:
 | 
			
		||||
                        error_msg = 'Error processing stream handler: %s'
 | 
			
		||||
                        log.exception(error_msg % handler.name)
 | 
			
		||||
                        args[0].exception(e)
 | 
			
		||||
                        orig.exception(e)
 | 
			
		||||
                elif etype == 'schedule':
 | 
			
		||||
                    try:
 | 
			
		||||
                        log.debug(args)
 | 
			
		||||
                        log.debug('Scheduled event: %s' % args)
 | 
			
		||||
                        handler(*args[0])
 | 
			
		||||
                    except:
 | 
			
		||||
                        log.exception('Error processing scheduled task')
 | 
			
		||||
                elif etype == 'event':
 | 
			
		||||
                    func, threaded, disposable = handler
 | 
			
		||||
                    orig = copy.copy(args[0])
 | 
			
		||||
                    try:
 | 
			
		||||
                        if threaded:
 | 
			
		||||
                            x = threading.Thread(
 | 
			
		||||
@@ -908,13 +1004,14 @@ class XMLStream(object):
 | 
			
		||||
                    except Exception as e:
 | 
			
		||||
                        error_msg = 'Error processing event handler: %s'
 | 
			
		||||
                        log.exception(error_msg % str(func))
 | 
			
		||||
                        if hasattr(args[0], 'exception'):
 | 
			
		||||
                            args[0].exception(e)
 | 
			
		||||
                        if hasattr(orig, 'exception'):
 | 
			
		||||
                            orig.exception(e)
 | 
			
		||||
                elif etype == 'quit':
 | 
			
		||||
                    log.debug("Quitting event runner thread")
 | 
			
		||||
                    return False
 | 
			
		||||
        except KeyboardInterrupt:
 | 
			
		||||
            log.debug("Keyboard Escape Detected in _event_runner")
 | 
			
		||||
            self.event('killed', direct=True)
 | 
			
		||||
            self.disconnect()
 | 
			
		||||
            return
 | 
			
		||||
        except SystemExit:
 | 
			
		||||
@@ -928,6 +1025,11 @@ class XMLStream(object):
 | 
			
		||||
        """
 | 
			
		||||
        try:
 | 
			
		||||
            while not self.stop.isSet():
 | 
			
		||||
                self.session_started_event.wait()
 | 
			
		||||
                if self.__failed_send_stanza is not None:
 | 
			
		||||
                    data = self.__failed_send_stanza
 | 
			
		||||
                    self.__failed_send_stanza = None
 | 
			
		||||
                else:
 | 
			
		||||
                    try:
 | 
			
		||||
                        data = self.send_queue.get(True, 1)
 | 
			
		||||
                    except queue.Empty:
 | 
			
		||||
@@ -935,14 +1037,56 @@ class XMLStream(object):
 | 
			
		||||
                log.debug("SEND: %s" % data)
 | 
			
		||||
                try:
 | 
			
		||||
                    self.socket.send(data.encode('utf-8'))
 | 
			
		||||
                except:
 | 
			
		||||
                except Socket.error as serr:
 | 
			
		||||
                    self.event('socket_error', serr)
 | 
			
		||||
                    log.warning("Failed to send %s" % data)
 | 
			
		||||
                    self.__failed_send_stanza = data
 | 
			
		||||
                    self.disconnect(self.auto_reconnect)
 | 
			
		||||
        except KeyboardInterrupt:
 | 
			
		||||
            log.debug("Keyboard Escape Detected in _send_thread")
 | 
			
		||||
            self.event('killed', direct=True)
 | 
			
		||||
            self.disconnect()
 | 
			
		||||
            return
 | 
			
		||||
        except SystemExit:
 | 
			
		||||
            self.disconnect()
 | 
			
		||||
            self.event_queue.put(('quit', None, None))
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
    def _thread_excepthook(self):
 | 
			
		||||
        """
 | 
			
		||||
        If a threaded event handler raises an exception, there is no way to
 | 
			
		||||
        catch it except with an excepthook. Currently, each thread has its own
 | 
			
		||||
        excepthook, but ideally we could use the main sys.excepthook.
 | 
			
		||||
 | 
			
		||||
        Modifies threading.Thread to use sys.excepthook when an exception
 | 
			
		||||
        is not caught.
 | 
			
		||||
        """
 | 
			
		||||
        init_old = threading.Thread.__init__
 | 
			
		||||
 | 
			
		||||
        def init(self, *args, **kwargs):
 | 
			
		||||
            init_old(self, *args, **kwargs)
 | 
			
		||||
            run_old = self.run
 | 
			
		||||
 | 
			
		||||
            def run_with_except_hook(*args, **kw):
 | 
			
		||||
                try:
 | 
			
		||||
                    run_old(*args, **kw)
 | 
			
		||||
                except (KeyboardInterrupt, SystemExit):
 | 
			
		||||
                    raise
 | 
			
		||||
                except:
 | 
			
		||||
                    sys.excepthook(*sys.exc_info())
 | 
			
		||||
            self.run = run_with_except_hook
 | 
			
		||||
        threading.Thread.__init__ = init
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# To comply with PEP8, method names now use underscores.
 | 
			
		||||
# Deprecated method names are re-mapped for backwards compatibility.
 | 
			
		||||
XMLStream.startTLS = XMLStream.start_tls
 | 
			
		||||
XMLStream.registerStanza = XMLStream.register_stanza
 | 
			
		||||
XMLStream.removeStanza = XMLStream.remove_stanza
 | 
			
		||||
XMLStream.registerHandler = XMLStream.register_handler
 | 
			
		||||
XMLStream.removeHandler = XMLStream.remove_handler
 | 
			
		||||
XMLStream.setSocket = XMLStream.set_socket
 | 
			
		||||
XMLStream.sendRaw = XMLStream.send_raw
 | 
			
		||||
XMLStream.getId = XMLStream.get_id
 | 
			
		||||
XMLStream.getNewId = XMLStream.new_id
 | 
			
		||||
XMLStream.sendXML = XMLStream.send_xml
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										57
									
								
								tests/live_multiple_streams.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								tests/live_multiple_streams.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,57 @@
 | 
			
		||||
import logging
 | 
			
		||||
 | 
			
		||||
from sleekxmpp.test import *
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestMultipleStreams(SleekTest):
 | 
			
		||||
    """
 | 
			
		||||
    Test that we can test a live stanza stream.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    def setUp(self):
 | 
			
		||||
        self.client1 = SleekTest()
 | 
			
		||||
        self.client2 = SleekTest()
 | 
			
		||||
 | 
			
		||||
    def tearDown(self):
 | 
			
		||||
        self.client1.stream_close()
 | 
			
		||||
        self.client2.stream_close()
 | 
			
		||||
 | 
			
		||||
    def testMultipleStreams(self):
 | 
			
		||||
        """Test that we can interact with multiple live ClientXMPP instance."""
 | 
			
		||||
 | 
			
		||||
        client1 = self.client1
 | 
			
		||||
        client2 = self.client2
 | 
			
		||||
 | 
			
		||||
        client1.stream_start(mode='client',
 | 
			
		||||
                             socket='live',
 | 
			
		||||
                             skip=True,
 | 
			
		||||
                             jid='user@localhost/test1',
 | 
			
		||||
                             password='user')
 | 
			
		||||
        client2.stream_start(mode='client',
 | 
			
		||||
                             socket='live',
 | 
			
		||||
                             skip=True,
 | 
			
		||||
                             jid='user@localhost/test2',
 | 
			
		||||
                             password='user')
 | 
			
		||||
 | 
			
		||||
        client1.xmpp.send_message(mto='user@localhost/test2',
 | 
			
		||||
                                  mbody='test')
 | 
			
		||||
 | 
			
		||||
        client1.send('message@body=test', method='stanzapath')
 | 
			
		||||
        client2.recv('message@body=test', method='stanzapath')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
suite = unittest.TestLoader().loadTestsFromTestCase(TestMultipleStreams)
 | 
			
		||||
 | 
			
		||||
if __name__ == '__main__':
 | 
			
		||||
    logging.basicConfig(level=logging.DEBUG,
 | 
			
		||||
                        format='%(levelname)-8s %(message)s')
 | 
			
		||||
 | 
			
		||||
    tests = unittest.TestSuite([suite])
 | 
			
		||||
    result = unittest.TextTestRunner(verbosity=2).run(tests)
 | 
			
		||||
    test_ns = 'http://andyet.net/protocol/tests'
 | 
			
		||||
    print("<tests xmlns='%s' %s %s %s %s />" % (
 | 
			
		||||
        test_ns,
 | 
			
		||||
        'ran="%s"' % result.testsRun,
 | 
			
		||||
        'errors="%s"' % len(result.errors),
 | 
			
		||||
        'fails="%s"' % len(result.failures),
 | 
			
		||||
        'success="%s"' % result.wasSuccessful()))
 | 
			
		||||
@@ -1,5 +1,6 @@
 | 
			
		||||
import logging
 | 
			
		||||
 | 
			
		||||
from sleekxmpp.test import *
 | 
			
		||||
import sleekxmpp.plugins.xep_0033 as xep_0033
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestLiveStream(SleekTest):
 | 
			
		||||
@@ -29,10 +30,6 @@ class TestLiveStream(SleekTest):
 | 
			
		||||
              <mechanism>DIGEST-MD5</mechanism>
 | 
			
		||||
              <mechanism>PLAIN</mechanism>
 | 
			
		||||
            </mechanisms>
 | 
			
		||||
            <c xmlns="http://jabber.org/protocol/caps"
 | 
			
		||||
               node="http://www.process-one.net/en/ejabberd/"
 | 
			
		||||
               ver="TQ2JFyRoSa70h2G1bpgjzuXb2sU=" hash="sha-1" />
 | 
			
		||||
            <register xmlns="http://jabber.org/features/iq-register" />
 | 
			
		||||
          </stream:features>
 | 
			
		||||
        """)
 | 
			
		||||
        self.send_feature("""
 | 
			
		||||
@@ -49,11 +46,6 @@ class TestLiveStream(SleekTest):
 | 
			
		||||
              <mechanism>DIGEST-MD5</mechanism>
 | 
			
		||||
              <mechanism>PLAIN</mechanism>
 | 
			
		||||
            </mechanisms>
 | 
			
		||||
            <c xmlns="http://jabber.org/protocol/caps"
 | 
			
		||||
               node="http://www.process-one.net/en/ejabberd/"
 | 
			
		||||
               ver="TQ2JFyRoSa70h2G1bpgjzuXb2sU="
 | 
			
		||||
               hash="sha-1" />
 | 
			
		||||
            <register xmlns="http://jabber.org/features/iq-register" />
 | 
			
		||||
          </stream:features>
 | 
			
		||||
        """)
 | 
			
		||||
        self.send_feature("""
 | 
			
		||||
@@ -69,11 +61,6 @@ class TestLiveStream(SleekTest):
 | 
			
		||||
          <stream:features>
 | 
			
		||||
            <bind xmlns="urn:ietf:params:xml:ns:xmpp-bind" />
 | 
			
		||||
            <session xmlns="urn:ietf:params:xml:ns:xmpp-session" />
 | 
			
		||||
            <c xmlns="http://jabber.org/protocol/caps"
 | 
			
		||||
               node="http://www.process-one.net/en/ejabberd/"
 | 
			
		||||
               ver="TQ2JFyRoSa70h2G1bpgjzuXb2sU="
 | 
			
		||||
               hash="sha-1" />
 | 
			
		||||
            <register xmlns="http://jabber.org/features/iq-register" />
 | 
			
		||||
          </stream:features>
 | 
			
		||||
        """)
 | 
			
		||||
 | 
			
		||||
@@ -99,6 +86,9 @@ class TestLiveStream(SleekTest):
 | 
			
		||||
suite = unittest.TestLoader().loadTestsFromTestCase(TestLiveStream)
 | 
			
		||||
 | 
			
		||||
if __name__ == '__main__':
 | 
			
		||||
    logging.basicConfig(level=logging.DEBUG,
 | 
			
		||||
                        format='%(levelname)-8s %(message)s')
 | 
			
		||||
 | 
			
		||||
    tests = unittest.TestSuite([suite])
 | 
			
		||||
    result = unittest.TextTestRunner(verbosity=2).run(tests)
 | 
			
		||||
    test_ns = 'http://andyet.net/protocol/tests'
 | 
			
		||||
 
 | 
			
		||||
@@ -53,9 +53,8 @@ class TestElementBase(SleekTest):
 | 
			
		||||
            name = "foo"
 | 
			
		||||
            namespace = "foo"
 | 
			
		||||
            interfaces = set(('bar', 'baz'))
 | 
			
		||||
            subitem = set((TestSubStanza,))
 | 
			
		||||
 | 
			
		||||
        register_stanza_plugin(TestStanza, TestStanzaPlugin)
 | 
			
		||||
        register_stanza_plugin(TestStanza, TestStanzaPlugin, iterable=True)
 | 
			
		||||
 | 
			
		||||
        stanza = TestStanza()
 | 
			
		||||
        stanza['bar'] = 'a'
 | 
			
		||||
@@ -100,8 +99,8 @@ class TestElementBase(SleekTest):
 | 
			
		||||
            name = "foo"
 | 
			
		||||
            namespace = "foo"
 | 
			
		||||
            interfaces = set(('bar', 'baz'))
 | 
			
		||||
            subitem = set((TestSubStanza,))
 | 
			
		||||
 | 
			
		||||
        register_stanza_plugin(TestStanza, TestSubStanza, iterable=True)
 | 
			
		||||
        register_stanza_plugin(TestStanza, TestStanzaPlugin)
 | 
			
		||||
        register_stanza_plugin(TestStanza, TestStanzaPlugin2)
 | 
			
		||||
 | 
			
		||||
@@ -115,7 +114,7 @@ class TestElementBase(SleekTest):
 | 
			
		||||
                  'substanzas': [{'__childtag__': '{foo}subfoo',
 | 
			
		||||
                                  'bar': 'c',
 | 
			
		||||
                                  'baz': ''}]}
 | 
			
		||||
        stanza.setStanzaValues(values)
 | 
			
		||||
        stanza.values = values
 | 
			
		||||
 | 
			
		||||
        self.check(stanza, """
 | 
			
		||||
          <foo xmlns="foo" bar="a">
 | 
			
		||||
@@ -143,7 +142,7 @@ class TestElementBase(SleekTest):
 | 
			
		||||
            plugin_attrib = "foobar"
 | 
			
		||||
            interfaces = set(('fizz',))
 | 
			
		||||
 | 
			
		||||
        TestStanza.subitem = (TestStanza,)
 | 
			
		||||
        register_stanza_plugin(TestStanza, TestStanza, iterable=True)
 | 
			
		||||
        register_stanza_plugin(TestStanza, TestStanzaPlugin)
 | 
			
		||||
 | 
			
		||||
        stanza = TestStanza()
 | 
			
		||||
@@ -457,7 +456,6 @@ class TestElementBase(SleekTest):
 | 
			
		||||
            namespace = "foo"
 | 
			
		||||
            interfaces = set(('bar','baz', 'qux'))
 | 
			
		||||
            sub_interfaces = set(('qux',))
 | 
			
		||||
            subitem = (TestSubStanza,)
 | 
			
		||||
 | 
			
		||||
            def setQux(self, value):
 | 
			
		||||
                self._set_sub_text('qux', text=value)
 | 
			
		||||
@@ -470,6 +468,7 @@ class TestElementBase(SleekTest):
 | 
			
		||||
            namespace = "http://test/slash/bar"
 | 
			
		||||
            interfaces = set(('attrib',))
 | 
			
		||||
 | 
			
		||||
        register_stanza_plugin(TestStanza, TestSubStanza, iterable=True)
 | 
			
		||||
        register_stanza_plugin(TestStanza, TestStanzaPlugin)
 | 
			
		||||
 | 
			
		||||
        stanza = TestStanza()
 | 
			
		||||
@@ -590,7 +589,8 @@ class TestElementBase(SleekTest):
 | 
			
		||||
            name = "foo"
 | 
			
		||||
            namespace = "foo"
 | 
			
		||||
            interfaces = set(('bar', 'baz'))
 | 
			
		||||
            subitem = (TestSubStanza,)
 | 
			
		||||
 | 
			
		||||
        register_stanza_plugin(TestStanza, TestSubStanza, iterable=True)
 | 
			
		||||
 | 
			
		||||
        stanza = TestStanza()
 | 
			
		||||
        substanza1 = TestSubStanza()
 | 
			
		||||
@@ -657,4 +657,87 @@ class TestElementBase(SleekTest):
 | 
			
		||||
        self.failUnless(stanza1 != stanza2,
 | 
			
		||||
            "Divergent stanza copies incorrectly compared equal.")
 | 
			
		||||
 | 
			
		||||
    def testExtension(self):
 | 
			
		||||
        """Testing using is_extension."""
 | 
			
		||||
 | 
			
		||||
        class TestStanza(ElementBase):
 | 
			
		||||
            name = "foo"
 | 
			
		||||
            namespace = "foo"
 | 
			
		||||
            interfaces = set(('bar', 'baz'))
 | 
			
		||||
 | 
			
		||||
        class TestExtension(ElementBase):
 | 
			
		||||
            name = 'extended'
 | 
			
		||||
            namespace = 'foo'
 | 
			
		||||
            plugin_attrib = name
 | 
			
		||||
            interfaces = set((name,))
 | 
			
		||||
            is_extension = True
 | 
			
		||||
 | 
			
		||||
            def set_extended(self, value):
 | 
			
		||||
                self.xml.text = value
 | 
			
		||||
 | 
			
		||||
            def get_extended(self):
 | 
			
		||||
                return self.xml.text
 | 
			
		||||
 | 
			
		||||
            def del_extended(self):
 | 
			
		||||
                self.parent().xml.remove(self.xml)
 | 
			
		||||
 | 
			
		||||
        register_stanza_plugin(TestStanza, TestExtension)
 | 
			
		||||
 | 
			
		||||
        stanza = TestStanza()
 | 
			
		||||
        stanza['extended'] = 'testing'
 | 
			
		||||
 | 
			
		||||
        self.check(stanza, """
 | 
			
		||||
          <foo xmlns="foo">
 | 
			
		||||
            <extended>testing</extended>
 | 
			
		||||
          </foo>
 | 
			
		||||
        """)
 | 
			
		||||
 | 
			
		||||
        self.failUnless(stanza['extended'] == 'testing',
 | 
			
		||||
                "Could not retrieve stanza extension value.")
 | 
			
		||||
 | 
			
		||||
        del stanza['extended']
 | 
			
		||||
        self.check(stanza, """
 | 
			
		||||
          <foo xmlns="foo" />
 | 
			
		||||
        """)
 | 
			
		||||
 | 
			
		||||
    def testOverrides(self):
 | 
			
		||||
        """Test using interface overrides."""
 | 
			
		||||
 | 
			
		||||
        class TestStanza(ElementBase):
 | 
			
		||||
            name = "foo"
 | 
			
		||||
            namespace = "foo"
 | 
			
		||||
            interfaces = set(('bar', 'baz'))
 | 
			
		||||
 | 
			
		||||
        class TestOverride(ElementBase):
 | 
			
		||||
            name = 'overrider'
 | 
			
		||||
            namespace = 'foo'
 | 
			
		||||
            plugin_attrib = name
 | 
			
		||||
            interfaces = set(('bar',))
 | 
			
		||||
            overrides = ['set_bar']
 | 
			
		||||
 | 
			
		||||
            def setup(self, xml):
 | 
			
		||||
                # Don't create XML for the plugin
 | 
			
		||||
                self.xml = ET.Element('')
 | 
			
		||||
 | 
			
		||||
            def set_bar(self, value):
 | 
			
		||||
                if not value.startswith('override-'):
 | 
			
		||||
                    self.parent()._set_attr('bar', 'override-%s' % value)
 | 
			
		||||
                else:
 | 
			
		||||
                    self.parent()._set_attr('bar', value)
 | 
			
		||||
 | 
			
		||||
        stanza = TestStanza()
 | 
			
		||||
        stanza['bar'] = 'foo'
 | 
			
		||||
        self.check(stanza, """
 | 
			
		||||
          <foo xmlns="foo" bar="foo" />
 | 
			
		||||
        """)
 | 
			
		||||
 | 
			
		||||
        register_stanza_plugin(TestStanza, TestOverride, overrides=True)
 | 
			
		||||
 | 
			
		||||
        stanza = TestStanza()
 | 
			
		||||
        stanza['bar'] = 'foo'
 | 
			
		||||
        self.check(stanza, """
 | 
			
		||||
          <foo xmlns="foo" bar="override-foo" />
 | 
			
		||||
        """)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
suite = unittest.TestLoader().loadTestsFromTestCase(TestElementBase)
 | 
			
		||||
 
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user