Compare commits
	
		
			33 Commits
		
	
	
		
			sleek-1.1.
			...
			sleek-1.1.
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 8a745c5e81 | ||
|   | bf0a157c5d | ||
|   | f49818be06 | ||
|   | 1ad171dfe5 | ||
|   | 2a78570d65 | ||
|   | 7a112f2523 | ||
|   | e86444e5fb | ||
|   | 36c11ad9de | ||
|   | 019a4b20ae | ||
|   | 433ee08687 | ||
|   | 7858d969d8 | ||
|   | 8119551049 | ||
|   | 061489f03a | ||
|   | d92aa05b5c | ||
|   | f7a74d960e | ||
|   | 95a0e51b41 | ||
|   | 110e45e187 | ||
|   | 534aaf2b2a | ||
|   | 4cc20fdd05 | ||
|   | f3fae192a8 | ||
|   | 7d59a8a0ad | ||
|   | 8da387a38a | ||
|   | ff6fc44215 | ||
|   | 62391a895a | ||
|   | 9bcdd7d18f | ||
|   | 5c4f7bfe8b | ||
|   | 0b7f134021 | ||
|   | 378a42889f | ||
|   | f824950552 | ||
|   | 3d2d11f169 | ||
|   | 181aea737d | ||
|   | ee702f4071 | ||
|   | a08c2161a7 | 
| @@ -45,7 +45,7 @@ The latest source code for SleekXMPP may be found on `Github | ||||
| ``develop`` branch. | ||||
|  | ||||
| **Latest Release** | ||||
|     - `1.1.4 <http://github.com/fritzy/SleekXMPP/zipball/1.1.4>`_ | ||||
|     - `1.1.7 <http://github.com/fritzy/SleekXMPP/zipball/1.1.7>`_ | ||||
|  | ||||
| **Develop Releases** | ||||
|     - `Latest Develop Version <http://github.com/fritzy/SleekXMPP/zipball/develop>`_ | ||||
|   | ||||
							
								
								
									
										165
									
								
								examples/gtalk_custom_domain.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										165
									
								
								examples/gtalk_custom_domain.py
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,165 @@ | ||||
| #!/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 getpass | ||||
| from optparse import OptionParser | ||||
|  | ||||
| import sleekxmpp | ||||
|  | ||||
| import ssl | ||||
| from sleekxmpp.xmlstream import cert | ||||
|  | ||||
|  | ||||
| # 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') | ||||
| else: | ||||
|     raw_input = input | ||||
|  | ||||
|  | ||||
| class GTalkBot(sleekxmpp.ClientXMPP): | ||||
|  | ||||
|     """ | ||||
|     A demonstration of using SleekXMPP with accounts from a Google Apps | ||||
|     account with a custom domain, because it requires custom certificate | ||||
|     validation. | ||||
|     """ | ||||
|  | ||||
|     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 initialize | ||||
|         # our roster. | ||||
|         self.add_event_handler("session_start", self.start) | ||||
|  | ||||
|         # The message event is triggered whenever a message | ||||
|         # stanza is received. Be aware that that includes | ||||
|         # MUC messages and error messages. | ||||
|         self.add_event_handler("message", self.message) | ||||
|  | ||||
|         # Using a Google Apps custom domain, the certificate | ||||
|         # does not contain the custom domain, just the GTalk | ||||
|         # server name. So we will need to process invalid | ||||
|         # certifcates ourselves and check that it really | ||||
|         # is from Google. | ||||
|         self.add_event_handler("ssl_invalid_cert", self.invalid_cert) | ||||
|  | ||||
|     def invalid_cert(self, pem_cert): | ||||
|         der_cert = ssl.PEM_cert_to_DER_cert(pem_cert) | ||||
|         try: | ||||
|             cert.verify('talk.google.com', der_cert) | ||||
|             logging.debug("CERT: Found GTalk certificate") | ||||
|         except cert.CertificateError as err: | ||||
|             log.error(err.message) | ||||
|             self.disconnect(send_close=False) | ||||
|  | ||||
|     def start(self, event): | ||||
|         """ | ||||
|         Process the session_start event. | ||||
|  | ||||
|         Typical actions for the session_start event are | ||||
|         requesting the roster and broadcasting an initial | ||||
|         presence stanza. | ||||
|  | ||||
|         Arguments: | ||||
|             event -- An empty dictionary. The session_start | ||||
|                      event does not provide any additional | ||||
|                      data. | ||||
|         """ | ||||
|         self.send_presence() | ||||
|         self.get_roster() | ||||
|  | ||||
|     def message(self, msg): | ||||
|         """ | ||||
|         Process incoming message stanzas. Be aware that this also | ||||
|         includes MUC messages and error messages. It is usually | ||||
|         a good idea to check the messages's type before processing | ||||
|         or sending replies. | ||||
|  | ||||
|         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['type'] in ('chat', 'normal'): | ||||
|             msg.reply("Thanks for sending\n%(body)s" % msg).send() | ||||
|  | ||||
|  | ||||
| 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 GTalkBot and register plugins. Note that while plugins may | ||||
|     # have interdependencies, the order in which you register them does | ||||
|     # not matter. | ||||
|     xmpp = GTalkBot(opts.jid, opts.password) | ||||
|     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 dnspython 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(block=True) | ||||
|         print("Done") | ||||
|     else: | ||||
|         print("Unable to connect.") | ||||
							
								
								
									
										149
									
								
								examples/ibb_transfer/ibb_receiver.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										149
									
								
								examples/ibb_transfer/ibb_receiver.py
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,149 @@ | ||||
| #!/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 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') | ||||
| else: | ||||
|     raw_input = input | ||||
|  | ||||
|  | ||||
| class IBBReceiver(sleekxmpp.ClientXMPP): | ||||
|  | ||||
|     """ | ||||
|     A basic example of creating and using an in-band bytestream. | ||||
|     """ | ||||
|  | ||||
|     def __init__(self, jid, password): | ||||
|         sleekxmpp.ClientXMPP.__init__(self, jid, password) | ||||
|  | ||||
|         self.register_plugin('xep_0030') # Service Discovery | ||||
|         self.register_plugin('xep_0047', { | ||||
|             'accept_stream': self.accept_stream | ||||
|         }) # In-band Bytestreams | ||||
|  | ||||
|         # 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 initialize | ||||
|         # our roster. | ||||
|         self.add_event_handler("session_start", self.start) | ||||
|  | ||||
|         self.add_event_handler("ibb_stream_start", self.stream_opened) | ||||
|         self.add_event_handler("ibb_stream_data", self.stream_data) | ||||
|  | ||||
|     def start(self, event): | ||||
|         """ | ||||
|         Process the session_start event. | ||||
|  | ||||
|         Typical actions for the session_start event are | ||||
|         requesting the roster and broadcasting an initial | ||||
|         presence stanza. | ||||
|  | ||||
|         Arguments: | ||||
|             event -- An empty dictionary. The session_start | ||||
|                      event does not provide any additional | ||||
|                      data. | ||||
|         """ | ||||
|         self.send_presence() | ||||
|         self.get_roster() | ||||
|  | ||||
|     def accept_stream(self, iq): | ||||
|         """ | ||||
|         Check that it is ok to accept a stream request.  | ||||
|  | ||||
|         Controlling stream acceptance can be done via either: | ||||
|             - setting 'auto_accept' to False in the plugin | ||||
|               configuration. The default is True. | ||||
|             - setting 'accept_stream' to a function which accepts | ||||
|               an Iq stanza as its argument, like this one. | ||||
|  | ||||
|         The accept_stream function will be used if it exists, and the | ||||
|         auto_accept value will be used otherwise. | ||||
|         """ | ||||
|         return True | ||||
|  | ||||
|     def stream_opened(self, stream): | ||||
|         # NOTE: IBB streams are bi-directional, so the original sender is | ||||
|         # now the opened stream's receiver. | ||||
|         print('Stream opened: %s from ' % (stream.sid, stream.receiver)) | ||||
|  | ||||
|         # You could run a loop reading from the stream using stream.recv(), | ||||
|         # or use the ibb_stream_data event. | ||||
|  | ||||
|     def stream_data(self, event): | ||||
|         print(event['data']) | ||||
|  | ||||
| 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: ") | ||||
|  | ||||
|     xmpp = IBBReceiver(opts.jid, opts.password) | ||||
|  | ||||
|     # 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 dnspython 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(block=True) | ||||
|         print("Done") | ||||
|     else: | ||||
|         print("Unable to connect.") | ||||
							
								
								
									
										145
									
								
								examples/ibb_transfer/ibb_sender.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										145
									
								
								examples/ibb_transfer/ibb_sender.py
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,145 @@ | ||||
| #!/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 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') | ||||
| else: | ||||
|     raw_input = input | ||||
|  | ||||
|  | ||||
| class IBBSender(sleekxmpp.ClientXMPP): | ||||
|  | ||||
|     """ | ||||
|     A basic example of creating and using an in-band bytestream. | ||||
|     """ | ||||
|  | ||||
|     def __init__(self, jid, password, receiver, filename): | ||||
|         sleekxmpp.ClientXMPP.__init__(self, jid, password) | ||||
|  | ||||
|         self.receiver = receiver | ||||
|         self.filename = filename | ||||
|  | ||||
|         # 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 initialize | ||||
|         # 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 initial | ||||
|         presence stanza. | ||||
|  | ||||
|         Arguments: | ||||
|             event -- An empty dictionary. The session_start | ||||
|                      event does not provide any additional | ||||
|                      data. | ||||
|         """ | ||||
|         self.send_presence() | ||||
|         self.get_roster() | ||||
|  | ||||
|         # For the purpose of demonstration, we'll set a very small block | ||||
|         # size. The default block size is 4096. We'll also use a window | ||||
|         # allowing sending multiple blocks at a time; in this case, three | ||||
|         # block transfers may be in progress at any time. | ||||
|         stream = self['xep_0047'].open_stream(self.receiver) | ||||
|  | ||||
|         with open(self.filename) as f: | ||||
|             data = f.read() | ||||
|             stream.sendall(data) | ||||
|  | ||||
|  | ||||
| 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", "--receiver", dest="receiver", | ||||
|                     help="JID to use") | ||||
|     optp.add_option("-f", "--file", dest="filename", | ||||
|                     help="JID 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: ") | ||||
|     if opts.receiver is None: | ||||
|         opts.receiver = raw_input("Receiver: ") | ||||
|     if opts.filename is None: | ||||
|         opts.filename = raw_input("File path: ") | ||||
|  | ||||
|     # Setup the EchoBot and register plugins. Note that while plugins may | ||||
|     # have interdependencies, the order in which you register them does | ||||
|     # not matter. | ||||
|     xmpp = IBBSender(opts.jid, opts.password, opts.receiver, opts.filename) | ||||
|     xmpp.register_plugin('xep_0030') # Service Discovery | ||||
|     xmpp.register_plugin('xep_0004') # Data Forms | ||||
|     xmpp.register_plugin('xep_0047') # In-band Bytestreams | ||||
|     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 dnspython 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(block=True) | ||||
|         print("Done") | ||||
|     else: | ||||
|         print("Unable to connect.") | ||||
							
								
								
									
										5
									
								
								setup.py
									
									
									
									
									
								
							
							
						
						
									
										5
									
								
								setup.py
									
									
									
									
									
								
							| @@ -61,6 +61,7 @@ packages     = [ 'sleekxmpp', | ||||
|                  'sleekxmpp/plugins/xep_0027', | ||||
|                  'sleekxmpp/plugins/xep_0030', | ||||
|                  'sleekxmpp/plugins/xep_0030/stanza', | ||||
|                  'sleekxmpp/plugins/xep_0033', | ||||
|                  'sleekxmpp/plugins/xep_0047', | ||||
|                  'sleekxmpp/plugins/xep_0050', | ||||
|                  'sleekxmpp/plugins/xep_0054', | ||||
| @@ -71,6 +72,7 @@ packages     = [ 'sleekxmpp', | ||||
|                  'sleekxmpp/plugins/xep_0077', | ||||
|                  'sleekxmpp/plugins/xep_0078', | ||||
|                  'sleekxmpp/plugins/xep_0080', | ||||
|                  'sleekxmpp/plugins/xep_0084', | ||||
|                  'sleekxmpp/plugins/xep_0085', | ||||
|                  'sleekxmpp/plugins/xep_0086', | ||||
|                  'sleekxmpp/plugins/xep_0092', | ||||
| @@ -82,13 +84,16 @@ packages     = [ 'sleekxmpp', | ||||
|                  'sleekxmpp/plugins/xep_0153', | ||||
|                  'sleekxmpp/plugins/xep_0172', | ||||
|                  'sleekxmpp/plugins/xep_0184', | ||||
|                  'sleekxmpp/plugins/xep_0186', | ||||
|                  'sleekxmpp/plugins/xep_0198', | ||||
|                  'sleekxmpp/plugins/xep_0199', | ||||
|                  'sleekxmpp/plugins/xep_0202', | ||||
|                  'sleekxmpp/plugins/xep_0203', | ||||
|                  'sleekxmpp/plugins/xep_0221', | ||||
|                  'sleekxmpp/plugins/xep_0224', | ||||
|                  'sleekxmpp/plugins/xep_0231', | ||||
|                  'sleekxmpp/plugins/xep_0249', | ||||
|                  'sleekxmpp/plugins/xep_0258', | ||||
|                  'sleekxmpp/features', | ||||
|                  'sleekxmpp/features/feature_mechanisms', | ||||
|                  'sleekxmpp/features/feature_mechanisms/stanza', | ||||
|   | ||||
| @@ -16,24 +16,24 @@ class APIWrapper(object): | ||||
|         elif attr == 'settings': | ||||
|             return self.api.settings[self.name] | ||||
|         elif attr == 'register': | ||||
|             def curried_handler(handler, op, jid=None, node=None, default=False): | ||||
|             def partial(handler, op, jid=None, node=None, default=False): | ||||
|                 register = getattr(self.api, attr) | ||||
|                 return register(handler, self.name, op, jid, node, default) | ||||
|             return curried_handler | ||||
|             return partial | ||||
|         elif attr == 'register_default': | ||||
|             def curried_handler(handler, op, jid=None, node=None): | ||||
|             def partial(handler, op, jid=None, node=None): | ||||
|                 return getattr(self.api, attr)(handler, self.name, op) | ||||
|             return curried_handler | ||||
|             return partial | ||||
|         elif attr in ('run', 'restore_default', 'unregister'): | ||||
|             def curried_handler(*args, **kwargs): | ||||
|             def partial(*args, **kwargs): | ||||
|                 return getattr(self.api, attr)(self.name, *args, **kwargs) | ||||
|             return curried_handler | ||||
|             return partial | ||||
|         return None | ||||
|  | ||||
|     def __getitem__(self, attr): | ||||
|         def curried_handler(jid=None, node=None, ifrom=None, args=None): | ||||
|         def partial(jid=None, node=None, ifrom=None, args=None): | ||||
|             return self.api.run(self.name, attr, jid, node, ifrom, args) | ||||
|         return curried_handler | ||||
|         return partial | ||||
|  | ||||
|  | ||||
| class APIRegistry(object): | ||||
| @@ -42,7 +42,7 @@ class APIRegistry(object): | ||||
|         self._handlers = {} | ||||
|         self._handler_defaults = {} | ||||
|         self.xmpp = xmpp | ||||
|         self.settings = {}  | ||||
|         self.settings = {} | ||||
|  | ||||
|     def _setup(self, ctype, op): | ||||
|         """Initialize the API callback dictionaries. | ||||
| @@ -138,8 +138,8 @@ class APIRegistry(object): | ||||
|         """Register an API callback, with JID+node specificity. | ||||
|  | ||||
|         The API callback can later be executed based on the | ||||
|         specificity of the provided JID+node combination.  | ||||
|          | ||||
|         specificity of the provided JID+node combination. | ||||
|  | ||||
|         See :meth:`~ApiRegistry.run` for more details. | ||||
|  | ||||
|         :param string ctype: The name of the API to use. | ||||
|   | ||||
| @@ -31,6 +31,7 @@ from sleekxmpp.xmlstream import XMLStream, JID | ||||
| from sleekxmpp.xmlstream import ET, register_stanza_plugin | ||||
| from sleekxmpp.xmlstream.matcher import MatchXPath | ||||
| from sleekxmpp.xmlstream.handler import Callback | ||||
| from sleekxmpp.xmlstream.stanzabase import XML_NS | ||||
|  | ||||
| from sleekxmpp.features import * | ||||
| from sleekxmpp.plugins import PluginManager, register_plugin, load_plugin | ||||
| @@ -66,7 +67,7 @@ class BaseXMPP(XMLStream): | ||||
|         #: An identifier for the stream as given by the server. | ||||
|         self.stream_id = None | ||||
|  | ||||
|         #: The JabberID (JID) used by this connection.  | ||||
|         #: The JabberID (JID) used by this connection. | ||||
|         self.boundjid = JID(jid) | ||||
|         self._expected_server_name = self.boundjid.host | ||||
|  | ||||
| @@ -102,7 +103,7 @@ class BaseXMPP(XMLStream): | ||||
|         #: The API registry is a way to process callbacks based on | ||||
|         #: JID+node combinations. Each callback in the registry is | ||||
|         #: marked with: | ||||
|         #:  | ||||
|         #: | ||||
|         #:   - An API name, e.g. xep_0030 | ||||
|         #:   - The name of an action, e.g. get_info | ||||
|         #:   - The JID that will be affected | ||||
| @@ -180,6 +181,8 @@ class BaseXMPP(XMLStream): | ||||
|         :param xml: The incoming stream's root element. | ||||
|         """ | ||||
|         self.stream_id = xml.get('id', '') | ||||
|         self.stream_version = xml.get('version', '') | ||||
|         self.peer_default_lang = xml.get('{%s}lang' % XML_NS, None) | ||||
|  | ||||
|     def process(self, *args, **kwargs): | ||||
|         """Initialize plugins and begin processing the XML stream. | ||||
| @@ -199,7 +202,7 @@ class BaseXMPP(XMLStream): | ||||
|                     Defaults to ``True``. This does **not** mean that no | ||||
|                     threads are used at all if ``threaded=False``. | ||||
|  | ||||
|         Regardless of these threading options, these threads will  | ||||
|         Regardless of these threading options, these threads will | ||||
|         always exist: | ||||
|  | ||||
|         - The event queue processor | ||||
| @@ -272,7 +275,9 @@ class BaseXMPP(XMLStream): | ||||
|  | ||||
|     def Message(self, *args, **kwargs): | ||||
|         """Create a Message stanza associated with this stream.""" | ||||
|         return Message(self, *args, **kwargs) | ||||
|         msg = Message(self, *args, **kwargs) | ||||
|         msg['lang'] = self.default_lang | ||||
|         return msg | ||||
|  | ||||
|     def Iq(self, *args, **kwargs): | ||||
|         """Create an Iq stanza associated with this stream.""" | ||||
| @@ -280,18 +285,20 @@ class BaseXMPP(XMLStream): | ||||
|  | ||||
|     def Presence(self, *args, **kwargs): | ||||
|         """Create a Presence stanza associated with this stream.""" | ||||
|         return Presence(self, *args, **kwargs) | ||||
|         pres = Presence(self, *args, **kwargs) | ||||
|         pres['lang'] = self.default_lang | ||||
|         return pres | ||||
|  | ||||
|     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. | ||||
|  | ||||
|         :param id: An ideally unique ID value for this stanza thread. | ||||
|                    Defaults to 0. | ||||
|         :param ifrom: The from :class:`~sleekxmpp.xmlstream.jid.JID`  | ||||
|         :param ifrom: The from :class:`~sleekxmpp.xmlstream.jid.JID` | ||||
|                       to use for this stanza. | ||||
|         :param ito: The destination :class:`~sleekxmpp.xmlstream.jid.JID` | ||||
|                     for this stanza. | ||||
|         :param itype: The :class:`~sleekxmpp.stanza.iq.Iq`'s type,  | ||||
|         :param itype: The :class:`~sleekxmpp.stanza.iq.Iq`'s type, | ||||
|                       one of: ``'get'``, ``'set'``, ``'result'``, | ||||
|                       or ``'error'``. | ||||
|         :param iquery: Optional namespace for adding a query element. | ||||
| @@ -329,7 +336,7 @@ class BaseXMPP(XMLStream): | ||||
|  | ||||
|     def make_iq_result(self, id=None, ito=None, ifrom=None, iq=None): | ||||
|         """ | ||||
|         Create an :class:`~sleekxmpp.stanza.iq.Iq` stanza of type  | ||||
|         Create an :class:`~sleekxmpp.stanza.iq.Iq` stanza of type | ||||
|         ``'result'`` with the given ID value. | ||||
|  | ||||
|         :param id: An ideally unique ID value. May use :meth:`new_id()`. | ||||
| @@ -359,10 +366,10 @@ class BaseXMPP(XMLStream): | ||||
|         Optionally, a substanza may be given to use as the | ||||
|         stanza's payload. | ||||
|  | ||||
|         :param sub: Either an  | ||||
|         :param sub: Either an | ||||
|                     :class:`~sleekxmpp.xmlstream.stanzabase.ElementBase` | ||||
|                     stanza object or an | ||||
|                     :class:`~xml.etree.ElementTree.Element` XML object  | ||||
|                     :class:`~xml.etree.ElementTree.Element` XML object | ||||
|                     to use as the :class:`~sleekxmpp.stanza.iq.Iq`'s payload. | ||||
|         :param ito: The destination :class:`~sleekxmpp.xmlstream.jid.JID` | ||||
|                     for this stanza. | ||||
| @@ -389,9 +396,9 @@ class BaseXMPP(XMLStream): | ||||
|         Create an :class:`~sleekxmpp.stanza.iq.Iq` stanza of type ``'error'``. | ||||
|  | ||||
|         :param id: An ideally unique ID value. May use :meth:`new_id()`. | ||||
|         :param type: The type of the error, such as ``'cancel'`` or  | ||||
|         :param type: The type of the error, such as ``'cancel'`` or | ||||
|                      ``'modify'``. Defaults to ``'cancel'``. | ||||
|         :param condition: The error condition. Defaults to  | ||||
|         :param condition: The error condition. Defaults to | ||||
|                           ``'feature-not-implemented'``. | ||||
|         :param text: A message describing the cause of the error. | ||||
|         :param ito: The destination :class:`~sleekxmpp.xmlstream.jid.JID` | ||||
| @@ -415,7 +422,7 @@ class BaseXMPP(XMLStream): | ||||
|  | ||||
|     def make_iq_query(self, iq=None, xmlns='', ito=None, ifrom=None): | ||||
|         """ | ||||
|         Create or modify an :class:`~sleekxmpp.stanza.iq.Iq` stanza  | ||||
|         Create or modify an :class:`~sleekxmpp.stanza.iq.Iq` stanza | ||||
|         to use the given query namespace. | ||||
|  | ||||
|         :param iq: Optionally use an existing stanza instead | ||||
| @@ -448,7 +455,7 @@ class BaseXMPP(XMLStream): | ||||
|     def make_message(self, mto, mbody=None, msubject=None, mtype=None, | ||||
|                      mhtml=None, mfrom=None, mnick=None): | ||||
|         """ | ||||
|         Create and initialize a new  | ||||
|         Create and initialize a new | ||||
|         :class:`~sleekxmpp.stanza.message.Message` stanza. | ||||
|  | ||||
|         :param mto: The recipient of the message. | ||||
| @@ -474,7 +481,7 @@ class BaseXMPP(XMLStream): | ||||
|     def make_presence(self, pshow=None, pstatus=None, ppriority=None, | ||||
|                       pto=None, ptype=None, pfrom=None, pnick=None): | ||||
|         """ | ||||
|         Create and initialize a new  | ||||
|         Create and initialize a new | ||||
|         :class:`~sleekxmpp.stanza.presence.Presence` stanza. | ||||
|  | ||||
|         :param pshow: The presence's show value. | ||||
| @@ -498,7 +505,7 @@ class BaseXMPP(XMLStream): | ||||
|     def send_message(self, mto, mbody, msubject=None, mtype=None, | ||||
|                      mhtml=None, mfrom=None, mnick=None): | ||||
|         """ | ||||
|         Create, initialize, and send a new  | ||||
|         Create, initialize, and send a new | ||||
|         :class:`~sleekxmpp.stanza.message.Message` stanza. | ||||
|  | ||||
|         :param mto: The recipient of the message. | ||||
| @@ -518,7 +525,7 @@ class BaseXMPP(XMLStream): | ||||
|     def send_presence(self, pshow=None, pstatus=None, ppriority=None, | ||||
|                       pto=None, pfrom=None, ptype=None, pnick=None): | ||||
|         """ | ||||
|         Create, initialize, and send a new  | ||||
|         Create, initialize, and send a new | ||||
|         :class:`~sleekxmpp.stanza.presence.Presence` stanza. | ||||
|  | ||||
|         :param pshow: The presence's show value. | ||||
| @@ -529,23 +536,13 @@ class BaseXMPP(XMLStream): | ||||
|         :param pfrom: The sender of the presence. | ||||
|         :param pnick: Optional nickname of the presence's sender. | ||||
|         """ | ||||
|         # Python2.6 chokes on Unicode strings for dict keys. | ||||
|         args = {str('pto'): pto, | ||||
|                 str('ptype'): ptype, | ||||
|                 str('pshow'): pshow, | ||||
|                 str('pstatus'): pstatus, | ||||
|                 str('ppriority'): ppriority, | ||||
|                 str('pnick'): pnick} | ||||
|  | ||||
|         if self.is_component: | ||||
|             self.roster[pfrom].send_presence(**args) | ||||
|         else: | ||||
|             self.client_roster.send_presence(**args) | ||||
|         self.make_presence(pshow, pstatus, ppriority, pto, | ||||
|                            ptype, pfrom, pnick).send() | ||||
|  | ||||
|     def send_presence_subscription(self, pto, pfrom=None, | ||||
|                                    ptype='subscribe', pnick=None): | ||||
|         """ | ||||
|         Create, initialize, and send a new  | ||||
|         Create, initialize, and send a new | ||||
|         :class:`~sleekxmpp.stanza.presence.Presence` stanza of | ||||
|         type ``'subscribe'``. | ||||
|  | ||||
| @@ -554,14 +551,10 @@ class BaseXMPP(XMLStream): | ||||
|         :param ptype: The type of presence, such as ``'subscribe'``. | ||||
|         :param pnick: Optional nickname of the presence's sender. | ||||
|         """ | ||||
|         presence = self.makePresence(ptype=ptype, | ||||
|                                      pfrom=pfrom, | ||||
|                                      pto=self.getjidbare(pto)) | ||||
|         if pnick: | ||||
|             nick = ET.Element('{http://jabber.org/protocol/nick}nick') | ||||
|             nick.text = pnick | ||||
|             presence.append(nick) | ||||
|         presence.send() | ||||
|         self.make_presence(ptype=ptype, | ||||
|                            pfrom=pfrom, | ||||
|                            pto=JID(pto).bare, | ||||
|                            pnick=pnick).send() | ||||
|  | ||||
|     @property | ||||
|     def jid(self): | ||||
| @@ -750,7 +743,7 @@ class BaseXMPP(XMLStream): | ||||
|             return | ||||
|  | ||||
|     def exception(self, exception): | ||||
|         """Process any uncaught exceptions, notably  | ||||
|         """Process any uncaught exceptions, notably | ||||
|         :class:`~sleekxmpp.exceptions.IqError` and | ||||
|         :class:`~sleekxmpp.exceptions.IqTimeout` exceptions. | ||||
|  | ||||
|   | ||||
| @@ -54,14 +54,14 @@ class ClientXMPP(BaseXMPP): | ||||
|     :param password: The password for the XMPP user account. | ||||
|     :param ssl: **Deprecated.** | ||||
|     :param plugin_config: A dictionary of plugin configurations. | ||||
|     :param plugin_whitelist: A list of approved plugins that  | ||||
|                     will be loaded when calling  | ||||
|     :param plugin_whitelist: A list of approved plugins that | ||||
|                     will be loaded when calling | ||||
|                     :meth:`~sleekxmpp.basexmpp.BaseXMPP.register_plugins()`. | ||||
|     :param escape_quotes: **Deprecated.** | ||||
|     """ | ||||
|  | ||||
|     def __init__(self, jid, password, ssl=False, plugin_config={}, | ||||
|                  plugin_whitelist=[], escape_quotes=True, sasl_mech=None): | ||||
|     def __init__(self, jid, password, plugin_config={}, plugin_whitelist=[], | ||||
|                  escape_quotes=True, sasl_mech=None, lang='en'): | ||||
|         BaseXMPP.__init__(self, jid, 'jabber:client') | ||||
|  | ||||
|         self.set_jid(jid) | ||||
| @@ -69,15 +69,18 @@ class ClientXMPP(BaseXMPP): | ||||
|         self.plugin_config = plugin_config | ||||
|         self.plugin_whitelist = plugin_whitelist | ||||
|         self.default_port = 5222 | ||||
|         self.default_lang = lang | ||||
|  | ||||
|         self.credentials = {} | ||||
|  | ||||
|         self.password = password | ||||
|  | ||||
|         self.stream_header = "<stream:stream to='%s' %s %s version='1.0'>" % ( | ||||
|         self.stream_header = "<stream:stream to='%s' %s %s %s %s>" % ( | ||||
|                 self.boundjid.host, | ||||
|                 "xmlns:stream='%s'" % self.stream_ns, | ||||
|                 "xmlns='%s'" % self.default_ns) | ||||
|                 "xmlns='%s'" % self.default_ns, | ||||
|                 "xml:lang='%s'" % self.default_lang, | ||||
|                 "version='1.0'") | ||||
|         self.stream_footer = "</stream:stream>" | ||||
|  | ||||
|         self.features = set() | ||||
| @@ -186,7 +189,7 @@ class ClientXMPP(BaseXMPP): | ||||
|                       occurs. Defaults to ``True``. | ||||
|         :param timeout: The length of time (in seconds) to wait | ||||
|                         for a response before continuing if blocking | ||||
|                         is used. Defaults to  | ||||
|                         is used. Defaults to | ||||
|             :attr:`~sleekxmpp.xmlstream.xmlstream.XMLStream.response_timeout`. | ||||
|         :param callback: Optional reference to a stream handler function. | ||||
|                          Will be executed when the roster is received. | ||||
| @@ -197,7 +200,7 @@ class ClientXMPP(BaseXMPP): | ||||
|  | ||||
|     def del_roster_item(self, jid): | ||||
|         """Remove an item from the roster. | ||||
|          | ||||
|  | ||||
|         This is done by setting its subscription status to ``'remove'``. | ||||
|  | ||||
|         :param jid: The JID of the item to remove. | ||||
| @@ -212,7 +215,7 @@ class ClientXMPP(BaseXMPP): | ||||
|                       Defaults to ``True``. | ||||
|         :param timeout: The length of time (in seconds) to wait for a response | ||||
|                         before continuing if blocking is used. | ||||
|                         Defaults to  | ||||
|                         Defaults to | ||||
|             :attr:`~sleekxmpp.xmlstream.xmlstream.XMLStream.response_timeout`. | ||||
|         :param callback: Optional reference to a stream handler function. Will | ||||
|                          be executed when the roster is received. | ||||
| @@ -230,7 +233,7 @@ class ClientXMPP(BaseXMPP): | ||||
|         response = iq.send(block, timeout, callback) | ||||
|         self.event('roster_received', response) | ||||
|  | ||||
|         if block:  | ||||
|         if block: | ||||
|             self._handle_roster(response) | ||||
|             return response | ||||
|  | ||||
| @@ -276,7 +279,7 @@ class ClientXMPP(BaseXMPP): | ||||
|             roster[jid]['pending_out'] = (item['ask'] == 'subscribe') | ||||
|  | ||||
|             roster[jid].save(remove=(item['subscription'] == 'remove')) | ||||
|                   | ||||
|  | ||||
|         self.event("roster_update", iq) | ||||
|         if iq['type'] == 'set': | ||||
|             resp = self.Iq(stype='result', | ||||
|   | ||||
| @@ -40,8 +40,8 @@ class ComponentXMPP(BaseXMPP): | ||||
|     :param host: The server accepting the component. | ||||
|     :param port: The port used to connect to the server. | ||||
|     :param plugin_config: A dictionary of plugin configurations. | ||||
|     :param plugin_whitelist: A list of approved plugins that  | ||||
|                     will be loaded when calling  | ||||
|     :param plugin_whitelist: A list of approved plugins that | ||||
|                     will be loaded when calling | ||||
|                     :meth:`~sleekxmpp.basexmpp.BaseXMPP.register_plugins()`. | ||||
|     :param use_jc_ns: Indicates if the ``'jabber:client'`` namespace | ||||
|                       should be used instead of the standard | ||||
| @@ -78,7 +78,7 @@ class ComponentXMPP(BaseXMPP): | ||||
|         self.add_event_handler('presence_probe', | ||||
|                                self._handle_probe) | ||||
|  | ||||
|     def connect(self, host=None, port=None, use_ssl=False,  | ||||
|     def connect(self, host=None, port=None, use_ssl=False, | ||||
|                       use_tls=False, reattempt=True): | ||||
|         """Connect to the server. | ||||
|  | ||||
|   | ||||
| @@ -69,10 +69,11 @@ class IqTimeout(XMPPError): | ||||
|                 condition='remote-server-timeout', | ||||
|                 etype='cancel') | ||||
|  | ||||
|         #: The :class:`~sleekxmpp.stanza.iq.Iq` stanza whose response  | ||||
|         #: The :class:`~sleekxmpp.stanza.iq.Iq` stanza whose response | ||||
|         #: did not arrive before the timeout expired. | ||||
|         self.iq = iq | ||||
|  | ||||
|  | ||||
| class IqError(XMPPError): | ||||
|  | ||||
|     """ | ||||
|   | ||||
| @@ -7,9 +7,9 @@ | ||||
| """ | ||||
|  | ||||
| __all__ = [ | ||||
|     'feature_starttls',  | ||||
|     'feature_mechanisms',  | ||||
|     'feature_bind',  | ||||
|     'feature_starttls', | ||||
|     'feature_mechanisms', | ||||
|     'feature_bind', | ||||
|     'feature_session', | ||||
|     'feature_rosterver' | ||||
| ] | ||||
|   | ||||
| @@ -23,7 +23,7 @@ class Auth(StanzaBase): | ||||
|     interfaces = set(('mechanism', 'value')) | ||||
|     plugin_attrib = name | ||||
|  | ||||
|     #: Some SASL mechs require sending values as is,  | ||||
|     #: Some SASL mechs require sending values as is, | ||||
|     #: without converting base64. | ||||
|     plain_mechs = set(['X-MESSENGER-OAUTH2']) | ||||
|  | ||||
|   | ||||
| @@ -47,7 +47,7 @@ class Failure(StanzaBase): | ||||
|  | ||||
|     def get_condition(self): | ||||
|         """Return the condition element's name.""" | ||||
|         for child in self.xml.getchildren(): | ||||
|         for child in self.xml: | ||||
|             if "{%s}" % self.namespace in child.tag: | ||||
|                 cond = child.tag.split('}', 1)[-1] | ||||
|                 if cond in self.conditions: | ||||
| @@ -68,7 +68,7 @@ class Failure(StanzaBase): | ||||
|  | ||||
|     def del_condition(self): | ||||
|         """Remove the condition element.""" | ||||
|         for child in self.xml.getchildren(): | ||||
|         for child in self.xml: | ||||
|             if "{%s}" % self.condition_ns in child.tag: | ||||
|                 tag = child.tag.split('}', 1)[-1] | ||||
|                 if tag in self.conditions: | ||||
|   | ||||
| @@ -32,6 +32,7 @@ __all__ = [ | ||||
| #   'xep_0078',  # Non-SASL auth. Don't automatically load | ||||
|     'xep_0080',  # User Location | ||||
|     'xep_0082',  # XMPP Date and Time Profiles | ||||
|     'xep_0084',  # User Avatar | ||||
|     'xep_0085',  # Chat State Notifications | ||||
|     'xep_0086',  # Legacy Error Codes | ||||
|     'xep_0092',  # Software Version | ||||
| @@ -44,11 +45,16 @@ __all__ = [ | ||||
|     'xep_0163',  # Personal Eventing Protocol | ||||
|     'xep_0172',  # User Nickname | ||||
|     'xep_0184',  # Message Receipts | ||||
|     'xep_0186',  # Invisible Command | ||||
|     'xep_0198',  # Stream Management | ||||
|     'xep_0199',  # Ping | ||||
|     'xep_0202',  # Entity Time | ||||
|     'xep_0203',  # Delayed Delivery | ||||
|     'xep_0221',  # Data Forms Media Element | ||||
|     'xep_0222',  # Persistent Storage of Public Data via Pubsub | ||||
|     'xep_0223',  # Persistent Storage of Private Data via Pubsub | ||||
|     'xep_0224',  # Attention | ||||
|     'xep_0231',  # Bits of Binary | ||||
|     'xep_0249',  # Direct MUC Invitations | ||||
|     'xep_0258',  # Security Labels in XMPP | ||||
| ] | ||||
|   | ||||
| @@ -31,10 +31,10 @@ log = logging.getLogger(__name__) | ||||
| PLUGIN_REGISTRY = {} | ||||
|  | ||||
| #: In order to do cascading plugin disabling, reverse dependencies | ||||
| #: must be tracked.  | ||||
| #: must be tracked. | ||||
| PLUGIN_DEPENDENTS = {} | ||||
|  | ||||
| #: Only allow one thread to manipulate the plugin registry at a time.  | ||||
| #: Only allow one thread to manipulate the plugin registry at a time. | ||||
| REGISTRY_LOCK = threading.RLock() | ||||
|  | ||||
|  | ||||
| @@ -75,7 +75,7 @@ def load_plugin(name, module=None): | ||||
|                      plugins are in packages matching their name, | ||||
|                      even though the plugin class name does not | ||||
|                      have to match. | ||||
|     :param str module: The name of the base module to search  | ||||
|     :param str module: The name of the base module to search | ||||
|                        for the plugin. | ||||
|     """ | ||||
|     try: | ||||
| @@ -84,7 +84,7 @@ def load_plugin(name, module=None): | ||||
|                 module = 'sleekxmpp.plugins.%s' % name | ||||
|                 __import__(module) | ||||
|                 mod = sys.modules[module] | ||||
|             except: | ||||
|             except ImportError: | ||||
|                 module = 'sleekxmpp.features.%s' % name | ||||
|                 __import__(module) | ||||
|                 mod = sys.modules[module] | ||||
| @@ -103,11 +103,11 @@ def load_plugin(name, module=None): | ||||
|                 # we can work around dependency issues. | ||||
|                 plugin.old_style = True | ||||
|             register_plugin(plugin, name) | ||||
|     except: | ||||
|     except ImportError: | ||||
|         log.exception("Unable to load plugin: %s", name) | ||||
|  | ||||
|  | ||||
| class PluginManager(object):  | ||||
| class PluginManager(object): | ||||
|     def __init__(self, xmpp, config=None): | ||||
|         #: We will track all enabled plugins in a set so that we | ||||
|         #: can enable plugins in batches and pull in dependencies | ||||
| @@ -181,7 +181,7 @@ class PluginManager(object): | ||||
|  | ||||
|     def enable_all(self, names=None, config=None): | ||||
|         """Enable all registered plugins. | ||||
|          | ||||
|  | ||||
|         :param list names: A list of plugin names to enable. If | ||||
|                            none are provided, all registered plugins | ||||
|                            will be enabled. | ||||
| @@ -292,7 +292,7 @@ class BasePlugin(object): | ||||
|  | ||||
|     def post_init(self): | ||||
|         """Initialize any cross-plugin state. | ||||
|         | ||||
|  | ||||
|         Only needed if the plugin has circular dependencies. | ||||
|         """ | ||||
|         pass | ||||
|   | ||||
| @@ -81,7 +81,8 @@ class XEP_0027(BasePlugin): | ||||
|  | ||||
|     def _sign_presence(self, stanza): | ||||
|         if isinstance(stanza, Presence): | ||||
|             if stanza['type'] == 'available' or stanza['type'] in Presence.showtypes: | ||||
|             if stanza['type'] == 'available' or \ | ||||
|                     stanza['type'] in Presence.showtypes: | ||||
|                 stanza['signed'] = stanza['status'] | ||||
|         return stanza | ||||
|  | ||||
|   | ||||
| @@ -51,6 +51,3 @@ class Encrypted(ElementBase): | ||||
|         if self.xml.text: | ||||
|             return xmpp['xep_0027'].decrypt(self.xml.text, parent['to']) | ||||
|         return None | ||||
|  | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -339,8 +339,8 @@ class XEP_0030(BasePlugin): | ||||
|         if local: | ||||
|             log.debug("Looking up local disco#info data " + \ | ||||
|                       "for %s, node %s.", jid, node) | ||||
|             info = self.api['get_info'](jid, node,  | ||||
|                     kwargs.get('ifrom', None),  | ||||
|             info = self.api['get_info'](jid, node, | ||||
|                     kwargs.get('ifrom', None), | ||||
|                     kwargs) | ||||
|             info = self._fix_default_info(info) | ||||
|             return self._wrap(kwargs.get('ifrom', None), jid, info) | ||||
| @@ -348,8 +348,8 @@ class XEP_0030(BasePlugin): | ||||
|         if cached: | ||||
|             log.debug("Looking up cached disco#info data " + \ | ||||
|                       "for %s, node %s.", jid, node) | ||||
|             info = self.api['get_cached_info'](jid, node,  | ||||
|                     kwargs.get('ifrom', None),  | ||||
|             info = self.api['get_cached_info'](jid, node, | ||||
|                     kwargs.get('ifrom', None), | ||||
|                     kwargs) | ||||
|             if info is not None: | ||||
|                 return self._wrap(kwargs.get('ifrom', None), jid, info) | ||||
| @@ -405,8 +405,8 @@ class XEP_0030(BasePlugin): | ||||
|                         Otherwise the parameter is ignored. | ||||
|         """ | ||||
|         if local or jid is None: | ||||
|             items = self.api['get_items'](jid, node,  | ||||
|                     kwargs.get('ifrom', None),  | ||||
|             items = self.api['get_items'](jid, node, | ||||
|                     kwargs.get('ifrom', None), | ||||
|                     kwargs) | ||||
|             return self._wrap(kwargs.get('ifrom', None), jid, items) | ||||
|  | ||||
|   | ||||
| @@ -1,167 +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 sleekxmpp import Message | ||||
| from sleekxmpp.xmlstream.handler.callback import Callback | ||||
| from sleekxmpp.xmlstream.matcher.xpath import MatchXPath | ||||
| from sleekxmpp.xmlstream import register_stanza_plugin, ElementBase, ET, JID | ||||
| from sleekxmpp.plugins import BasePlugin, register_plugin | ||||
|  | ||||
|  | ||||
| class Addresses(ElementBase): | ||||
|     namespace = 'http://jabber.org/protocol/address' | ||||
|     name = 'addresses' | ||||
|     plugin_attrib = 'addresses' | ||||
|     interfaces = set(('addresses', 'bcc', 'cc', 'noreply', 'replyroom', 'replyto', 'to')) | ||||
|  | ||||
|     def addAddress(self, atype='to', jid='', node='', uri='', desc='', delivered=False): | ||||
|         address = Address(parent=self) | ||||
|         address['type'] = atype | ||||
|         address['jid'] = jid | ||||
|         address['node'] = node | ||||
|         address['uri'] = uri | ||||
|         address['desc'] = desc | ||||
|         address['delivered'] = delivered | ||||
|         return address | ||||
|  | ||||
|     def getAddresses(self, atype=None): | ||||
|         addresses = [] | ||||
|         for addrXML in self.xml.findall('{%s}address' % Address.namespace): | ||||
|             # ElementTree 1.2.6 does not support [@attr='value'] in findall | ||||
|             if atype is None or addrXML.attrib.get('type') == atype: | ||||
|                 addresses.append(Address(xml=addrXML, parent=None)) | ||||
|         return addresses | ||||
|  | ||||
|     def setAddresses(self, addresses, set_type=None): | ||||
|         self.delAddresses(set_type) | ||||
|         for addr in addresses: | ||||
|             addr = dict(addr) | ||||
|             # Remap 'type' to 'atype' to match the add method | ||||
|             if set_type is not None: | ||||
|                 addr['type'] = set_type | ||||
|             curr_type = addr.get('type', None) | ||||
|             if curr_type is not None: | ||||
|                 del addr['type'] | ||||
|                 addr['atype'] = curr_type | ||||
|             self.addAddress(**addr) | ||||
|  | ||||
|     def delAddresses(self, atype=None): | ||||
|         if atype is None: | ||||
|             return | ||||
|         for addrXML in self.xml.findall('{%s}address' % Address.namespace): | ||||
|             # ElementTree 1.2.6 does not support [@attr='value'] in findall | ||||
|             if addrXML.attrib.get('type') == atype: | ||||
|                 self.xml.remove(addrXML) | ||||
|  | ||||
|     # -------------------------------------------------------------- | ||||
|  | ||||
|     def delBcc(self): | ||||
|         self.delAddresses('bcc') | ||||
|  | ||||
|     def delCc(self): | ||||
|         self.delAddresses('cc') | ||||
|  | ||||
|     def delNoreply(self): | ||||
|         self.delAddresses('noreply') | ||||
|  | ||||
|     def delReplyroom(self): | ||||
|         self.delAddresses('replyroom') | ||||
|  | ||||
|     def delReplyto(self): | ||||
|         self.delAddresses('replyto') | ||||
|  | ||||
|     def delTo(self): | ||||
|         self.delAddresses('to') | ||||
|  | ||||
|     # -------------------------------------------------------------- | ||||
|  | ||||
|     def getBcc(self): | ||||
|         return self.getAddresses('bcc') | ||||
|  | ||||
|     def getCc(self): | ||||
|         return self.getAddresses('cc') | ||||
|  | ||||
|     def getNoreply(self): | ||||
|         return self.getAddresses('noreply') | ||||
|  | ||||
|     def getReplyroom(self): | ||||
|         return self.getAddresses('replyroom') | ||||
|  | ||||
|     def getReplyto(self): | ||||
|         return self.getAddresses('replyto') | ||||
|  | ||||
|     def getTo(self): | ||||
|         return self.getAddresses('to') | ||||
|  | ||||
|     # -------------------------------------------------------------- | ||||
|  | ||||
|     def setBcc(self, addresses): | ||||
|         self.setAddresses(addresses, 'bcc') | ||||
|  | ||||
|     def setCc(self, addresses): | ||||
|         self.setAddresses(addresses, 'cc') | ||||
|  | ||||
|     def setNoreply(self, addresses): | ||||
|         self.setAddresses(addresses, 'noreply') | ||||
|  | ||||
|     def setReplyroom(self, addresses): | ||||
|         self.setAddresses(addresses, 'replyroom') | ||||
|  | ||||
|     def setReplyto(self, addresses): | ||||
|         self.setAddresses(addresses, 'replyto') | ||||
|  | ||||
|     def setTo(self, addresses): | ||||
|         self.setAddresses(addresses, 'to') | ||||
|  | ||||
|  | ||||
| class Address(ElementBase): | ||||
|     namespace = 'http://jabber.org/protocol/address' | ||||
|     name = 'address' | ||||
|     plugin_attrib = 'address' | ||||
|     interfaces = set(('delivered', 'desc', 'jid', 'node', 'type', 'uri')) | ||||
|     address_types = set(('bcc', 'cc', 'noreply', 'replyroom', 'replyto', 'to')) | ||||
|  | ||||
|     def getDelivered(self): | ||||
|         return self.xml.attrib.get('delivered', False) | ||||
|  | ||||
|     def setDelivered(self, delivered): | ||||
|         if delivered: | ||||
|             self.xml.attrib['delivered'] = "true" | ||||
|         else: | ||||
|             del self['delivered'] | ||||
|  | ||||
|     def setUri(self, uri): | ||||
|         if uri: | ||||
|             del self['jid'] | ||||
|             del self['node'] | ||||
|             self.xml.attrib['uri'] = uri | ||||
|         elif 'uri' in self.xml.attrib: | ||||
|             del self.xml.attrib['uri'] | ||||
|  | ||||
|  | ||||
| class XEP_0033(BasePlugin): | ||||
|  | ||||
|     """ | ||||
|     XEP-0033: Extended Stanza Addressing | ||||
|     """ | ||||
|  | ||||
|     name = 'xep_0033' | ||||
|     description = 'XEP-0033: Extended Stanza Addressing' | ||||
|     dependencies = set(['xep_0033']) | ||||
|  | ||||
|     def plugin_init(self): | ||||
|         self.xep = '0033' | ||||
|  | ||||
|         register_stanza_plugin(Message, Addresses) | ||||
|  | ||||
|         self.xmpp.plugin['xep_0030'].add_feature(Addresses.namespace) | ||||
|  | ||||
|  | ||||
| xep_0033 = XEP_0033 | ||||
| register_plugin(XEP_0033) | ||||
							
								
								
									
										20
									
								
								sleekxmpp/plugins/xep_0033/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								sleekxmpp/plugins/xep_0033/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,20 @@ | ||||
| """ | ||||
|     SleekXMPP: The Sleek XMPP Library | ||||
|     Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout | ||||
|     This file is part of SleekXMPP. | ||||
|  | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
|  | ||||
| from sleekxmpp.plugins.base import register_plugin | ||||
|  | ||||
| from sleekxmpp.plugins.xep_0033 import stanza | ||||
| from sleekxmpp.plugins.xep_0033.stanza import Addresses, Address | ||||
| from sleekxmpp.plugins.xep_0033.addresses import XEP_0033 | ||||
|  | ||||
|  | ||||
| register_plugin(XEP_0033) | ||||
|  | ||||
| # Retain some backwards compatibility | ||||
| xep_0033 = XEP_0033 | ||||
| Addresses.addAddress = Addresses.add_address | ||||
							
								
								
									
										32
									
								
								sleekxmpp/plugins/xep_0033/addresses.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								sleekxmpp/plugins/xep_0033/addresses.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,32 @@ | ||||
| """ | ||||
|     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 sleekxmpp import Message, Presence | ||||
| from sleekxmpp.xmlstream import register_stanza_plugin | ||||
| from sleekxmpp.plugins import BasePlugin | ||||
| from sleekxmpp.plugins.xep_0033 import stanza, Addresses | ||||
|  | ||||
|  | ||||
| class XEP_0033(BasePlugin): | ||||
|  | ||||
|     """ | ||||
|     XEP-0033: Extended Stanza Addressing | ||||
|     """ | ||||
|  | ||||
|     name = 'xep_0033' | ||||
|     description = 'XEP-0033: Extended Stanza Addressing' | ||||
|     dependencies = set(['xep_0030']) | ||||
|     stanza = stanza | ||||
|  | ||||
|     def plugin_init(self): | ||||
|         self.xmpp['xep_0030'].add_feature(Addresses.namespace) | ||||
|  | ||||
|         register_stanza_plugin(Message, Addresses) | ||||
|         register_stanza_plugin(Presence, Addresses) | ||||
							
								
								
									
										131
									
								
								sleekxmpp/plugins/xep_0033/stanza.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										131
									
								
								sleekxmpp/plugins/xep_0033/stanza.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,131 @@ | ||||
| """ | ||||
|     SleekXMPP: The Sleek XMPP Library | ||||
|     Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout | ||||
|     This file is part of SleekXMPP. | ||||
|  | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
|  | ||||
| from sleekxmpp.xmlstream import JID, ElementBase, ET, register_stanza_plugin | ||||
|  | ||||
|  | ||||
| class Addresses(ElementBase): | ||||
|  | ||||
|     name = 'addresses' | ||||
|     namespace = 'http://jabber.org/protocol/address' | ||||
|     plugin_attrib = 'addresses' | ||||
|     interfaces = set() | ||||
|  | ||||
|     def add_address(self, atype='to', jid='', node='', uri='', | ||||
|                           desc='', delivered=False): | ||||
|         addr = Address(parent=self) | ||||
|         addr['type'] = atype | ||||
|         addr['jid'] = jid | ||||
|         addr['node'] = node | ||||
|         addr['uri'] = uri | ||||
|         addr['desc'] = desc | ||||
|         addr['delivered'] = delivered | ||||
|  | ||||
|         return addr | ||||
|  | ||||
|     # Additional methods for manipulating sets of addresses | ||||
|     # based on type are generated below. | ||||
|  | ||||
|  | ||||
| class Address(ElementBase): | ||||
|  | ||||
|     name = 'address' | ||||
|     namespace = 'http://jabber.org/protocol/address' | ||||
|     plugin_attrib = 'address' | ||||
|     interfaces = set(['type', 'jid', 'node', 'uri', 'desc', 'delivered']) | ||||
|  | ||||
|     address_types = set(('bcc', 'cc', 'noreply', 'replyroom', 'replyto', 'to')) | ||||
|  | ||||
|     def get_jid(self): | ||||
|         return JID(self._get_attr('jid')) | ||||
|  | ||||
|     def set_jid(self, value): | ||||
|         self._set_attr('jid', str(value)) | ||||
|  | ||||
|     def get_delivered(self): | ||||
|         value = self._get_attr('delivered', False) | ||||
|         return value and value.lower() in ('true', '1') | ||||
|  | ||||
|     def set_delivered(self, delivered): | ||||
|         if delivered: | ||||
|             self._set_attr('delivered', 'true') | ||||
|         else: | ||||
|             del self['delivered'] | ||||
|  | ||||
|     def set_uri(self, uri): | ||||
|         if uri: | ||||
|             del self['jid'] | ||||
|             del self['node'] | ||||
|             self._set_attr('uri', uri) | ||||
|         else: | ||||
|             self._del_attr('uri') | ||||
|  | ||||
|  | ||||
| # ===================================================================== | ||||
| # Auto-generate address type filters for the Addresses class. | ||||
|  | ||||
| def _addr_filter(atype): | ||||
|     def _type_filter(addr): | ||||
|         if isinstance(addr, Address): | ||||
|             if atype == 'all' or addr['type'] == atype: | ||||
|                 return True | ||||
|         return False | ||||
|     return _type_filter | ||||
|  | ||||
|  | ||||
| def _build_methods(atype): | ||||
|  | ||||
|     def get_multi(self): | ||||
|         return list(filter(_addr_filter(atype), self)) | ||||
|  | ||||
|     def set_multi(self, value): | ||||
|         del self[atype] | ||||
|         for addr in value: | ||||
|  | ||||
|             # Support assigning dictionary versions of addresses | ||||
|             # instead of full Address objects. | ||||
|             if not isinstance(addr, Address): | ||||
|                 if atype != 'all': | ||||
|                     addr['type'] = atype | ||||
|                 elif 'atype' in addr and 'type' not in addr: | ||||
|                     addr['type'] = addr['atype'] | ||||
|                 addrObj = Address() | ||||
|                 addrObj.values = addr | ||||
|                 addr = addrObj | ||||
|  | ||||
|             self.append(addr) | ||||
|  | ||||
|     def del_multi(self): | ||||
|         res = list(filter(_addr_filter(atype), self)) | ||||
|         for addr in res: | ||||
|             self.iterables.remove(addr) | ||||
|             self.xml.remove(addr.xml) | ||||
|  | ||||
|     return get_multi, set_multi, del_multi | ||||
|  | ||||
|  | ||||
| for atype in ('all', 'bcc', 'cc', 'noreply', 'replyroom', 'replyto', 'to'): | ||||
|     get_multi, set_multi, del_multi = _build_methods(atype) | ||||
|  | ||||
|     Addresses.interfaces.add(atype) | ||||
|     setattr(Addresses, "get_%s" % atype, get_multi) | ||||
|     setattr(Addresses, "set_%s" % atype, set_multi) | ||||
|     setattr(Addresses, "del_%s" % atype, del_multi) | ||||
|  | ||||
|     # To retain backwards compatibility: | ||||
|     setattr(Addresses, "get%s" % atype.title(), get_multi) | ||||
|     setattr(Addresses, "set%s" % atype.title(), set_multi) | ||||
|     setattr(Addresses, "del%s" % atype.title(), del_multi) | ||||
|     if atype == 'all': | ||||
|         Addresses.interfaces.add('addresses') | ||||
|         setattr(Addresses, "getAddresses", get_multi) | ||||
|         setattr(Addresses, "setAddresses", set_multi) | ||||
|         setattr(Addresses, "delAddresses", del_multi) | ||||
|  | ||||
|  | ||||
| register_stanza_plugin(Addresses, Address, iterable=True) | ||||
| @@ -110,14 +110,14 @@ class Command(ElementBase): | ||||
|         """ | ||||
|         Return the set of allowable next actions. | ||||
|         """ | ||||
|         actions = [] | ||||
|         actions = set() | ||||
|         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) | ||||
|                     actions.add(action) | ||||
|         return actions | ||||
|  | ||||
|     def del_actions(self): | ||||
|   | ||||
| @@ -72,6 +72,7 @@ class Nickname(ElementBase): | ||||
|     name = 'NICKNAME' | ||||
|     namespace = 'vcard-temp' | ||||
|     plugin_attrib = name | ||||
|     plugin_multi_attrib = 'nicknames' | ||||
|     interfaces = set([name]) | ||||
|     is_extension = True | ||||
|  | ||||
| @@ -94,6 +95,7 @@ class Email(ElementBase): | ||||
|     name = 'EMAIL' | ||||
|     namespace = 'vcard-temp' | ||||
|     plugin_attrib = name | ||||
|     plugin_multi_attrib = 'emails' | ||||
|     interfaces = set(['HOME', 'WORK', 'INTERNET', 'PREF', 'X400', 'USERID']) | ||||
|     sub_interfaces = set(['USERID']) | ||||
|     bool_interfaces = set(['HOME', 'WORK', 'INTERNET', 'PREF', 'X400']) | ||||
| @@ -103,8 +105,9 @@ class Address(ElementBase): | ||||
|     name = 'ADR' | ||||
|     namespace = 'vcard-temp' | ||||
|     plugin_attrib = name | ||||
|     interfaces = set(['HOME', 'WORK', 'POSTAL', 'PARCEL', 'DOM', 'INTL',  | ||||
|                       'PREF', 'POBOX', 'EXTADD', 'STREET', 'LOCALITY',  | ||||
|     plugin_multi_attrib = 'addresses' | ||||
|     interfaces = set(['HOME', 'WORK', 'POSTAL', 'PARCEL', 'DOM', 'INTL', | ||||
|                       'PREF', 'POBOX', 'EXTADD', 'STREET', 'LOCALITY', | ||||
|                       'REGION', 'PCODE', 'CTRY']) | ||||
|     sub_interfaces = set(['POBOX', 'EXTADD', 'STREET', 'LOCALITY', | ||||
|                           'REGION', 'PCODE', 'CTRY']) | ||||
| @@ -115,12 +118,13 @@ class Telephone(ElementBase): | ||||
|     name = 'TEL' | ||||
|     namespace = 'vcard-temp' | ||||
|     plugin_attrib = name | ||||
|     plugin_multi_attrib = 'telephone_numbers' | ||||
|     interfaces = set(['HOME', 'WORK', 'VOICE', 'FAX', 'PAGER', 'MSG', | ||||
|                       'CELL', 'VIDEO', 'BBS', 'MODEM', 'ISDN', 'PCS', | ||||
|                       'PREF', 'NUMBER']) | ||||
|     sub_interfaces = set(['NUMBER']) | ||||
|     bool_interfaces = set(['HOME', 'WORK', 'VOICE', 'FAX', 'PAGER',  | ||||
|                            'MSG', 'CELL', 'VIDEO', 'BBS', 'MODEM',  | ||||
|     bool_interfaces = set(['HOME', 'WORK', 'VOICE', 'FAX', 'PAGER', | ||||
|                            'MSG', 'CELL', 'VIDEO', 'BBS', 'MODEM', | ||||
|                            'ISDN', 'PCS', 'PREF']) | ||||
|  | ||||
|     def setup(self, xml=None): | ||||
| @@ -138,9 +142,10 @@ class Label(ElementBase): | ||||
|     name = 'LABEL' | ||||
|     namespace = 'vcard-temp' | ||||
|     plugin_attrib = name | ||||
|     plugin_multi_attrib = 'labels' | ||||
|     interfaces = set(['HOME', 'WORK', 'POSTAL', 'PARCEL', 'DOM', 'INT', | ||||
|                       'PREF', 'lines']) | ||||
|     bool_interfaces = set(['HOME', 'WORK', 'POSTAL', 'PARCEL', 'DOM',  | ||||
|     bool_interfaces = set(['HOME', 'WORK', 'POSTAL', 'PARCEL', 'DOM', | ||||
|                            'INT', 'PREF']) | ||||
|  | ||||
|     def add_line(self, value): | ||||
| @@ -171,6 +176,7 @@ class Geo(ElementBase): | ||||
|     name = 'GEO' | ||||
|     namespace = 'vcard-temp' | ||||
|     plugin_attrib = name | ||||
|     plugin_multi_attrib = 'geolocations' | ||||
|     interfaces = set(['LAT', 'LON']) | ||||
|     sub_interfaces = interfaces | ||||
|  | ||||
| @@ -179,6 +185,7 @@ class Org(ElementBase): | ||||
|     name = 'ORG' | ||||
|     namespace = 'vcard-temp' | ||||
|     plugin_attrib = name | ||||
|     plugin_multi_attrib = 'organizations' | ||||
|     interfaces = set(['ORGNAME', 'ORGUNIT', 'orgunits']) | ||||
|     sub_interfaces = set(['ORGNAME', 'ORGUNIT']) | ||||
|  | ||||
| @@ -210,6 +217,7 @@ class Photo(ElementBase): | ||||
|     name = 'PHOTO' | ||||
|     namespace = 'vcard-temp' | ||||
|     plugin_attrib = name | ||||
|     plugin_multi_attrib = 'photos' | ||||
|     interfaces = set(['TYPE', 'EXTVAL']) | ||||
|     sub_interfaces = interfaces | ||||
|  | ||||
| @@ -218,14 +226,16 @@ class Logo(ElementBase): | ||||
|     name = 'LOGO' | ||||
|     namespace = 'vcard-temp' | ||||
|     plugin_attrib = name | ||||
|     plugin_multi_attrib = 'logos' | ||||
|     interfaces = set(['TYPE', 'EXTVAL']) | ||||
|     sub_interfaces = interfaces | ||||
|  | ||||
|  | ||||
| class Sound(ElementBase): | ||||
|     name = 'LOGO' | ||||
|     name = 'SOUND' | ||||
|     namespace = 'vcard-temp' | ||||
|     plugin_attrib = name | ||||
|     plugin_multi_attrib = 'sounds' | ||||
|     interfaces = set(['PHONETC', 'EXTVAL']) | ||||
|     sub_interfaces = interfaces | ||||
|  | ||||
| @@ -264,6 +274,7 @@ class Classification(ElementBase): | ||||
|     name = 'CLASS' | ||||
|     namespace = 'vcard-temp' | ||||
|     plugin_attrib = name | ||||
|     plugin_multi_attrib = 'classifications' | ||||
|     interfaces = set(['PUBLIC', 'PRIVATE', 'CONFIDENTIAL']) | ||||
|     bool_interfaces = interfaces | ||||
|  | ||||
| @@ -272,6 +283,7 @@ class Categories(ElementBase): | ||||
|     name = 'CATEGORIES' | ||||
|     namespace = 'vcard-temp' | ||||
|     plugin_attrib = name | ||||
|     plugin_multi_attrib = 'categories' | ||||
|     interfaces = set([name]) | ||||
|     is_extension = True | ||||
|  | ||||
| @@ -301,6 +313,7 @@ class Birthday(ElementBase): | ||||
|     name = 'BDAY' | ||||
|     namespace = 'vcard-temp' | ||||
|     plugin_attrib = name | ||||
|     plugin_multi_attrib = 'birthdays' | ||||
|     interfaces = set([name]) | ||||
|     is_extension = True | ||||
|  | ||||
| @@ -319,6 +332,7 @@ class Rev(ElementBase): | ||||
|     name = 'REV' | ||||
|     namespace = 'vcard-temp' | ||||
|     plugin_attrib = name | ||||
|     plugin_multi_attrib = 'revision_dates' | ||||
|     interfaces = set([name]) | ||||
|     is_extension = True | ||||
|  | ||||
| @@ -337,6 +351,7 @@ class Title(ElementBase): | ||||
|     name = 'TITLE' | ||||
|     namespace = 'vcard-temp' | ||||
|     plugin_attrib = name | ||||
|     plugin_multi_attrib = 'titles' | ||||
|     interfaces = set([name]) | ||||
|     is_extension = True | ||||
|  | ||||
| @@ -351,6 +366,7 @@ class Role(ElementBase): | ||||
|     name = 'ROLE' | ||||
|     namespace = 'vcard-temp' | ||||
|     plugin_attrib = name | ||||
|     plugin_multi_attrib = 'roles' | ||||
|     interfaces = set([name]) | ||||
|     is_extension = True | ||||
|  | ||||
| @@ -365,6 +381,7 @@ class Note(ElementBase): | ||||
|     name = 'NOTE' | ||||
|     namespace = 'vcard-temp' | ||||
|     plugin_attrib = name | ||||
|     plugin_multi_attrib = 'notes' | ||||
|     interfaces = set([name]) | ||||
|     is_extension = True | ||||
|  | ||||
| @@ -379,6 +396,7 @@ class Desc(ElementBase): | ||||
|     name = 'DESC' | ||||
|     namespace = 'vcard-temp' | ||||
|     plugin_attrib = name | ||||
|     plugin_multi_attrib = 'descriptions' | ||||
|     interfaces = set([name]) | ||||
|     is_extension = True | ||||
|  | ||||
| @@ -393,6 +411,7 @@ class URL(ElementBase): | ||||
|     name = 'URL' | ||||
|     namespace = 'vcard-temp' | ||||
|     plugin_attrib = name | ||||
|     plugin_multi_attrib = 'urls' | ||||
|     interfaces = set([name]) | ||||
|     is_extension = True | ||||
|  | ||||
| @@ -407,6 +426,7 @@ class UID(ElementBase): | ||||
|     name = 'UID' | ||||
|     namespace = 'vcard-temp' | ||||
|     plugin_attrib = name | ||||
|     plugin_multi_attrib = 'uids' | ||||
|     interfaces = set([name]) | ||||
|     is_extension = True | ||||
|  | ||||
| @@ -421,6 +441,7 @@ class ProdID(ElementBase): | ||||
|     name = 'PRODID' | ||||
|     namespace = 'vcard-temp' | ||||
|     plugin_attrib = name | ||||
|     plugin_multi_attrib = 'product_ids' | ||||
|     interfaces = set([name]) | ||||
|     is_extension = True | ||||
|  | ||||
| @@ -435,6 +456,7 @@ class Mailer(ElementBase): | ||||
|     name = 'MAILER' | ||||
|     namespace = 'vcard-temp' | ||||
|     plugin_attrib = name | ||||
|     plugin_multi_attrib = 'mailers' | ||||
|     interfaces = set([name]) | ||||
|     is_extension = True | ||||
|  | ||||
| @@ -449,6 +471,7 @@ class SortString(ElementBase): | ||||
|     name = 'SORT-STRING' | ||||
|     namespace = 'vcard-temp' | ||||
|     plugin_attrib = 'SORT_STRING' | ||||
|     plugin_multi_attrib = 'sort_strings' | ||||
|     interfaces = set([name]) | ||||
|     is_extension = True | ||||
|  | ||||
| @@ -463,6 +486,7 @@ class Agent(ElementBase): | ||||
|     name = 'AGENT' | ||||
|     namespace = 'vcard-temp' | ||||
|     plugin_attrib = name | ||||
|     plugin_multi_attrib = 'agents' | ||||
|     interfaces = set(['EXTVAL']) | ||||
|     sub_interfaces = interfaces | ||||
|  | ||||
| @@ -471,6 +495,7 @@ class JabberID(ElementBase): | ||||
|     name = 'JABBERID' | ||||
|     namespace = 'vcard-temp' | ||||
|     plugin_attrib = name | ||||
|     plugin_multi_attrib = 'jids' | ||||
|     interfaces = set([name]) | ||||
|     is_extension = True | ||||
|  | ||||
| @@ -485,11 +510,12 @@ class TimeZone(ElementBase): | ||||
|     name = 'TZ' | ||||
|     namespace = 'vcard-temp' | ||||
|     plugin_attrib = name | ||||
|     plugin_multi_attrib = 'timezones' | ||||
|     interfaces = set([name]) | ||||
|     is_extension = True | ||||
|  | ||||
|     def set_tz(self, value): | ||||
|         time = xep_0082.time(offset=value)  | ||||
|         time = xep_0082.time(offset=value) | ||||
|         if time[-1] == 'Z': | ||||
|             self.xml.text = 'Z' | ||||
|         else: | ||||
|   | ||||
| @@ -53,7 +53,7 @@ class XEP_0054(BasePlugin): | ||||
|     def make_vcard(self): | ||||
|         return VCardTemp() | ||||
|  | ||||
|     def get_vcard(self, jid=None, ifrom=None, local=False, cached=False,  | ||||
|     def get_vcard(self, jid=None, ifrom=None, local=False, cached=False, | ||||
|                   block=True, callback=None, timeout=None): | ||||
|         if self.xmpp.is_component and jid.domain == self.xmpp.boundjid.domain: | ||||
|             local = True | ||||
| @@ -84,12 +84,12 @@ class XEP_0054(BasePlugin): | ||||
|         iq.enable('vcard_temp') | ||||
|  | ||||
|         vcard = iq.send(block=block, callback=callback, timeout=timeout) | ||||
|          | ||||
|  | ||||
|         if block: | ||||
|             self.api['set_vcard'](vcard['from'], args=vcard['vcard_temp']) | ||||
|             return vcard | ||||
|  | ||||
|     def publish_vcard(self, vcard=None, jid=None, block=True, ifrom=None,  | ||||
|     def publish_vcard(self, vcard=None, jid=None, block=True, ifrom=None, | ||||
|                       callback=None, timeout=None): | ||||
|         if self.xmpp.is_component: | ||||
|             self.api['set_vcard'](jid, None, ifrom, vcard) | ||||
| @@ -107,7 +107,7 @@ class XEP_0054(BasePlugin): | ||||
|             self.api['set_vcard'](jid=iq['from'], args=iq['vcard_temp']) | ||||
|             return | ||||
|         elif iq['type'] == 'get': | ||||
|             vcard = self.api['get_vard'](iq['from'].bare) | ||||
|             vcard = self.api['get_vcard'](iq['from'].bare) | ||||
|             if isinstance(vcard, Iq): | ||||
|                 vcard.send() | ||||
|             else: | ||||
|   | ||||
| @@ -74,7 +74,7 @@ class Set(ElementBase): | ||||
|         if fi is not None: | ||||
|             if val: | ||||
|                 fi.attrib['index'] = val | ||||
|             else: | ||||
|             elif 'index' in fi.attrib: | ||||
|                 del fi.attrib['index'] | ||||
|         elif val: | ||||
|             fi = ET.Element("{%s}first" % (self.namespace)) | ||||
| @@ -93,7 +93,7 @@ class Set(ElementBase): | ||||
|  | ||||
|     def set_before(self, val): | ||||
|         b = self.xml.find("{%s}before" % (self.namespace)) | ||||
|         if b is None and val == True: | ||||
|         if b is None and val is True: | ||||
|             self._set_sub_text('{%s}before' % self.namespace, '', True) | ||||
|         else: | ||||
|             self._set_sub_text('{%s}before' % self.namespace, val) | ||||
|   | ||||
| @@ -77,12 +77,12 @@ class Item(ElementBase): | ||||
|         self.append(value) | ||||
|  | ||||
|     def get_payload(self): | ||||
|         childs = self.xml.getchildren() | ||||
|         childs = list(self.xml) | ||||
|         if len(childs) > 0: | ||||
|             return childs[0] | ||||
|  | ||||
|     def del_payload(self): | ||||
|         for child in self.xml.getchildren(): | ||||
|         for child in self.xml: | ||||
|             self.xml.remove(child) | ||||
|  | ||||
|  | ||||
| @@ -254,12 +254,12 @@ class PubsubState(ElementBase): | ||||
|         self.xml.append(value) | ||||
|  | ||||
|     def get_payload(self): | ||||
|         childs = self.xml.getchildren() | ||||
|         childs = list(self.xml) | ||||
|         if len(childs) > 0: | ||||
|             return childs[0] | ||||
|  | ||||
|     def del_payload(self): | ||||
|         for child in self.xml.getchildren(): | ||||
|         for child in self.xml: | ||||
|             self.xml.remove(child) | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -33,7 +33,7 @@ class PubsubErrorCondition(ElementBase): | ||||
|  | ||||
|     def get_condition(self): | ||||
|         """Return the condition element's name.""" | ||||
|         for child in self.parent().xml.getchildren(): | ||||
|         for child in self.parent().xml: | ||||
|             if "{%s}" % self.condition_ns in child.tag: | ||||
|                 cond = child.tag.split('}', 1)[-1] | ||||
|                 if cond in self.conditions: | ||||
| @@ -55,7 +55,7 @@ class PubsubErrorCondition(ElementBase): | ||||
|  | ||||
|     def del_condition(self): | ||||
|         """Remove the condition element.""" | ||||
|         for child in self.parent().xml.getchildren(): | ||||
|         for child in self.parent().xml: | ||||
|             if "{%s}" % self.condition_ns in child.tag: | ||||
|                 tag = child.tag.split('}', 1)[-1] | ||||
|                 if tag in self.conditions: | ||||
|   | ||||
| @@ -31,12 +31,12 @@ class EventItem(ElementBase): | ||||
|         self.xml.append(value) | ||||
|  | ||||
|     def get_payload(self): | ||||
|         childs = self.xml.getchildren() | ||||
|         childs = list(self.xml) | ||||
|         if len(childs) > 0: | ||||
|             return childs[0] | ||||
|  | ||||
|     def del_payload(self): | ||||
|         for child in self.xml.getchildren(): | ||||
|         for child in self.xml: | ||||
|             self.xml.remove(child) | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -39,5 +39,3 @@ class AuthFeature(ElementBase): | ||||
|     interfaces = set() | ||||
|     plugin_tag_map = {} | ||||
|     plugin_attrib_map = {} | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -40,33 +40,33 @@ class XEP_0080(BasePlugin): | ||||
|             accuracy    -- Horizontal GPS error in meters. | ||||
|             alt         -- Altitude in meters above or below sea level. | ||||
|             area        -- A named area such as a campus or neighborhood. | ||||
|             bearing     -- GPS bearing (direction in which the entity is  | ||||
|                            heading to reach its next waypoint), measured in  | ||||
|             bearing     -- GPS bearing (direction in which the entity is | ||||
|                            heading to reach its next waypoint), measured in | ||||
|                            decimal degrees relative to true north. | ||||
|             building    -- A specific building on a street or in an area. | ||||
|             country     -- The nation where the user is located. | ||||
|             countrycode -- The ISO 3166 two-letter country code. | ||||
|             datum       -- GPS datum. | ||||
|             description -- A natural-language name for or description of  | ||||
|             description -- A natural-language name for or description of | ||||
|                            the location. | ||||
|             error       -- Horizontal GPS error in arc minutes. Obsoleted by | ||||
|                            the accuracy parameter. | ||||
|             floor       -- A particular floor in a building. | ||||
|             lat         -- Latitude in decimal degrees North. | ||||
|             locality    -- A locality within the administrative region, such  | ||||
|             locality    -- A locality within the administrative region, such | ||||
|                            as a town or city. | ||||
|             lon         -- Longitude in decimal degrees East. | ||||
|             postalcode  -- A code used for postal delivery. | ||||
|             region      -- An administrative region of the nation, such  | ||||
|             region      -- An administrative region of the nation, such | ||||
|                            as a state or province. | ||||
|             room        -- A particular room in a building. | ||||
|             speed       -- The speed at which the entity is moving,  | ||||
|             speed       -- The speed at which the entity is moving, | ||||
|                            in meters per second. | ||||
|             street      -- A thoroughfare within the locality, or a crossing | ||||
|                            of two thoroughfares. | ||||
|             text        -- A catch-all element that captures any other  | ||||
|             text        -- A catch-all element that captures any other | ||||
|                            information about the location. | ||||
|             timestamp   -- UTC timestamp specifying the moment when the  | ||||
|             timestamp   -- UTC timestamp specifying the moment when the | ||||
|                            reading was taken. | ||||
|             uri         -- A URI or URL pointing to information about | ||||
|                            the location. | ||||
| @@ -115,7 +115,7 @@ class XEP_0080(BasePlugin): | ||||
|                         be executed when a reply stanza is received. | ||||
|         """ | ||||
|         geoloc = Geoloc() | ||||
|         return self.xmpp['xep_0163'].publish(geoloc,  | ||||
|         return self.xmpp['xep_0163'].publish(geoloc, | ||||
|                 ifrom=ifrom, | ||||
|                 block=block, | ||||
|                 callback=callback, | ||||
|   | ||||
| @@ -31,33 +31,33 @@ class Geoloc(ElementBase): | ||||
|         accuracy    -- Horizontal GPS error in meters. | ||||
|         alt         -- Altitude in meters above or below sea level. | ||||
|         area        -- A named area such as a campus or neighborhood. | ||||
|         bearing     -- GPS bearing (direction in which the entity is  | ||||
|                        heading to reach its next waypoint), measured in  | ||||
|         bearing     -- GPS bearing (direction in which the entity is | ||||
|                        heading to reach its next waypoint), measured in | ||||
|                        decimal degrees relative to true north. | ||||
|         building    -- A specific building on a street or in an area. | ||||
|         country     -- The nation where the user is located. | ||||
|         countrycode -- The ISO 3166 two-letter country code. | ||||
|         datum       -- GPS datum. | ||||
|         description -- A natural-language name for or description of  | ||||
|         description -- A natural-language name for or description of | ||||
|                        the location. | ||||
|         error       -- Horizontal GPS error in arc minutes. Obsoleted by | ||||
|                        the accuracy parameter. | ||||
|         floor       -- A particular floor in a building. | ||||
|         lat         -- Latitude in decimal degrees North. | ||||
|         locality    -- A locality within the administrative region, such  | ||||
|         locality    -- A locality within the administrative region, such | ||||
|                        as a town or city. | ||||
|         lon         -- Longitude in decimal degrees East. | ||||
|         postalcode  -- A code used for postal delivery. | ||||
|         region      -- An administrative region of the nation, such  | ||||
|         region      -- An administrative region of the nation, such | ||||
|                        as a state or province. | ||||
|         room        -- A particular room in a building. | ||||
|         speed       -- The speed at which the entity is moving,  | ||||
|         speed       -- The speed at which the entity is moving, | ||||
|                        in meters per second. | ||||
|         street      -- A thoroughfare within the locality, or a crossing | ||||
|                        of two thoroughfares. | ||||
|         text        -- A catch-all element that captures any other  | ||||
|         text        -- A catch-all element that captures any other | ||||
|                        information about the location. | ||||
|         timestamp   -- UTC timestamp specifying the moment when the  | ||||
|         timestamp   -- UTC timestamp specifying the moment when the | ||||
|                        reading was taken. | ||||
|         uri         -- A URI or URL pointing to information about | ||||
|                        the location. | ||||
| @@ -65,10 +65,10 @@ class Geoloc(ElementBase): | ||||
|  | ||||
|     namespace = 'http://jabber.org/protocol/geoloc' | ||||
|     name = 'geoloc' | ||||
|     interfaces = set(('accuracy', 'alt', 'area', 'bearing', 'building',  | ||||
|                       'country', 'countrycode', 'datum', 'dscription',  | ||||
|                       'error', 'floor', 'lat', 'locality', 'lon',  | ||||
|                       'postalcode', 'region', 'room', 'speed', 'street',  | ||||
|     interfaces = set(('accuracy', 'alt', 'area', 'bearing', 'building', | ||||
|                       'country', 'countrycode', 'datum', 'dscription', | ||||
|                       'error', 'floor', 'lat', 'locality', 'lon', | ||||
|                       'postalcode', 'region', 'room', 'speed', 'street', | ||||
|                       'text', 'timestamp', 'uri')) | ||||
|     sub_interfaces = interfaces | ||||
|     plugin_attrib = name | ||||
| @@ -88,7 +88,7 @@ class Geoloc(ElementBase): | ||||
|         """ | ||||
|         self._set_sub_text('accuracy', text=str(accuracy)) | ||||
|         return self | ||||
|      | ||||
|  | ||||
|     def get_accuracy(self): | ||||
|         """ | ||||
|         Return the value of the <accuracy> element as an integer. | ||||
| @@ -111,7 +111,7 @@ class Geoloc(ElementBase): | ||||
|         """ | ||||
|         self._set_sub_text('alt', text=str(alt)) | ||||
|         return self | ||||
|      | ||||
|  | ||||
|     def get_alt(self): | ||||
|         """ | ||||
|         Return the value of the <alt> element as an integer. | ||||
| @@ -130,8 +130,8 @@ class Geoloc(ElementBase): | ||||
|         Set the value of the <bearing> element. | ||||
|  | ||||
|         Arguments: | ||||
|             bearing -- GPS bearing (direction in which the entity is heading  | ||||
|                        to reach its next waypoint), measured in decimal  | ||||
|             bearing -- GPS bearing (direction in which the entity is heading | ||||
|                        to reach its next waypoint), measured in decimal | ||||
|                        degrees relative to true north | ||||
|         """ | ||||
|         self._set_sub_text('bearing', text=str(bearing)) | ||||
| @@ -155,7 +155,7 @@ class Geoloc(ElementBase): | ||||
|         Set the value of the <error> element. | ||||
|  | ||||
|         Arguments: | ||||
|             error -- Horizontal GPS error in arc minutes; this  | ||||
|             error -- Horizontal GPS error in arc minutes; this | ||||
|                      element is deprecated in favor of <accuracy/> | ||||
|         """ | ||||
|         self._set_sub_text('error', text=str(error)) | ||||
| @@ -183,7 +183,7 @@ class Geoloc(ElementBase): | ||||
|         """ | ||||
|         self._set_sub_text('lat', text=str(lat)) | ||||
|         return self | ||||
|      | ||||
|  | ||||
|     def get_lat(self): | ||||
|         """ | ||||
|         Return the value of the <lat> element as a float. | ||||
| @@ -196,7 +196,7 @@ class Geoloc(ElementBase): | ||||
|                 return float(p) | ||||
|             except ValueError: | ||||
|                 return None | ||||
|    | ||||
|  | ||||
|     def set_lon(self, lon): | ||||
|         """ | ||||
|         Set the value of the <lon> element. | ||||
| @@ -225,12 +225,12 @@ class Geoloc(ElementBase): | ||||
|         Set the value of the <speed> element. | ||||
|  | ||||
|         Arguments: | ||||
|             speed -- The speed at which the entity is moving,  | ||||
|             speed -- The speed at which the entity is moving, | ||||
|                      in meters per second | ||||
|         """ | ||||
|         self._set_sub_text('speed', text=str(speed)) | ||||
|         return self | ||||
|      | ||||
|  | ||||
|     def get_speed(self): | ||||
|         """ | ||||
|         Return the value of the <speed> element as a float. | ||||
|   | ||||
| @@ -42,6 +42,7 @@ def format_date(time_obj): | ||||
|         time_obj = time_obj.date() | ||||
|     return time_obj.isoformat() | ||||
|  | ||||
|  | ||||
| def format_time(time_obj): | ||||
|     """ | ||||
|     Return a formatted string version of a time object. | ||||
| @@ -60,6 +61,7 @@ def format_time(time_obj): | ||||
|         return '%sZ' % timestamp | ||||
|     return timestamp | ||||
|  | ||||
|  | ||||
| def format_datetime(time_obj): | ||||
|     """ | ||||
|     Return a formatted string version of a datetime object. | ||||
| @@ -76,6 +78,7 @@ def format_datetime(time_obj): | ||||
|         return '%sZ' % timestamp | ||||
|     return timestamp | ||||
|  | ||||
|  | ||||
| def date(year=None, month=None, day=None, obj=False): | ||||
|     """ | ||||
|     Create a date only timestamp for the given instant. | ||||
| @@ -98,9 +101,10 @@ def date(year=None, month=None, day=None, obj=False): | ||||
|         day = today.day | ||||
|     value = dt.date(year, month, day) | ||||
|     if obj: | ||||
|         return value  | ||||
|         return value | ||||
|     return format_date(value) | ||||
|  | ||||
|  | ||||
| def time(hour=None, min=None, sec=None, micro=None, offset=None, obj=False): | ||||
|     """ | ||||
|     Create a time only timestamp for the given instant. | ||||
| @@ -136,6 +140,7 @@ def time(hour=None, min=None, sec=None, micro=None, offset=None, obj=False): | ||||
|         return value | ||||
|     return format_time(value) | ||||
|  | ||||
|  | ||||
| def datetime(year=None, month=None, day=None, hour=None, | ||||
|              min=None, sec=None, micro=None, offset=None, | ||||
|              separators=True, obj=False): | ||||
| @@ -181,7 +186,7 @@ def datetime(year=None, month=None, day=None, hour=None, | ||||
|     value = dt.datetime(year, month, day, hour, | ||||
|                        min, sec, micro, offset) | ||||
|     if obj: | ||||
|         return value  | ||||
|         return value | ||||
|     return format_datetime(value) | ||||
|  | ||||
|  | ||||
|   | ||||
							
								
								
									
										16
									
								
								sleekxmpp/plugins/xep_0084/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								sleekxmpp/plugins/xep_0084/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,16 @@ | ||||
| """ | ||||
|     SleekXMPP: The Sleek XMPP Library | ||||
|     Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout | ||||
|     This file is part of SleekXMPP. | ||||
|  | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
|  | ||||
| from sleekxmpp.plugins.base import register_plugin | ||||
|  | ||||
| from sleekxmpp.plugins.xep_0084 import stanza | ||||
| from sleekxmpp.plugins.xep_0084.stanza import Data, MetaData | ||||
| from sleekxmpp.plugins.xep_0084.avatar import XEP_0084 | ||||
|  | ||||
|  | ||||
| register_plugin(XEP_0084) | ||||
							
								
								
									
										101
									
								
								sleekxmpp/plugins/xep_0084/avatar.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										101
									
								
								sleekxmpp/plugins/xep_0084/avatar.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,101 @@ | ||||
| """ | ||||
|     SleekXMPP: The Sleek XMPP Library | ||||
|     Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout | ||||
|     This file is part of SleekXMPP. | ||||
|  | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
|  | ||||
| import hashlib | ||||
| import logging | ||||
|  | ||||
| from sleekxmpp import Iq | ||||
| from sleekxmpp.plugins import BasePlugin | ||||
| from sleekxmpp.xmlstream.handler import Callback | ||||
| from sleekxmpp.xmlstream.matcher import StanzaPath | ||||
| from sleekxmpp.xmlstream import register_stanza_plugin, JID | ||||
| from sleekxmpp.plugins.xep_0084 import stanza, Data, MetaData | ||||
|  | ||||
|  | ||||
| log = logging.getLogger(__name__) | ||||
|  | ||||
|  | ||||
| class XEP_0084(BasePlugin): | ||||
|  | ||||
|     name = 'xep_0084' | ||||
|     description = 'XEP-0084: User Avatar' | ||||
|     dependencies = set(['xep_0163', 'xep_0060']) | ||||
|     stanza = stanza | ||||
|  | ||||
|     def plugin_init(self): | ||||
|         self.xmpp['xep_0163'].register_pep('avatar_metadata', MetaData) | ||||
|  | ||||
|         pubsub_stanza = self.xmpp['xep_0060'].stanza | ||||
|         register_stanza_plugin(pubsub_stanza.Item, Data) | ||||
|         register_stanza_plugin(pubsub_stanza.EventItem, Data) | ||||
|  | ||||
|         self.xmpp['xep_0060'].map_node_event(Data.namespace, 'avatar_data') | ||||
|  | ||||
|     def retrieve_avatar(self, jid, id, url=None, ifrom=None, block=True, | ||||
|                               callback=None, timeout=None): | ||||
|         return self.xmpp['xep_0060'].get_item(jid, Data.namespace, id, | ||||
|                 ifrom=ifrom, | ||||
|                 block=block, | ||||
|                 callback=callback, | ||||
|                 timeout=timeout) | ||||
|  | ||||
|     def publish_avatar(self, data, ifrom=None, block=True, callback=None, | ||||
|                              timeout=None): | ||||
|         payload = Data() | ||||
|         payload['value'] = data | ||||
|         return self.xmpp['xep_0163'].publish(payload, | ||||
|                 node=Data.namespace, | ||||
|                 id=hashlib.sha1(data).hexdigest(), | ||||
|                 ifrom=ifrom, | ||||
|                 block=block, | ||||
|                 callback=callback, | ||||
|                 timeout=timeout) | ||||
|  | ||||
|     def publish_avatar_metadata(self, items=None, pointers=None, | ||||
|                                       ifrom=None, block=True, | ||||
|                                       callback=None, timeout=None): | ||||
|         metadata = MetaData() | ||||
|         if items is None: | ||||
|             items = [] | ||||
|         for info in items: | ||||
|             metadata.add_info(info['id'], info['type'], info['bytes'], | ||||
|                     height=info.get('height', ''), | ||||
|                     width=info.get('width', ''), | ||||
|                     url=info.get('url', '')) | ||||
|         for pointer in pointers: | ||||
|             metadata.add_pointer(pointer) | ||||
|  | ||||
|         return self.xmpp['xep_0163'].publish(payload, | ||||
|                 node=Data.namespace, | ||||
|                 id=hashlib.sha1(data).hexdigest(), | ||||
|                 ifrom=ifrom, | ||||
|                 block=block, | ||||
|                 callback=callback, | ||||
|                 timeout=timeout) | ||||
|  | ||||
|     def stop(self, ifrom=None, block=True, callback=None, timeout=None): | ||||
|         """ | ||||
|         Clear existing avatar metadata information to stop notifications. | ||||
|  | ||||
|         Arguments: | ||||
|             ifrom    -- Specify the sender's JID. | ||||
|             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. | ||||
|         """ | ||||
|         metadata = MetaData() | ||||
|         return self.xmpp['xep_0163'].publish(metadata, | ||||
|                 node=MetaData.namespace, | ||||
|                 ifrom=ifrom, | ||||
|                 block=block, | ||||
|                 callback=callback, | ||||
|                 timeout=timeout) | ||||
							
								
								
									
										78
									
								
								sleekxmpp/plugins/xep_0084/stanza.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										78
									
								
								sleekxmpp/plugins/xep_0084/stanza.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,78 @@ | ||||
| """ | ||||
|     SleekXMPP: The Sleek XMPP Library | ||||
|     Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout | ||||
|     This file is part of SleekXMPP. | ||||
|  | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
|  | ||||
| from base64 import b64encode, b64decode | ||||
| from sleekxmpp.thirdparty.suelta.util import bytes | ||||
|  | ||||
| from sleekxmpp.xmlstream import ET, ElementBase, register_stanza_plugin | ||||
|  | ||||
|  | ||||
| class Data(ElementBase): | ||||
|     name = 'data' | ||||
|     namespace = 'urn:xmpp:avatar:data' | ||||
|     plugin_attrib = 'avatar_data' | ||||
|     interfaces = set(['value']) | ||||
|  | ||||
|     def get_value(self): | ||||
|         if self.xml.text: | ||||
|             return b64decode(bytes(self.xml.text)) | ||||
|         return '' | ||||
|  | ||||
|     def set_value(self, value): | ||||
|         if value: | ||||
|             self.xml.text = b64encode(bytes(value)) | ||||
|         else: | ||||
|             self.xml.text = '' | ||||
|  | ||||
|     def del_value(self): | ||||
|         self.xml.text = '' | ||||
|  | ||||
|  | ||||
| class MetaData(ElementBase): | ||||
|     name = 'metadata' | ||||
|     namespace = 'urn:xmpp:avatar:metadata' | ||||
|     plugin_attrib = 'avatar_metadata' | ||||
|     interfaces = set() | ||||
|  | ||||
|     def add_info(self, id, itype, ibytes, height=None, width=None, url=None): | ||||
|         info = Info() | ||||
|         info.values = {'id': id, | ||||
|                        'type': itype, | ||||
|                        'bytes': ibytes, | ||||
|                        'height': height, | ||||
|                        'width': width, | ||||
|                        'url': url} | ||||
|         self.append(info) | ||||
|  | ||||
|     def add_pointer(self, xml): | ||||
|         if not isinstance(xml, Pointer): | ||||
|             pointer = Pointer() | ||||
|             pointer.append(xml) | ||||
|             self.append(pointer) | ||||
|         else: | ||||
|             self.append(xml) | ||||
|  | ||||
|  | ||||
| class Info(ElementBase): | ||||
|     name = 'info' | ||||
|     namespace = 'urn:xmpp:avatar:metadata' | ||||
|     plugin_attrib = 'info' | ||||
|     plugin_multi_attrib = 'items' | ||||
|     interfaces = set(['bytes', 'height', 'id', 'type', 'url', 'width']) | ||||
|  | ||||
|  | ||||
| class Pointer(ElementBase): | ||||
|     name = 'pointer' | ||||
|     namespace = 'urn:xmpp:avatar:metadata' | ||||
|     plugin_attrib = 'pointer' | ||||
|     plugin_multi_attrib = 'pointers' | ||||
|     interfaces = set() | ||||
|  | ||||
|  | ||||
| register_stanza_plugin(MetaData, Info, iterable=True) | ||||
| register_stanza_plugin(MetaData, Pointer, iterable=True) | ||||
| @@ -47,28 +47,28 @@ class LegacyError(ElementBase): | ||||
|     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')} | ||||
|     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.""" | ||||
|   | ||||
| @@ -79,5 +79,7 @@ class XEP_0092(BasePlugin): | ||||
|         result = iq.send() | ||||
|  | ||||
|         if result and result['type'] != 'error': | ||||
|             return result['software_version'].values | ||||
|             values = result['software_version'].values | ||||
|             del values['lang'] | ||||
|             return values | ||||
|         return False | ||||
|   | ||||
| @@ -34,7 +34,7 @@ class XEP_0107(BasePlugin): | ||||
|         register_stanza_plugin(Message, UserMood) | ||||
|         self.xmpp['xep_0163'].register_pep('user_mood', UserMood) | ||||
|  | ||||
|     def publish_mood(self, value=None, text=None, options=None,  | ||||
|     def publish_mood(self, value=None, text=None, options=None, | ||||
|                      ifrom=None, block=True, callback=None, timeout=None): | ||||
|         """ | ||||
|         Publish the user's current mood. | ||||
| @@ -79,7 +79,7 @@ class XEP_0107(BasePlugin): | ||||
|                         be executed when a reply stanza is received. | ||||
|         """ | ||||
|         mood = UserMood() | ||||
|         return self.xmpp['xep_0163'].publish(mood,  | ||||
|         return self.xmpp['xep_0163'].publish(mood, | ||||
|                 node=UserMood.namespace, | ||||
|                 ifrom=ifrom, | ||||
|                 block=block, | ||||
|   | ||||
| @@ -21,7 +21,7 @@ class UserActivity(ElementBase): | ||||
|                    'talking', 'traveling', 'undefined', 'working']) | ||||
|     specific = set(['at_the_spa', 'brushing_teeth', 'buying_groceries', | ||||
|                     'cleaning', 'coding', 'commuting', 'cooking', 'cycling', | ||||
|                     'dancing', 'day_off', 'doing_maintenance',  | ||||
|                     'dancing', 'day_off', 'doing_maintenance', | ||||
|                     'doing_the_dishes', 'doing_the_laundry', 'driving', | ||||
|                     'fishing', 'gaming', 'gardening', 'getting_a_haircut', | ||||
|                     'going_out', 'hanging_out', 'having_a_beer', | ||||
| @@ -31,11 +31,11 @@ class UserActivity(ElementBase): | ||||
|                     'jogging', 'on_a_bus', 'on_a_plane', 'on_a_train', | ||||
|                     'on_a_trip', 'on_the_phone', 'on_vacation', | ||||
|                     'on_video_phone', 'other', 'partying', 'playing_sports', | ||||
|                     'praying', 'reading', 'rehearsing', 'running',  | ||||
|                     'praying', 'reading', 'rehearsing', 'running', | ||||
|                     'running_an_errand', 'scheduled_holiday', 'shaving', | ||||
|                     'shopping', 'skiing', 'sleeping', 'smoking', | ||||
|                     'socializing', 'studying', 'sunbathing', 'swimming', | ||||
|                     'taking_a_bath', 'taking_a_shower', 'thinking',  | ||||
|                     'taking_a_bath', 'taking_a_shower', 'thinking', | ||||
|                     'walking', 'walking_the_dog', 'watching_a_movie', | ||||
|                     'watching_tv', 'working_out', 'writing']) | ||||
|  | ||||
| @@ -46,7 +46,7 @@ class UserActivity(ElementBase): | ||||
|         if isinstance(value, tuple) or isinstance(value, list): | ||||
|             general = value[0] | ||||
|             specific = value[1] | ||||
|             | ||||
|  | ||||
|         if general in self.general: | ||||
|             gen_xml = ET.Element('{%s}%s' % (self.namespace, general)) | ||||
|             if specific: | ||||
|   | ||||
| @@ -29,7 +29,7 @@ class XEP_0108(BasePlugin): | ||||
|     def plugin_init(self): | ||||
|         self.xmpp['xep_0163'].register_pep('user_activity', UserActivity) | ||||
|  | ||||
|     def publish_activity(self, general, specific=None, text=None, options=None,  | ||||
|     def publish_activity(self, general, specific=None, text=None, options=None, | ||||
|                      ifrom=None, block=True, callback=None, timeout=None): | ||||
|         """ | ||||
|         Publish the user's current activity. | ||||
| @@ -76,7 +76,7 @@ class XEP_0108(BasePlugin): | ||||
|                         be executed when a reply stanza is received. | ||||
|         """ | ||||
|         activity = UserActivity() | ||||
|         return self.xmpp['xep_0163'].publish(activity,  | ||||
|         return self.xmpp['xep_0163'].publish(activity, | ||||
|                 node=UserActivity.namespace, | ||||
|                 ifrom=ifrom, | ||||
|                 block=block, | ||||
|   | ||||
| @@ -35,7 +35,7 @@ class XEP_0115(BasePlugin): | ||||
|     stanza = stanza | ||||
|  | ||||
|     def plugin_init(self): | ||||
|         self.hashes = {'sha-1': hashlib.sha1,  | ||||
|         self.hashes = {'sha-1': hashlib.sha1, | ||||
|                        'sha1': hashlib.sha1, | ||||
|                        'md5': hashlib.md5} | ||||
|  | ||||
| @@ -124,7 +124,7 @@ class XEP_0115(BasePlugin): | ||||
|         existing_verstring = self.get_verstring(pres['from'].full) | ||||
|         if str(existing_verstring) == str(pres['caps']['ver']): | ||||
|             return | ||||
|       | ||||
|  | ||||
|         if pres['caps']['hash'] not in self.hashes: | ||||
|             try: | ||||
|                 log.debug("Unknown caps hash: %s", pres['caps']['hash']) | ||||
| @@ -132,7 +132,7 @@ class XEP_0115(BasePlugin): | ||||
|                 return | ||||
|             except XMPPError: | ||||
|                 return | ||||
|     | ||||
|  | ||||
|         log.debug("New caps verification string: %s", pres['caps']['ver']) | ||||
|         try: | ||||
|             node = '%s#%s' % (pres['caps']['node'], pres['caps']['ver']) | ||||
| @@ -140,7 +140,7 @@ class XEP_0115(BasePlugin): | ||||
|  | ||||
|             if isinstance(caps, Iq): | ||||
|                 caps = caps['disco_info'] | ||||
|                      | ||||
|  | ||||
|             if self._validate_caps(caps, pres['caps']['hash'], | ||||
|                                          pres['caps']['ver']): | ||||
|                 self.assign_verstring(pres['from'], pres['caps']['ver']) | ||||
| @@ -173,7 +173,8 @@ class XEP_0115(BasePlugin): | ||||
|                     form_types.append(f_type) | ||||
|                     deduped_form_types.add(f_type) | ||||
|                     if len(form_types) != len(deduped_form_types): | ||||
|                         log.debug("Duplicated FORM_TYPE values, invalid for caps") | ||||
|                         log.debug("Duplicated FORM_TYPE values, " + \ | ||||
|                                   "invalid for caps") | ||||
|                         return False | ||||
|  | ||||
|                     if len(f_type) > 1: | ||||
| @@ -183,7 +184,8 @@ class XEP_0115(BasePlugin): | ||||
|                             return False | ||||
|  | ||||
|                     if stanza['fields']['FORM_TYPE']['type'] != 'hidden': | ||||
|                         log.debug("Field FORM_TYPE type not 'hidden', ignoring form for caps") | ||||
|                         log.debug("Field FORM_TYPE type not 'hidden', " + \ | ||||
|                                   "ignoring form for caps") | ||||
|                         caps.xml.remove(stanza.xml) | ||||
|                 else: | ||||
|                     log.debug("No FORM_TYPE found, ignoring form for caps") | ||||
| @@ -212,7 +214,7 @@ class XEP_0115(BasePlugin): | ||||
|  | ||||
|         identities = sorted(('/'.join(i) for i in identities)) | ||||
|         features = sorted(info['features']) | ||||
|   | ||||
|  | ||||
|         S += '<'.join(identities) + '<' | ||||
|         S += '<'.join(features) + '<' | ||||
|  | ||||
| @@ -254,7 +256,7 @@ class XEP_0115(BasePlugin): | ||||
|                 info = info['disco_info'] | ||||
|             ver = self.generate_verstring(info, self.hash) | ||||
|             self.xmpp['xep_0030'].set_info( | ||||
|                     jid=jid,  | ||||
|                     jid=jid, | ||||
|                     node='%s#%s' % (self.caps_node, ver), | ||||
|                     info=info) | ||||
|             self.cache_caps(ver, info) | ||||
|   | ||||
| @@ -69,7 +69,7 @@ class StaticCaps(object): | ||||
|                 return True | ||||
|  | ||||
|         try: | ||||
|             info = self.disco.get_info(jid=jid, node=node,  | ||||
|             info = self.disco.get_info(jid=jid, node=node, | ||||
|                                        ifrom=ifrom, **data) | ||||
|             info = self.disco._wrap(ifrom, jid, info, True) | ||||
|             return feature in info['disco_info']['features'] | ||||
| @@ -99,7 +99,7 @@ class StaticCaps(object): | ||||
|                         be skipped, even if a result has already been | ||||
|                         cached. Defaults to false. | ||||
|         """ | ||||
|         identity = (data.get('category', None),  | ||||
|         identity = (data.get('category', None), | ||||
|                     data.get('itype', None), | ||||
|                     data.get('lang', None)) | ||||
|  | ||||
| @@ -114,7 +114,7 @@ class StaticCaps(object): | ||||
|                 return True | ||||
|  | ||||
|         try: | ||||
|             info = self.disco.get_info(jid=jid, node=node,  | ||||
|             info = self.disco.get_info(jid=jid, node=node, | ||||
|                                        ifrom=ifrom, **data) | ||||
|             info = self.disco._wrap(ifrom, jid, info, True) | ||||
|             return identity in map(trunc, info['disco_info']['identities']) | ||||
|   | ||||
| @@ -14,7 +14,7 @@ class UserTune(ElementBase): | ||||
|     name = 'tune' | ||||
|     namespace = 'http://jabber.org/protocol/tune' | ||||
|     plugin_attrib = 'tune' | ||||
|     interfaces = set(['artist', 'length', 'rating', 'source',  | ||||
|     interfaces = set(['artist', 'length', 'rating', 'source', | ||||
|                       'title', 'track', 'uri']) | ||||
|     sub_interfaces = interfaces | ||||
|  | ||||
|   | ||||
| @@ -30,7 +30,7 @@ class XEP_0118(BasePlugin): | ||||
|         self.xmpp['xep_0163'].register_pep('user_tune', UserTune) | ||||
|  | ||||
|     def publish_tune(self, artist=None, length=None, rating=None, source=None, | ||||
|                      title=None, track=None, uri=None, options=None,  | ||||
|                      title=None, track=None, uri=None, options=None, | ||||
|                      ifrom=None, block=True, callback=None, timeout=None): | ||||
|         """ | ||||
|         Publish the user's current tune. | ||||
| @@ -61,7 +61,7 @@ class XEP_0118(BasePlugin): | ||||
|         tune['title'] = title | ||||
|         tune['track'] = track | ||||
|         tune['uri'] = uri | ||||
|         return self.xmpp['xep_0163'].publish(tune,  | ||||
|         return self.xmpp['xep_0163'].publish(tune, | ||||
|                 node=UserTune.namespace, | ||||
|                 options=options, | ||||
|                 ifrom=ifrom, | ||||
| @@ -84,7 +84,7 @@ class XEP_0118(BasePlugin): | ||||
|                         be executed when a reply stanza is received. | ||||
|         """ | ||||
|         tune = UserTune() | ||||
|         return self.xmpp['xep_0163'].publish(tune,  | ||||
|         return self.xmpp['xep_0163'].publish(tune, | ||||
|                 node=UserTune.namespace, | ||||
|                 ifrom=ifrom, | ||||
|                 block=block, | ||||
|   | ||||
| @@ -45,7 +45,7 @@ class XEP_0153(BasePlugin): | ||||
|         self.api.register(self._set_hash, 'set_hash', default=True) | ||||
|         self.api.register(self._get_hash, 'get_hash', default=True) | ||||
|  | ||||
|     def set_avatar(self, jid=None, avatar=None, mtype=None, block=True,  | ||||
|     def set_avatar(self, jid=None, avatar=None, mtype=None, block=True, | ||||
|                    timeout=None, callback=None): | ||||
|         vcard = self.xmpp['xep_0054'].get_vcard(jid, cached=True) | ||||
|         vcard = vcard['vcard_temp'] | ||||
| @@ -69,7 +69,7 @@ class XEP_0153(BasePlugin): | ||||
|         own_jid = (jid.bare == self.xmpp.boundjid.bare) | ||||
|         if self.xmpp.is_component: | ||||
|             own_jid = (jid.domain == self.xmpp.boundjid.domain) | ||||
|       | ||||
|  | ||||
|         if jid is not None: | ||||
|             jid = jid.bare | ||||
|         self.api['set_hash'](jid, args=None) | ||||
| @@ -77,7 +77,7 @@ class XEP_0153(BasePlugin): | ||||
|             self.xmpp.roster[jid].send_last_presence() | ||||
|  | ||||
|         iq = self.xmpp['xep_0054'].get_vcard( | ||||
|                 jid=jid,  | ||||
|                 jid=jid, | ||||
|                 ifrom=self.xmpp.boundjid) | ||||
|         data = iq['vcard_temp']['PHOTO']['BINVAL'] | ||||
|         if not data: | ||||
|   | ||||
| @@ -56,7 +56,7 @@ class XEP_0163(BasePlugin): | ||||
|             jid       -- Optionally specify the JID. | ||||
|         """ | ||||
|         if not isinstance(namespace, set) and not isinstance(namespace, list): | ||||
|            namespace = [namespace] | ||||
|             namespace = [namespace] | ||||
|  | ||||
|         for ns in namespace: | ||||
|             self.xmpp['xep_0030'].add_feature('%s+notify' % ns, | ||||
| @@ -75,7 +75,7 @@ class XEP_0163(BasePlugin): | ||||
|             jid       -- Optionally specify the JID. | ||||
|         """ | ||||
|         if not isinstance(namespace, set) and not isinstance(namespace, list): | ||||
|            namespace = [namespace] | ||||
|             namespace = [namespace] | ||||
|  | ||||
|         for ns in namespace: | ||||
|             self.xmpp['xep_0030'].del_feature(jid=jid, | ||||
| @@ -109,6 +109,7 @@ class XEP_0163(BasePlugin): | ||||
|             node = stanza.namespace | ||||
|  | ||||
|         return self.xmpp['xep_0060'].publish(ifrom, node, | ||||
|                 id=id, | ||||
|                 payload=stanza.xml, | ||||
|                 options=options, | ||||
|                 ifrom=ifrom, | ||||
|   | ||||
| @@ -78,7 +78,7 @@ class XEP_0172(BasePlugin): | ||||
|                         be executed when a reply stanza is received. | ||||
|         """ | ||||
|         nick = UserNick() | ||||
|         return self.xmpp['xep_0163'].publish(nick,  | ||||
|         return self.xmpp['xep_0163'].publish(nick, | ||||
|                 node=UserNick.namespace, | ||||
|                 ifrom=ifrom, | ||||
|                 block=block, | ||||
|   | ||||
| @@ -100,13 +100,13 @@ class XEP_0184(BasePlugin): | ||||
|  | ||||
|         if not isinstance(stanza, Message): | ||||
|             return stanza | ||||
|                  | ||||
|  | ||||
|         if stanza['request_receipt']: | ||||
|             return stanza | ||||
|  | ||||
|         if not stanza['type'] in self.ack_types: | ||||
|             return stanza | ||||
|                      | ||||
|  | ||||
|         if stanza['receipt']: | ||||
|             return stanza | ||||
|  | ||||
|   | ||||
							
								
								
									
										16
									
								
								sleekxmpp/plugins/xep_0186/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								sleekxmpp/plugins/xep_0186/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,16 @@ | ||||
| """ | ||||
|     SleekXMPP: The Sleek XMPP Library | ||||
|     Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout | ||||
|     This file is part of SleekXMPP. | ||||
|  | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
|  | ||||
| from sleekxmpp.plugins.base import register_plugin | ||||
|  | ||||
| from sleekxmpp.plugins.xep_0186 import stanza | ||||
| from sleekxmpp.plugins.xep_0186.stanza import Invisible, Visible | ||||
| from sleekxmpp.plugins.xep_0186.invisible_command import XEP_0186 | ||||
|  | ||||
|  | ||||
| register_plugin(XEP_0186) | ||||
							
								
								
									
										44
									
								
								sleekxmpp/plugins/xep_0186/invisible_command.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								sleekxmpp/plugins/xep_0186/invisible_command.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,44 @@ | ||||
| """ | ||||
|     SleekXMPP: The Sleek XMPP Library | ||||
|     Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout | ||||
|     This file is part of SleekXMPP. | ||||
|  | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
|  | ||||
| import logging | ||||
|  | ||||
| from sleekxmpp import Iq | ||||
| from sleekxmpp.plugins import BasePlugin | ||||
| from sleekxmpp.xmlstream import register_stanza_plugin | ||||
| from sleekxmpp.plugins.xep_0186 import stanza, Visible, Invisible | ||||
|  | ||||
|  | ||||
| log = logging.getLogger(__name__) | ||||
|  | ||||
|  | ||||
| class XEP_0186(BasePlugin): | ||||
|  | ||||
|     name = 'xep_0186' | ||||
|     description = 'XEP-0186: Invisible Command' | ||||
|     dependencies = set(['xep_0030']) | ||||
|  | ||||
|     def plugin_init(self): | ||||
|         register_stanza_plugin(Iq, Visible) | ||||
|         register_stanza_plugin(Iq, Invisible) | ||||
|  | ||||
|     def set_invisible(self, ifrom=None, block=True, callback=None, | ||||
|                             timeout=None): | ||||
|         iq = self.xmpp.Iq() | ||||
|         iq['type'] = 'set' | ||||
|         iq['from'] = ifrom | ||||
|         iq.enable('invisible') | ||||
|         iq.send(block=block, callback=callback, timeout=timeout) | ||||
|  | ||||
|     def set_visible(self, ifrom=None, block=True, callback=None, | ||||
|                           timeout=None): | ||||
|         iq = self.xmpp.Iq() | ||||
|         iq['type'] = 'set' | ||||
|         iq['from'] = ifrom | ||||
|         iq.enable('visible') | ||||
|         iq.send(block=block, callback=callback, timeout=timeout) | ||||
							
								
								
									
										23
									
								
								sleekxmpp/plugins/xep_0186/stanza.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								sleekxmpp/plugins/xep_0186/stanza.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,23 @@ | ||||
| """ | ||||
|     SleekXMPP: The Sleek XMPP Library | ||||
|     Copyright (C) 2012 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 Invisible(ElementBase): | ||||
|     name = 'invisible' | ||||
|     namespace = 'urn:xmpp:invisible:0' | ||||
|     plugin_attrib = 'invisible' | ||||
|     interfaces = set() | ||||
|  | ||||
|  | ||||
| class Visible(ElementBase): | ||||
|     name = 'visible' | ||||
|     namespace = 'urn:xmpp:visible:0' | ||||
|     plugin_attrib = 'visible' | ||||
|     interfaces = set() | ||||
| @@ -82,7 +82,6 @@ class Resumed(StanzaBase): | ||||
|         self._set_attr('h', str(val)) | ||||
|  | ||||
|  | ||||
|  | ||||
| class Failed(StanzaBase, Error): | ||||
|     name = 'failed' | ||||
|     namespace = 'urn:xmpp:sm:3' | ||||
| @@ -106,7 +105,7 @@ class StreamManagement(ElementBase): | ||||
|         self.del_required() | ||||
|         if val: | ||||
|             self._set_sub_text('required', '', keep=True) | ||||
|          | ||||
|  | ||||
|     def del_required(self): | ||||
|         self._del_sub('required') | ||||
|  | ||||
| @@ -117,7 +116,7 @@ class StreamManagement(ElementBase): | ||||
|         self.del_optional() | ||||
|         if val: | ||||
|             self._set_sub_text('optional', '', keep=True) | ||||
|          | ||||
|  | ||||
|     def del_optional(self): | ||||
|         self._del_sub('optional') | ||||
|  | ||||
|   | ||||
| @@ -21,7 +21,7 @@ from sleekxmpp.plugins.xep_0198 import stanza | ||||
| log = logging.getLogger(__name__) | ||||
|  | ||||
|  | ||||
| MAX_SEQ = 2**32 | ||||
| MAX_SEQ = 2 ** 32 | ||||
|  | ||||
|  | ||||
| class XEP_0198(BasePlugin): | ||||
| @@ -69,7 +69,7 @@ class XEP_0198(BasePlugin): | ||||
|  | ||||
|         self.enabled = threading.Event() | ||||
|         self.unacked_queue = collections.deque() | ||||
|      | ||||
|  | ||||
|         self.seq_lock = threading.Lock() | ||||
|         self.handled_lock = threading.Lock() | ||||
|         self.ack_lock = threading.Lock() | ||||
| @@ -197,7 +197,7 @@ class XEP_0198(BasePlugin): | ||||
|  | ||||
|     def _handle_enabled(self, stanza): | ||||
|         """Save the SM-ID, if provided. | ||||
|          | ||||
|  | ||||
|         Raises an :term:`sm_enabled` event. | ||||
|         """ | ||||
|         self.xmpp.features.add('stream_management') | ||||
| @@ -231,7 +231,7 @@ class XEP_0198(BasePlugin): | ||||
|  | ||||
|     def _handle_ack(self, ack): | ||||
|         """Process a server ack by freeing acked stanzas from the queue. | ||||
|          | ||||
|  | ||||
|         Raises a :term:`stanza_acked` event for each acked stanza. | ||||
|         """ | ||||
|         if ack['h'] == self.last_ack: | ||||
| @@ -243,10 +243,10 @@ class XEP_0198(BasePlugin): | ||||
|             log.debug("Ack: %s, Last Ack: %s, " + \ | ||||
|                       "Unacked: %s, Num Acked: %s, " + \ | ||||
|                       "Remaining: %s", | ||||
|                 ack['h'],  | ||||
|                 self.last_ack,  | ||||
|                 ack['h'], | ||||
|                 self.last_ack, | ||||
|                 num_unacked, | ||||
|                 num_acked,  | ||||
|                 num_acked, | ||||
|                 num_unacked - num_acked) | ||||
|             for x in range(num_acked): | ||||
|                 seq, stanza = self.unacked_queue.popleft() | ||||
|   | ||||
| @@ -40,8 +40,12 @@ class XEP_0202(BasePlugin): | ||||
|         # custom function can be supplied which accepts | ||||
|         # the JID of the entity to query for the time. | ||||
|         self.local_time = self.config.get('local_time', None) | ||||
|  | ||||
|         def default_local_time(jid): | ||||
|             return xep_0082.datetime(offset=self.tz_offset) | ||||
|  | ||||
|         if not self.local_time: | ||||
|             self.local_time = lambda x: xep_0082.datetime(offset=self.tz_offset) | ||||
|             self.local_time = default_local_time | ||||
|  | ||||
|         self.xmpp.registerHandler( | ||||
|             Callback('Entity Time', | ||||
|   | ||||
| @@ -13,9 +13,7 @@ from sleekxmpp.plugins.xep_0203.stanza import Delay | ||||
| from sleekxmpp.plugins.xep_0203.delay import XEP_0203 | ||||
|  | ||||
|  | ||||
|  | ||||
| register_plugin(XEP_0203) | ||||
|  | ||||
|  | ||||
| # Retain some backwards compatibility | ||||
| xep_0203 = XEP_0203 | ||||
|   | ||||
							
								
								
									
										16
									
								
								sleekxmpp/plugins/xep_0221/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								sleekxmpp/plugins/xep_0221/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,16 @@ | ||||
| """ | ||||
|     SleekXMPP: The Sleek XMPP Library | ||||
|     Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout | ||||
|     This file is part of SleekXMPP. | ||||
|  | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
|  | ||||
| from sleekxmpp.plugins.base import register_plugin | ||||
|  | ||||
| from sleekxmpp.plugins.xep_0221 import stanza | ||||
| from sleekxmpp.plugins.xep_0221.stanza import Media, URI | ||||
| from sleekxmpp.plugins.xep_0221.media import XEP_0221 | ||||
|  | ||||
|  | ||||
| register_plugin(XEP_0221) | ||||
							
								
								
									
										27
									
								
								sleekxmpp/plugins/xep_0221/media.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								sleekxmpp/plugins/xep_0221/media.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,27 @@ | ||||
| """ | ||||
|     SleekXMPP: The Sleek XMPP Library | ||||
|     Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout | ||||
|     This file is part of SleekXMPP. | ||||
|  | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
|  | ||||
| import logging | ||||
|  | ||||
| from sleekxmpp.plugins import BasePlugin | ||||
| from sleekxmpp.xmlstream import register_stanza_plugin | ||||
| from sleekxmpp.plugins.xep_0221 import stanza, Media, URI | ||||
| from sleekxmpp.plugins.xep_0004 import FormField | ||||
|  | ||||
|  | ||||
| log = logging.getLogger(__name__) | ||||
|  | ||||
|  | ||||
| class XEP_0221(BasePlugin): | ||||
|  | ||||
|     name = 'xep_0221' | ||||
|     description = 'XEP-0221: Data Forms Media Element' | ||||
|     dependencies = set(['xep_0004']) | ||||
|  | ||||
|     def plugin_init(self): | ||||
|         register_stanza_plugin(FormField, Media) | ||||
							
								
								
									
										42
									
								
								sleekxmpp/plugins/xep_0221/stanza.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								sleekxmpp/plugins/xep_0221/stanza.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,42 @@ | ||||
| """ | ||||
|     SleekXMPP: The Sleek XMPP Library | ||||
|     Copyright (C) 2012 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, register_stanza_plugin | ||||
|  | ||||
|  | ||||
| class Media(ElementBase): | ||||
|     name = 'media' | ||||
|     namespace = 'urn:xmpp:media-element' | ||||
|     plugin_attrib = 'media' | ||||
|     interfaces = set(['height', 'width', 'alt']) | ||||
|  | ||||
|     def add_uri(self, value, itype): | ||||
|         uri = URI() | ||||
|         uri['value'] = value | ||||
|         uri['type'] = itype | ||||
|         self.append(uri) | ||||
|  | ||||
|  | ||||
| class URI(ElementBase): | ||||
|     name = 'uri' | ||||
|     namespace = 'urn:xmpp:media-element' | ||||
|     plugin_attrib = 'uri' | ||||
|     plugin_multi_attrib = 'uris' | ||||
|     interfaces = set(['type', 'value']) | ||||
|  | ||||
|     def get_value(self): | ||||
|         return self.xml.text | ||||
|  | ||||
|     def set_value(self, value): | ||||
|         self.xml.text = value | ||||
|  | ||||
|     def del_value(self): | ||||
|         sel.xml.text = '' | ||||
|  | ||||
|  | ||||
| register_stanza_plugin(Media, URI, iterable=True) | ||||
							
								
								
									
										126
									
								
								sleekxmpp/plugins/xep_0222.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										126
									
								
								sleekxmpp/plugins/xep_0222.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,126 @@ | ||||
| """ | ||||
|     SleekXMPP: The Sleek XMPP Library | ||||
|     Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout | ||||
|     This file is part of SleekXMPP. | ||||
|  | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
|  | ||||
| import logging | ||||
|  | ||||
| from sleekxmpp.xmlstream import register_stanza_plugin | ||||
| from sleekxmpp.plugins.base import BasePlugin, register_plugin | ||||
|  | ||||
|  | ||||
| log = logging.getLogger(__name__) | ||||
|  | ||||
|  | ||||
| class XEP_0222(BasePlugin): | ||||
|  | ||||
|     """ | ||||
|     XEP-0222: Persistent Storage of Public Data via PubSub | ||||
|     """ | ||||
|  | ||||
|     name = 'xep_0222' | ||||
|     description = 'XEP-0222: Persistent Storage of Private Data via PubSub' | ||||
|     dependencies = set(['xep_0163', 'xep_0060', 'xep_0004']) | ||||
|  | ||||
|     profile = {'pubsub#persist_items': True, | ||||
|                'pubsub#send_last_published_item': 'never'} | ||||
|  | ||||
|     def configure(self, node): | ||||
|         """ | ||||
|         Update a node's configuration to match the public storage profile. | ||||
|         """ | ||||
|         config = self.xmpp['xep_0004'].Form() | ||||
|         config['type'] = 'submit' | ||||
|  | ||||
|         for field, value in self.profile.items(): | ||||
|             config.add_field(var=field, value=value) | ||||
|  | ||||
|         return self.xmpp['xep_0060'].set_node_config(None, node, config, | ||||
|                 ifrom=ifrom, | ||||
|                 block=block, | ||||
|                 callback=callback, | ||||
|                 timeout=timeout) | ||||
|  | ||||
|     def store(self, stanza, node=None, id=None, ifrom=None, options=None, | ||||
|               block=True, callback=None, timeout=None): | ||||
|         """ | ||||
|         Store public data via PEP. | ||||
|  | ||||
|         This is just a (very) thin wrapper around the XEP-0060 publish() | ||||
|         method to set the defaults expected by PEP. | ||||
|  | ||||
|         Arguments: | ||||
|             stanza   -- The private content to store. | ||||
|             node     -- The node to publish the content to. If not specified, | ||||
|                         the stanza's namespace will be used. | ||||
|             id       -- Optionally specify the ID of the item. | ||||
|             options  -- Publish options to use, which will be modified to | ||||
|                         fit the persistent storage option profile. | ||||
|             ifrom    -- Specify the sender's JID. | ||||
|             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. | ||||
|         """ | ||||
|         if not options: | ||||
|             options = self.xmpp['xep_0004'].stanza.Form() | ||||
|             options['type'] = 'submit' | ||||
|             options.add_field( | ||||
|                 var='FORM_TYPE', | ||||
|                 ftype='hidden', | ||||
|                 value='http://jabber.org/protocol/pubsub#publish-options') | ||||
|  | ||||
|         for field, value in self.profile.items(): | ||||
|             if field not in options.fields: | ||||
|                 options.add_field(var=field) | ||||
|             options.fields[field]['value'] = value | ||||
|  | ||||
|         return self.xmpp['xep_0163'].publish(stanza, node, | ||||
|                 options=options, | ||||
|                 ifrom=ifrom, | ||||
|                 block=block, | ||||
|                 callback=callback, | ||||
|                 timeout=timeout) | ||||
|  | ||||
|     def retrieve(self, node, id=None, item_ids=None, ifrom=None, | ||||
|                  block=True, callback=None, timeout=None): | ||||
|         """ | ||||
|         Retrieve public data via PEP. | ||||
|  | ||||
|         This is just a (very) thin wrapper around the XEP-0060 publish() | ||||
|         method to set the defaults expected by PEP. | ||||
|  | ||||
|         Arguments: | ||||
|             node     -- The node to retrieve content from. | ||||
|             id       -- Optionally specify the ID of the item. | ||||
|             item_ids -- Specify a group of IDs. If id is also specified, it | ||||
|                         will be included in item_ids. | ||||
|             ifrom    -- Specify the sender's JID. | ||||
|             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. | ||||
|         """ | ||||
|         if item_ids is None: | ||||
|             item_ids = [] | ||||
|         if id is not None: | ||||
|             item_ids.append(id) | ||||
|  | ||||
|         return self.xmpp['xep_0060'].get_items(None, node, | ||||
|                 item_ids=item_ids, | ||||
|                 ifrom=ifrom, | ||||
|                 block=block, | ||||
|                 callback=callback, | ||||
|                 timeout=timeout) | ||||
|  | ||||
|  | ||||
| register_plugin(XEP_0222) | ||||
							
								
								
									
										126
									
								
								sleekxmpp/plugins/xep_0223.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										126
									
								
								sleekxmpp/plugins/xep_0223.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,126 @@ | ||||
| """ | ||||
|     SleekXMPP: The Sleek XMPP Library | ||||
|     Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout | ||||
|     This file is part of SleekXMPP. | ||||
|  | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
|  | ||||
| import logging | ||||
|  | ||||
| from sleekxmpp.xmlstream import register_stanza_plugin | ||||
| from sleekxmpp.plugins.base import BasePlugin, register_plugin | ||||
|  | ||||
|  | ||||
| log = logging.getLogger(__name__) | ||||
|  | ||||
|  | ||||
| class XEP_0223(BasePlugin): | ||||
|  | ||||
|     """ | ||||
|     XEP-0223: Persistent Storage of Private Data via PubSub | ||||
|     """ | ||||
|  | ||||
|     name = 'xep_0223' | ||||
|     description = 'XEP-0223: Persistent Storage of Private Data via PubSub' | ||||
|     dependencies = set(['xep_0163', 'xep_0060', 'xep_0004']) | ||||
|  | ||||
|     profile = {'pubsub#persist_items': True, | ||||
|                'pubsub#send_last_published_item': 'never'} | ||||
|  | ||||
|     def configure(self, node): | ||||
|         """ | ||||
|         Update a node's configuration to match the public storage profile. | ||||
|         """ | ||||
|         config = self.xmpp['xep_0004'].Form() | ||||
|         config['type'] = 'submit' | ||||
|  | ||||
|         for field, value in self.profile.items(): | ||||
|             config.add_field(var=field, value=value) | ||||
|  | ||||
|         return self.xmpp['xep_0060'].set_node_config(None, node, config, | ||||
|                 ifrom=ifrom, | ||||
|                 block=block, | ||||
|                 callback=callback, | ||||
|                 timeout=timeout) | ||||
|  | ||||
|     def store(self, stanza, node=None, id=None, ifrom=None, options=None, | ||||
|               block=True, callback=None, timeout=None): | ||||
|         """ | ||||
|         Store private data via PEP. | ||||
|  | ||||
|         This is just a (very) thin wrapper around the XEP-0060 publish() | ||||
|         method to set the defaults expected by PEP. | ||||
|  | ||||
|         Arguments: | ||||
|             stanza   -- The private content to store. | ||||
|             node     -- The node to publish the content to. If not specified, | ||||
|                         the stanza's namespace will be used. | ||||
|             id       -- Optionally specify the ID of the item. | ||||
|             options  -- Publish options to use, which will be modified to | ||||
|                         fit the persistent storage option profile. | ||||
|             ifrom    -- Specify the sender's JID. | ||||
|             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. | ||||
|         """ | ||||
|         if not options: | ||||
|             options = self.xmpp['xep_0004'].stanza.Form() | ||||
|             options['type'] = 'submit' | ||||
|             options.add_field( | ||||
|                 var='FORM_TYPE', | ||||
|                 ftype='hidden', | ||||
|                 value='http://jabber.org/protocol/pubsub#publish-options') | ||||
|  | ||||
|         for field, value in self.profile.items(): | ||||
|             if field not in options.fields: | ||||
|                 options.add_field(var=field) | ||||
|             options.fields[field]['value'] = value | ||||
|  | ||||
|         return self.xmpp['xep_0163'].publish(stanza, node, | ||||
|                 options=options, | ||||
|                 ifrom=ifrom, | ||||
|                 block=block, | ||||
|                 callback=callback, | ||||
|                 timeout=timeout) | ||||
|  | ||||
|     def retrieve(self, node, id=None, item_ids=None, ifrom=None, | ||||
|                  block=True, callback=None, timeout=None): | ||||
|         """ | ||||
|         Retrieve private data via PEP. | ||||
|  | ||||
|         This is just a (very) thin wrapper around the XEP-0060 publish() | ||||
|         method to set the defaults expected by PEP. | ||||
|  | ||||
|         Arguments: | ||||
|             node     -- The node to retrieve content from. | ||||
|             id       -- Optionally specify the ID of the item. | ||||
|             item_ids -- Specify a group of IDs. If id is also specified, it | ||||
|                         will be included in item_ids. | ||||
|             ifrom    -- Specify the sender's JID. | ||||
|             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. | ||||
|         """ | ||||
|         if item_ids is None: | ||||
|             item_ids = [] | ||||
|         if id is not None: | ||||
|             item_ids.append(id) | ||||
|  | ||||
|         return self.xmpp['xep_0060'].get_items(None, node, | ||||
|                 item_ids=item_ids, | ||||
|                 ifrom=ifrom, | ||||
|                 block=block, | ||||
|                 callback=callback, | ||||
|                 timeout=timeout) | ||||
|  | ||||
|  | ||||
| register_plugin(XEP_0223) | ||||
| @@ -1,6 +1,6 @@ | ||||
| """ | ||||
|     SleekXMPP: The Sleek XMPP Library | ||||
|     Copyright (C) 2012 Nathanael C. Fritz,  | ||||
|     Copyright (C) 2012 Nathanael C. Fritz, | ||||
|                        Emmanuel Gil Peyrot <linkmauve@linkmauve.fr> | ||||
|     This file is part of SleekXMPP. | ||||
|  | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| """ | ||||
|     SleekXMPP: The Sleek XMPP Library | ||||
|     Copyright (C) 2012 Nathanael C. Fritz,  | ||||
|     Copyright (C) 2012 Nathanael C. Fritz, | ||||
|                        Emmanuel Gil Peyrot <linkmauve@linkmauve.fr> | ||||
|     This file is part of SleekXMPP. | ||||
|  | ||||
| @@ -58,7 +58,6 @@ class XEP_0231(BasePlugin): | ||||
|         self.api.register(self._set_bob, 'set_bob', default=True) | ||||
|         self.api.register(self._del_bob, 'del_bob', default=True) | ||||
|  | ||||
|  | ||||
|     def set_bob(self, data, mtype, cid=None, max_age=None): | ||||
|         if cid is None: | ||||
|             cid = 'sha1+%s@bob.xmpp.org' % hashlib.sha1(data).hexdigest() | ||||
| @@ -73,7 +72,7 @@ class XEP_0231(BasePlugin): | ||||
|  | ||||
|         return cid | ||||
|  | ||||
|     def get_bob(self, jid=None, cid=None, cached=True, ifrom=None,  | ||||
|     def get_bob(self, jid=None, cid=None, cached=True, ifrom=None, | ||||
|                 block=True, timeout=None, callback=None): | ||||
|         if cached: | ||||
|             data = self.api['get_bob'](None, None, ifrom, args=cid) | ||||
| @@ -112,7 +111,7 @@ class XEP_0231(BasePlugin): | ||||
|             iq.send() | ||||
|  | ||||
|     def _handle_bob(self, stanza): | ||||
|         self.api['set_bob'](stanza['from'], None,  | ||||
|         self.api['set_bob'](stanza['from'], None, | ||||
|                             stanza['to'], args=stanza['bob']) | ||||
|         self.xmpp.event('bob', stanza) | ||||
|  | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| """ | ||||
|     SleekXMPP: The Sleek XMPP Library | ||||
|     Copyright (C) 2012 Nathanael C. Fritz,  | ||||
|     Copyright (C) 2012 Nathanael C. Fritz, | ||||
|                        Emmanuel Gil Peyrot <linkmauve@linkmauve.fr> | ||||
|     This file is part of SleekXMPP. | ||||
|  | ||||
|   | ||||
							
								
								
									
										18
									
								
								sleekxmpp/plugins/xep_0258/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								sleekxmpp/plugins/xep_0258/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | ||||
| """ | ||||
|     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.base import register_plugin | ||||
|  | ||||
| from sleekxmpp.plugins.xep_0258 import stanza | ||||
| from sleekxmpp.plugins.xep_0258.stanza import SecurityLabel, Label | ||||
| from sleekxmpp.plugins.xep_0258.stanza import DisplayMarking, EquivalentLabel | ||||
| from sleekxmpp.plugins.xep_0258.stanza import ESSLabel, Catalog, CatalogItem | ||||
| from sleekxmpp.plugins.xep_0258.security_labels import XEP_0258 | ||||
|  | ||||
|  | ||||
| register_plugin(XEP_0258) | ||||
							
								
								
									
										40
									
								
								sleekxmpp/plugins/xep_0258/security_labels.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								sleekxmpp/plugins/xep_0258/security_labels.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,40 @@ | ||||
| """ | ||||
|     SleekXMPP: The Sleek XMPP Library | ||||
|     Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout | ||||
|     This file is part of SleekXMPP. | ||||
|  | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
|  | ||||
| import logging | ||||
|  | ||||
| from sleekxmpp import Iq, Message | ||||
| from sleekxmpp.plugins import BasePlugin | ||||
| from sleekxmpp.xmlstream import register_stanza_plugin | ||||
| from sleekxmpp.plugins.xep_0258 import stanza, SecurityLabel, Catalog | ||||
|  | ||||
|  | ||||
| log = logging.getLogger(__name__) | ||||
|  | ||||
|  | ||||
| class XEP_0258(BasePlugin): | ||||
|  | ||||
|     name = 'xep_0258' | ||||
|     description = 'XEP-0258: Security Labels in XMPP' | ||||
|     dependencies = set(['xep_0030']) | ||||
|     stanza = stanza | ||||
|  | ||||
|     def plugin_init(self): | ||||
|         self.xmpp['xep_0030'].add_feature(SecurityLabel.namespace) | ||||
|  | ||||
|         register_stanza_plugin(Message, SecurityLabel) | ||||
|         register_stanza_plugin(Iq, Catalog) | ||||
|  | ||||
|     def get_catalog(self, jid, ifrom=None, block=True, | ||||
|                           callback=None, timeout=None): | ||||
|         iq = self.xmpp.Iq() | ||||
|         iq['to'] = jid | ||||
|         iq['from'] = ifrom | ||||
|         iq['type'] = 'get' | ||||
|         iq.enable('security_label_catalog') | ||||
|         return iq.send(block=block, callback=callback, timeout=timeout) | ||||
							
								
								
									
										142
									
								
								sleekxmpp/plugins/xep_0258/stanza.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										142
									
								
								sleekxmpp/plugins/xep_0258/stanza.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,142 @@ | ||||
| """ | ||||
|     SleekXMPP: The Sleek XMPP Library | ||||
|     Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout | ||||
|     This file is part of SleekXMPP. | ||||
|  | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
|  | ||||
| from base64 import b64encode, b64decode | ||||
|  | ||||
| from sleekxmpp.thirdparty.suelta.util import bytes | ||||
|  | ||||
| from sleekxmpp.xmlstream import ElementBase, ET, register_stanza_plugin | ||||
|  | ||||
|  | ||||
| class SecurityLabel(ElementBase): | ||||
|     name = 'securitylabel' | ||||
|     namespace = 'urn:xmpp:sec-label:0' | ||||
|     plugin_attrib = 'security_label' | ||||
|  | ||||
|     def add_equivalent(self, label): | ||||
|         equiv = EquivalentLabel(parent=self) | ||||
|         equiv.append(label) | ||||
|         return equiv | ||||
|  | ||||
|  | ||||
| class Label(ElementBase): | ||||
|     name = 'label' | ||||
|     namespace = 'urn:xmpp:sec-label:0' | ||||
|     plugin_attrib = 'label' | ||||
|  | ||||
|  | ||||
| class DisplayMarking(ElementBase): | ||||
|     name = 'displaymarking' | ||||
|     namespace = 'urn:xmpp:sec-label:0' | ||||
|     plugin_attrib = 'display_marking' | ||||
|     interfaces = set(['fgcolor', 'bgcolor', 'value']) | ||||
|  | ||||
|     def get_fgcolor(self): | ||||
|         return self._get_attr('fgcolor', 'black') | ||||
|  | ||||
|     def get_bgcolor(self): | ||||
|         return self._get_attr('fgcolor', 'white') | ||||
|  | ||||
|     def get_value(self): | ||||
|         return self.xml.text | ||||
|  | ||||
|     def set_value(self, value): | ||||
|         self.xml.text = value | ||||
|  | ||||
|     def del_value(self): | ||||
|         self.xml.text = '' | ||||
|  | ||||
|  | ||||
| class EquivalentLabel(ElementBase): | ||||
|     name = 'equivalentlabel' | ||||
|     namespace = 'urn:xmpp:sec-label:0' | ||||
|     plugin_attrib = 'equivalent_label' | ||||
|     plugin_multi_attrib = 'equivalent_labels' | ||||
|  | ||||
|  | ||||
| class Catalog(ElementBase): | ||||
|     name = 'catalog' | ||||
|     namespace = 'urn:xmpp:sec-label:catalog:2' | ||||
|     plugin_attrib = 'security_label_catalog' | ||||
|     interfaces = set(['to', 'from', 'name', 'desc', 'id', 'size', 'restrict']) | ||||
|  | ||||
|     def get_to(self): | ||||
|         return JID(self._get_attr('to')) | ||||
|         pass | ||||
|  | ||||
|     def set_to(self, value): | ||||
|         return self._set_attr('to', str(value)) | ||||
|  | ||||
|     def get_from(self): | ||||
|         return JID(self._get_attr('from')) | ||||
|  | ||||
|     def set_from(self, value): | ||||
|         return self._set_attr('from', str(value)) | ||||
|  | ||||
|     def get_restrict(self): | ||||
|         value = self._get_attr('restrict', '') | ||||
|         if value and value.lower() in ('true', '1'): | ||||
|             return True | ||||
|         return False | ||||
|  | ||||
|     def set_restrict(self, value): | ||||
|         self._del_attr('restrict') | ||||
|         if value: | ||||
|             self._set_attr('restrict', 'true') | ||||
|         elif value is False: | ||||
|             self._set_attr('restrict', 'false') | ||||
|  | ||||
|  | ||||
| class CatalogItem(ElementBase): | ||||
|     name = 'catalog' | ||||
|     namespace = 'urn:xmpp:sec-label:catalog:2' | ||||
|     plugin_attrib = 'item' | ||||
|     plugin_multi_attrib = 'items' | ||||
|     interfaces = set(['selector', 'default']) | ||||
|  | ||||
|     def get_default(self): | ||||
|         value = self._get_attr('default', '') | ||||
|         if value.lower() in ('true', '1'): | ||||
|             return True | ||||
|         return False | ||||
|  | ||||
|     def set_default(self, value): | ||||
|         self._del_attr('default') | ||||
|         if value: | ||||
|             self._set_attr('default', 'true') | ||||
|         elif value is False: | ||||
|             self._set_attr('default', 'false') | ||||
|  | ||||
|  | ||||
| class ESSLabel(ElementBase): | ||||
|     name = 'esssecuritylabel' | ||||
|     namespace = 'urn:xmpp:sec-label:ess:0' | ||||
|     plugin_attrib = 'ess' | ||||
|     interfaces = set(['value']) | ||||
|  | ||||
|     def get_value(self): | ||||
|         if self.xml.text: | ||||
|             return b64decode(bytes(self.xml.text)) | ||||
|         return '' | ||||
|  | ||||
|     def set_value(self, value): | ||||
|         self.xml.text = '' | ||||
|         if value: | ||||
|             self.xml.text = b64encode(bytes(value)) | ||||
|  | ||||
|     def del_value(self): | ||||
|         self.xml.text = '' | ||||
|  | ||||
|  | ||||
| register_stanza_plugin(Catalog, CatalogItem, iterable=True) | ||||
| register_stanza_plugin(CatalogItem, SecurityLabel) | ||||
| register_stanza_plugin(EquivalentLabel, ESSLabel) | ||||
| register_stanza_plugin(Label, ESSLabel) | ||||
| register_stanza_plugin(SecurityLabel, DisplayMarking) | ||||
| register_stanza_plugin(SecurityLabel, EquivalentLabel, iterable=True) | ||||
| register_stanza_plugin(SecurityLabel, Label) | ||||
| @@ -307,34 +307,29 @@ class RosterItem(object): | ||||
|             p['from'] = self.owner | ||||
|         p.send() | ||||
|  | ||||
|     def send_presence(self, ptype=None, pshow=None, pstatus=None, | ||||
|                             ppriority=None, pnick=None): | ||||
|     def send_presence(self, **kwargs): | ||||
|         """ | ||||
|         Create, initialize, and send a Presence stanza. | ||||
|  | ||||
|         If no recipient is specified, send the presence immediately. | ||||
|         Otherwise, forward the send request to the recipient's roster | ||||
|         entry for processing. | ||||
|  | ||||
|         Arguments: | ||||
|             pshow     -- The presence's show value. | ||||
|             pstatus   -- The presence's status message. | ||||
|             ppriority -- This connections' priority. | ||||
|             pto       -- The recipient of a directed presence. | ||||
|             pfrom     -- The sender of a directed presence, which should | ||||
|                          be the owner JID plus resource. | ||||
|             ptype     -- The type of presence, such as 'subscribe'. | ||||
|             pnick     -- Optional nickname of the presence's sender. | ||||
|         """ | ||||
|         p = self.xmpp.make_presence(pshow=pshow, | ||||
|                                     pstatus=pstatus, | ||||
|                                     ppriority=ppriority, | ||||
|                                     ptype=ptype, | ||||
|                                     pnick=pnick, | ||||
|                                     pto=self.jid) | ||||
|         if self.xmpp.is_component: | ||||
|             p['from'] = self.owner | ||||
|         if p['type'] in p.showtypes or \ | ||||
|            p['type'] in ['available', 'unavailable']: | ||||
|             self.last_status = p | ||||
|         p.send() | ||||
|  | ||||
|         if not self.xmpp.sentpresence: | ||||
|             self.xmpp.event('sent_presence') | ||||
|             self.xmpp.sentpresence = True | ||||
|         if self.xmpp.is_component and not kwargs.get('pfrom', ''): | ||||
|             kwargs['pfrom'] = self.owner | ||||
|         if not kwargs.get('pto', ''): | ||||
|             kwargs['pto'] = self.jid | ||||
|         self.xmpp.send_presence(**kwargs) | ||||
|  | ||||
|     def send_last_presence(self): | ||||
|         if self.last_status is None: | ||||
|   | ||||
| @@ -6,9 +6,11 @@ | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
|  | ||||
| from sleekxmpp.stanza import Presence | ||||
| from sleekxmpp.xmlstream import JID | ||||
| from sleekxmpp.roster import RosterNode | ||||
|  | ||||
|  | ||||
| class Roster(object): | ||||
|  | ||||
|     """ | ||||
| @@ -55,6 +57,33 @@ class Roster(object): | ||||
|             for node in self.db.entries(None, {}): | ||||
|                 self.add(node) | ||||
|  | ||||
|         self.xmpp.add_filter('out', self._save_last_status) | ||||
|  | ||||
|     def _save_last_status(self, stanza): | ||||
|  | ||||
|         if isinstance(stanza, Presence): | ||||
|             sfrom = stanza['from'].full | ||||
|             sto = stanza['to'].full | ||||
|  | ||||
|             if not sfrom: | ||||
|                 sfrom = self.xmpp.boundjid | ||||
|  | ||||
|             if stanza['type'] in stanza.showtypes or \ | ||||
|                stanza['type'] in ('available', 'unavailable'): | ||||
|                 if sto: | ||||
|                     self[sfrom][sto].last_status = stanza | ||||
|                 else: | ||||
|                     self[sfrom].last_status = stanza | ||||
|                     with self[sfrom]._last_status_lock: | ||||
|                         for jid in self[sfrom]: | ||||
|                             self[sfrom][jid].last_status = None | ||||
|  | ||||
|                 if not self.xmpp.sentpresence: | ||||
|                     self.xmpp.event('sent_presence') | ||||
|                     self.xmpp.sentpresence = True | ||||
|  | ||||
|         return stanza | ||||
|  | ||||
|     def __getitem__(self, key): | ||||
|         """ | ||||
|         Return the roster node for a JID. | ||||
| @@ -121,29 +150,27 @@ class Roster(object): | ||||
|         for node in self: | ||||
|             self[node].reset() | ||||
|  | ||||
|     def send_presence(self, pshow=None, pstatus=None, ppriority=None, | ||||
|                       pto=None, pfrom=None, ptype=None, pnick=None): | ||||
|     def send_presence(self, **kwargs): | ||||
|         """ | ||||
|         Create, initialize, and send a Presence stanza. | ||||
|  | ||||
|         Forwards the send request to the appropriate roster to | ||||
|         perform the actual sending. | ||||
|         If no recipient is specified, send the presence immediately. | ||||
|         Otherwise, forward the send request to the recipient's roster | ||||
|         entry for processing. | ||||
|  | ||||
|         Arguments: | ||||
|             pshow     -- The presence's show value. | ||||
|             pstatus   -- The presence's status message. | ||||
|             ppriority -- This connections' priority. | ||||
|             pto       -- The recipient of a directed presence. | ||||
|             pfrom     -- The sender of a directed presence, which should | ||||
|                          be the owner JID plus resource. | ||||
|             ptype     -- The type of presence, such as 'subscribe'. | ||||
|             pfrom     -- The sender of the presence. | ||||
|             pnick     -- Optional nickname of the presence's sender. | ||||
|         """ | ||||
|         self[pfrom].send_presence(ptype=ptype, | ||||
|                                   pshow=pshow, | ||||
|                                   pstatus=pstatus, | ||||
|                                   ppriority=ppriority, | ||||
|                                   pnick=pnick, | ||||
|                                   pto=pto) | ||||
|         if self.xmpp.is_component and not kwargs.get('pfrom', ''): | ||||
|             kwargs['pfrom'] = self.jid | ||||
|         self.xmpp.send_presence(**kwargs) | ||||
|  | ||||
|     @property | ||||
|     def auto_authorize(self): | ||||
|   | ||||
| @@ -6,6 +6,8 @@ | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
|  | ||||
| import threading | ||||
|  | ||||
| from sleekxmpp.xmlstream import JID | ||||
| from sleekxmpp.roster import RosterItem | ||||
|  | ||||
| @@ -59,13 +61,14 @@ class RosterNode(object): | ||||
|         self.last_status = None | ||||
|         self._version = '' | ||||
|         self._jids = {} | ||||
|         self._last_status_lock = threading.Lock() | ||||
|  | ||||
|         if self.db: | ||||
|             if hasattr(self.db, 'version'): | ||||
|                 self._version = self.db.version(self.jid) | ||||
|             for jid in self.db.entries(self.jid): | ||||
|                 self.add(jid) | ||||
|      | ||||
|  | ||||
|     @property | ||||
|     def version(self): | ||||
|         """Retrieve the roster's version ID.""" | ||||
| @@ -146,7 +149,7 @@ class RosterNode(object): | ||||
|         self.db = db | ||||
|         existing_entries = set(self._jids) | ||||
|         new_entries = set(self.db.entries(self.jid, {})) | ||||
|          | ||||
|  | ||||
|         for jid in existing_entries: | ||||
|             self._jids[jid].set_backend(db, save) | ||||
|         for jid in new_entries - existing_entries: | ||||
| @@ -291,8 +294,7 @@ class RosterNode(object): | ||||
|         for jid in self: | ||||
|             self[jid].reset() | ||||
|  | ||||
|     def send_presence(self, ptype=None, pshow=None, pstatus=None, | ||||
|                             ppriority=None, pnick=None, pto=None): | ||||
|     def send_presence(self, **kwargs): | ||||
|         """ | ||||
|         Create, initialize, and send a Presence stanza. | ||||
|  | ||||
| @@ -305,27 +307,14 @@ class RosterNode(object): | ||||
|             pstatus   -- The presence's status message. | ||||
|             ppriority -- This connections' priority. | ||||
|             pto       -- The recipient of a directed presence. | ||||
|             pfrom     -- The sender of a directed presence, which should | ||||
|                          be the owner JID plus resource. | ||||
|             ptype     -- The type of presence, such as 'subscribe'. | ||||
|             pnick     -- Optional nickname of the presence's sender. | ||||
|         """ | ||||
|         if pto: | ||||
|             self[pto].send_presence(ptype, pshow, pstatus, | ||||
|                                     ppriority, pnick) | ||||
|         else: | ||||
|             p = self.xmpp.make_presence(pshow=pshow, | ||||
|                                         pstatus=pstatus, | ||||
|                                         ppriority=ppriority, | ||||
|                                         ptype=ptype, | ||||
|                                         pnick=pnick) | ||||
|             if self.xmpp.is_component: | ||||
|                 p['from'] = self.jid | ||||
|             if p['type'] in p.showtypes or \ | ||||
|                p['type'] in ['available', 'unavailable']: | ||||
|                 self.last_status = p | ||||
|             p.send() | ||||
|  | ||||
|             if not self.xmpp.sentpresence: | ||||
|                 self.xmpp.event('sent_presence') | ||||
|                 self.xmpp.sentpresence = True | ||||
|         if self.xmpp.is_component and not kwargs.get('pfrom', ''): | ||||
|             kwargs['pfrom'] = self.jid | ||||
|         self.xmpp.send_presence(**kwargs) | ||||
|  | ||||
|     def send_last_presence(self): | ||||
|         if self.last_status is None: | ||||
|   | ||||
| @@ -51,7 +51,8 @@ class Error(ElementBase): | ||||
|     namespace = 'jabber:client' | ||||
|     name = 'error' | ||||
|     plugin_attrib = 'error' | ||||
|     interfaces = set(('code', 'condition', 'text', 'type')) | ||||
|     interfaces = set(('code', 'condition', 'text', 'type', | ||||
|                       'gone', 'redirect')) | ||||
|     sub_interfaces = set(('text',)) | ||||
|     plugin_attrib_map = {} | ||||
|     plugin_tag_map = {} | ||||
| @@ -88,7 +89,7 @@ class Error(ElementBase): | ||||
|  | ||||
|     def get_condition(self): | ||||
|         """Return the condition element's name.""" | ||||
|         for child in self.xml.getchildren(): | ||||
|         for child in self.xml: | ||||
|             if "{%s}" % self.condition_ns in child.tag: | ||||
|                 cond = child.tag.split('}', 1)[-1] | ||||
|                 if cond in self.conditions: | ||||
| @@ -109,7 +110,7 @@ class Error(ElementBase): | ||||
|  | ||||
|     def del_condition(self): | ||||
|         """Remove the condition element.""" | ||||
|         for child in self.xml.getchildren(): | ||||
|         for child in self.xml: | ||||
|             if "{%s}" % self.condition_ns in child.tag: | ||||
|                 tag = child.tag.split('}', 1)[-1] | ||||
|                 if tag in self.conditions: | ||||
| @@ -135,6 +136,33 @@ class Error(ElementBase): | ||||
|         self._del_sub('{%s}text' % self.condition_ns) | ||||
|         return self | ||||
|  | ||||
|     def get_gone(self): | ||||
|         return self._get_sub_text('{%s}gone' % self.condition_ns, '') | ||||
|  | ||||
|     def get_redirect(self): | ||||
|         return self._get_sub_text('{%s}redirect' % self.condition_ns, '') | ||||
|  | ||||
|     def set_gone(self, value): | ||||
|         if value: | ||||
|             del self['condition'] | ||||
|             return self._set_sub_text('{%s}gone' % self.condition_ns, value) | ||||
|         elif self['condition'] == 'gone': | ||||
|             del self['condition'] | ||||
|  | ||||
|     def set_redirect(self, value): | ||||
|         if value: | ||||
|             del self['condition'] | ||||
|             ns = self.condition_ns | ||||
|             return self._set_sub_text('{%s}redirect' % ns, value) | ||||
|         elif self['condition'] == 'redirect': | ||||
|             del self['condition'] | ||||
|  | ||||
|     def del_gone(self): | ||||
|         self._del_sub('{%s}gone' % self.condition_ns) | ||||
|  | ||||
|     def del_redirect(self): | ||||
|         self._del_sub('{%s}redirect' % self.condition_ns) | ||||
|  | ||||
|  | ||||
| # To comply with PEP8, method names now use underscores. | ||||
| # Deprecated method names are re-mapped for backwards compatibility. | ||||
|   | ||||
| @@ -122,7 +122,7 @@ class Iq(RootStanza): | ||||
|  | ||||
|     def get_query(self): | ||||
|         """Return the namespace of the <query> element.""" | ||||
|         for child in self.xml.getchildren(): | ||||
|         for child in self.xml: | ||||
|             if child.tag.endswith('query'): | ||||
|                 ns = child.tag.split('}')[0] | ||||
|                 if '{' in ns: | ||||
| @@ -132,7 +132,7 @@ class Iq(RootStanza): | ||||
|  | ||||
|     def del_query(self): | ||||
|         """Remove the <query> element.""" | ||||
|         for child in self.xml.getchildren(): | ||||
|         for child in self.xml: | ||||
|             if child.tag.endswith('query'): | ||||
|                 self.xml.remove(child) | ||||
|         return self | ||||
|   | ||||
| @@ -78,7 +78,8 @@ class RootStanza(StanzaBase): | ||||
|             self['error']['type'] = 'cancel' | ||||
|             self.send() | ||||
|             # log the error | ||||
|             log.exception('Error handling {%s}%s stanza' ,  self.namespace, self.name) | ||||
|             log.exception('Error handling {%s}%s stanza', | ||||
|                           self.namespace, self.name) | ||||
|             # Finally raise the exception to a global exception handler | ||||
|             self.stream.exception(e) | ||||
|  | ||||
|   | ||||
| @@ -47,7 +47,7 @@ class Roster(ElementBase): | ||||
|         roster versioning. | ||||
|         """ | ||||
|         return self.xml.attrib.get('ver', None) | ||||
|   | ||||
|  | ||||
|     def set_ver(self, ver): | ||||
|         """ | ||||
|         Ensure handling an empty ver attribute propery. | ||||
| @@ -101,7 +101,8 @@ class Roster(ElementBase): | ||||
|                 items[item['jid']] = item.values | ||||
|                 # Remove extra JID reference to keep everything | ||||
|                 # backward compatible | ||||
|                 del items[item['jid']]['jid']  | ||||
|                 del items[item['jid']]['jid'] | ||||
|                 del items[item['jid']]['lang'] | ||||
|         return items | ||||
|  | ||||
|     def del_items(self): | ||||
|   | ||||
| @@ -54,7 +54,7 @@ class StreamError(Error, StanzaBase): | ||||
|     """ | ||||
|  | ||||
|     namespace = 'http://etherx.jabber.org/streams' | ||||
|     interfaces = set(('condition', 'text')) | ||||
|     interfaces = set(('condition', 'text', 'see_other_host')) | ||||
|     conditions = set(( | ||||
|         'bad-format', 'bad-namespace-prefix', 'conflict', | ||||
|         'connection-timeout', 'host-gone', 'host-unknown', | ||||
| @@ -66,3 +66,18 @@ class StreamError(Error, StanzaBase): | ||||
|         'unsupported-feature', 'unsupported-stanza-type', | ||||
|         'unsupported-version')) | ||||
|     condition_ns = 'urn:ietf:params:xml:ns:xmpp-streams' | ||||
|  | ||||
|     def get_see_other_host(self): | ||||
|         ns = self.condition_ns | ||||
|         return self._get_sub_text('{%s}see-other-host' % ns, '') | ||||
|  | ||||
|     def set_see_other_host(self, value): | ||||
|         if value: | ||||
|             del self['condition'] | ||||
|             ns = self.condition_ns | ||||
|             return self._set_sub_text('{%s}see-other-host' % ns, value) | ||||
|         elif self['condition'] == 'see-other-host': | ||||
|             del self['condition'] | ||||
|  | ||||
|     def del_see_other_host(self): | ||||
|         self._del_sub('{%s}see-other-host' % self.condition_ns) | ||||
|   | ||||
| @@ -6,6 +6,7 @@ | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
|  | ||||
| from sleekxmpp.thirdparty import OrderedDict | ||||
| from sleekxmpp.xmlstream import StanzaBase | ||||
|  | ||||
|  | ||||
| @@ -28,7 +29,10 @@ class StreamFeatures(StanzaBase): | ||||
|     def get_features(self): | ||||
|         """ | ||||
|         """ | ||||
|         return self.plugins | ||||
|         features = OrderedDict() | ||||
|         for (name, lang), plugin in self.plugins.items(): | ||||
|             features[name] = plugin | ||||
|         return features | ||||
|  | ||||
|     def set_features(self, value): | ||||
|         """ | ||||
|   | ||||
| @@ -76,7 +76,7 @@ class SleekTest(unittest.TestCase): | ||||
|                             known_prefixes[prefix], | ||||
|                             xml_string) | ||||
|                 xml = self.parse_xml(xml_string) | ||||
|                 xml = xml.getchildren()[0] | ||||
|                 xml = list(xml)[0] | ||||
|                 return xml | ||||
|             else: | ||||
|                 self.fail("XML data was mal-formed:\n%s" % xml_string) | ||||
| @@ -333,6 +333,8 @@ class SleekTest(unittest.TestCase): | ||||
|         # Remove unique ID prefix to make it easier to test | ||||
|         self.xmpp._id_prefix = '' | ||||
|         self.xmpp._disconnect_wait_for_threads = False | ||||
|         self.xmpp.default_lang = None | ||||
|         self.xmpp.peer_default_lang = None | ||||
|  | ||||
|         # We will use this to wait for the session_start event | ||||
|         # for live connections. | ||||
| @@ -386,6 +388,7 @@ class SleekTest(unittest.TestCase): | ||||
|                           sid='', | ||||
|                           stream_ns="http://etherx.jabber.org/streams", | ||||
|                           default_ns="jabber:client", | ||||
|                           default_lang="en", | ||||
|                           version="1.0", | ||||
|                           xml_header=True): | ||||
|         """ | ||||
| @@ -413,6 +416,8 @@ class SleekTest(unittest.TestCase): | ||||
|             parts.append('from="%s"' % sfrom) | ||||
|         if sid: | ||||
|             parts.append('id="%s"' % sid) | ||||
|         if default_lang: | ||||
|             parts.append('xml:lang="%s"' % default_lang) | ||||
|         parts.append('version="%s"' % version) | ||||
|         parts.append('xmlns:stream="%s"' % stream_ns) | ||||
|         parts.append('xmlns="%s"' % default_ns) | ||||
| @@ -512,9 +517,9 @@ class SleekTest(unittest.TestCase): | ||||
|         if '{%s}lang' % xml_ns in recv_xml.attrib: | ||||
|             del recv_xml.attrib['{%s}lang' % xml_ns] | ||||
|  | ||||
|         if recv_xml.getchildren: | ||||
|         if list(recv_xml): | ||||
|             # We received more than just the header | ||||
|             for xml in recv_xml.getchildren(): | ||||
|             for xml in recv_xml: | ||||
|                 self.xmpp.socket.recv_data(tostring(xml)) | ||||
|  | ||||
|             attrib = recv_xml.attrib | ||||
| @@ -564,6 +569,7 @@ class SleekTest(unittest.TestCase): | ||||
|                           sid='', | ||||
|                           stream_ns="http://etherx.jabber.org/streams", | ||||
|                           default_ns="jabber:client", | ||||
|                           default_lang="en", | ||||
|                           version="1.0", | ||||
|                           xml_header=False, | ||||
|                           timeout=1): | ||||
| @@ -585,6 +591,7 @@ class SleekTest(unittest.TestCase): | ||||
|         header = self.make_header(sto, sfrom, sid, | ||||
|                                   stream_ns=stream_ns, | ||||
|                                   default_ns=default_ns, | ||||
|                                   default_lang=default_lang, | ||||
|                                   version=version, | ||||
|                                   xml_header=xml_header) | ||||
|         sent_header = self.xmpp.socket.next_sent(timeout) | ||||
| @@ -691,7 +698,7 @@ class SleekTest(unittest.TestCase): | ||||
|         if xml.tag.startswith('{'): | ||||
|             return | ||||
|         xml.tag = '{%s}%s' % (ns, xml.tag) | ||||
|         for child in xml.getchildren(): | ||||
|         for child in xml: | ||||
|             self.fix_namespaces(child, ns) | ||||
|  | ||||
|     def compare(self, xml, *other): | ||||
| @@ -734,7 +741,7 @@ class SleekTest(unittest.TestCase): | ||||
|             return False | ||||
|  | ||||
|         # Step 4: Check children count | ||||
|         if len(xml.getchildren()) != len(other.getchildren()): | ||||
|         if len(list(xml)) != len(list(other)): | ||||
|             return False | ||||
|  | ||||
|         # Step 5: Recursively check children | ||||
|   | ||||
| @@ -9,5 +9,5 @@ | ||||
| # We don't want to have to import the entire library | ||||
| # just to get the version info for setup.py | ||||
|  | ||||
| __version__ = '1.1.4' | ||||
| __version_info__ = (1, 1, 4, '', 0) | ||||
| __version__ = '1.1.7' | ||||
| __version_info__ = (1, 1, 7, '', 0) | ||||
|   | ||||
| @@ -7,11 +7,12 @@ try: | ||||
|     from pyasn1.type.univ import Any, ObjectIdentifier, OctetString | ||||
|     from pyasn1.type.char import BMPString, IA5String, UTF8String | ||||
|     from pyasn1.type.useful import GeneralizedTime | ||||
|     from pyasn1_modules.rfc2459 import Certificate, DirectoryString, SubjectAltName, GeneralNames, GeneralName | ||||
|     from pyasn1_modules.rfc2459 import (Certificate, DirectoryString, | ||||
|                                         SubjectAltName, GeneralNames, | ||||
|                                         GeneralName) | ||||
|     from pyasn1_modules.rfc2459 import id_ce_subjectAltName as SUBJECT_ALT_NAME | ||||
|     from pyasn1_modules.rfc2459 import id_at_commonName as COMMON_NAME | ||||
|  | ||||
|  | ||||
|     XMPP_ADDR = ObjectIdentifier('1.3.6.1.5.5.7.8.5') | ||||
|     SRV_NAME = ObjectIdentifier('1.3.6.1.5.5.7.8.7') | ||||
|  | ||||
| @@ -42,7 +43,7 @@ def extract_names(raw_cert): | ||||
|     cert = decoder.decode(raw_cert, asn1Spec=Certificate())[0] | ||||
|     tbs = cert.getComponentByName('tbsCertificate') | ||||
|     subject = tbs.getComponentByName('subject') | ||||
|     extensions = tbs.getComponentByName('extensions') | ||||
|     extensions = tbs.getComponentByName('extensions') or [] | ||||
|  | ||||
|     # Extract the CommonName(s) from the cert. | ||||
|     for rdnss in subject: | ||||
| @@ -149,7 +150,7 @@ def verify(expected, raw_cert): | ||||
|     expected_wild = expected[expected.index('.'):] | ||||
|     expected_srv = '_xmpp-client.%s' % expected | ||||
|  | ||||
|     for name in cert_names['XMPPAddr']:  | ||||
|     for name in cert_names['XMPPAddr']: | ||||
|         if name == expected: | ||||
|             return True | ||||
|     for name in cert_names['SRV']: | ||||
|   | ||||
| @@ -49,7 +49,7 @@ class BaseHandler(object): | ||||
|     def match(self, xml): | ||||
|         """Compare a stanza or XML object with the handler's matcher. | ||||
|  | ||||
|         :param xml: An XML or  | ||||
|         :param xml: An XML or | ||||
|             :class:`~sleekxmpp.xmlstream.stanzabase.ElementBase` object | ||||
|         """ | ||||
|         return self._matcher.match(xml) | ||||
| @@ -73,7 +73,7 @@ class BaseHandler(object): | ||||
|         self._payload = payload | ||||
|  | ||||
|     def check_delete(self): | ||||
|         """Check if the handler should be removed from the list  | ||||
|         """Check if the handler should be removed from the list | ||||
|         of stream handlers. | ||||
|         """ | ||||
|         return self._destroy | ||||
|   | ||||
| @@ -33,7 +33,7 @@ class Callback(BaseHandler): | ||||
|     :param matcher: A :class:`~sleekxmpp.xmlstream.matcher.base.MatcherBase` | ||||
|                     derived object for matching stanza objects. | ||||
|     :param pointer: The function to execute during callback. | ||||
|     :param bool thread: **DEPRECATED.** Remains only for  | ||||
|     :param bool thread: **DEPRECATED.** Remains only for | ||||
|                         backwards compatibility. | ||||
|     :param bool once: Indicates if the handler should be used only | ||||
|                       once. Defaults to False. | ||||
|   | ||||
| @@ -34,9 +34,9 @@ class MatchXMLMask(MatcherBase): | ||||
|  | ||||
|         <message xmlns="jabber:client"><body /></message> | ||||
|  | ||||
|     Use of XMLMask is discouraged, and  | ||||
|     :class:`~sleekxmpp.xmlstream.matcher.xpath.MatchXPath` or  | ||||
|     :class:`~sleekxmpp.xmlstream.matcher.stanzapath.StanzaPath`  | ||||
|     Use of XMLMask is discouraged, and | ||||
|     :class:`~sleekxmpp.xmlstream.matcher.xpath.MatchXPath` or | ||||
|     :class:`~sleekxmpp.xmlstream.matcher.stanzapath.StanzaPath` | ||||
|     should be used instead. | ||||
|  | ||||
|     The use of namespaces in the mask comparison is controlled by | ||||
| @@ -151,8 +151,8 @@ class MatchXMLMask(MatcherBase): | ||||
|         """ | ||||
|         tag = tag.split('}')[-1] | ||||
|         try: | ||||
|             children = [c.tag.split('}')[-1] for c in xml.getchildren()] | ||||
|             children = [c.tag.split('}')[-1] for c in xml] | ||||
|             index = children.index(tag) | ||||
|         except ValueError: | ||||
|             return None | ||||
|         return xml.getchildren()[index] | ||||
|         return list(xml)[index] | ||||
|   | ||||
| @@ -77,10 +77,10 @@ class MatchXPath(MatcherBase): | ||||
|                     # Skip empty tag name artifacts from the cleanup phase. | ||||
|                     continue | ||||
|  | ||||
|                 children = [c.tag.split('}')[-1] for c in xml.getchildren()] | ||||
|                 children = [c.tag.split('}')[-1] for c in xml] | ||||
|                 try: | ||||
|                     index = children.index(tag) | ||||
|                 except ValueError: | ||||
|                     return False | ||||
|                 xml = xml.getchildren()[index] | ||||
|                 xml = list(xml)[index] | ||||
|             return True | ||||
|   | ||||
| @@ -52,7 +52,8 @@ def default_resolver(): | ||||
|     return None | ||||
|  | ||||
|  | ||||
| def resolve(host, port=None, service=None, proto='tcp', resolver=None): | ||||
| def resolve(host, port=None, service=None, proto='tcp', | ||||
|             resolver=None, use_ipv6=True): | ||||
|     """Peform DNS resolution for a given hostname. | ||||
|  | ||||
|     Resolution may perform SRV record lookups if a service and protocol | ||||
| @@ -73,16 +74,23 @@ def resolve(host, port=None, service=None, proto='tcp', resolver=None): | ||||
|     :param    proto: Optional SRV protocol name without leading underscore. | ||||
|     :param resolver: Optionally provide a DNS resolver object that has | ||||
|                      been custom configured. | ||||
|     :param use_ipv6: Optionally control the use of IPv6 in situations | ||||
|                      where it is either not available, or performance | ||||
|                      is degraded. Defaults to ``True``. | ||||
|  | ||||
|     :type     host: string | ||||
|     :type     port: int | ||||
|     :type  service: string | ||||
|     :type    proto: string | ||||
|     :type resolver: :class:`dns.resolver.Resolver` | ||||
|     :type use_ipv6: bool | ||||
|  | ||||
|     :return: An iterable of IP address, port pairs in the order | ||||
|              dictated by SRV priorities and weights, if applicable. | ||||
|     """ | ||||
|     if not use_ipv6: | ||||
|         log.debug("DNS: Use of IPv6 has been disabled.") | ||||
|  | ||||
|     if resolver is None and USE_DNSPYTHON: | ||||
|         resolver = dns.resolver.get_default_resolver() | ||||
|  | ||||
| @@ -98,13 +106,15 @@ def resolve(host, port=None, service=None, proto='tcp', resolver=None): | ||||
|     except socket.error: | ||||
|         pass | ||||
|  | ||||
|     try: | ||||
|         # Likewise, If `host` is an IPv6 literal, we can return it immediately. | ||||
|         if hasattr(socket, 'inet_pton'): | ||||
|             ipv6 = socket.inet_pton(socket.AF_INET6, host) | ||||
|             yield (host, port) | ||||
|     except socket.error: | ||||
|         pass | ||||
|     if use_ipv6: | ||||
|         try: | ||||
|             # Likewise, If `host` is an IPv6 literal, we can return | ||||
|             # it immediately. | ||||
|             if hasattr(socket, 'inet_pton'): | ||||
|                 ipv6 = socket.inet_pton(socket.AF_INET6, host) | ||||
|                 yield (host, port) | ||||
|         except socket.error: | ||||
|             pass | ||||
|  | ||||
|     # If no service was provided, then we can just do A/AAAA lookups on the | ||||
|     # provided host. Otherwise we need to get an ordered list of hosts to | ||||
| @@ -117,10 +127,12 @@ def resolve(host, port=None, service=None, proto='tcp', resolver=None): | ||||
|     for host, port in hosts: | ||||
|         results = [] | ||||
|         if host == 'localhost': | ||||
|             results.append(('::1', port)) | ||||
|             if use_ipv6: | ||||
|                 results.append(('::1', port)) | ||||
|             results.append(('127.0.0.1', port)) | ||||
|         for address in get_AAAA(host, resolver=resolver): | ||||
|             results.append((address, port)) | ||||
|         if use_ipv6: | ||||
|             for address in get_AAAA(host, resolver=resolver): | ||||
|                 results.append((address, port)) | ||||
|         for address in get_A(host, resolver=resolver): | ||||
|             results.append((address, port)) | ||||
|  | ||||
| @@ -160,13 +172,13 @@ def get_A(host, resolver=None): | ||||
|         recs = resolver.query(host, dns.rdatatype.A) | ||||
|         return [rec.to_text() for rec in recs] | ||||
|     except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer): | ||||
|         log.debug("DNS: No A records for %s." % host) | ||||
|         log.debug("DNS: No A records for %s" % host) | ||||
|         return [] | ||||
|     except dns.exception.Timeout: | ||||
|         log.debug("DNS: A record resolution timed out for %s." % host) | ||||
|         log.debug("DNS: A record resolution timed out for %s" % host) | ||||
|         return [] | ||||
|     except dns.exception.DNSException as e: | ||||
|         log.debug("DNS: Error querying A records for %s." % host) | ||||
|         log.debug("DNS: Error querying A records for %s" % host) | ||||
|         log.exception(e) | ||||
|         return [] | ||||
|  | ||||
| @@ -204,13 +216,13 @@ def get_AAAA(host, resolver=None): | ||||
|         recs = resolver.query(host, dns.rdatatype.AAAA) | ||||
|         return [rec.to_text() for rec in recs] | ||||
|     except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer): | ||||
|         log.debug("DNS: No AAAA records for %s." % host) | ||||
|         log.debug("DNS: No AAAA records for %s" % host) | ||||
|         return [] | ||||
|     except dns.exception.Timeout: | ||||
|         log.debug("DNS: AAAA record resolution timed out for %s." % host) | ||||
|         log.debug("DNS: AAAA record resolution timed out for %s" % host) | ||||
|         return [] | ||||
|     except dns.exception.DNSException as e: | ||||
|         log.debug("DNS: Error querying AAAA records for %s." % host) | ||||
|         log.debug("DNS: Error querying AAAA records for %s" % host) | ||||
|         log.exception(e) | ||||
|         return [] | ||||
|  | ||||
| @@ -244,7 +256,7 @@ def get_SRV(host, port, service, proto='tcp', resolver=None): | ||||
|     if resolver is None: | ||||
|         return [(host, port)] | ||||
|  | ||||
|     log.debug("Querying SRV records for %s" % host) | ||||
|     log.debug("DNS: Querying SRV records for %s" % host) | ||||
|     try: | ||||
|         recs = resolver.query('_%s._%s.%s' % (service, proto, host), | ||||
|                               dns.rdatatype.SRV) | ||||
|   | ||||
| @@ -57,7 +57,7 @@ class Task(object): | ||||
|  | ||||
|         #: The keyword arguments to pass to :attr:`callback`. | ||||
|         self.kwargs = kwargs or {} | ||||
|          | ||||
|  | ||||
|         #: Indicates if the task should repeat after executing, | ||||
|         #: using the same :attr:`seconds` delay. | ||||
|         self.repeat = repeat | ||||
| @@ -103,7 +103,7 @@ class Scheduler(object): | ||||
|     def __init__(self, parentstop=None): | ||||
|         #: A queue for storing tasks | ||||
|         self.addq = queue.Queue() | ||||
|          | ||||
|  | ||||
|         #: A list of tasks in order of execution time. | ||||
|         self.schedule = [] | ||||
|  | ||||
|   | ||||
| @@ -12,6 +12,8 @@ | ||||
|     :license: MIT, see LICENSE for more details | ||||
| """ | ||||
|  | ||||
| from __future__ import with_statement, unicode_literals | ||||
|  | ||||
| import copy | ||||
| import logging | ||||
| import weakref | ||||
| @@ -29,6 +31,9 @@ log = logging.getLogger(__name__) | ||||
| XML_TYPE = type(ET.Element('xml')) | ||||
|  | ||||
|  | ||||
| XML_NS = 'http://www.w3.org/XML/1998/namespace' | ||||
|  | ||||
|  | ||||
| def register_stanza_plugin(stanza, plugin, iterable=False, overrides=False): | ||||
|     """ | ||||
|     Associate a stanza object as a plugin for another stanza. | ||||
| @@ -40,7 +45,7 @@ def register_stanza_plugin(stanza, plugin, iterable=False, overrides=False): | ||||
|     substanzas for the parent, using ``parent['substanzas']``. If the | ||||
|     attribute ``plugin_multi_attrib`` was defined for the plugin, then | ||||
|     the substanza set can be filtered to only instances of the plugin | ||||
|     class. For example, given a plugin class ``Foo`` with  | ||||
|     class. For example, given a plugin class ``Foo`` with | ||||
|     ``plugin_multi_attrib = 'foos'`` then:: | ||||
|  | ||||
|         parent['foos'] | ||||
| @@ -94,6 +99,14 @@ def multifactory(stanza, plugin_attrib): | ||||
|     """ | ||||
|     Returns a ElementBase class for handling reoccuring child stanzas | ||||
|     """ | ||||
|  | ||||
|     def plugin_filter(self): | ||||
|         return lambda x: isinstance(x, self._multistanza) | ||||
|  | ||||
|     def plugin_lang_filter(self, lang): | ||||
|         return lambda x: isinstance(x, self._multistanza) and \ | ||||
|                          x['lang'] == lang | ||||
|  | ||||
|     class Multi(ElementBase): | ||||
|         """ | ||||
|         Template class for multifactory | ||||
| @@ -101,28 +114,45 @@ def multifactory(stanza, plugin_attrib): | ||||
|         def setup(self, xml=None): | ||||
|             self.xml = ET.Element('') | ||||
|  | ||||
|     def get_multi(self): | ||||
|     def get_multi(self, lang=None): | ||||
|         parent = self.parent() | ||||
|         res = filter(lambda sub: isinstance(sub, self._multistanza), parent) | ||||
|         if not lang or lang == '*': | ||||
|             res = filter(plugin_filter(self), parent) | ||||
|         else: | ||||
|             res = filter(plugin_filter(self, lang), parent) | ||||
|         return list(res) | ||||
|  | ||||
|     def set_multi(self, val): | ||||
|     def set_multi(self, val, lang=None): | ||||
|         parent = self.parent() | ||||
|         del parent[self.plugin_attrib] | ||||
|         del_multi = getattr(self, 'del_%s' % plugin_attrib) | ||||
|         del_multi(lang) | ||||
|         for sub in val: | ||||
|             parent.append(sub) | ||||
|  | ||||
|     def del_multi(self): | ||||
|     def del_multi(self, lang=None): | ||||
|         parent = self.parent() | ||||
|         res = filter(lambda sub: isinstance(sub, self._multistanza), parent) | ||||
|         for stanza in list(res): | ||||
|             parent.iterables.remove(stanza) | ||||
|             parent.xml.remove(stanza.xml) | ||||
|         if not lang or lang == '*': | ||||
|             res = filter(plugin_filter(self), parent) | ||||
|         else: | ||||
|             res = filter(plugin_filter(self, lang), parent) | ||||
|         res = list(res) | ||||
|         if not res: | ||||
|             del parent.plugins[(plugin_attrib, None)] | ||||
|             parent.loaded_plugins.remove(plugin_attrib) | ||||
|             try: | ||||
|                 parent.xml.remove(self.xml) | ||||
|             except: | ||||
|                 pass | ||||
|         else: | ||||
|             for stanza in list(res): | ||||
|                 parent.iterables.remove(stanza) | ||||
|                 parent.xml.remove(stanza.xml) | ||||
|  | ||||
|     Multi.is_extension = True | ||||
|     Multi.plugin_attrib = plugin_attrib | ||||
|     Multi._multistanza = stanza | ||||
|     Multi.interfaces = (plugin_attrib,) | ||||
|     Multi.interfaces = set([plugin_attrib]) | ||||
|     Multi.lang_interfaces = set([plugin_attrib]) | ||||
|     setattr(Multi, "get_%s" % plugin_attrib, get_multi) | ||||
|     setattr(Multi, "set_%s" % plugin_attrib, set_multi) | ||||
|     setattr(Multi, "del_%s" % plugin_attrib, del_multi) | ||||
| @@ -231,8 +261,10 @@ class ElementBase(object): | ||||
|     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 | ||||
|         >>> # Same as using message['custom']['custom'] | ||||
|         >>> message['custom'] = 'bar' | ||||
|         >>> # Must use all interfaces | ||||
|         >>> message['custom']['custom'] | ||||
|         'bar' | ||||
|  | ||||
|     If the plugin sets :attr:`is_extension` to ``True``, then both setting | ||||
| @@ -245,13 +277,13 @@ class ElementBase(object): | ||||
|  | ||||
|  | ||||
|     :param xml: Initialize the stanza object with an existing XML object. | ||||
|     :param parent: Optionally specify a parent stanza object will will | ||||
|     :param parent: Optionally specify a parent stanza object will | ||||
|                    contain this substanza. | ||||
|     """ | ||||
|  | ||||
|     #: The XML tag name of the element, not including any namespace | ||||
|     #: prefixes. For example, an :class:`ElementBase` object for ``<message />`` | ||||
|     #: would use ``name = 'message'``. | ||||
|     #: prefixes. For example, an :class:`ElementBase` object for | ||||
|     #: ``<message />`` would use ``name = 'message'``. | ||||
|     name = 'stanza' | ||||
|  | ||||
|     #: The XML namespace for the element. Given ``<foo xmlns="bar" />``, | ||||
| @@ -289,14 +321,17 @@ class ElementBase(object): | ||||
|     #: subelements of the underlying XML object. Using this set, the text | ||||
|     #: of these subelements may be set, retrieved, or removed without | ||||
|     #: needing to define custom methods. | ||||
|     sub_interfaces = tuple() | ||||
|     sub_interfaces = set() | ||||
|  | ||||
|     #: A subset of :attr:`interfaces` which maps the presence of | ||||
|     #: subelements to boolean values. Using this set allows for quickly | ||||
|     #: checking for the existence of empty subelements like ``<required />``. | ||||
|     #: | ||||
|     #: .. versionadded:: 1.1 | ||||
|     bool_interfaces = tuple() | ||||
|     bool_interfaces = set() | ||||
|  | ||||
|     #: .. versionadded:: 1.1.2 | ||||
|     lang_interfaces = set() | ||||
|  | ||||
|     #: In some cases you may wish to override the behaviour of one of the | ||||
|     #: parent stanza's interfaces. The ``overrides`` list specifies the | ||||
| @@ -363,7 +398,7 @@ class ElementBase(object): | ||||
|     subitem = set() | ||||
|  | ||||
|     #: The default XML namespace: ``http://www.w3.org/XML/1998/namespace``. | ||||
|     xml_ns = 'http://www.w3.org/XML/1998/namespace' | ||||
|     xml_ns = XML_NS | ||||
|  | ||||
|     def __init__(self, xml=None, parent=None): | ||||
|         self._index = 0 | ||||
| @@ -375,6 +410,7 @@ class ElementBase(object): | ||||
|         #: An ordered dictionary of plugin stanzas, mapped by their | ||||
|         #: :attr:`plugin_attrib` value. | ||||
|         self.plugins = OrderedDict() | ||||
|         self.loaded_plugins = set() | ||||
|  | ||||
|         #: A list of child stanzas whose class is included in | ||||
|         #: :attr:`plugin_iterables`. | ||||
| @@ -385,6 +421,12 @@ class ElementBase(object): | ||||
|         #: ``'{namespace}elementname'``. | ||||
|         self.tag = self.tag_name() | ||||
|  | ||||
|         if 'lang' not in self.interfaces: | ||||
|             if isinstance(self.interfaces, tuple): | ||||
|                 self.interfaces += ('lang',) | ||||
|             else: | ||||
|                 self.interfaces.add('lang') | ||||
|  | ||||
|         #: A :class:`weakref.weakref` to the parent stanza, if there is one. | ||||
|         #: If not, then :attr:`parent` is ``None``. | ||||
|         self.parent = None | ||||
| @@ -403,13 +445,12 @@ class ElementBase(object): | ||||
|             return | ||||
|  | ||||
|         # Initialize values using provided XML | ||||
|         for child in self.xml.getchildren(): | ||||
|         for child in self.xml: | ||||
|             if child.tag in self.plugin_tag_map: | ||||
|                 plugin_class = self.plugin_tag_map[child.tag] | ||||
|                 plugin = plugin_class(child, self) | ||||
|                 self.plugins[plugin.plugin_attrib] = plugin | ||||
|                 if plugin_class in self.plugin_iterables: | ||||
|                     self.iterables.append(plugin) | ||||
|                 self.init_plugin(plugin_class.plugin_attrib, | ||||
|                                  existing_xml=child, | ||||
|                                  reuse=False) | ||||
|  | ||||
|     def setup(self, xml=None): | ||||
|         """Initialize the stanza's XML contents. | ||||
| @@ -443,7 +484,7 @@ class ElementBase(object): | ||||
|             # We did not generate XML | ||||
|             return False | ||||
|  | ||||
|     def enable(self, attrib): | ||||
|     def enable(self, attrib, lang=None): | ||||
|         """Enable and initialize a stanza plugin. | ||||
|  | ||||
|         Alias for :meth:`init_plugin`. | ||||
| @@ -451,24 +492,67 @@ class ElementBase(object): | ||||
|         :param string attrib: The :attr:`plugin_attrib` value of the | ||||
|                               plugin to enable. | ||||
|         """ | ||||
|         return self.init_plugin(attrib) | ||||
|         return self.init_plugin(attrib, lang) | ||||
|  | ||||
|     def init_plugin(self, attrib): | ||||
|     def _get_plugin(self, name, lang=None): | ||||
|         if lang is None: | ||||
|             lang = self.get_lang() | ||||
|  | ||||
|         if name not in self.plugin_attrib_map: | ||||
|             return None | ||||
|  | ||||
|         plugin_class = self.plugin_attrib_map[name] | ||||
|  | ||||
|         if plugin_class.is_extension: | ||||
|             if (name, None) in self.plugins: | ||||
|                 return self.plugins[(name, None)] | ||||
|             else: | ||||
|                 return self.init_plugin(name, lang) | ||||
|         else: | ||||
|             if (name, lang) in self.plugins: | ||||
|                 return self.plugins[(name, lang)] | ||||
|             else: | ||||
|                 return self.init_plugin(name, lang) | ||||
|  | ||||
|     def init_plugin(self, attrib, lang=None, existing_xml=None, reuse=True): | ||||
|         """Enable and initialize a stanza plugin. | ||||
|  | ||||
|         :param string attrib: The :attr:`plugin_attrib` value of the | ||||
|                               plugin to enable. | ||||
|         """ | ||||
|         if attrib not in self.plugins: | ||||
|             plugin_class = self.plugin_attrib_map[attrib] | ||||
|         if lang is None: | ||||
|             lang = self.get_lang() | ||||
|  | ||||
|         plugin_class = self.plugin_attrib_map[attrib] | ||||
|  | ||||
|         if plugin_class.is_extension and (attrib, None) in self.plugins: | ||||
|             return self.plugins[(attrib, None)] | ||||
|         if reuse and (attrib, lang) in self.plugins: | ||||
|             return self.plugins[(attrib, lang)] | ||||
|  | ||||
|         if existing_xml is None: | ||||
|             existing_xml = self.xml.find(plugin_class.tag_name()) | ||||
|             plugin = plugin_class(parent=self, xml=existing_xml) | ||||
|             self.plugins[attrib] = plugin | ||||
|             if plugin_class in self.plugin_iterables: | ||||
|                 self.iterables.append(plugin) | ||||
|                 if plugin_class.plugin_multi_attrib: | ||||
|                     self.init_plugin(plugin_class.plugin_multi_attrib) | ||||
|         return self | ||||
|  | ||||
|         if existing_xml is not None: | ||||
|             if existing_xml.attrib.get('{%s}lang' % XML_NS, '') != lang: | ||||
|                 existing_xml = None | ||||
|  | ||||
|         plugin = plugin_class(parent=self, xml=existing_xml) | ||||
|  | ||||
|         if plugin.is_extension: | ||||
|             self.plugins[(attrib, None)] = plugin | ||||
|         else: | ||||
|             plugin['lang'] = lang | ||||
|             self.plugins[(attrib, lang)] = plugin | ||||
|  | ||||
|         if plugin_class in self.plugin_iterables: | ||||
|             self.iterables.append(plugin) | ||||
|             if plugin_class.plugin_multi_attrib: | ||||
|                 self.init_plugin(plugin_class.plugin_multi_attrib) | ||||
|  | ||||
|         self.loaded_plugins.add(attrib) | ||||
|  | ||||
|         return plugin | ||||
|  | ||||
|     def _get_stanza_values(self): | ||||
|         """Return A JSON/dictionary version of the XML content | ||||
| @@ -492,8 +576,14 @@ class ElementBase(object): | ||||
|         values = {} | ||||
|         for interface in self.interfaces: | ||||
|             values[interface] = self[interface] | ||||
|             if interface in self.lang_interfaces: | ||||
|                 values['%s|*' % interface] = self['%s|*' % interface] | ||||
|         for plugin, stanza in self.plugins.items(): | ||||
|             values[plugin] = stanza.values | ||||
|             lang = stanza['lang'] | ||||
|             if lang: | ||||
|                 values['%s|%s' % (plugin, lang)] = stanza.values | ||||
|             else: | ||||
|                 values[plugin[0]] = stanza.values | ||||
|         if self.iterables: | ||||
|             iterables = [] | ||||
|             for stanza in self.iterables: | ||||
| @@ -517,6 +607,11 @@ class ElementBase(object): | ||||
|                                     p in self.plugin_iterables] | ||||
|  | ||||
|         for interface, value in values.items(): | ||||
|             full_interface = interface | ||||
|             interface_lang = ('%s|' % interface).split('|') | ||||
|             interface = interface_lang[0] | ||||
|             lang = interface_lang[1] or self.get_lang() | ||||
|  | ||||
|             if interface == 'substanzas': | ||||
|                 # Remove existing substanzas | ||||
|                 for stanza in self.iterables: | ||||
| @@ -535,12 +630,12 @@ class ElementBase(object): | ||||
|                                 self.iterables.append(sub) | ||||
|                                 break | ||||
|             elif interface in self.interfaces: | ||||
|                 self[interface] = value | ||||
|                 self[full_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].values = value | ||||
|                     plugin = self._get_plugin(interface, lang) | ||||
|                     if plugin: | ||||
|                         plugin.values = value | ||||
|         return self | ||||
|  | ||||
|     def __getitem__(self, attrib): | ||||
| @@ -572,6 +667,15 @@ class ElementBase(object): | ||||
|  | ||||
|         :param string attrib: The name of the requested stanza interface. | ||||
|         """ | ||||
|         full_attrib = attrib | ||||
|         attrib_lang = ('%s|' % attrib).split('|') | ||||
|         attrib = attrib_lang[0] | ||||
|         lang = attrib_lang[1] or '' | ||||
|  | ||||
|         kwargs = {} | ||||
|         if lang and attrib in self.lang_interfaces: | ||||
|             kwargs['lang'] = lang | ||||
|  | ||||
|         if attrib == 'substanzas': | ||||
|             return self.iterables | ||||
|         elif attrib in self.interfaces: | ||||
| @@ -579,32 +683,31 @@ class ElementBase(object): | ||||
|             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() | ||||
|                 name = self.plugin_overrides.get(get_method, None) | ||||
|                 if name: | ||||
|                     plugin = self._get_plugin(name, lang) | ||||
|                     if plugin: | ||||
|                         handler = getattr(plugin, get_method, None) | ||||
|                         if handler: | ||||
|                             return handler(**kwargs) | ||||
|  | ||||
|             if hasattr(self, get_method): | ||||
|                 return getattr(self, get_method)() | ||||
|                 return getattr(self, get_method)(**kwargs) | ||||
|             elif hasattr(self, get_method2): | ||||
|                 return getattr(self, get_method2)() | ||||
|                 return getattr(self, get_method2)(**kwargs) | ||||
|             else: | ||||
|                 if attrib in self.sub_interfaces: | ||||
|                     return self._get_sub_text(attrib) | ||||
|                     return self._get_sub_text(attrib, lang=lang) | ||||
|                 elif attrib in self.bool_interfaces: | ||||
|                     elem = self.xml.find('{%s}%s' % (self.namespace, attrib)) | ||||
|                     return elem is not None | ||||
|                 else: | ||||
|                     return self._get_attr(attrib) | ||||
|         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] | ||||
|             plugin = self._get_plugin(attrib, lang) | ||||
|             if plugin and plugin.is_extension: | ||||
|                 return plugin[full_attrib] | ||||
|             return plugin | ||||
|         else: | ||||
|             return '' | ||||
|  | ||||
| @@ -640,41 +743,58 @@ class ElementBase(object): | ||||
|         :param string attrib: The name of the stanza interface to modify. | ||||
|         :param value: The new value of the stanza interface. | ||||
|         """ | ||||
|         full_attrib = attrib | ||||
|         attrib_lang = ('%s|' % attrib).split('|') | ||||
|         attrib = attrib_lang[0] | ||||
|         lang = attrib_lang[1] or '' | ||||
|  | ||||
|         kwargs = {} | ||||
|         if lang and attrib in self.lang_interfaces: | ||||
|             kwargs['lang'] = lang | ||||
|  | ||||
|         if attrib in self.interfaces: | ||||
|             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) | ||||
|                     name = self.plugin_overrides.get(set_method, None) | ||||
|                     if name: | ||||
|                         plugin = self._get_plugin(name, lang) | ||||
|                         if plugin: | ||||
|                             handler = getattr(plugin, set_method, None) | ||||
|                             if handler: | ||||
|                                 return handler(value, **kwargs) | ||||
|  | ||||
|                 if hasattr(self, set_method): | ||||
|                     getattr(self, set_method)(value,) | ||||
|                     getattr(self, set_method)(value, **kwargs) | ||||
|                 elif hasattr(self, set_method2): | ||||
|                     getattr(self, set_method2)(value,) | ||||
|                     getattr(self, set_method2)(value, **kwargs) | ||||
|                 else: | ||||
|                     if attrib in self.sub_interfaces: | ||||
|                         return self._set_sub_text(attrib, text=value) | ||||
|                         if lang == '*': | ||||
|                             return self._set_all_sub_text(attrib, | ||||
|                                                           value, | ||||
|                                                           lang='*') | ||||
|                         return self._set_sub_text(attrib, text=value, | ||||
|                                                           lang=lang) | ||||
|                     elif attrib in self.bool_interfaces: | ||||
|                         if value: | ||||
|                             return self._set_sub_text(attrib, '', keep=True) | ||||
|                             return self._set_sub_text(attrib, '', | ||||
|                                     keep=True, | ||||
|                                     lang=lang) | ||||
|                         else: | ||||
|                             return self._set_sub_text(attrib, '', keep=False) | ||||
|                             return self._set_sub_text(attrib, '', | ||||
|                                     keep=False, | ||||
|                                     lang=lang) | ||||
|                     else: | ||||
|                         self._set_attr(attrib, value) | ||||
|             else: | ||||
|                 self.__delitem__(attrib) | ||||
|         elif attrib in self.plugin_attrib_map: | ||||
|             if attrib not in self.plugins: | ||||
|                 self.init_plugin(attrib) | ||||
|             self.plugins[attrib][attrib] = value | ||||
|             plugin = self._get_plugin(attrib, lang) | ||||
|             if plugin: | ||||
|                 plugin[full_attrib] = value | ||||
|         return self | ||||
|  | ||||
|     def __delitem__(self, attrib): | ||||
| @@ -709,40 +829,53 @@ class ElementBase(object): | ||||
|  | ||||
|         :param attrib: The name of the affected stanza interface. | ||||
|         """ | ||||
|         full_attrib = attrib | ||||
|         attrib_lang = ('%s|' % attrib).split('|') | ||||
|         attrib = attrib_lang[0] | ||||
|         lang = attrib_lang[1] or '' | ||||
|  | ||||
|         kwargs = {} | ||||
|         if lang and attrib in self.lang_interfaces: | ||||
|             kwargs['lang'] = lang | ||||
|  | ||||
|         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() | ||||
|                 name = self.plugin_overrides.get(del_method, None) | ||||
|                 if name: | ||||
|                     plugin = self._get_plugin(attrib, lang) | ||||
|                     if plugin: | ||||
|                         handler = getattr(plugin, del_method, None) | ||||
|                         if handler: | ||||
|                             return handler(**kwargs) | ||||
|  | ||||
|             if hasattr(self, del_method): | ||||
|                 getattr(self, del_method)() | ||||
|                 getattr(self, del_method)(**kwargs) | ||||
|             elif hasattr(self, del_method2): | ||||
|                 getattr(self, del_method2)() | ||||
|                 getattr(self, del_method2)(**kwargs) | ||||
|             else: | ||||
|                 if attrib in self.sub_interfaces: | ||||
|                     return self._del_sub(attrib) | ||||
|                     return self._del_sub(attrib, lang=lang) | ||||
|                 elif attrib in self.bool_interfaces: | ||||
|                     return self._del_sub(attrib) | ||||
|                     return self._del_sub(attrib, lang=lang) | ||||
|                 else: | ||||
|                     self._del_attr(attrib) | ||||
|         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 | ||||
|             plugin = self._get_plugin(attrib, lang) | ||||
|             if not plugin: | ||||
|                 return self | ||||
|             if plugin.is_extension: | ||||
|                 del plugin[full_attrib] | ||||
|                 del self.plugins[(attrib, None)] | ||||
|             else: | ||||
|                 del self.plugins[(attrib, lang)] | ||||
|             self.loaded_plugins.remove(attrib) | ||||
|             try: | ||||
|                 self.xml.remove(plugin.xml) | ||||
|             except: | ||||
|                 pass | ||||
|         return self | ||||
|  | ||||
|     def _set_attr(self, name, value): | ||||
| @@ -781,7 +914,7 @@ class ElementBase(object): | ||||
|         """ | ||||
|         return self.xml.attrib.get(name, default) | ||||
|  | ||||
|     def _get_sub_text(self, name, default=''): | ||||
|     def _get_sub_text(self, name, default='', lang=None): | ||||
|         """Return the text contents of a sub element. | ||||
|  | ||||
|         In case the element does not exist, or it has no textual content, | ||||
| @@ -793,13 +926,38 @@ class ElementBase(object): | ||||
|                         not exists. An empty string is returned otherwise. | ||||
|         """ | ||||
|         name = self._fix_ns(name) | ||||
|         stanza = self.xml.find(name) | ||||
|         if stanza is None or stanza.text is None: | ||||
|             return default | ||||
|         else: | ||||
|             return stanza.text | ||||
|         if lang == '*': | ||||
|             return self._get_all_sub_text(name, default, None) | ||||
|  | ||||
|     def _set_sub_text(self, name, text=None, keep=False): | ||||
|         default_lang = self.get_lang() | ||||
|         if not lang: | ||||
|             lang = default_lang | ||||
|  | ||||
|         stanzas = self.xml.findall(name) | ||||
|         if not stanzas: | ||||
|             return default | ||||
|         for stanza in stanzas: | ||||
|             if stanza.attrib.get('{%s}lang' % XML_NS, default_lang) == lang: | ||||
|                 if stanza.text is None: | ||||
|                     return default | ||||
|                 return stanza.text | ||||
|         return default | ||||
|  | ||||
|     def _get_all_sub_text(self, name, default='', lang=None): | ||||
|         name = self._fix_ns(name) | ||||
|  | ||||
|         default_lang = self.get_lang() | ||||
|         results = OrderedDict() | ||||
|         stanzas = self.xml.findall(name) | ||||
|         if stanzas: | ||||
|             for stanza in stanzas: | ||||
|                 stanza_lang = stanza.attrib.get('{%s}lang' % XML_NS, | ||||
|                                                 default_lang) | ||||
|                 if not lang or lang == '*' or stanza_lang == lang: | ||||
|                     results[stanza_lang] = stanza.text | ||||
|         return results | ||||
|  | ||||
|     def _set_sub_text(self, name, text=None, keep=False, lang=None): | ||||
|         """Set the text contents of a sub element. | ||||
|  | ||||
|         In case the element does not exist, a element will be created, | ||||
| @@ -817,9 +975,14 @@ class ElementBase(object): | ||||
|         """ | ||||
|         path = self._fix_ns(name, split=True) | ||||
|         element = self.xml.find(name) | ||||
|         parent = self.xml | ||||
|  | ||||
|         default_lang = self.get_lang() | ||||
|         if lang is None: | ||||
|             lang = default_lang | ||||
|  | ||||
|         if not text and not keep: | ||||
|             return self._del_sub(name) | ||||
|             return self._del_sub(name, lang=lang) | ||||
|  | ||||
|         if element is None: | ||||
|             # We need to add the element. If the provided name was | ||||
| @@ -833,14 +996,31 @@ class ElementBase(object): | ||||
|                 element = self.xml.find("/".join(walked)) | ||||
|                 if element is None: | ||||
|                     element = ET.Element(ename) | ||||
|                     if lang: | ||||
|                         element.attrib['{%s}lang' % XML_NS] = lang | ||||
|                     last_xml.append(element) | ||||
|                 parent = last_xml | ||||
|                 last_xml = element | ||||
|             element = last_xml | ||||
|  | ||||
|         if lang: | ||||
|             if element.attrib.get('{%s}lang' % XML_NS, default_lang) != lang: | ||||
|                 element = ET.Element(ename) | ||||
|                 element.attrib['{%s}lang' % XML_NS] = lang | ||||
|                 parent.append(element) | ||||
|  | ||||
|         element.text = text | ||||
|         return element | ||||
|  | ||||
|     def _del_sub(self, name, all=False): | ||||
|     def _set_all_sub_text(self, name, values, keep=False, lang=None): | ||||
|         self._del_sub(name, lang) | ||||
|         for value_lang, value in values.items(): | ||||
|             if not lang or lang == '*' or value_lang == lang: | ||||
|                 self._set_sub_text(name, text=value, | ||||
|                                          keep=keep, | ||||
|                                          lang=value_lang) | ||||
|  | ||||
|     def _del_sub(self, name, all=False, lang=None): | ||||
|         """Remove sub elements that match the given name or XPath. | ||||
|  | ||||
|         If the element is in a path, then any parent elements that become | ||||
| @@ -854,6 +1034,10 @@ class ElementBase(object): | ||||
|         path = self._fix_ns(name, split=True) | ||||
|         original_target = path[-1] | ||||
|  | ||||
|         default_lang = self.get_lang() | ||||
|         if not lang: | ||||
|             lang = default_lang | ||||
|  | ||||
|         for level, _ in enumerate(path): | ||||
|             # Generate the paths to the target elements and their parent. | ||||
|             element_path = "/".join(path[:len(path) - level]) | ||||
| @@ -866,11 +1050,13 @@ class ElementBase(object): | ||||
|                 if parent is None: | ||||
|                     parent = self.xml | ||||
|                 for element in elements: | ||||
|                     if element.tag == original_target or \ | ||||
|                         not element.getchildren(): | ||||
|                     if element.tag == original_target or not list(element): | ||||
|                         # Only delete the originally requested elements, and | ||||
|                         # any parent elements that have become empty. | ||||
|                         parent.remove(element) | ||||
|                         elem_lang = element.attrib.get('{%s}lang' % XML_NS, | ||||
|                                                        default_lang) | ||||
|                         if lang == '*' or elem_lang == lang: | ||||
|                             parent.remove(element) | ||||
|             if not all: | ||||
|                 # If we don't want to delete elements up the tree, stop | ||||
|                 # after deleting the first level of elements. | ||||
| @@ -903,7 +1089,7 @@ class ElementBase(object): | ||||
|         attributes = components[1:] | ||||
|  | ||||
|         if tag not in (self.name, "{%s}%s" % (self.namespace, self.name)) and \ | ||||
|             tag not in self.plugins and tag not in self.plugin_attrib: | ||||
|             tag not in self.loaded_plugins and tag not in self.plugin_attrib: | ||||
|             # The requested tag is not in this stanza, so no match. | ||||
|             return False | ||||
|  | ||||
| @@ -932,10 +1118,12 @@ class ElementBase(object): | ||||
|         if not matched_substanzas and len(xpath) > 1: | ||||
|             # Convert {namespace}tag@attribs to just tag | ||||
|             next_tag = xpath[1].split('@')[0].split('}')[-1] | ||||
|             if next_tag in self.plugins: | ||||
|                 return self.plugins[next_tag].match(xpath[1:]) | ||||
|             else: | ||||
|                 return False | ||||
|             langs = [name[1] for name in self.plugins if name[0] == next_tag] | ||||
|             for lang in langs: | ||||
|                 plugin = self._get_plugin(next_tag, lang) | ||||
|                 if plugin and plugin.match(xpath[1:]): | ||||
|                     return True | ||||
|             return False | ||||
|  | ||||
|         # Everything matched. | ||||
|         return True | ||||
| @@ -995,7 +1183,7 @@ class ElementBase(object): | ||||
|         """ | ||||
|         out = [] | ||||
|         out += [x for x in self.interfaces] | ||||
|         out += [x for x in self.plugins] | ||||
|         out += [x for x in self.loaded_plugins] | ||||
|         if self.iterables: | ||||
|             out.append('substanzas') | ||||
|         return out | ||||
| @@ -1075,6 +1263,23 @@ class ElementBase(object): | ||||
|         """ | ||||
|         return "{%s}%s" % (cls.namespace, cls.name) | ||||
|  | ||||
|     def get_lang(self): | ||||
|         result = self.xml.attrib.get('{%s}lang' % XML_NS, '') | ||||
|         if not result and self.parent and self.parent(): | ||||
|             return self.parent()['lang'] | ||||
|         return result | ||||
|  | ||||
|     def set_lang(self, lang): | ||||
|         self.del_lang() | ||||
|         attr = '{%s}lang' % XML_NS | ||||
|         if lang: | ||||
|             self.xml.attrib[attr] = lang | ||||
|  | ||||
|     def del_lang(self): | ||||
|         attr = '{%s}lang' % XML_NS | ||||
|         if attr in self.xml.attrib: | ||||
|             del self.xml.attrib[attr] | ||||
|  | ||||
|     @property | ||||
|     def attrib(self): | ||||
|         """Return the stanza object itself. | ||||
| @@ -1090,8 +1295,8 @@ class ElementBase(object): | ||||
|         return self | ||||
|  | ||||
|     def _fix_ns(self, xpath, split=False, propagate_ns=True): | ||||
|         return fix_ns(xpath, split=split,  | ||||
|                              propagate_ns=propagate_ns,  | ||||
|         return fix_ns(xpath, split=split, | ||||
|                              propagate_ns=propagate_ns, | ||||
|                              default_ns=self.namespace) | ||||
|  | ||||
|     def __eq__(self, other): | ||||
| @@ -1219,6 +1424,8 @@ class StanzaBase(ElementBase): | ||||
|     :param sfrom: Optional string or :class:`sleekxmpp.xmlstream.JID` | ||||
|                   object of the sender's JID. | ||||
|     :param string sid: Optional ID value for the stanza. | ||||
|     :param parent: Optionally specify a parent stanza object will | ||||
|                    contain this substanza. | ||||
|     """ | ||||
|  | ||||
|     #: The default XMPP client namespace | ||||
| @@ -1233,11 +1440,11 @@ class StanzaBase(ElementBase): | ||||
|     types = set(('get', 'set', 'error', None, 'unavailable', 'normal', 'chat')) | ||||
|  | ||||
|     def __init__(self, stream=None, xml=None, stype=None, | ||||
|                  sto=None, sfrom=None, sid=None): | ||||
|                  sto=None, sfrom=None, sid=None, parent=None): | ||||
|         self.stream = stream | ||||
|         if stream is not None: | ||||
|             self.namespace = stream.default_ns | ||||
|         ElementBase.__init__(self, xml) | ||||
|         ElementBase.__init__(self, xml, parent) | ||||
|         if stype is not None: | ||||
|             self['type'] = stype | ||||
|         if sto is not None: | ||||
| @@ -1285,7 +1492,7 @@ class StanzaBase(ElementBase): | ||||
|  | ||||
|     def get_payload(self): | ||||
|         """Return a list of XML objects contained in the stanza.""" | ||||
|         return self.xml.getchildren() | ||||
|         return list(self.xml) | ||||
|  | ||||
|     def set_payload(self, value): | ||||
|         """Add XML content to the stanza. | ||||
|   | ||||
| @@ -13,14 +13,19 @@ | ||||
|     :license: MIT, see LICENSE for more details | ||||
| """ | ||||
|  | ||||
| from __future__ import unicode_literals | ||||
|  | ||||
| import sys | ||||
|  | ||||
| if sys.version_info < (3, 0): | ||||
|     import types | ||||
|  | ||||
|  | ||||
| XML_NS = 'http://www.w3.org/XML/1998/namespace' | ||||
|  | ||||
|  | ||||
| def tostring(xml=None, xmlns='', stanza_ns='', stream=None, | ||||
|              outbuffer='', top_level=False): | ||||
|              outbuffer='', top_level=False, open_only=False): | ||||
|     """Serialize an XML object to a Unicode string. | ||||
|  | ||||
|     If namespaces are provided using ``xmlns`` or ``stanza_ns``, then | ||||
| @@ -88,6 +93,13 @@ def tostring(xml=None, xmlns='', stanza_ns='', stream=None, | ||||
|                     output.append(' %s:%s="%s"' % (mapped_ns, | ||||
|                                                    attrib, | ||||
|                                                    value)) | ||||
|             elif attrib_ns == XML_NS: | ||||
|                 output.append(' xml:%s="%s"' % (attrib, value)) | ||||
|  | ||||
|     if open_only: | ||||
|         # Only output the opening tag, regardless of content. | ||||
|         output.append(">") | ||||
|         return ''.join(output) | ||||
|  | ||||
|     if len(xml) or xml.text: | ||||
|         # If there are additional child elements to serialize. | ||||
| @@ -95,7 +107,7 @@ def tostring(xml=None, xmlns='', stanza_ns='', stream=None, | ||||
|         if xml.text: | ||||
|             output.append(xml_escape(xml.text)) | ||||
|         if len(xml): | ||||
|             for child in xml.getchildren(): | ||||
|             for child in xml: | ||||
|                 output.append(tostring(child, tag_xmlns, stanza_ns, stream)) | ||||
|         output.append("</%s>" % tag_name) | ||||
|     elif xml.text: | ||||
|   | ||||
| @@ -55,7 +55,7 @@ RESPONSE_TIMEOUT = 30 | ||||
| WAIT_TIMEOUT = 0.1 | ||||
|  | ||||
| #: The number of threads to use to handle XML stream events. This is not the | ||||
| #: same as the number of custom event handling threads.  | ||||
| #: same as the number of custom event handling threads. | ||||
| #: :data:`HANDLER_THREADS` must be at least 1. For Python implementations | ||||
| #: with a GIL, this should be left at 1, but for implemetnations without | ||||
| #: a GIL increasing this value can provide better performance. | ||||
| @@ -124,7 +124,7 @@ class XMLStream(object): | ||||
|         self.ssl_support = SSL_SUPPORT | ||||
|  | ||||
|         #: Most XMPP servers support TLSv1, but OpenFire in particular | ||||
|         #: does not work well with it. For OpenFire, set  | ||||
|         #: does not work well with it. For OpenFire, set | ||||
|         #: :attr:`ssl_version` to use ``SSLv23``:: | ||||
|         #: | ||||
|         #:     import ssl | ||||
| @@ -134,30 +134,30 @@ class XMLStream(object): | ||||
|         #: Path to a file containing certificates for verifying the | ||||
|         #: server SSL certificate. A non-``None`` value will trigger | ||||
|         #: certificate checking. | ||||
|         #:  | ||||
|         #: | ||||
|         #: .. note:: | ||||
|         #: | ||||
|         #:     On Mac OS X, certificates in the system keyring will | ||||
|         #:     be consulted, even if they are not in the provided file. | ||||
|         self.ca_certs = None | ||||
|  | ||||
|         #: The time in seconds to wait for events from the event queue,  | ||||
|         #: The time in seconds to wait for events from the event queue, | ||||
|         #: and also the time between checks for the process stop signal. | ||||
|         self.wait_timeout = WAIT_TIMEOUT | ||||
|  | ||||
|         #: The time in seconds to wait before timing out waiting  | ||||
|         #: The time in seconds to wait before timing out waiting | ||||
|         #: for response stanzas. | ||||
|         self.response_timeout = RESPONSE_TIMEOUT | ||||
|  | ||||
|         #: The current amount to time to delay attempting to reconnect.  | ||||
|         #: The current amount to time to delay attempting to reconnect. | ||||
|         #: This value doubles (with some jitter) with each failed | ||||
|         #: connection attempt up to :attr:`reconnect_max_delay` seconds. | ||||
|         self.reconnect_delay = None | ||||
|              | ||||
|  | ||||
|         #: Maximum time to delay between connection attempts is one hour. | ||||
|         self.reconnect_max_delay = RECONNECT_MAX_DELAY | ||||
|  | ||||
|         #: Maximum number of attempts to connect to the server before  | ||||
|         #: Maximum number of attempts to connect to the server before | ||||
|         #: quitting and raising a 'connect_failed' event. Setting to | ||||
|         #: ``None`` allows infinite reattempts, while setting it to ``0`` | ||||
|         #: will disable reconnection attempts. Defaults to ``None``. | ||||
| @@ -178,16 +178,16 @@ class XMLStream(object): | ||||
|  | ||||
|         #: The default port to return when querying DNS records. | ||||
|         self.default_port = int(port) | ||||
|          | ||||
|         #: The domain to try when querying DNS records.  | ||||
|  | ||||
|         #: The domain to try when querying DNS records. | ||||
|         self.default_domain = '' | ||||
|  | ||||
|         #: The expected name of the server, for validation. | ||||
|         self._expected_server_name = '' | ||||
|      | ||||
|  | ||||
|         #: The desired, or actual, address of the connected server. | ||||
|         self.address = (host, int(port)) | ||||
|          | ||||
|  | ||||
|         #: A file-like wrapper for the socket for use with the | ||||
|         #: :mod:`~xml.etree.ElementTree` module. | ||||
|         self.filesocket = None | ||||
| @@ -212,6 +212,9 @@ class XMLStream(object): | ||||
|         #: proxy based on the settings in :attr:`proxy_config`. | ||||
|         self.use_proxy = False | ||||
|  | ||||
|         #: If set to ``True``, attempt to use IPv6. | ||||
|         self.use_ipv6 = True | ||||
|  | ||||
|         #: An optional dictionary of proxy settings. It may provide: | ||||
|         #: :host: The host offering proxy services. | ||||
|         #: :port: The port for the proxy service. | ||||
| @@ -223,6 +226,9 @@ class XMLStream(object): | ||||
|         #: stream wrapper itself. | ||||
|         self.default_ns = '' | ||||
|  | ||||
|         self.default_lang = None | ||||
|         self.peer_default_lang = None | ||||
|  | ||||
|         #: The namespace of the enveloping stream element. | ||||
|         self.stream_ns = '' | ||||
|  | ||||
| @@ -255,7 +261,7 @@ class XMLStream(object): | ||||
|         #: and data is sent immediately over the wire. | ||||
|         self.session_started_event = threading.Event() | ||||
|  | ||||
|         #: The default time in seconds to wait for a session to start  | ||||
|         #: The default time in seconds to wait for a session to start | ||||
|         #: after connecting before reconnecting and trying again. | ||||
|         self.session_timeout = 45 | ||||
|  | ||||
| @@ -414,12 +420,12 @@ class XMLStream(object): | ||||
|         if use_tls is not None: | ||||
|             self.use_tls = use_tls | ||||
|  | ||||
|  | ||||
|         # Repeatedly attempt to connect until a successful connection | ||||
|         # is established. | ||||
|         attempts = self.reconnect_max_attempts | ||||
|         connected = self.state.transition('disconnected', 'connected', | ||||
|                                           func=self._connect, args=(reattempt,)) | ||||
|                                           func=self._connect, | ||||
|                                           args=(reattempt,)) | ||||
|         while reattempt and not connected and not self.stop.is_set(): | ||||
|             connected = self.state.transition('disconnected', 'connected', | ||||
|                                               func=self._connect) | ||||
| @@ -434,7 +440,7 @@ class XMLStream(object): | ||||
|     def _connect(self, reattempt=True): | ||||
|         self.scheduler.remove('Session timeout check') | ||||
|         self.stop.clear() | ||||
|                  | ||||
|  | ||||
|         if self.reconnect_delay is None or not reattempt: | ||||
|             delay = 1.0 | ||||
|         else: | ||||
| @@ -480,7 +486,7 @@ class XMLStream(object): | ||||
|         if self.use_proxy: | ||||
|             connected = self._connect_proxy() | ||||
|             if not connected: | ||||
|                 if reattempt:  | ||||
|                 if reattempt: | ||||
|                     self.reconnect_delay = delay | ||||
|                 return False | ||||
|  | ||||
| @@ -517,7 +523,8 @@ class XMLStream(object): | ||||
|                     except (Socket.error, ssl.SSLError): | ||||
|                         log.error('CERT: Invalid certificate trust chain.') | ||||
|                         if not self.event_handled('ssl_invalid_chain'): | ||||
|                             self.disconnect(self.auto_reconnect, send_close=False) | ||||
|                             self.disconnect(self.auto_reconnect, | ||||
|                                             send_close=False) | ||||
|                         else: | ||||
|                             self.event('ssl_invalid_chain', direct=True) | ||||
|                         return False | ||||
| @@ -525,7 +532,7 @@ class XMLStream(object): | ||||
|                     self._der_cert = self.socket.getpeercert(binary_form=True) | ||||
|                     pem_cert = ssl.DER_cert_to_PEM_cert(self._der_cert) | ||||
|                     log.debug('CERT: %s', pem_cert) | ||||
|                      | ||||
|  | ||||
|                     self.event('ssl_cert', pem_cert, direct=True) | ||||
|                     try: | ||||
|                         cert.verify(self._expected_server_name, self._der_cert) | ||||
| @@ -534,7 +541,9 @@ class XMLStream(object): | ||||
|                         if not self.event_handled('ssl_invalid_cert'): | ||||
|                             self.disconnect(send_close=False) | ||||
|                         else: | ||||
|                             self.event('ssl_invalid_cert', cert, direct=True) | ||||
|                             self.event('ssl_invalid_cert', | ||||
|                                        pem_cert, | ||||
|                                        direct=True) | ||||
|  | ||||
|             self.set_socket(self.socket, ignore=True) | ||||
|             #this event is where you should set your application state | ||||
| @@ -627,7 +636,7 @@ class XMLStream(object): | ||||
|  | ||||
|         If the disconnect should take place after all items | ||||
|         in the send queue have been sent, use ``wait=True``. | ||||
|          | ||||
|  | ||||
|         .. warning:: | ||||
|  | ||||
|             If you are constantly adding items to the queue | ||||
| @@ -648,7 +657,7 @@ class XMLStream(object): | ||||
|         """ | ||||
|         self.state.transition('connected', 'disconnected', | ||||
|                               wait=2.0, | ||||
|                               func=self._disconnect,  | ||||
|                               func=self._disconnect, | ||||
|                               args=(reconnect, wait, send_close)) | ||||
|  | ||||
|     def _disconnect(self, reconnect=False, wait=None, send_close=True): | ||||
| @@ -702,16 +711,18 @@ class XMLStream(object): | ||||
|         """Reset the stream's state and reconnect to the server.""" | ||||
|         log.debug("reconnecting...") | ||||
|         if self.state.ensure('connected'): | ||||
|             self.state.transition('connected', 'disconnected',  | ||||
|             self.state.transition('connected', 'disconnected', | ||||
|                     wait=2.0, | ||||
|                     func=self._disconnect,  | ||||
|                     func=self._disconnect, | ||||
|                     args=(True, wait, send_close)) | ||||
|  | ||||
|         attempts = self.reconnect_max_attempts | ||||
|  | ||||
|         log.debug("connecting...") | ||||
|         connected = self.state.transition('disconnected', 'connected', | ||||
|                                           wait=2.0, func=self._connect, args=(reattempt,)) | ||||
|                                           wait=2.0, | ||||
|                                           func=self._connect, | ||||
|                                           args=(reattempt,)) | ||||
|         while reattempt and not connected and not self.stop.is_set(): | ||||
|             connected = self.state.transition('disconnected', 'connected', | ||||
|                                               wait=2.0, func=self._connect) | ||||
| @@ -759,8 +770,8 @@ class XMLStream(object): | ||||
|         """ | ||||
|         Configure and set options for a :class:`~dns.resolver.Resolver` | ||||
|         instance, and other DNS related tasks. For example, you | ||||
|         can also check :meth:`~socket.socket.getaddrinfo` to see  | ||||
|         if you need to call out to ``libresolv.so.2`` to  | ||||
|         can also check :meth:`~socket.socket.getaddrinfo` to see | ||||
|         if you need to call out to ``libresolv.so.2`` to | ||||
|         run ``res_init()``. | ||||
|  | ||||
|         Meant to be overridden. | ||||
| @@ -814,14 +825,14 @@ class XMLStream(object): | ||||
|             log.debug('CERT: %s', pem_cert) | ||||
|             self.event('ssl_cert', pem_cert, direct=True) | ||||
|  | ||||
|             try:  | ||||
|             try: | ||||
|                 cert.verify(self._expected_server_name, self._der_cert) | ||||
|             except cert.CertificateError as err: | ||||
|                 log.error(err.message) | ||||
|                 if not self.event_handled('ssl_invalid_cert'): | ||||
|                     self.disconnect(self.auto_reconnect, send_close=False) | ||||
|                 else: | ||||
|                     self.event('ssl_invalid_cert', cert, direct=True) | ||||
|                     self.event('ssl_invalid_cert', pem_cert, direct=True) | ||||
|  | ||||
|             self.set_socket(self.socket) | ||||
|             return True | ||||
| @@ -874,8 +885,8 @@ class XMLStream(object): | ||||
|         self.schedule('Whitespace Keepalive', | ||||
|                       self.whitespace_keepalive_interval, | ||||
|                       self.send_raw, | ||||
|                       args = (' ',), | ||||
|                       kwargs = {'now': True}, | ||||
|                       args=(' ',), | ||||
|                       kwargs={'now': True}, | ||||
|                       repeat=True) | ||||
|  | ||||
|     def _remove_schedules(self, event): | ||||
| @@ -884,7 +895,7 @@ class XMLStream(object): | ||||
|         self.scheduler.remove('Certificate Expiration') | ||||
|  | ||||
|     def start_stream_handler(self, xml): | ||||
|         """Perform any initialization actions, such as handshakes,  | ||||
|         """Perform any initialization actions, such as handshakes, | ||||
|         once the stream header has been sent. | ||||
|  | ||||
|         Meant to be overridden. | ||||
| @@ -892,8 +903,8 @@ class XMLStream(object): | ||||
|         pass | ||||
|  | ||||
|     def register_stanza(self, stanza_class): | ||||
|         """Add a stanza object class as a known root stanza.  | ||||
|          | ||||
|         """Add a stanza object class as a known root stanza. | ||||
|  | ||||
|         A root stanza is one that appears as a direct child of the stream's | ||||
|         root element. | ||||
|  | ||||
| @@ -910,8 +921,8 @@ class XMLStream(object): | ||||
|         self.__root_stanza.append(stanza_class) | ||||
|  | ||||
|     def remove_stanza(self, stanza_class): | ||||
|         """Remove a stanza from being a known root stanza.  | ||||
|          | ||||
|         """Remove a stanza from being a known root stanza. | ||||
|  | ||||
|         A root stanza is one that appears as a direct child of the stream's | ||||
|         root element. | ||||
|  | ||||
| @@ -976,8 +987,9 @@ class XMLStream(object): | ||||
|         """Add a stream event handler that will be executed when a matching | ||||
|         stanza is received. | ||||
|  | ||||
|         :param handler: The :class:`~sleekxmpp.xmlstream.handler.base.BaseHandler` | ||||
|                         derived object to execute. | ||||
|         :param handler: | ||||
|                 The :class:`~sleekxmpp.xmlstream.handler.base.BaseHandler` | ||||
|                 derived object to execute. | ||||
|         """ | ||||
|         if handler.stream is None: | ||||
|             self.__handlers.append(handler) | ||||
| @@ -1004,11 +1016,13 @@ class XMLStream(object): | ||||
|         """ | ||||
|         if port is None: | ||||
|             port = self.default_port | ||||
|              | ||||
|  | ||||
|         resolver = default_resolver() | ||||
|         self.configure_dns(resolver, domain=domain, port=port) | ||||
|  | ||||
|         return resolve(domain, port, service=self.dns_service, resolver=resolver) | ||||
|         return resolve(domain, port, service=self.dns_service, | ||||
|                                      resolver=resolver, | ||||
|                                      use_ipv6=self.use_ipv6) | ||||
|  | ||||
|     def pick_dns_answer(self, domain, port=None): | ||||
|         """Pick a server and port from DNS answers. | ||||
| @@ -1026,7 +1040,7 @@ class XMLStream(object): | ||||
|             return self.dns_answers.next() | ||||
|         else: | ||||
|             return next(self.dns_answers) | ||||
|              | ||||
|  | ||||
|     def add_event_handler(self, name, pointer, | ||||
|                           threaded=False, disposable=False): | ||||
|         """Add a custom event handler that will be executed whenever | ||||
| @@ -1141,9 +1155,9 @@ class XMLStream(object): | ||||
|  | ||||
|         May optionally block until an expected response is received. | ||||
|  | ||||
|         :param data: The :class:`~sleekxmpp.xmlstream.stanzabase.ElementBase`  | ||||
|         :param data: The :class:`~sleekxmpp.xmlstream.stanzabase.ElementBase` | ||||
|                      stanza to send on the stream. | ||||
|         :param mask: **DEPRECATED**  | ||||
|         :param mask: **DEPRECATED** | ||||
|                      An XML string snippet matching the structure | ||||
|                      of the expected response. Execution will block | ||||
|                      in this thread until the response is received | ||||
| @@ -1195,9 +1209,9 @@ class XMLStream(object): | ||||
|         """Send an XML object on the stream, and optionally wait | ||||
|         for a response. | ||||
|  | ||||
|         :param data: The :class:`~xml.etree.ElementTree.Element` XML object  | ||||
|         :param data: The :class:`~xml.etree.ElementTree.Element` XML object | ||||
|                      to send on the stream. | ||||
|         :param mask: **DEPRECATED**  | ||||
|         :param mask: **DEPRECATED** | ||||
|                      An XML string snippet matching the structure | ||||
|                      of the expected response. Execution will block | ||||
|                      in this thread until the response is received | ||||
| @@ -1237,14 +1251,15 @@ class XMLStream(object): | ||||
|                             count += 1 | ||||
|                         except ssl.SSLError as serr: | ||||
|                             if tries >= self.ssl_retry_max: | ||||
|                                 log.debug('SSL error - max retries reached') | ||||
|                                 log.debug('SSL error: max retries reached') | ||||
|                                 self.exception(serr) | ||||
|                                 log.warning("Failed to send %s", data) | ||||
|                                 if reconnect is None: | ||||
|                                     reconnect = self.auto_reconnect | ||||
|                                 if not self.stop.is_set(): | ||||
|                                     self.disconnect(reconnect, send_close=False) | ||||
|                             log.warning('SSL write error - reattempting') | ||||
|                                     self.disconnect(reconnect, | ||||
|                                                     send_close=False) | ||||
|                                 log.warning('SSL write error: retrying') | ||||
|                             if not self.stop.is_set(): | ||||
|                                 time.sleep(self.ssl_retry_delay) | ||||
|                             tries += 1 | ||||
| @@ -1299,7 +1314,7 @@ class XMLStream(object): | ||||
|     def _wait_for_threads(self): | ||||
|         with self.__thread_cond: | ||||
|             if self.__thread_count != 0: | ||||
|                 log.debug("Waiting for %s threads to exit." %  | ||||
|                 log.debug("Waiting for %s threads to exit." % | ||||
|                         self.__thread_count) | ||||
|                 name = threading.current_thread().name | ||||
|                 if name in self.__thread: | ||||
| @@ -1331,7 +1346,7 @@ class XMLStream(object): | ||||
|                     Defaults to ``True``. This does **not** mean that no | ||||
|                     threads are used at all if ``threaded=False``. | ||||
|  | ||||
|         Regardless of these threading options, these threads will  | ||||
|         Regardless of these threading options, these threads will | ||||
|         always exist: | ||||
|  | ||||
|         - The event queue processor | ||||
| @@ -1421,7 +1436,7 @@ class XMLStream(object): | ||||
|  | ||||
|     def __read_xml(self): | ||||
|         """Parse the incoming XML stream | ||||
|          | ||||
|  | ||||
|         Stream events are raised for each received stanza. | ||||
|         """ | ||||
|         depth = 0 | ||||
| @@ -1431,6 +1446,10 @@ class XMLStream(object): | ||||
|                 if depth == 0: | ||||
|                     # We have received the start of the root element. | ||||
|                     root = xml | ||||
|                     log.debug('RECV: %s', tostring(root, xmlns=self.default_ns, | ||||
|                                                          stream=self, | ||||
|                                                          top_level=True, | ||||
|                                                          open_only=True)) | ||||
|                     # Perform any stream initialization actions, such | ||||
|                     # as handshakes. | ||||
|                     self.stream_end_event.clear() | ||||
| @@ -1461,10 +1480,10 @@ class XMLStream(object): | ||||
|         """Create a stanza object from a given XML object. | ||||
|  | ||||
|         If a specialized stanza type is not found for the XML, then | ||||
|         a generic :class:`~sleekxmpp.xmlstream.stanzabase.StanzaBase`  | ||||
|         a generic :class:`~sleekxmpp.xmlstream.stanzabase.StanzaBase` | ||||
|         stanza will be returned. | ||||
|  | ||||
|         :param xml: The :class:`~xml.etree.ElementTree.Element` XML object  | ||||
|         :param xml: The :class:`~xml.etree.ElementTree.Element` XML object | ||||
|                     to convert into a stanza object. | ||||
|         :param default_ns: Optional default namespace to use instead of the | ||||
|                            stream's current default namespace. | ||||
| @@ -1478,6 +1497,8 @@ class XMLStream(object): | ||||
|                 stanza_type = stanza_class | ||||
|                 break | ||||
|         stanza = stanza_type(self, xml) | ||||
|         if stanza['lang'] is None and self.peer_default_lang: | ||||
|             stanza['lang'] = self.peer_default_lang | ||||
|         return stanza | ||||
|  | ||||
|     def __spawn_event(self, xml): | ||||
| @@ -1647,12 +1668,13 @@ class XMLStream(object): | ||||
|                                 count += 1 | ||||
|                             except ssl.SSLError as serr: | ||||
|                                 if tries >= self.ssl_retry_max: | ||||
|                                     log.debug('SSL error - max retries reached') | ||||
|                                     log.debug('SSL error: max retries reached') | ||||
|                                     self.exception(serr) | ||||
|                                     log.warning("Failed to send %s", data) | ||||
|                                     if not self.stop.is_set(): | ||||
|                                         self.disconnect(self.auto_reconnect, send_close=False) | ||||
|                                 log.warning('SSL write error - reattempting') | ||||
|                                         self.disconnect(self.auto_reconnect, | ||||
|                                                         send_close=False) | ||||
|                                     log.warning('SSL write error: retrying') | ||||
|                                 if not self.stop.is_set(): | ||||
|                                     time.sleep(self.ssl_retry_delay) | ||||
|                                 tries += 1 | ||||
|   | ||||
| @@ -64,14 +64,18 @@ class TestElementBase(SleekTest): | ||||
|         stanza.append(substanza) | ||||
|  | ||||
|         values = stanza.getStanzaValues() | ||||
|         expected = {'bar': 'a', | ||||
|         expected = {'lang': '', | ||||
|                     'bar': 'a', | ||||
|                     'baz': '', | ||||
|                     'foo2': {'bar': '', | ||||
|                     'foo2': {'lang': '', | ||||
|                              'bar': '', | ||||
|                              'baz': 'b'}, | ||||
|                     'substanzas': [{'__childtag__': '{foo}foo2', | ||||
|                                     'lang': '', | ||||
|                                     'bar': '', | ||||
|                                     'baz': 'b'}, | ||||
|                                    {'__childtag__': '{foo}subfoo', | ||||
|                                     'lang': '', | ||||
|                                     'bar': 'c', | ||||
|                                     'baz': ''}]} | ||||
|         self.failUnless(values == expected, | ||||
| @@ -555,12 +559,12 @@ class TestElementBase(SleekTest): | ||||
|  | ||||
|         stanza = TestStanza() | ||||
|  | ||||
|         self.failUnless(set(stanza.keys()) == set(('bar', 'baz')), | ||||
|         self.failUnless(set(stanza.keys()) == set(('lang', 'bar', 'baz')), | ||||
|             "Returned set of interface keys does not match expected.") | ||||
|  | ||||
|         stanza.enable('qux') | ||||
|  | ||||
|         self.failUnless(set(stanza.keys()) == set(('bar', 'baz', 'qux')), | ||||
|         self.failUnless(set(stanza.keys()) == set(('lang', 'bar', 'baz', 'qux')), | ||||
|             "Incorrect set of interface and plugin keys.") | ||||
|  | ||||
|     def testGet(self): | ||||
|   | ||||
| @@ -49,7 +49,7 @@ class TestAdHocCommandStanzas(SleekTest): | ||||
|         iq['command']['actions'] = ['prev', 'next'] | ||||
|  | ||||
|         results = iq['command']['actions'] | ||||
|         expected = ['prev', 'next'] | ||||
|         expected = set(['prev', 'next']) | ||||
|         self.assertEqual(results, expected, | ||||
|                          "Incorrect next actions: %s" % results) | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user