Compare commits
	
		
			98 Commits
		
	
	
		
			sleek-1.1.
			...
			sleek-1.1.
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | e4b4c67637 | ||
|   | 422e77ae40 | ||
|   | 5ae6c8f8fa | ||
|   | 54656b331a | ||
|   | 9047b627a4 | ||
|   | 6645a3be40 | ||
|   | e3fab66dfb | ||
|   | 5867f08bf1 | ||
|   | a06fa2de67 | ||
|   | 35396d2977 | ||
|   | 3bff743d9f | ||
|   | 5a878f829b | ||
|   | 26dc6e90ea | ||
|   | 94c749fd5a | ||
|   | 7b80ed0807 | ||
|   | 98b7e8b10a | ||
|   | 9d8de7fc15 | ||
|   | 70883086b7 | ||
|   | 9a08dfc7d4 | ||
|   | 3e43b36a9d | ||
|   | 352ee2f2fd | ||
|   | 78aa5c3dfa | ||
|   | 613323b5fb | ||
|   | 6c4b01db8a | ||
|   | d06897a635 | ||
|   | 1600bb0aaf | ||
|   | b5c9c98a8b | ||
|   | e4e18a416f | ||
|   | 01cc0e6def | ||
|   | d571d691a7 | ||
|   | fb221a8dc0 | ||
|   | 459e1ed345 | ||
|   | 6680c244f5 | ||
|   | 06423964ec | ||
|   | 474390fa00 | ||
|   | 81d3723084 | ||
|   | 32e798967e | ||
|   | acd9c32a9f | ||
|   | b8581b0278 | ||
|   | 917faecdcb | ||
|   | f6edaa56a6 | ||
|   | 51fee28bf4 | ||
|   | e8a3e92ceb | ||
|   | 5df3839b7a | ||
|   | 8dcb441f44 | ||
|   | a347cf625a | ||
|   | 46f49c7a12 | ||
|   | 99701c947e | ||
|   | 1baae1b81e | ||
|   | 7d20f0e9a6 | ||
|   | fbad22a1cd | ||
|   | 5af2f62c04 | ||
|   | 4a4a03858e | ||
|   | 6819b57353 | ||
|   | 88b5e60807 | ||
|   | a26a8bd79c | ||
|   | 9307a6915f | ||
|   | 85ef2d8d0b | ||
|   | c2c7cc032b | ||
|   | e4911e9391 | ||
|   | b11e1ee92d | ||
|   | 5027d00c10 | ||
|   | 69ddeceb49 | ||
|   | 82698672bb | ||
|   | 9cec284947 | ||
|   | dc501d1902 | ||
|   | 100e504b7f | ||
|   | 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 | 
| @@ -45,13 +45,11 @@ The latest source code for SleekXMPP may be found on `Github | |||||||
| ``develop`` branch. | ``develop`` branch. | ||||||
|  |  | ||||||
| **Latest Release** | **Latest Release** | ||||||
|     - `1.1.5 <http://github.com/fritzy/SleekXMPP/zipball/1.1.5>`_ |     - `1.1.10 <http://github.com/fritzy/SleekXMPP/zipball/1.1.10>`_ | ||||||
|  |  | ||||||
| **Develop Releases** | **Develop Releases** | ||||||
|     - `Latest Develop Version <http://github.com/fritzy/SleekXMPP/zipball/develop>`_ |     - `Latest Develop Version <http://github.com/fritzy/SleekXMPP/zipball/develop>`_ | ||||||
|  |  | ||||||
| **Older Stable Releases** |  | ||||||
|     - `1.0 <http://github.com/fritzy/SleekXMPP/zipball/1.0>`_ |  | ||||||
|  |  | ||||||
| Installing DNSPython | Installing DNSPython | ||||||
| --------------------- | --------------------- | ||||||
| @@ -74,6 +72,7 @@ help with SleekXMPP. | |||||||
| **Chat** | **Chat** | ||||||
|     `sleek@conference.jabber.org <xmpp:sleek@conference.jabber.org?join>`_ |     `sleek@conference.jabber.org <xmpp:sleek@conference.jabber.org?join>`_ | ||||||
|  |  | ||||||
|  |  | ||||||
| Documentation and Testing | Documentation and Testing | ||||||
| ------------------------- | ------------------------- | ||||||
| Documentation can be found both inline in the code, and as a Sphinx project in ``/docs``. | Documentation can be found both inline in the code, and as a Sphinx project in ``/docs``. | ||||||
|   | |||||||
							
								
								
									
										178
									
								
								examples/admin_commands.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										178
									
								
								examples/admin_commands.py
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,178 @@ | |||||||
|  | #!/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 AdminCommands(sleekxmpp.ClientXMPP): | ||||||
|  |  | ||||||
|  |     """ | ||||||
|  |     A simple SleekXMPP bot that uses admin commands to | ||||||
|  |     add a new user to a server. | ||||||
|  |     """ | ||||||
|  |  | ||||||
|  |     def __init__(self, jid, password, command): | ||||||
|  |         sleekxmpp.ClientXMPP.__init__(self, jid, password) | ||||||
|  |  | ||||||
|  |         self.command = command | ||||||
|  |  | ||||||
|  |         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() | ||||||
|  |  | ||||||
|  |         def command_success(iq, session): | ||||||
|  |             print('Command completed') | ||||||
|  |             if iq['command']['form']: | ||||||
|  |                 for var, field in iq['command']['form']['fields'].items(): | ||||||
|  |                     print('%s: %s' % (var, field['value'])) | ||||||
|  |             if iq['command']['notes']: | ||||||
|  |                 print('Command Notes:') | ||||||
|  |                 for note in iq['command']['notes']: | ||||||
|  |                     print('%s: %s' % note) | ||||||
|  |             self.disconnect() | ||||||
|  |  | ||||||
|  |         def command_error(iq, session): | ||||||
|  |             print('Error completing command') | ||||||
|  |             print('%s: %s' % (iq['error']['condition'], | ||||||
|  |                               iq['error']['text'])) | ||||||
|  |             self['xep_0050'].terminate_command(session) | ||||||
|  |             self.disconnect() | ||||||
|  |  | ||||||
|  |         def process_form(iq, session): | ||||||
|  |             form = iq['command']['form'] | ||||||
|  |             answers = {} | ||||||
|  |             for var, field in form['fields'].items(): | ||||||
|  |                 if var != 'FORM_TYPE': | ||||||
|  |                     if field['type'] == 'boolean': | ||||||
|  |                         answers[var] = raw_input('%s (y/n): ' % field['label']) | ||||||
|  |                         if answers[var].lower() in ('1', 'true', 'y', 'yes'): | ||||||
|  |                             answers[var] = '1' | ||||||
|  |                         else: | ||||||
|  |                             answers[var] = '0' | ||||||
|  |                     else: | ||||||
|  |                         answers[var] = raw_input('%s: ' % field['label']) | ||||||
|  |                 else: | ||||||
|  |                     answers['FORM_TYPE'] = field['value'] | ||||||
|  |             form['type'] = 'submit' | ||||||
|  |             form['values'] = answers | ||||||
|  |  | ||||||
|  |             session['next'] = command_success | ||||||
|  |             session['payload'] = form | ||||||
|  |  | ||||||
|  |             self['xep_0050'].complete_command(session) | ||||||
|  |  | ||||||
|  |         session = {'next': process_form, | ||||||
|  |                    'error': command_error} | ||||||
|  |  | ||||||
|  |         command = self.command.replace('-', '_') | ||||||
|  |         handler = getattr(self['xep_0133'], command, None) | ||||||
|  |  | ||||||
|  |         if handler: | ||||||
|  |             handler(session={ | ||||||
|  |                 'next': process_form, | ||||||
|  |                 'error': command_error | ||||||
|  |             }) | ||||||
|  |         else: | ||||||
|  |             print('Invalid command name: %s' % self.command) | ||||||
|  |             self.disconnect() | ||||||
|  |  | ||||||
|  |  | ||||||
|  | if __name__ == '__main__': | ||||||
|  |     # Setup the command line arguments. | ||||||
|  |     optp = OptionParser() | ||||||
|  |  | ||||||
|  |     # Output verbosity options. | ||||||
|  |     optp.add_option('-q', '--quiet', help='set logging to ERROR', | ||||||
|  |                     action='store_const', dest='loglevel', | ||||||
|  |                     const=logging.ERROR, default=logging.INFO) | ||||||
|  |     optp.add_option('-d', '--debug', help='set logging to DEBUG', | ||||||
|  |                     action='store_const', dest='loglevel', | ||||||
|  |                     const=logging.DEBUG, default=logging.INFO) | ||||||
|  |     optp.add_option('-v', '--verbose', help='set logging to COMM', | ||||||
|  |                     action='store_const', dest='loglevel', | ||||||
|  |                     const=5, default=logging.INFO) | ||||||
|  |  | ||||||
|  |     # 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("-c", "--command", dest="command", | ||||||
|  |                     help="admin command 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.command is None: | ||||||
|  |         opts.command = raw_input("Admin command: ") | ||||||
|  |  | ||||||
|  |     # Setup the CommandBot and register plugins. Note that while plugins may | ||||||
|  |     # have interdependencies, the order in which you register them does | ||||||
|  |     # not matter. | ||||||
|  |     xmpp = AdminCommands(opts.jid, opts.password, opts.command) | ||||||
|  |     xmpp.register_plugin('xep_0133') # Service Administration | ||||||
|  |  | ||||||
|  |     # 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.") | ||||||
							
								
								
									
										184
									
								
								examples/download_avatars.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										184
									
								
								examples/download_avatars.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,184 @@ | |||||||
|  | #!/usr/bin/env python | ||||||
|  | # -*- coding: utf-8 -*- | ||||||
|  |  | ||||||
|  | """ | ||||||
|  |     SleekXMPP: The Sleek XMPP Library | ||||||
|  |     Copyright (C) 2012  Nathanael C. Fritz | ||||||
|  |     This file is part of SleekXMPP. | ||||||
|  |  | ||||||
|  |     See the file LICENSE for copying permission. | ||||||
|  | """ | ||||||
|  |  | ||||||
|  | import sys | ||||||
|  | import logging | ||||||
|  | import getpass | ||||||
|  | import threading | ||||||
|  | from optparse import OptionParser | ||||||
|  |  | ||||||
|  | import sleekxmpp | ||||||
|  | from sleekxmpp.exceptions import XMPPError | ||||||
|  |  | ||||||
|  |  | ||||||
|  | # 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 | ||||||
|  |  | ||||||
|  |  | ||||||
|  | FILE_TYPES = { | ||||||
|  |     'image/png': 'png', | ||||||
|  |     'image/gif': 'gif', | ||||||
|  |     'image/jpeg': 'jpg' | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class AvatarDownloader(sleekxmpp.ClientXMPP): | ||||||
|  |  | ||||||
|  |     """ | ||||||
|  |     A basic script for downloading the avatars for a user's contacts. | ||||||
|  |     """ | ||||||
|  |  | ||||||
|  |     def __init__(self, jid, password): | ||||||
|  |         sleekxmpp.ClientXMPP.__init__(self, jid, password) | ||||||
|  |         self.add_event_handler("session_start", self.start, threaded=True) | ||||||
|  |         self.add_event_handler("changed_status", self.wait_for_presences) | ||||||
|  |  | ||||||
|  |         self.add_event_handler('vcard_avatar_update', self.on_vcard_avatar) | ||||||
|  |         self.add_event_handler('avatar_metadata_publish', self.on_avatar) | ||||||
|  |  | ||||||
|  |         self.received = set() | ||||||
|  |         self.presences_received = threading.Event() | ||||||
|  |  | ||||||
|  |     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() | ||||||
|  |  | ||||||
|  |         print('Waiting for presence updates...\n') | ||||||
|  |         self.presences_received.wait(15) | ||||||
|  |         self.disconnect(wait=True) | ||||||
|  |  | ||||||
|  |     def on_vcard_avatar(self, pres): | ||||||
|  |         print("Received vCard avatar update from %s" % pres['from'].bare) | ||||||
|  |         try: | ||||||
|  |             result = self['xep_0054'].get_vcard(pres['from'], cached=True) | ||||||
|  |         except XMPPError: | ||||||
|  |             print("Error retrieving avatar for %s" % pres['from']) | ||||||
|  |             return | ||||||
|  |         avatar = result['vcard_temp']['PHOTO'] | ||||||
|  |  | ||||||
|  |         filetype = FILE_TYPES.get(avatar['TYPE'], 'png') | ||||||
|  |         filename = 'vcard_avatar_%s_%s.%s' % ( | ||||||
|  |                 pres['from'].bare, | ||||||
|  |                 pres['vcard_temp_update']['photo'], | ||||||
|  |                 filetype) | ||||||
|  |         with open(filename, 'w+') as img: | ||||||
|  |             img.write(avatar['BINVAL']) | ||||||
|  |  | ||||||
|  |     def on_avatar(self, msg): | ||||||
|  |         print("Received avatar update from %s" % msg['from']) | ||||||
|  |         metadata = msg['pubsub_event']['items']['item']['avatar_metadata'] | ||||||
|  |         for info in metadata['items']: | ||||||
|  |             if not info['url']: | ||||||
|  |                 try: | ||||||
|  |                     result = self['xep_0084'].retrieve_avatar(msg['from'], info['id']) | ||||||
|  |                 except XMPPError: | ||||||
|  |                     print("Error retrieving avatar for %s" % msg['from']) | ||||||
|  |                     return | ||||||
|  |  | ||||||
|  |                 avatar = result['pubsub']['items']['item']['avatar_data'] | ||||||
|  |  | ||||||
|  |                 filetype = FILE_TYPES.get(metadata['type'], 'png') | ||||||
|  |                 filename = 'avatar_%s_%s.%s' % (msg['from'].bare, info['id'], filetype) | ||||||
|  |                 with open(filename, 'w+') as img: | ||||||
|  |                     img.write(avatar['value']) | ||||||
|  |             else: | ||||||
|  |                 # We could retrieve the avatar via HTTP, etc here instead. | ||||||
|  |                 pass | ||||||
|  |  | ||||||
|  |     def wait_for_presences(self, pres): | ||||||
|  |         """ | ||||||
|  |         Wait to receive updates from all roster contacts. | ||||||
|  |         """ | ||||||
|  |         self.received.add(pres['from'].bare) | ||||||
|  |         if len(self.received) >= len(self.client_roster.keys()): | ||||||
|  |             self.presences_received.set() | ||||||
|  |         else: | ||||||
|  |             self.presences_received.clear() | ||||||
|  |  | ||||||
|  |  | ||||||
|  | if __name__ == '__main__': | ||||||
|  |     # Setup the command line arguments. | ||||||
|  |     optp = OptionParser() | ||||||
|  |     optp.add_option('-q','--quiet', help='set logging to ERROR', | ||||||
|  |                     action='store_const', | ||||||
|  |                     dest='loglevel', | ||||||
|  |                     const=logging.ERROR, | ||||||
|  |                     default=logging.ERROR) | ||||||
|  |     optp.add_option('-d','--debug', help='set logging to DEBUG', | ||||||
|  |                     action='store_const', | ||||||
|  |                     dest='loglevel', | ||||||
|  |                     const=logging.DEBUG, | ||||||
|  |                     default=logging.ERROR) | ||||||
|  |     optp.add_option('-v','--verbose', help='set logging to COMM', | ||||||
|  |                     action='store_const', | ||||||
|  |                     dest='loglevel', | ||||||
|  |                     const=5, | ||||||
|  |                     default=logging.ERROR) | ||||||
|  |  | ||||||
|  |     # JID and password options. | ||||||
|  |     optp.add_option("-j", "--jid", dest="jid", | ||||||
|  |                     help="JID to use") | ||||||
|  |     optp.add_option("-p", "--password", dest="password", | ||||||
|  |                     help="password to use") | ||||||
|  |     opts,args = optp.parse_args() | ||||||
|  |  | ||||||
|  |     # Setup logging. | ||||||
|  |     logging.basicConfig(level=opts.loglevel, | ||||||
|  |                         format='%(levelname)-8s %(message)s') | ||||||
|  |  | ||||||
|  |     if opts.jid is None: | ||||||
|  |         opts.jid = raw_input("Username: ") | ||||||
|  |     if opts.password is None: | ||||||
|  |         opts.password = getpass.getpass("Password: ") | ||||||
|  |  | ||||||
|  |     xmpp = AvatarDownloader(opts.jid, opts.password) | ||||||
|  |     xmpp.register_plugin('xep_0054') | ||||||
|  |     xmpp.register_plugin('xep_0153') | ||||||
|  |     xmpp.register_plugin('xep_0084') | ||||||
|  |  | ||||||
|  |     # 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) | ||||||
|  |     else: | ||||||
|  |         print("Unable to connect.") | ||||||
| @@ -122,6 +122,19 @@ if __name__ == '__main__': | |||||||
|     xmpp.register_plugin('xep_0060') # PubSub |     xmpp.register_plugin('xep_0060') # PubSub | ||||||
|     xmpp.register_plugin('xep_0199') # XMPP Ping |     xmpp.register_plugin('xep_0199') # XMPP Ping | ||||||
|  |  | ||||||
|  |     # If you are connecting to Facebook and wish to use the | ||||||
|  |     # X-FACEBOOK-PLATFORM authentication mechanism, you will need | ||||||
|  |     # your API key and an access token. Then you'll set: | ||||||
|  |     # xmpp.credentials['api_key'] = 'THE_API_KEY' | ||||||
|  |     # xmpp.credentials['access_token'] = 'THE_ACCESS_TOKEN' | ||||||
|  |  | ||||||
|  |     # If you are connecting to MSN, then you will need an | ||||||
|  |     # access token, and it does not matter what JID you | ||||||
|  |     # specify other than that the domain is 'messenger.live.com', | ||||||
|  |     # so '_@messenger.live.com' will work. You can specify | ||||||
|  |     # the access token as so: | ||||||
|  |     # xmpp.credentials['access_token'] = 'THE_ACCESS_TOKEN' | ||||||
|  |  | ||||||
|     # If you are working with an OpenFire server, you may need |     # If you are working with an OpenFire server, you may need | ||||||
|     # to adjust the SSL version used: |     # to adjust the SSL version used: | ||||||
|     # xmpp.ssl_version = ssl.PROTOCOL_SSLv3 |     # xmpp.ssl_version = ssl.PROTOCOL_SSLv3 | ||||||
|   | |||||||
							
								
								
									
										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.") | ||||||
							
								
								
									
										174
									
								
								examples/set_avatar.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										174
									
								
								examples/set_avatar.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,174 @@ | |||||||
|  | #!/usr/bin/env python | ||||||
|  | # -*- coding: utf-8 -*- | ||||||
|  |  | ||||||
|  | """ | ||||||
|  |     SleekXMPP: The Sleek XMPP Library | ||||||
|  |     Copyright (C) 2012  Nathanael C. Fritz | ||||||
|  |     This file is part of SleekXMPP. | ||||||
|  |  | ||||||
|  |     See the file LICENSE for copying permission. | ||||||
|  | """ | ||||||
|  |  | ||||||
|  | import os | ||||||
|  | import sys | ||||||
|  | import imghdr | ||||||
|  | import logging | ||||||
|  | import getpass | ||||||
|  | import threading | ||||||
|  | from optparse import OptionParser | ||||||
|  |  | ||||||
|  | import sleekxmpp | ||||||
|  | from sleekxmpp.exceptions import XMPPError | ||||||
|  |  | ||||||
|  |  | ||||||
|  | # 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 AvatarSetter(sleekxmpp.ClientXMPP): | ||||||
|  |  | ||||||
|  |     """ | ||||||
|  |     A basic script for downloading the avatars for a user's contacts. | ||||||
|  |     """ | ||||||
|  |  | ||||||
|  |     def __init__(self, jid, password, filepath): | ||||||
|  |         sleekxmpp.ClientXMPP.__init__(self, jid, password) | ||||||
|  |  | ||||||
|  |         self.add_event_handler("session_start", self.start, threaded=True) | ||||||
|  |  | ||||||
|  |         self.filepath = filepath | ||||||
|  |  | ||||||
|  |     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() | ||||||
|  |  | ||||||
|  |         avatar_file = None | ||||||
|  |         try: | ||||||
|  |             avatar_file = open(os.path.expanduser(self.filepath)) | ||||||
|  |         except IOError: | ||||||
|  |             print('Could not find file: %s' % self.filepath) | ||||||
|  |             return self.disconnect() | ||||||
|  |  | ||||||
|  |         avatar = avatar_file.read() | ||||||
|  |  | ||||||
|  |         avatar_type = 'image/%s' % imghdr.what('', avatar) | ||||||
|  |         avatar_id = self['xep_0084'].generate_id(avatar) | ||||||
|  |         avatar_bytes = len(avatar) | ||||||
|  |  | ||||||
|  |         avatar_file.close() | ||||||
|  |  | ||||||
|  |         used_xep84 = False | ||||||
|  |         try: | ||||||
|  |             print('Publish XEP-0084 avatar data') | ||||||
|  |             self['xep_0084'].publish_avatar(avatar) | ||||||
|  |             used_xep84 = True | ||||||
|  |         except XMPPError: | ||||||
|  |             print('Could not publish XEP-0084 avatar') | ||||||
|  |  | ||||||
|  |         try: | ||||||
|  |             print('Update vCard with avatar') | ||||||
|  |             self['xep_0153'].set_avatar(avatar=avatar, mtype=avatar_type) | ||||||
|  |         except XMPPError: | ||||||
|  |             print('Could not set vCard avatar') | ||||||
|  |  | ||||||
|  |         if used_xep84: | ||||||
|  |             try: | ||||||
|  |                 print('Advertise XEP-0084 avatar metadata') | ||||||
|  |                 self['xep_0084'].publish_avatar_metadata([ | ||||||
|  |                     {'id': avatar_id, | ||||||
|  |                      'type': avatar_type, | ||||||
|  |                      'bytes': avatar_bytes} | ||||||
|  |                     # We could advertise multiple avatars to provide | ||||||
|  |                     # options in image type, source (HTTP vs pubsub), | ||||||
|  |                     # size, etc. | ||||||
|  |                     # {'id': ....} | ||||||
|  |                 ]) | ||||||
|  |             except XMPPError: | ||||||
|  |                 print('Could not publish XEP-0084 metadata') | ||||||
|  |  | ||||||
|  |         print('Wait for presence updates to propagate...') | ||||||
|  |         self.schedule('end', 5, self.disconnect, kwargs={'wait': True}) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | if __name__ == '__main__': | ||||||
|  |     # Setup the command line arguments. | ||||||
|  |     optp = OptionParser() | ||||||
|  |     optp.add_option('-q','--quiet', help='set logging to ERROR', | ||||||
|  |                     action='store_const', | ||||||
|  |                     dest='loglevel', | ||||||
|  |                     const=logging.ERROR, | ||||||
|  |                     default=logging.ERROR) | ||||||
|  |     optp.add_option('-d','--debug', help='set logging to DEBUG', | ||||||
|  |                     action='store_const', | ||||||
|  |                     dest='loglevel', | ||||||
|  |                     const=logging.DEBUG, | ||||||
|  |                     default=logging.ERROR) | ||||||
|  |     optp.add_option('-v','--verbose', help='set logging to COMM', | ||||||
|  |                     action='store_const', | ||||||
|  |                     dest='loglevel', | ||||||
|  |                     const=5, | ||||||
|  |                     default=logging.ERROR) | ||||||
|  |  | ||||||
|  |     # JID and password options. | ||||||
|  |     optp.add_option("-j", "--jid", dest="jid", | ||||||
|  |                     help="JID to use") | ||||||
|  |     optp.add_option("-p", "--password", dest="password", | ||||||
|  |                     help="password to use") | ||||||
|  |     optp.add_option("-f", "--file", dest="filepath", | ||||||
|  |                     help="path to the avatar file") | ||||||
|  |     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.filepath is None: | ||||||
|  |         opts.filepath = raw_input("Avatar file location: ") | ||||||
|  |  | ||||||
|  |     xmpp = AvatarSetter(opts.jid, opts.password, opts.filepath) | ||||||
|  |     xmpp.register_plugin('xep_0054') | ||||||
|  |     xmpp.register_plugin('xep_0153') | ||||||
|  |     xmpp.register_plugin('xep_0084') | ||||||
|  |  | ||||||
|  |     # 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) | ||||||
|  |     else: | ||||||
|  |         print("Unable to connect.") | ||||||
							
								
								
									
										8
									
								
								setup.py
									
									
									
									
									
								
							
							
						
						
									
										8
									
								
								setup.py
									
									
									
									
									
								
							| @@ -49,6 +49,7 @@ packages     = [ 'sleekxmpp', | |||||||
|                  'sleekxmpp/stanza', |                  'sleekxmpp/stanza', | ||||||
|                  'sleekxmpp/test', |                  'sleekxmpp/test', | ||||||
|                  'sleekxmpp/roster', |                  'sleekxmpp/roster', | ||||||
|  |                  'sleekxmpp/util', | ||||||
|                  'sleekxmpp/xmlstream', |                  'sleekxmpp/xmlstream', | ||||||
|                  'sleekxmpp/xmlstream/matcher', |                  'sleekxmpp/xmlstream/matcher', | ||||||
|                  'sleekxmpp/xmlstream/handler', |                  'sleekxmpp/xmlstream/handler', | ||||||
| @@ -61,6 +62,7 @@ packages     = [ 'sleekxmpp', | |||||||
|                  'sleekxmpp/plugins/xep_0027', |                  'sleekxmpp/plugins/xep_0027', | ||||||
|                  'sleekxmpp/plugins/xep_0030', |                  'sleekxmpp/plugins/xep_0030', | ||||||
|                  'sleekxmpp/plugins/xep_0030/stanza', |                  'sleekxmpp/plugins/xep_0030/stanza', | ||||||
|  |                  'sleekxmpp/plugins/xep_0033', | ||||||
|                  'sleekxmpp/plugins/xep_0047', |                  'sleekxmpp/plugins/xep_0047', | ||||||
|                  'sleekxmpp/plugins/xep_0050', |                  'sleekxmpp/plugins/xep_0050', | ||||||
|                  'sleekxmpp/plugins/xep_0054', |                  'sleekxmpp/plugins/xep_0054', | ||||||
| @@ -71,6 +73,7 @@ packages     = [ 'sleekxmpp', | |||||||
|                  'sleekxmpp/plugins/xep_0077', |                  'sleekxmpp/plugins/xep_0077', | ||||||
|                  'sleekxmpp/plugins/xep_0078', |                  'sleekxmpp/plugins/xep_0078', | ||||||
|                  'sleekxmpp/plugins/xep_0080', |                  'sleekxmpp/plugins/xep_0080', | ||||||
|  |                  'sleekxmpp/plugins/xep_0084', | ||||||
|                  'sleekxmpp/plugins/xep_0085', |                  'sleekxmpp/plugins/xep_0085', | ||||||
|                  'sleekxmpp/plugins/xep_0086', |                  'sleekxmpp/plugins/xep_0086', | ||||||
|                  'sleekxmpp/plugins/xep_0092', |                  'sleekxmpp/plugins/xep_0092', | ||||||
| @@ -79,16 +82,21 @@ packages     = [ 'sleekxmpp', | |||||||
|                  'sleekxmpp/plugins/xep_0115', |                  'sleekxmpp/plugins/xep_0115', | ||||||
|                  'sleekxmpp/plugins/xep_0118', |                  'sleekxmpp/plugins/xep_0118', | ||||||
|                  'sleekxmpp/plugins/xep_0128', |                  'sleekxmpp/plugins/xep_0128', | ||||||
|  |                  'sleekxmpp/plugins/xep_0131', | ||||||
|                  'sleekxmpp/plugins/xep_0153', |                  'sleekxmpp/plugins/xep_0153', | ||||||
|                  'sleekxmpp/plugins/xep_0172', |                  'sleekxmpp/plugins/xep_0172', | ||||||
|                  'sleekxmpp/plugins/xep_0184', |                  'sleekxmpp/plugins/xep_0184', | ||||||
|  |                  'sleekxmpp/plugins/xep_0186', | ||||||
|  |                  'sleekxmpp/plugins/xep_0191', | ||||||
|                  'sleekxmpp/plugins/xep_0198', |                  'sleekxmpp/plugins/xep_0198', | ||||||
|                  'sleekxmpp/plugins/xep_0199', |                  'sleekxmpp/plugins/xep_0199', | ||||||
|                  'sleekxmpp/plugins/xep_0202', |                  'sleekxmpp/plugins/xep_0202', | ||||||
|                  'sleekxmpp/plugins/xep_0203', |                  'sleekxmpp/plugins/xep_0203', | ||||||
|  |                  'sleekxmpp/plugins/xep_0221', | ||||||
|                  'sleekxmpp/plugins/xep_0224', |                  'sleekxmpp/plugins/xep_0224', | ||||||
|                  'sleekxmpp/plugins/xep_0231', |                  'sleekxmpp/plugins/xep_0231', | ||||||
|                  'sleekxmpp/plugins/xep_0249', |                  'sleekxmpp/plugins/xep_0249', | ||||||
|  |                  'sleekxmpp/plugins/xep_0258', | ||||||
|                  'sleekxmpp/features', |                  'sleekxmpp/features', | ||||||
|                  'sleekxmpp/features/feature_mechanisms', |                  'sleekxmpp/features/feature_mechanisms', | ||||||
|                  'sleekxmpp/features/feature_mechanisms/stanza', |                  'sleekxmpp/features/feature_mechanisms/stanza', | ||||||
|   | |||||||
| @@ -10,6 +10,7 @@ from sleekxmpp.basexmpp import BaseXMPP | |||||||
| from sleekxmpp.clientxmpp import ClientXMPP | from sleekxmpp.clientxmpp import ClientXMPP | ||||||
| from sleekxmpp.componentxmpp import ComponentXMPP | from sleekxmpp.componentxmpp import ComponentXMPP | ||||||
| from sleekxmpp.stanza import Message, Presence, Iq | from sleekxmpp.stanza import Message, Presence, Iq | ||||||
|  | from sleekxmpp.jid import JID, InvalidJID | ||||||
| from sleekxmpp.xmlstream.handler import * | from sleekxmpp.xmlstream.handler import * | ||||||
| from sleekxmpp.xmlstream import XMLStream, RestartStream | from sleekxmpp.xmlstream import XMLStream, RestartStream | ||||||
| from sleekxmpp.xmlstream.matcher import * | from sleekxmpp.xmlstream.matcher import * | ||||||
|   | |||||||
| @@ -16,24 +16,24 @@ class APIWrapper(object): | |||||||
|         elif attr == 'settings': |         elif attr == 'settings': | ||||||
|             return self.api.settings[self.name] |             return self.api.settings[self.name] | ||||||
|         elif attr == 'register': |         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) |                 register = getattr(self.api, attr) | ||||||
|                 return register(handler, self.name, op, jid, node, default) |                 return register(handler, self.name, op, jid, node, default) | ||||||
|             return curried_handler |             return partial | ||||||
|         elif attr == 'register_default': |         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 getattr(self.api, attr)(handler, self.name, op) | ||||||
|             return curried_handler |             return partial | ||||||
|         elif attr in ('run', 'restore_default', 'unregister'): |         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 getattr(self.api, attr)(self.name, *args, **kwargs) | ||||||
|             return curried_handler |             return partial | ||||||
|         return None |         return None | ||||||
|  |  | ||||||
|     def __getitem__(self, attr): |     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 self.api.run(self.name, attr, jid, node, ifrom, args) | ||||||
|         return curried_handler |         return partial | ||||||
|  |  | ||||||
|  |  | ||||||
| class APIRegistry(object): | class APIRegistry(object): | ||||||
| @@ -42,7 +42,7 @@ class APIRegistry(object): | |||||||
|         self._handlers = {} |         self._handlers = {} | ||||||
|         self._handler_defaults = {} |         self._handler_defaults = {} | ||||||
|         self.xmpp = xmpp |         self.xmpp = xmpp | ||||||
|         self.settings = {}  |         self.settings = {} | ||||||
|  |  | ||||||
|     def _setup(self, ctype, op): |     def _setup(self, ctype, op): | ||||||
|         """Initialize the API callback dictionaries. |         """Initialize the API callback dictionaries. | ||||||
| @@ -99,10 +99,12 @@ class APIRegistry(object): | |||||||
|         """ |         """ | ||||||
|         self._setup(ctype, op) |         self._setup(ctype, op) | ||||||
|  |  | ||||||
|         if jid in (None, ''): |         if not jid: | ||||||
|             jid = self.xmpp.boundjid |             jid = self.xmpp.boundjid | ||||||
|         if jid and not isinstance(jid, JID): |         elif jid and not isinstance(jid, JID): | ||||||
|             jid = JID(jid) |             jid = JID(jid) | ||||||
|  |         elif jid == JID(''): | ||||||
|  |             jid = self.xmpp.boundjid | ||||||
|  |  | ||||||
|         if node is None: |         if node is None: | ||||||
|             node = '' |             node = '' | ||||||
| @@ -113,7 +115,7 @@ class APIRegistry(object): | |||||||
|             else: |             else: | ||||||
|                 jid = jid.full |                 jid = jid.full | ||||||
|         else: |         else: | ||||||
|             if self.settings[ctype].get('client_bare', True): |             if self.settings[ctype].get('client_bare', False): | ||||||
|                 jid = jid.bare |                 jid = jid.bare | ||||||
|             else: |             else: | ||||||
|                 jid = jid.full |                 jid = jid.full | ||||||
| @@ -138,8 +140,8 @@ class APIRegistry(object): | |||||||
|         """Register an API callback, with JID+node specificity. |         """Register an API callback, with JID+node specificity. | ||||||
|  |  | ||||||
|         The API callback can later be executed based on the |         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. |         See :meth:`~ApiRegistry.run` for more details. | ||||||
|  |  | ||||||
|         :param string ctype: The name of the API to use. |         :param string ctype: The name of the API to use. | ||||||
|   | |||||||
| @@ -16,6 +16,7 @@ from __future__ import with_statement, unicode_literals | |||||||
|  |  | ||||||
| import sys | import sys | ||||||
| import logging | import logging | ||||||
|  | import threading | ||||||
|  |  | ||||||
| import sleekxmpp | import sleekxmpp | ||||||
| from sleekxmpp import plugins, features, roster | from sleekxmpp import plugins, features, roster | ||||||
| @@ -31,6 +32,7 @@ from sleekxmpp.xmlstream import XMLStream, JID | |||||||
| from sleekxmpp.xmlstream import ET, register_stanza_plugin | from sleekxmpp.xmlstream import ET, register_stanza_plugin | ||||||
| from sleekxmpp.xmlstream.matcher import MatchXPath | from sleekxmpp.xmlstream.matcher import MatchXPath | ||||||
| from sleekxmpp.xmlstream.handler import Callback | from sleekxmpp.xmlstream.handler import Callback | ||||||
|  | from sleekxmpp.xmlstream.stanzabase import XML_NS | ||||||
|  |  | ||||||
| from sleekxmpp.features import * | from sleekxmpp.features import * | ||||||
| from sleekxmpp.plugins import PluginManager, register_plugin, load_plugin | from sleekxmpp.plugins import PluginManager, register_plugin, load_plugin | ||||||
| @@ -66,9 +68,17 @@ class BaseXMPP(XMLStream): | |||||||
|         #: An identifier for the stream as given by the server. |         #: An identifier for the stream as given by the server. | ||||||
|         self.stream_id = None |         self.stream_id = None | ||||||
|  |  | ||||||
|         #: The JabberID (JID) used by this connection.  |         #: The JabberID (JID) used by this connection. | ||||||
|         self.boundjid = JID(jid) |         self.boundjid = JID(jid) | ||||||
|  |  | ||||||
|         self._expected_server_name = self.boundjid.host |         self._expected_server_name = self.boundjid.host | ||||||
|  |         self._redirect_attempts = 0 | ||||||
|  |  | ||||||
|  |         #: The maximum number of consecutive see-other-host | ||||||
|  |         #: redirections that will be followed before quitting. | ||||||
|  |         self.max_redirects = 5 | ||||||
|  |  | ||||||
|  |         self.session_bind_event = threading.Event() | ||||||
|  |  | ||||||
|         #: A dictionary mapping plugin names to plugins. |         #: A dictionary mapping plugin names to plugins. | ||||||
|         self.plugin = PluginManager(self) |         self.plugin = PluginManager(self) | ||||||
| @@ -86,13 +96,13 @@ class BaseXMPP(XMLStream): | |||||||
|         #: owner JIDs, as in the case for components. For clients |         #: owner JIDs, as in the case for components. For clients | ||||||
|         #: which only have a single JID, see :attr:`client_roster`. |         #: which only have a single JID, see :attr:`client_roster`. | ||||||
|         self.roster = roster.Roster(self) |         self.roster = roster.Roster(self) | ||||||
|         self.roster.add(self.boundjid.bare) |         self.roster.add(self.boundjid) | ||||||
|  |  | ||||||
|         #: The single roster for the bound JID. This is the |         #: The single roster for the bound JID. This is the | ||||||
|         #: equivalent of:: |         #: equivalent of:: | ||||||
|         #: |         #: | ||||||
|         #:     self.roster[self.boundjid.bare] |         #:     self.roster[self.boundjid.bare] | ||||||
|         self.client_roster = self.roster[self.boundjid.bare] |         self.client_roster = self.roster[self.boundjid] | ||||||
|  |  | ||||||
|         #: The distinction between clients and components can be |         #: The distinction between clients and components can be | ||||||
|         #: important, primarily for choosing how to handle the |         #: important, primarily for choosing how to handle the | ||||||
| @@ -102,7 +112,7 @@ class BaseXMPP(XMLStream): | |||||||
|         #: The API registry is a way to process callbacks based on |         #: The API registry is a way to process callbacks based on | ||||||
|         #: JID+node combinations. Each callback in the registry is |         #: JID+node combinations. Each callback in the registry is | ||||||
|         #: marked with: |         #: marked with: | ||||||
|         #:  |         #: | ||||||
|         #:   - An API name, e.g. xep_0030 |         #:   - An API name, e.g. xep_0030 | ||||||
|         #:   - The name of an action, e.g. get_info |         #:   - The name of an action, e.g. get_info | ||||||
|         #:   - The JID that will be affected |         #:   - The JID that will be affected | ||||||
| @@ -133,11 +143,14 @@ class BaseXMPP(XMLStream): | |||||||
|             Callback('Presence', |             Callback('Presence', | ||||||
|                      MatchXPath("{%s}presence" % self.default_ns), |                      MatchXPath("{%s}presence" % self.default_ns), | ||||||
|                      self._handle_presence)) |                      self._handle_presence)) | ||||||
|  |  | ||||||
|         self.register_handler( |         self.register_handler( | ||||||
|             Callback('Stream Error', |             Callback('Stream Error', | ||||||
|                      MatchXPath("{%s}error" % self.stream_ns), |                      MatchXPath("{%s}error" % self.stream_ns), | ||||||
|                      self._handle_stream_error)) |                      self._handle_stream_error)) | ||||||
|  |  | ||||||
|  |         self.add_event_handler('session_start', | ||||||
|  |                                self._handle_session_start) | ||||||
|         self.add_event_handler('disconnected', |         self.add_event_handler('disconnected', | ||||||
|                                self._handle_disconnected) |                                self._handle_disconnected) | ||||||
|         self.add_event_handler('presence_available', |         self.add_event_handler('presence_available', | ||||||
| @@ -180,6 +193,8 @@ class BaseXMPP(XMLStream): | |||||||
|         :param xml: The incoming stream's root element. |         :param xml: The incoming stream's root element. | ||||||
|         """ |         """ | ||||||
|         self.stream_id = xml.get('id', '') |         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): |     def process(self, *args, **kwargs): | ||||||
|         """Initialize plugins and begin processing the XML stream. |         """Initialize plugins and begin processing the XML stream. | ||||||
| @@ -199,7 +214,7 @@ class BaseXMPP(XMLStream): | |||||||
|                     Defaults to ``True``. This does **not** mean that no |                     Defaults to ``True``. This does **not** mean that no | ||||||
|                     threads are used at all if ``threaded=False``. |                     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: |         always exist: | ||||||
|  |  | ||||||
|         - The event queue processor |         - The event queue processor | ||||||
| @@ -272,7 +287,9 @@ class BaseXMPP(XMLStream): | |||||||
|  |  | ||||||
|     def Message(self, *args, **kwargs): |     def Message(self, *args, **kwargs): | ||||||
|         """Create a Message stanza associated with this stream.""" |         """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): |     def Iq(self, *args, **kwargs): | ||||||
|         """Create an Iq stanza associated with this stream.""" |         """Create an Iq stanza associated with this stream.""" | ||||||
| @@ -280,18 +297,20 @@ class BaseXMPP(XMLStream): | |||||||
|  |  | ||||||
|     def Presence(self, *args, **kwargs): |     def Presence(self, *args, **kwargs): | ||||||
|         """Create a Presence stanza associated with this stream.""" |         """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): |     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. |         """Create a new Iq stanza with a given Id and from JID. | ||||||
|  |  | ||||||
|         :param id: An ideally unique ID value for this stanza thread. |         :param id: An ideally unique ID value for this stanza thread. | ||||||
|                    Defaults to 0. |                    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. |                       to use for this stanza. | ||||||
|         :param ito: The destination :class:`~sleekxmpp.xmlstream.jid.JID` |         :param ito: The destination :class:`~sleekxmpp.xmlstream.jid.JID` | ||||||
|                     for this stanza. |                     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'``, |                       one of: ``'get'``, ``'set'``, ``'result'``, | ||||||
|                       or ``'error'``. |                       or ``'error'``. | ||||||
|         :param iquery: Optional namespace for adding a query element. |         :param iquery: Optional namespace for adding a query element. | ||||||
| @@ -329,7 +348,7 @@ class BaseXMPP(XMLStream): | |||||||
|  |  | ||||||
|     def make_iq_result(self, id=None, ito=None, ifrom=None, iq=None): |     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. |         ``'result'`` with the given ID value. | ||||||
|  |  | ||||||
|         :param id: An ideally unique ID value. May use :meth:`new_id()`. |         :param id: An ideally unique ID value. May use :meth:`new_id()`. | ||||||
| @@ -359,10 +378,10 @@ class BaseXMPP(XMLStream): | |||||||
|         Optionally, a substanza may be given to use as the |         Optionally, a substanza may be given to use as the | ||||||
|         stanza's payload. |         stanza's payload. | ||||||
|  |  | ||||||
|         :param sub: Either an  |         :param sub: Either an | ||||||
|                     :class:`~sleekxmpp.xmlstream.stanzabase.ElementBase` |                     :class:`~sleekxmpp.xmlstream.stanzabase.ElementBase` | ||||||
|                     stanza object or an |                     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. |                     to use as the :class:`~sleekxmpp.stanza.iq.Iq`'s payload. | ||||||
|         :param ito: The destination :class:`~sleekxmpp.xmlstream.jid.JID` |         :param ito: The destination :class:`~sleekxmpp.xmlstream.jid.JID` | ||||||
|                     for this stanza. |                     for this stanza. | ||||||
| @@ -389,9 +408,9 @@ class BaseXMPP(XMLStream): | |||||||
|         Create an :class:`~sleekxmpp.stanza.iq.Iq` stanza of type ``'error'``. |         Create an :class:`~sleekxmpp.stanza.iq.Iq` stanza of type ``'error'``. | ||||||
|  |  | ||||||
|         :param id: An ideally unique ID value. May use :meth:`new_id()`. |         :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'``. |                      ``'modify'``. Defaults to ``'cancel'``. | ||||||
|         :param condition: The error condition. Defaults to  |         :param condition: The error condition. Defaults to | ||||||
|                           ``'feature-not-implemented'``. |                           ``'feature-not-implemented'``. | ||||||
|         :param text: A message describing the cause of the error. |         :param text: A message describing the cause of the error. | ||||||
|         :param ito: The destination :class:`~sleekxmpp.xmlstream.jid.JID` |         :param ito: The destination :class:`~sleekxmpp.xmlstream.jid.JID` | ||||||
| @@ -415,7 +434,7 @@ class BaseXMPP(XMLStream): | |||||||
|  |  | ||||||
|     def make_iq_query(self, iq=None, xmlns='', ito=None, ifrom=None): |     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. |         to use the given query namespace. | ||||||
|  |  | ||||||
|         :param iq: Optionally use an existing stanza instead |         :param iq: Optionally use an existing stanza instead | ||||||
| @@ -448,7 +467,7 @@ class BaseXMPP(XMLStream): | |||||||
|     def make_message(self, mto, mbody=None, msubject=None, mtype=None, |     def make_message(self, mto, mbody=None, msubject=None, mtype=None, | ||||||
|                      mhtml=None, mfrom=None, mnick=None): |                      mhtml=None, mfrom=None, mnick=None): | ||||||
|         """ |         """ | ||||||
|         Create and initialize a new  |         Create and initialize a new | ||||||
|         :class:`~sleekxmpp.stanza.message.Message` stanza. |         :class:`~sleekxmpp.stanza.message.Message` stanza. | ||||||
|  |  | ||||||
|         :param mto: The recipient of the message. |         :param mto: The recipient of the message. | ||||||
| @@ -474,7 +493,7 @@ class BaseXMPP(XMLStream): | |||||||
|     def make_presence(self, pshow=None, pstatus=None, ppriority=None, |     def make_presence(self, pshow=None, pstatus=None, ppriority=None, | ||||||
|                       pto=None, ptype=None, pfrom=None, pnick=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. |         :class:`~sleekxmpp.stanza.presence.Presence` stanza. | ||||||
|  |  | ||||||
|         :param pshow: The presence's show value. |         :param pshow: The presence's show value. | ||||||
| @@ -498,7 +517,7 @@ class BaseXMPP(XMLStream): | |||||||
|     def send_message(self, mto, mbody, msubject=None, mtype=None, |     def send_message(self, mto, mbody, msubject=None, mtype=None, | ||||||
|                      mhtml=None, mfrom=None, mnick=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. |         :class:`~sleekxmpp.stanza.message.Message` stanza. | ||||||
|  |  | ||||||
|         :param mto: The recipient of the message. |         :param mto: The recipient of the message. | ||||||
| @@ -518,7 +537,7 @@ class BaseXMPP(XMLStream): | |||||||
|     def send_presence(self, pshow=None, pstatus=None, ppriority=None, |     def send_presence(self, pshow=None, pstatus=None, ppriority=None, | ||||||
|                       pto=None, pfrom=None, ptype=None, pnick=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. |         :class:`~sleekxmpp.stanza.presence.Presence` stanza. | ||||||
|  |  | ||||||
|         :param pshow: The presence's show value. |         :param pshow: The presence's show value. | ||||||
| @@ -529,23 +548,13 @@ class BaseXMPP(XMLStream): | |||||||
|         :param pfrom: The sender of the presence. |         :param pfrom: The sender of the presence. | ||||||
|         :param pnick: Optional nickname of the presence's sender. |         :param pnick: Optional nickname of the presence's sender. | ||||||
|         """ |         """ | ||||||
|         # Python2.6 chokes on Unicode strings for dict keys. |         self.make_presence(pshow, pstatus, ppriority, pto, | ||||||
|         args = {str('pto'): pto, |                            ptype, pfrom, pnick).send() | ||||||
|                 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) |  | ||||||
|  |  | ||||||
|     def send_presence_subscription(self, pto, pfrom=None, |     def send_presence_subscription(self, pto, pfrom=None, | ||||||
|                                    ptype='subscribe', pnick=None): |                                    ptype='subscribe', pnick=None): | ||||||
|         """ |         """ | ||||||
|         Create, initialize, and send a new  |         Create, initialize, and send a new | ||||||
|         :class:`~sleekxmpp.stanza.presence.Presence` stanza of |         :class:`~sleekxmpp.stanza.presence.Presence` stanza of | ||||||
|         type ``'subscribe'``. |         type ``'subscribe'``. | ||||||
|  |  | ||||||
| @@ -554,14 +563,10 @@ class BaseXMPP(XMLStream): | |||||||
|         :param ptype: The type of presence, such as ``'subscribe'``. |         :param ptype: The type of presence, such as ``'subscribe'``. | ||||||
|         :param pnick: Optional nickname of the presence's sender. |         :param pnick: Optional nickname of the presence's sender. | ||||||
|         """ |         """ | ||||||
|         presence = self.makePresence(ptype=ptype, |         self.make_presence(ptype=ptype, | ||||||
|                                      pfrom=pfrom, |                            pfrom=pfrom, | ||||||
|                                      pto=self.getjidbare(pto)) |                            pto=JID(pto).bare, | ||||||
|         if pnick: |                            pnick=pnick).send() | ||||||
|             nick = ET.Element('{http://jabber.org/protocol/nick}nick') |  | ||||||
|             nick.text = pnick |  | ||||||
|             presence.append(nick) |  | ||||||
|         presence.send() |  | ||||||
|  |  | ||||||
|     @property |     @property | ||||||
|     def jid(self): |     def jid(self): | ||||||
| @@ -593,7 +598,7 @@ class BaseXMPP(XMLStream): | |||||||
|  |  | ||||||
|     @resource.setter |     @resource.setter | ||||||
|     def resource(self, value): |     def resource(self, value): | ||||||
|         log.warning("fulljid property deprecated. Use boundjid.full") |         log.warning("fulljid property deprecated. Use boundjid.resource") | ||||||
|         self.boundjid.resource = value |         self.boundjid.resource = value | ||||||
|  |  | ||||||
|     @property |     @property | ||||||
| @@ -658,30 +663,61 @@ class BaseXMPP(XMLStream): | |||||||
|     def getjidbare(self, fulljid): |     def getjidbare(self, fulljid): | ||||||
|         return fulljid.split('/', 1)[0] |         return fulljid.split('/', 1)[0] | ||||||
|  |  | ||||||
|  |     def _handle_session_start(self, event): | ||||||
|  |         """Reset redirection attempt count.""" | ||||||
|  |         self._redirect_attempts = 0 | ||||||
|  |  | ||||||
|     def _handle_disconnected(self, event): |     def _handle_disconnected(self, event): | ||||||
|         """When disconnected, reset the roster""" |         """When disconnected, reset the roster""" | ||||||
|         self.roster.reset() |         self.roster.reset() | ||||||
|  |         self.session_bind_event.clear() | ||||||
|  |  | ||||||
|     def _handle_stream_error(self, error): |     def _handle_stream_error(self, error): | ||||||
|         self.event('stream_error', error) |         self.event('stream_error', error) | ||||||
|  |  | ||||||
|  |         if error['condition'] == 'see-other-host': | ||||||
|  |             other_host = error['see_other_host'] | ||||||
|  |             if not other_host: | ||||||
|  |                 log.warning("No other host specified.") | ||||||
|  |                 return | ||||||
|  |  | ||||||
|  |             if self._redirect_attempts > self.max_redirects: | ||||||
|  |                 log.error("Exceeded maximum number of redirection attempts.") | ||||||
|  |                 return | ||||||
|  |  | ||||||
|  |             self._redirect_attempts += 1 | ||||||
|  |  | ||||||
|  |             host = other_host | ||||||
|  |             port = 5222 | ||||||
|  |  | ||||||
|  |             if '[' in other_host and ']' in other_host: | ||||||
|  |                 host = other_host.split(']')[0][1:] | ||||||
|  |             elif ':' in other_host: | ||||||
|  |                 host = other_host.split(':')[0] | ||||||
|  |  | ||||||
|  |             port_sec = other_host.split(']')[-1] | ||||||
|  |             if ':' in port_sec: | ||||||
|  |                 port = int(port_sec.split(':')[1]) | ||||||
|  |  | ||||||
|  |             self.address = (host, port) | ||||||
|  |             self.default_domain = host | ||||||
|  |             self.dns_records = None | ||||||
|  |             self.reconnect_delay = None | ||||||
|  |             self.reconnect() | ||||||
|  |  | ||||||
|     def _handle_message(self, msg): |     def _handle_message(self, msg): | ||||||
|         """Process incoming message stanzas.""" |         """Process incoming message stanzas.""" | ||||||
|         if not self.is_component and not msg['to'].bare: |         if not self.is_component and not msg['to'].bare: | ||||||
|             msg['to'] = self.boundjid |             msg['to'] = self.boundjid | ||||||
|         self.event('message', msg) |         self.event('message', msg) | ||||||
|  |  | ||||||
|     def _handle_available(self, presence): |     def _handle_available(self, pres): | ||||||
|         pto = presence['to'].bare |         self.roster[pres['to']][pres['from']].handle_available(pres) | ||||||
|         pfrom = presence['from'].bare |  | ||||||
|         self.roster[pto][pfrom].handle_available(presence) |  | ||||||
|  |  | ||||||
|     def _handle_unavailable(self, presence): |     def _handle_unavailable(self, pres): | ||||||
|         pto = presence['to'].bare |         self.roster[pres['to']][pres['from']].handle_unavailable(pres) | ||||||
|         pfrom = presence['from'].bare |  | ||||||
|         self.roster[pto][pfrom].handle_unavailable(presence) |  | ||||||
|  |  | ||||||
|     def _handle_new_subscription(self, stanza): |     def _handle_new_subscription(self, pres): | ||||||
|         """Attempt to automatically handle subscription requests. |         """Attempt to automatically handle subscription requests. | ||||||
|  |  | ||||||
|         Subscriptions will be approved if the request is from |         Subscriptions will be approved if the request is from | ||||||
| @@ -693,8 +729,8 @@ class BaseXMPP(XMLStream): | |||||||
|         If a subscription is accepted, a request for a mutual |         If a subscription is accepted, a request for a mutual | ||||||
|         subscription will be sent if :attr:`auto_subscribe` is ``True``. |         subscription will be sent if :attr:`auto_subscribe` is ``True``. | ||||||
|         """ |         """ | ||||||
|         roster = self.roster[stanza['to'].bare] |         roster = self.roster[pres['to']] | ||||||
|         item = self.roster[stanza['to'].bare][stanza['from'].bare] |         item = self.roster[pres['to']][pres['from']] | ||||||
|         if item['whitelisted']: |         if item['whitelisted']: | ||||||
|             item.authorize() |             item.authorize() | ||||||
|         elif roster.auto_authorize: |         elif roster.auto_authorize: | ||||||
| @@ -704,30 +740,20 @@ class BaseXMPP(XMLStream): | |||||||
|         elif roster.auto_authorize == False: |         elif roster.auto_authorize == False: | ||||||
|             item.unauthorize() |             item.unauthorize() | ||||||
|  |  | ||||||
|     def _handle_removed_subscription(self, presence): |     def _handle_removed_subscription(self, pres): | ||||||
|         pto = presence['to'].bare |         self.roster[pres['to']][pres['from']].handle_unauthorize(pres) | ||||||
|         pfrom = presence['from'].bare |  | ||||||
|         self.roster[pto][pfrom].unauthorize() |  | ||||||
|  |  | ||||||
|     def _handle_subscribe(self, presence): |     def _handle_subscribe(self, pres): | ||||||
|         pto = presence['to'].bare |         self.roster[pres['to']][pres['from']].handle_subscribe(pres) | ||||||
|         pfrom = presence['from'].bare |  | ||||||
|         self.roster[pto][pfrom].handle_subscribe(presence) |  | ||||||
|  |  | ||||||
|     def _handle_subscribed(self, presence): |     def _handle_subscribed(self, pres): | ||||||
|         pto = presence['to'].bare |         self.roster[pres['to']][pres['from']].handle_subscribed(pres) | ||||||
|         pfrom = presence['from'].bare |  | ||||||
|         self.roster[pto][pfrom].handle_subscribed(presence) |  | ||||||
|  |  | ||||||
|     def _handle_unsubscribe(self, presence): |     def _handle_unsubscribe(self, pres): | ||||||
|         pto = presence['to'].bare |         self.roster[pres['to']][pres['from']].handle_unsubscribe(pres) | ||||||
|         pfrom = presence['from'].bare |  | ||||||
|         self.roster[pto][pfrom].handle_unsubscribe(presence) |  | ||||||
|  |  | ||||||
|     def _handle_unsubscribed(self, presence): |     def _handle_unsubscribed(self, pres): | ||||||
|         pto = presence['to'].bare |         self.roster[pres['to']][pres['from']].handle_unsubscribed(pres) | ||||||
|         pfrom = presence['from'].bare |  | ||||||
|         self.roster[pto][pfrom].handle_unsubscribed(presence) |  | ||||||
|  |  | ||||||
|     def _handle_presence(self, presence): |     def _handle_presence(self, presence): | ||||||
|         """Process incoming presence stanzas. |         """Process incoming presence stanzas. | ||||||
| @@ -750,7 +776,7 @@ class BaseXMPP(XMLStream): | |||||||
|             return |             return | ||||||
|  |  | ||||||
|     def exception(self, exception): |     def exception(self, exception): | ||||||
|         """Process any uncaught exceptions, notably  |         """Process any uncaught exceptions, notably | ||||||
|         :class:`~sleekxmpp.exceptions.IqError` and |         :class:`~sleekxmpp.exceptions.IqError` and | ||||||
|         :class:`~sleekxmpp.exceptions.IqTimeout` exceptions. |         :class:`~sleekxmpp.exceptions.IqTimeout` exceptions. | ||||||
|  |  | ||||||
|   | |||||||
| @@ -54,14 +54,14 @@ class ClientXMPP(BaseXMPP): | |||||||
|     :param password: The password for the XMPP user account. |     :param password: The password for the XMPP user account. | ||||||
|     :param ssl: **Deprecated.** |     :param ssl: **Deprecated.** | ||||||
|     :param plugin_config: A dictionary of plugin configurations. |     :param plugin_config: A dictionary of plugin configurations. | ||||||
|     :param plugin_whitelist: A list of approved plugins that  |     :param plugin_whitelist: A list of approved plugins that | ||||||
|                     will be loaded when calling  |                     will be loaded when calling | ||||||
|                     :meth:`~sleekxmpp.basexmpp.BaseXMPP.register_plugins()`. |                     :meth:`~sleekxmpp.basexmpp.BaseXMPP.register_plugins()`. | ||||||
|     :param escape_quotes: **Deprecated.** |     :param escape_quotes: **Deprecated.** | ||||||
|     """ |     """ | ||||||
|  |  | ||||||
|     def __init__(self, jid, password, ssl=False, plugin_config={}, |     def __init__(self, jid, password, plugin_config={}, plugin_whitelist=[], | ||||||
|                  plugin_whitelist=[], escape_quotes=True, sasl_mech=None): |                  escape_quotes=True, sasl_mech=None, lang='en'): | ||||||
|         BaseXMPP.__init__(self, jid, 'jabber:client') |         BaseXMPP.__init__(self, jid, 'jabber:client') | ||||||
|  |  | ||||||
|         self.set_jid(jid) |         self.set_jid(jid) | ||||||
| @@ -69,15 +69,18 @@ class ClientXMPP(BaseXMPP): | |||||||
|         self.plugin_config = plugin_config |         self.plugin_config = plugin_config | ||||||
|         self.plugin_whitelist = plugin_whitelist |         self.plugin_whitelist = plugin_whitelist | ||||||
|         self.default_port = 5222 |         self.default_port = 5222 | ||||||
|  |         self.default_lang = lang | ||||||
|  |  | ||||||
|         self.credentials = {} |         self.credentials = {} | ||||||
|  |  | ||||||
|         self.password = password |         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, |                 self.boundjid.host, | ||||||
|                 "xmlns:stream='%s'" % self.stream_ns, |                 "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.stream_footer = "</stream:stream>" | ||||||
|  |  | ||||||
|         self.features = set() |         self.features = set() | ||||||
| @@ -170,8 +173,13 @@ class ClientXMPP(BaseXMPP): | |||||||
|         self._stream_feature_order.append((order, name)) |         self._stream_feature_order.append((order, name)) | ||||||
|         self._stream_feature_order.sort() |         self._stream_feature_order.sort() | ||||||
|  |  | ||||||
|     def update_roster(self, jid, name=None, subscription=None, groups=[], |     def unregister_feature(self, name, order): | ||||||
|                             block=True, timeout=None, callback=None): |         if name in self._stream_feature_handlers: | ||||||
|  |             del self._stream_feature_handlers[name] | ||||||
|  |         self._stream_feature_order.remove((order, name)) | ||||||
|  |         self._stream_feature_order.sort() | ||||||
|  |  | ||||||
|  |     def update_roster(self, jid, **kwargs): | ||||||
|         """Add or change a roster item. |         """Add or change a roster item. | ||||||
|  |  | ||||||
|         :param jid: The JID of the entry to modify. |         :param jid: The JID of the entry to modify. | ||||||
| @@ -186,18 +194,28 @@ class ClientXMPP(BaseXMPP): | |||||||
|                       occurs. Defaults to ``True``. |                       occurs. Defaults to ``True``. | ||||||
|         :param timeout: The length of time (in seconds) to wait |         :param timeout: The length of time (in seconds) to wait | ||||||
|                         for a response before continuing if blocking |                         for a response before continuing if blocking | ||||||
|                         is used. Defaults to  |                         is used. Defaults to | ||||||
|             :attr:`~sleekxmpp.xmlstream.xmlstream.XMLStream.response_timeout`. |             :attr:`~sleekxmpp.xmlstream.xmlstream.XMLStream.response_timeout`. | ||||||
|         :param callback: Optional reference to a stream handler function. |         :param callback: Optional reference to a stream handler function. | ||||||
|                          Will be executed when the roster is received. |                          Will be executed when the roster is received. | ||||||
|                          Implies ``block=False``. |                          Implies ``block=False``. | ||||||
|         """ |         """ | ||||||
|  |         current = self.client_roster[jid] | ||||||
|  |  | ||||||
|  |         name = kwargs.get('name', current['name']) | ||||||
|  |         subscription = kwargs.get('subscription', current['subscription']) | ||||||
|  |         groups = kwargs.get('groups', current['groups']) | ||||||
|  |  | ||||||
|  |         block = kwargs.get('block', True) | ||||||
|  |         timeout = kwargs.get('timeout', None) | ||||||
|  |         callback = kwargs.get('callback', None) | ||||||
|  |  | ||||||
|         return self.client_roster.update(jid, name, subscription, groups, |         return self.client_roster.update(jid, name, subscription, groups, | ||||||
|                                          block, timeout, callback) |                                          block, timeout, callback) | ||||||
|  |  | ||||||
|     def del_roster_item(self, jid): |     def del_roster_item(self, jid): | ||||||
|         """Remove an item from the roster. |         """Remove an item from the roster. | ||||||
|          |  | ||||||
|         This is done by setting its subscription status to ``'remove'``. |         This is done by setting its subscription status to ``'remove'``. | ||||||
|  |  | ||||||
|         :param jid: The JID of the item to remove. |         :param jid: The JID of the item to remove. | ||||||
| @@ -212,7 +230,7 @@ class ClientXMPP(BaseXMPP): | |||||||
|                       Defaults to ``True``. |                       Defaults to ``True``. | ||||||
|         :param timeout: The length of time (in seconds) to wait for a response |         :param timeout: The length of time (in seconds) to wait for a response | ||||||
|                         before continuing if blocking is used. |                         before continuing if blocking is used. | ||||||
|                         Defaults to  |                         Defaults to | ||||||
|             :attr:`~sleekxmpp.xmlstream.xmlstream.XMLStream.response_timeout`. |             :attr:`~sleekxmpp.xmlstream.xmlstream.XMLStream.response_timeout`. | ||||||
|         :param callback: Optional reference to a stream handler function. Will |         :param callback: Optional reference to a stream handler function. Will | ||||||
|                          be executed when the roster is received. |                          be executed when the roster is received. | ||||||
| @@ -230,7 +248,7 @@ class ClientXMPP(BaseXMPP): | |||||||
|         response = iq.send(block, timeout, callback) |         response = iq.send(block, timeout, callback) | ||||||
|         self.event('roster_received', response) |         self.event('roster_received', response) | ||||||
|  |  | ||||||
|         if block:  |         if block: | ||||||
|             self._handle_roster(response) |             self._handle_roster(response) | ||||||
|             return response |             return response | ||||||
|  |  | ||||||
| @@ -267,8 +285,9 @@ class ClientXMPP(BaseXMPP): | |||||||
|         roster = self.client_roster |         roster = self.client_roster | ||||||
|         if iq['roster']['ver']: |         if iq['roster']['ver']: | ||||||
|             roster.version = iq['roster']['ver'] |             roster.version = iq['roster']['ver'] | ||||||
|         for jid in iq['roster']['items']: |         items = iq['roster']['items'] | ||||||
|             item = iq['roster']['items'][jid] |         for jid in items: | ||||||
|  |             item = items[jid] | ||||||
|             roster[jid]['name'] = item['name'] |             roster[jid]['name'] = item['name'] | ||||||
|             roster[jid]['groups'] = item['groups'] |             roster[jid]['groups'] = item['groups'] | ||||||
|             roster[jid]['from'] = item['subscription'] in ['from', 'both'] |             roster[jid]['from'] = item['subscription'] in ['from', 'both'] | ||||||
| @@ -276,7 +295,7 @@ class ClientXMPP(BaseXMPP): | |||||||
|             roster[jid]['pending_out'] = (item['ask'] == 'subscribe') |             roster[jid]['pending_out'] = (item['ask'] == 'subscribe') | ||||||
|  |  | ||||||
|             roster[jid].save(remove=(item['subscription'] == 'remove')) |             roster[jid].save(remove=(item['subscription'] == 'remove')) | ||||||
|                   |  | ||||||
|         self.event("roster_update", iq) |         self.event("roster_update", iq) | ||||||
|         if iq['type'] == 'set': |         if iq['type'] == 'set': | ||||||
|             resp = self.Iq(stype='result', |             resp = self.Iq(stype='result', | ||||||
|   | |||||||
| @@ -40,8 +40,8 @@ class ComponentXMPP(BaseXMPP): | |||||||
|     :param host: The server accepting the component. |     :param host: The server accepting the component. | ||||||
|     :param port: The port used to connect to the server. |     :param port: The port used to connect to the server. | ||||||
|     :param plugin_config: A dictionary of plugin configurations. |     :param plugin_config: A dictionary of plugin configurations. | ||||||
|     :param plugin_whitelist: A list of approved plugins that  |     :param plugin_whitelist: A list of approved plugins that | ||||||
|                     will be loaded when calling  |                     will be loaded when calling | ||||||
|                     :meth:`~sleekxmpp.basexmpp.BaseXMPP.register_plugins()`. |                     :meth:`~sleekxmpp.basexmpp.BaseXMPP.register_plugins()`. | ||||||
|     :param use_jc_ns: Indicates if the ``'jabber:client'`` namespace |     :param use_jc_ns: Indicates if the ``'jabber:client'`` namespace | ||||||
|                       should be used instead of the standard |                       should be used instead of the standard | ||||||
| @@ -78,7 +78,7 @@ class ComponentXMPP(BaseXMPP): | |||||||
|         self.add_event_handler('presence_probe', |         self.add_event_handler('presence_probe', | ||||||
|                                self._handle_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): |                       use_tls=False, reattempt=True): | ||||||
|         """Connect to the server. |         """Connect to the server. | ||||||
|  |  | ||||||
| @@ -156,10 +156,10 @@ class ComponentXMPP(BaseXMPP): | |||||||
|  |  | ||||||
|         :param xml: The reply handshake stanza. |         :param xml: The reply handshake stanza. | ||||||
|         """ |         """ | ||||||
|  |         self.session_bind_event.set() | ||||||
|         self.session_started_event.set() |         self.session_started_event.set() | ||||||
|  |         self.event("session_bind", self.boundjid, direct=True) | ||||||
|         self.event("session_start") |         self.event("session_start") | ||||||
|  |  | ||||||
|     def _handle_probe(self, presence): |     def _handle_probe(self, pres): | ||||||
|         pto = presence['to'].bare |         self.roster[pres['to']][pres['from']].handle_probe(pres) | ||||||
|         pfrom = presence['from'].bare |  | ||||||
|         self.roster[pto][pfrom].handle_probe(presence) |  | ||||||
|   | |||||||
| @@ -69,10 +69,11 @@ class IqTimeout(XMPPError): | |||||||
|                 condition='remote-server-timeout', |                 condition='remote-server-timeout', | ||||||
|                 etype='cancel') |                 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. |         #: did not arrive before the timeout expired. | ||||||
|         self.iq = iq |         self.iq = iq | ||||||
|  |  | ||||||
|  |  | ||||||
| class IqError(XMPPError): | class IqError(XMPPError): | ||||||
|  |  | ||||||
|     """ |     """ | ||||||
|   | |||||||
| @@ -7,9 +7,9 @@ | |||||||
| """ | """ | ||||||
|  |  | ||||||
| __all__ = [ | __all__ = [ | ||||||
|     'feature_starttls',  |     'feature_starttls', | ||||||
|     'feature_mechanisms',  |     'feature_mechanisms', | ||||||
|     'feature_bind',  |     'feature_bind', | ||||||
|     'feature_session', |     'feature_session', | ||||||
|     'feature_rosterver' |     'feature_rosterver' | ||||||
| ] | ] | ||||||
|   | |||||||
| @@ -51,6 +51,7 @@ class FeatureBind(BasePlugin): | |||||||
|         self.xmpp.set_jid(response['bind']['jid']) |         self.xmpp.set_jid(response['bind']['jid']) | ||||||
|         self.xmpp.bound = True |         self.xmpp.bound = True | ||||||
|         self.xmpp.event('session_bind', self.xmpp.boundjid, direct=True) |         self.xmpp.event('session_bind', self.xmpp.boundjid, direct=True) | ||||||
|  |         self.xmpp.session_bind_event.set() | ||||||
|  |  | ||||||
|         self.xmpp.features.add('bind') |         self.xmpp.features.add('bind') | ||||||
|  |  | ||||||
|   | |||||||
| @@ -29,10 +29,13 @@ class FeatureMechanisms(BasePlugin): | |||||||
|     description = 'RFC 6120: Stream Feature: SASL' |     description = 'RFC 6120: Stream Feature: SASL' | ||||||
|     dependencies = set() |     dependencies = set() | ||||||
|     stanza = stanza |     stanza = stanza | ||||||
|  |     default_config = { | ||||||
|  |         'use_mech': None, | ||||||
|  |         'sasl_callback': None, | ||||||
|  |         'order': 100 | ||||||
|  |     } | ||||||
|  |  | ||||||
|     def plugin_init(self): |     def plugin_init(self): | ||||||
|         self.use_mech = self.config.get('use_mech', None) |  | ||||||
|  |  | ||||||
|         if not self.use_mech and not self.xmpp.boundjid.user: |         if not self.use_mech and not self.xmpp.boundjid.user: | ||||||
|             self.use_mech = 'ANONYMOUS' |             self.use_mech = 'ANONYMOUS' | ||||||
|  |  | ||||||
| @@ -53,15 +56,14 @@ class FeatureMechanisms(BasePlugin): | |||||||
|                     values[value] = creds[value] |                     values[value] = creds[value] | ||||||
|             mech.fulfill(values) |             mech.fulfill(values) | ||||||
|  |  | ||||||
|         sasl_callback = self.config.get('sasl_callback', None) |         if self.sasl_callback is None: | ||||||
|         if sasl_callback is None: |             self.sasl_callback = basic_callback | ||||||
|             sasl_callback = basic_callback |  | ||||||
|  |  | ||||||
|         self.mech = None |         self.mech = None | ||||||
|         self.sasl = suelta.SASL(self.xmpp.boundjid.domain, 'xmpp', |         self.sasl = suelta.SASL(self.xmpp.boundjid.domain, 'xmpp', | ||||||
|                                 username=self.xmpp.boundjid.user, |                                 username=self.xmpp.boundjid.user, | ||||||
|                                 sec_query=suelta.sec_query_allow, |                                 sec_query=suelta.sec_query_allow, | ||||||
|                                 request_values=sasl_callback, |                                 request_values=self.sasl_callback, | ||||||
|                                 tls_active=tls_active, |                                 tls_active=tls_active, | ||||||
|                                 mech=self.use_mech) |                                 mech=self.use_mech) | ||||||
|  |  | ||||||
| @@ -95,7 +97,7 @@ class FeatureMechanisms(BasePlugin): | |||||||
|         self.xmpp.register_feature('mechanisms', |         self.xmpp.register_feature('mechanisms', | ||||||
|                 self._handle_sasl_auth, |                 self._handle_sasl_auth, | ||||||
|                 restart=True, |                 restart=True, | ||||||
|                 order=self.config.get('order', 100)) |                 order=self.order) | ||||||
|  |  | ||||||
|     def _handle_sasl_auth(self, features): |     def _handle_sasl_auth(self, features): | ||||||
|         """ |         """ | ||||||
|   | |||||||
| @@ -23,7 +23,7 @@ class Auth(StanzaBase): | |||||||
|     interfaces = set(('mechanism', 'value')) |     interfaces = set(('mechanism', 'value')) | ||||||
|     plugin_attrib = name |     plugin_attrib = name | ||||||
|  |  | ||||||
|     #: Some SASL mechs require sending values as is,  |     #: Some SASL mechs require sending values as is, | ||||||
|     #: without converting base64. |     #: without converting base64. | ||||||
|     plain_mechs = set(['X-MESSENGER-OAUTH2']) |     plain_mechs = set(['X-MESSENGER-OAUTH2']) | ||||||
|  |  | ||||||
|   | |||||||
| @@ -47,7 +47,7 @@ class Failure(StanzaBase): | |||||||
|  |  | ||||||
|     def get_condition(self): |     def get_condition(self): | ||||||
|         """Return the condition element's name.""" |         """Return the condition element's name.""" | ||||||
|         for child in self.xml.getchildren(): |         for child in self.xml: | ||||||
|             if "{%s}" % self.namespace in child.tag: |             if "{%s}" % self.namespace in child.tag: | ||||||
|                 cond = child.tag.split('}', 1)[-1] |                 cond = child.tag.split('}', 1)[-1] | ||||||
|                 if cond in self.conditions: |                 if cond in self.conditions: | ||||||
| @@ -68,7 +68,7 @@ class Failure(StanzaBase): | |||||||
|  |  | ||||||
|     def del_condition(self): |     def del_condition(self): | ||||||
|         """Remove the condition element.""" |         """Remove the condition element.""" | ||||||
|         for child in self.xml.getchildren(): |         for child in self.xml: | ||||||
|             if "{%s}" % self.condition_ns in child.tag: |             if "{%s}" % self.condition_ns in child.tag: | ||||||
|                 tag = child.tag.split('}', 1)[-1] |                 tag = child.tag.split('}', 1)[-1] | ||||||
|                 if tag in self.conditions: |                 if tag in self.conditions: | ||||||
|   | |||||||
							
								
								
									
										543
									
								
								sleekxmpp/jid.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										543
									
								
								sleekxmpp/jid.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,543 @@ | |||||||
|  | # -*- coding: utf-8 -*- | ||||||
|  | """ | ||||||
|  |     sleekxmpp.jid | ||||||
|  |     ~~~~~~~~~~~~~~~~~~~~~~~ | ||||||
|  |  | ||||||
|  |     This module allows for working with Jabber IDs (JIDs). | ||||||
|  |  | ||||||
|  |     Part of SleekXMPP: The Sleek XMPP Library | ||||||
|  |  | ||||||
|  |     :copyright: (c) 2011 Nathanael C. Fritz | ||||||
|  |     :license: MIT, see LICENSE for more details | ||||||
|  | """ | ||||||
|  |  | ||||||
|  | from __future__ import unicode_literals | ||||||
|  |  | ||||||
|  | import re | ||||||
|  | import socket | ||||||
|  | import stringprep | ||||||
|  | import encodings.idna | ||||||
|  |  | ||||||
|  | from sleekxmpp.util import stringprep_profiles | ||||||
|  |  | ||||||
|  | #: These characters are not allowed to appear in a JID. | ||||||
|  | ILLEGAL_CHARS = '\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r' + \ | ||||||
|  |                 '\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19' + \ | ||||||
|  |                 '\x1a\x1b\x1c\x1d\x1e\x1f' + \ | ||||||
|  |                 ' !"#$%&\'()*+,./:;<=>?@[\\]^_`{|}~\x7f' | ||||||
|  |  | ||||||
|  | #: The basic regex pattern that a JID must match in order to determine | ||||||
|  | #: the local, domain, and resource parts. This regex does NOT do any | ||||||
|  | #: validation, which requires application of nodeprep, resourceprep, etc. | ||||||
|  | JID_PATTERN = re.compile( | ||||||
|  |     "^(?:([^\"&'/:<>@]{1,1023})@)?([^/@]{1,1023})(?:/(.{1,1023}))?$" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | #: The set of escape sequences for the characters not allowed by nodeprep. | ||||||
|  | JID_ESCAPE_SEQUENCES = set(['\\20', '\\22', '\\26', '\\27', '\\2f', | ||||||
|  |                             '\\3a', '\\3c', '\\3e', '\\40', '\\5c']) | ||||||
|  |  | ||||||
|  | #: A mapping of unallowed characters to their escape sequences. An escape | ||||||
|  | #: sequence for '\' is also included since it must also be escaped in | ||||||
|  | #: certain situations. | ||||||
|  | JID_ESCAPE_TRANSFORMATIONS = {' ': '\\20', | ||||||
|  |                               '"': '\\22', | ||||||
|  |                               '&': '\\26', | ||||||
|  |                               "'": '\\27', | ||||||
|  |                               '/': '\\2f', | ||||||
|  |                               ':': '\\3a', | ||||||
|  |                               '<': '\\3c', | ||||||
|  |                               '>': '\\3e', | ||||||
|  |                               '@': '\\40', | ||||||
|  |                               '\\': '\\5c'} | ||||||
|  |  | ||||||
|  | #: The reverse mapping of escape sequences to their original forms. | ||||||
|  | JID_UNESCAPE_TRANSFORMATIONS = {'\\20': ' ', | ||||||
|  |                                 '\\22': '"', | ||||||
|  |                                 '\\26': '&', | ||||||
|  |                                 '\\27': "'", | ||||||
|  |                                 '\\2f': '/', | ||||||
|  |                                 '\\3a': ':', | ||||||
|  |                                 '\\3c': '<', | ||||||
|  |                                 '\\3e': '>', | ||||||
|  |                                 '\\40': '@', | ||||||
|  |                                 '\\5c': '\\'} | ||||||
|  |  | ||||||
|  |  | ||||||
|  | # pylint: disable=c0103 | ||||||
|  | #: The nodeprep profile of stringprep used to validate the local, | ||||||
|  | #: or username, portion of a JID. | ||||||
|  | nodeprep = stringprep_profiles.create( | ||||||
|  |     nfkc=True, | ||||||
|  |     bidi=True, | ||||||
|  |     mappings=[ | ||||||
|  |         stringprep_profiles.b1_mapping, | ||||||
|  |         stringprep_profiles.c12_mapping], | ||||||
|  |     prohibited=[ | ||||||
|  |         stringprep.in_table_c11, | ||||||
|  |         stringprep.in_table_c12, | ||||||
|  |         stringprep.in_table_c21, | ||||||
|  |         stringprep.in_table_c22, | ||||||
|  |         stringprep.in_table_c3, | ||||||
|  |         stringprep.in_table_c4, | ||||||
|  |         stringprep.in_table_c5, | ||||||
|  |         stringprep.in_table_c6, | ||||||
|  |         stringprep.in_table_c7, | ||||||
|  |         stringprep.in_table_c8, | ||||||
|  |         stringprep.in_table_c9, | ||||||
|  |         lambda c: c in ' \'"&/:<>@'], | ||||||
|  |     unassigned=[stringprep.in_table_a1]) | ||||||
|  |  | ||||||
|  | # pylint: disable=c0103 | ||||||
|  | #: The resourceprep profile of stringprep, which is used to validate | ||||||
|  | #: the resource portion of a JID. | ||||||
|  | resourceprep = stringprep_profiles.create( | ||||||
|  |     nfkc=True, | ||||||
|  |     bidi=True, | ||||||
|  |     mappings=[stringprep_profiles.b1_mapping], | ||||||
|  |     prohibited=[ | ||||||
|  |         stringprep.in_table_c12, | ||||||
|  |         stringprep.in_table_c21, | ||||||
|  |         stringprep.in_table_c22, | ||||||
|  |         stringprep.in_table_c3, | ||||||
|  |         stringprep.in_table_c4, | ||||||
|  |         stringprep.in_table_c5, | ||||||
|  |         stringprep.in_table_c6, | ||||||
|  |         stringprep.in_table_c7, | ||||||
|  |         stringprep.in_table_c8, | ||||||
|  |         stringprep.in_table_c9], | ||||||
|  |     unassigned=[stringprep.in_table_a1]) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def _parse_jid(data): | ||||||
|  |     """ | ||||||
|  |     Parse string data into the node, domain, and resource | ||||||
|  |     components of a JID, if possible. | ||||||
|  |  | ||||||
|  |     :param string data: A string that is potentially a JID. | ||||||
|  |  | ||||||
|  |     :raises InvalidJID: | ||||||
|  |  | ||||||
|  |     :returns: tuple of the validated local, domain, and resource strings | ||||||
|  |     """ | ||||||
|  |     match = JID_PATTERN.match(data) | ||||||
|  |     if not match: | ||||||
|  |         raise InvalidJID('JID could not be parsed') | ||||||
|  |  | ||||||
|  |     (node, domain, resource) = match.groups() | ||||||
|  |  | ||||||
|  |     node = _validate_node(node) | ||||||
|  |     domain = _validate_domain(domain) | ||||||
|  |     resource = _validate_resource(resource) | ||||||
|  |  | ||||||
|  |     return node, domain, resource | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def _validate_node(node): | ||||||
|  |     """Validate the local, or username, portion of a JID. | ||||||
|  |  | ||||||
|  |     :raises InvalidJID: | ||||||
|  |  | ||||||
|  |     :returns: The local portion of a JID, as validated by nodeprep. | ||||||
|  |     """ | ||||||
|  |     try: | ||||||
|  |         if node is not None: | ||||||
|  |             node = nodeprep(node) | ||||||
|  |  | ||||||
|  |             if not node: | ||||||
|  |                 raise InvalidJID('Localpart must not be 0 bytes') | ||||||
|  |             if len(node) > 1023: | ||||||
|  |                 raise InvalidJID('Localpart must be less than 1024 bytes') | ||||||
|  |             return node | ||||||
|  |     except stringprep_profiles.StringPrepError: | ||||||
|  |         raise InvalidJID('Invalid local part') | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def _validate_domain(domain): | ||||||
|  |     """Validate the domain portion of a JID. | ||||||
|  |  | ||||||
|  |     IP literal addresses are left as-is, if valid. Domain names | ||||||
|  |     are stripped of any trailing label separators (`.`), and are | ||||||
|  |     checked with the nameprep profile of stringprep. If the given | ||||||
|  |     domain is actually a punyencoded version of a domain name, it | ||||||
|  |     is converted back into its original Unicode form. Domains must | ||||||
|  |     also not start or end with a dash (`-`). | ||||||
|  |  | ||||||
|  |     :raises InvalidJID: | ||||||
|  |  | ||||||
|  |     :returns: The validated domain name | ||||||
|  |     """ | ||||||
|  |     ip_addr = False | ||||||
|  |  | ||||||
|  |     # First, check if this is an IPv4 address | ||||||
|  |     try: | ||||||
|  |         socket.inet_aton(domain) | ||||||
|  |         ip_addr = True | ||||||
|  |     except socket.error: | ||||||
|  |         pass | ||||||
|  |  | ||||||
|  |     # Check if this is an IPv6 address | ||||||
|  |     if not ip_addr and hasattr(socket, 'inet_pton'): | ||||||
|  |         try: | ||||||
|  |             socket.inet_pton(socket.AF_INET6, domain.strip('[]')) | ||||||
|  |             domain = '[%s]' % domain.strip('[]') | ||||||
|  |             ip_addr = True | ||||||
|  |         except socket.error: | ||||||
|  |             pass | ||||||
|  |  | ||||||
|  |     if not ip_addr: | ||||||
|  |         # This is a domain name, which must be checked further | ||||||
|  |  | ||||||
|  |         if domain and domain[-1] == '.': | ||||||
|  |             domain = domain[:-1] | ||||||
|  |  | ||||||
|  |         domain_parts = [] | ||||||
|  |         for label in domain.split('.'): | ||||||
|  |             try: | ||||||
|  |                 label = encodings.idna.nameprep(label) | ||||||
|  |                 encodings.idna.ToASCII(label) | ||||||
|  |                 pass_nameprep = True | ||||||
|  |             except UnicodeError: | ||||||
|  |                 pass_nameprep = False | ||||||
|  |  | ||||||
|  |             if not pass_nameprep: | ||||||
|  |                 raise InvalidJID('Could not encode domain as ASCII') | ||||||
|  |  | ||||||
|  |             if label.startswith('xn--'): | ||||||
|  |                 label = encodings.idna.ToUnicode(label) | ||||||
|  |  | ||||||
|  |             for char in label: | ||||||
|  |                 if char in ILLEGAL_CHARS: | ||||||
|  |                     raise InvalidJID('Domain contains illegar characters') | ||||||
|  |  | ||||||
|  |             if '-' in (label[0], label[-1]): | ||||||
|  |                 raise InvalidJID('Domain started or ended with -') | ||||||
|  |  | ||||||
|  |             domain_parts.append(label) | ||||||
|  |         domain = '.'.join(domain_parts) | ||||||
|  |  | ||||||
|  |     if not domain: | ||||||
|  |         raise InvalidJID('Domain must not be 0 bytes') | ||||||
|  |     if len(domain) > 1023: | ||||||
|  |         raise InvalidJID('Domain must be less than 1024 bytes') | ||||||
|  |  | ||||||
|  |     return domain | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def _validate_resource(resource): | ||||||
|  |     """Validate the resource portion of a JID. | ||||||
|  |  | ||||||
|  |     :raises InvalidJID: | ||||||
|  |  | ||||||
|  |     :returns: The local portion of a JID, as validated by resourceprep. | ||||||
|  |     """ | ||||||
|  |     try: | ||||||
|  |         if resource is not None: | ||||||
|  |             resource = resourceprep(resource) | ||||||
|  |  | ||||||
|  |             if not resource: | ||||||
|  |                 raise InvalidJID('Resource must not be 0 bytes') | ||||||
|  |             if len(resource) > 1023: | ||||||
|  |                 raise InvalidJID('Resource must be less than 1024 bytes') | ||||||
|  |             return resource | ||||||
|  |     except stringprep_profiles.StringPrepError: | ||||||
|  |         raise InvalidJID('Invalid resource') | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def _escape_node(node): | ||||||
|  |     """Escape the local portion of a JID.""" | ||||||
|  |     result = [] | ||||||
|  |  | ||||||
|  |     for i, char in enumerate(node): | ||||||
|  |         if char == '\\': | ||||||
|  |             if ''.join((node[i:i+3])) in JID_ESCAPE_SEQUENCES: | ||||||
|  |                 result.append('\\5c') | ||||||
|  |                 continue | ||||||
|  |         result.append(char) | ||||||
|  |  | ||||||
|  |     for i, char in enumerate(result): | ||||||
|  |         if char != '\\': | ||||||
|  |             result[i] = JID_ESCAPE_TRANSFORMATIONS.get(char, char) | ||||||
|  |  | ||||||
|  |     escaped = ''.join(result) | ||||||
|  |  | ||||||
|  |     if escaped.startswith('\\20') or escaped.endswith('\\20'): | ||||||
|  |         raise InvalidJID('Escaped local part starts or ends with "\\20"') | ||||||
|  |  | ||||||
|  |     _validate_node(escaped) | ||||||
|  |  | ||||||
|  |     return escaped | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def _unescape_node(node): | ||||||
|  |     """Unescape a local portion of a JID. | ||||||
|  |  | ||||||
|  |     .. note:: | ||||||
|  |         The unescaped local portion is meant ONLY for presentation, | ||||||
|  |         and should not be used for other purposes. | ||||||
|  |     """ | ||||||
|  |     unescaped = [] | ||||||
|  |     seq = '' | ||||||
|  |     for i, char in enumerate(node): | ||||||
|  |         if char == '\\': | ||||||
|  |             seq = node[i:i+3] | ||||||
|  |             if seq not in JID_ESCAPE_SEQUENCES: | ||||||
|  |                 seq = '' | ||||||
|  |         if seq: | ||||||
|  |             if len(seq) == 3: | ||||||
|  |                 unescaped.append(JID_UNESCAPE_TRANSFORMATIONS.get(seq, char)) | ||||||
|  |  | ||||||
|  |             # Pop character off the escape sequence, and ignore it | ||||||
|  |             seq = seq[1:] | ||||||
|  |         else: | ||||||
|  |             unescaped.append(char) | ||||||
|  |     unescaped = ''.join(unescaped) | ||||||
|  |  | ||||||
|  |     return unescaped | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def _format_jid(local=None, domain=None, resource=None): | ||||||
|  |     """Format the given JID components into a full or bare JID. | ||||||
|  |  | ||||||
|  |     :param string local: Optional. The local portion of the JID. | ||||||
|  |     :param string domain: Required. The domain name portion of the JID. | ||||||
|  |     :param strin resource: Optional. The resource portion of the JID. | ||||||
|  |  | ||||||
|  |     :return: A full or bare JID string. | ||||||
|  |     """ | ||||||
|  |     result = [] | ||||||
|  |     if local: | ||||||
|  |         result.append(local) | ||||||
|  |         result.append('@') | ||||||
|  |     if domain: | ||||||
|  |         result.append(domain) | ||||||
|  |     if resource: | ||||||
|  |         result.append('/') | ||||||
|  |         result.append(resource) | ||||||
|  |     return ''.join(result) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class InvalidJID(ValueError): | ||||||
|  |     """ | ||||||
|  |     Raised when attempting to create a JID that does not pass validation. | ||||||
|  |  | ||||||
|  |     It can also be raised if modifying an existing JID in such a way as | ||||||
|  |     to make it invalid, such trying to remove the domain from an existing | ||||||
|  |     full JID while the local and resource portions still exist. | ||||||
|  |     """ | ||||||
|  |  | ||||||
|  | # pylint: disable=R0903 | ||||||
|  | class UnescapedJID(object): | ||||||
|  |  | ||||||
|  |     """ | ||||||
|  |     .. versionadded:: 1.1.10 | ||||||
|  |     """ | ||||||
|  |  | ||||||
|  |     def __init__(self, local, domain, resource): | ||||||
|  |         self._jid = (local, domain, resource) | ||||||
|  |  | ||||||
|  |     # pylint: disable=R0911 | ||||||
|  |     def __getattr__(self, name): | ||||||
|  |         """Retrieve the given JID component. | ||||||
|  |  | ||||||
|  |         :param name: one of: user, server, domain, resource, | ||||||
|  |                      full, or bare. | ||||||
|  |         """ | ||||||
|  |         if name == 'resource': | ||||||
|  |             return self._jid[2] or '' | ||||||
|  |         elif name in ('user', 'username', 'local', 'node'): | ||||||
|  |             return self._jid[0] or '' | ||||||
|  |         elif name in ('server', 'domain', 'host'): | ||||||
|  |             return self._jid[1] or '' | ||||||
|  |         elif name in ('full', 'jid'): | ||||||
|  |             return _format_jid(*self._jid) | ||||||
|  |         elif name == 'bare': | ||||||
|  |             return _format_jid(self._jid[0], self._jid[1]) | ||||||
|  |         elif name == '_jid': | ||||||
|  |             return getattr(super(JID, self), '_jid') | ||||||
|  |         else: | ||||||
|  |             return None | ||||||
|  |  | ||||||
|  |     def __str__(self): | ||||||
|  |         """Use the full JID as the string value.""" | ||||||
|  |         return _format_jid(*self._jid) | ||||||
|  |  | ||||||
|  |     def __repr__(self): | ||||||
|  |         """Use the full JID as the representation.""" | ||||||
|  |         return self.__str__() | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class JID(object): | ||||||
|  |  | ||||||
|  |     """ | ||||||
|  |     A representation of a Jabber ID, or JID. | ||||||
|  |  | ||||||
|  |     Each JID may have three components: a user, a domain, and an optional | ||||||
|  |     resource. For example: user@domain/resource | ||||||
|  |  | ||||||
|  |     When a resource is not used, the JID is called a bare JID. | ||||||
|  |     The JID is a full JID otherwise. | ||||||
|  |  | ||||||
|  |     **JID Properties:** | ||||||
|  |         :jid: Alias for ``full``. | ||||||
|  |         :full: The string value of the full JID. | ||||||
|  |         :bare: The string value of the bare JID. | ||||||
|  |         :user: The username portion of the JID. | ||||||
|  |         :username: Alias for ``user``. | ||||||
|  |         :local: Alias for ``user``. | ||||||
|  |         :node: Alias for ``user``. | ||||||
|  |         :domain: The domain name portion of the JID. | ||||||
|  |         :server: Alias for ``domain``. | ||||||
|  |         :host: Alias for ``domain``. | ||||||
|  |         :resource: The resource portion of the JID. | ||||||
|  |  | ||||||
|  |     :param string jid: | ||||||
|  |         A string of the form ``'[user@]domain[/resource]'``. | ||||||
|  |     :param string local: | ||||||
|  |         Optional. Specify the local, or username, portion | ||||||
|  |         of the JID. If provided, it will override the local | ||||||
|  |         value provided by the `jid` parameter. The given | ||||||
|  |         local value will also be escaped if necessary. | ||||||
|  |     :param string domain: | ||||||
|  |         Optional. Specify the domain of the JID. If | ||||||
|  |         provided, it will override the domain given by | ||||||
|  |         the `jid` parameter. | ||||||
|  |     :param string resource: | ||||||
|  |         Optional. Specify the resource value of the JID. | ||||||
|  |         If provided, it will override the domain given | ||||||
|  |         by the `jid` parameter. | ||||||
|  |  | ||||||
|  |     :raises InvalidJID: | ||||||
|  |     """ | ||||||
|  |  | ||||||
|  |     # pylint: disable=W0212 | ||||||
|  |     def __init__(self, jid=None, **kwargs): | ||||||
|  |         self._jid = (None, None, None) | ||||||
|  |  | ||||||
|  |         if jid is None or jid == '': | ||||||
|  |             jid = (None, None, None) | ||||||
|  |         elif not isinstance(jid, JID): | ||||||
|  |             jid = _parse_jid(jid) | ||||||
|  |         else: | ||||||
|  |             jid = jid._jid | ||||||
|  |  | ||||||
|  |         local, domain, resource = jid | ||||||
|  |  | ||||||
|  |         local = kwargs.get('local', local) | ||||||
|  |         domain = kwargs.get('domain', domain) | ||||||
|  |         resource = kwargs.get('resource', resource) | ||||||
|  |  | ||||||
|  |         if 'local' in kwargs: | ||||||
|  |             local = _escape_node(local) | ||||||
|  |         if 'domain' in kwargs: | ||||||
|  |             domain = _validate_domain(domain) | ||||||
|  |         if 'resource' in kwargs: | ||||||
|  |             resource = _validate_resource(resource) | ||||||
|  |  | ||||||
|  |         self._jid = (local, domain, resource) | ||||||
|  |  | ||||||
|  |     def unescape(self): | ||||||
|  |         """Return an unescaped JID object. | ||||||
|  |  | ||||||
|  |         Using an unescaped JID is preferred for displaying JIDs | ||||||
|  |         to humans, and they should NOT be used for any other | ||||||
|  |         purposes than for presentation. | ||||||
|  |  | ||||||
|  |         :return: :class:`UnescapedJID` | ||||||
|  |  | ||||||
|  |         .. versionadded:: 1.1.10 | ||||||
|  |         """ | ||||||
|  |         return UnescapedJID(_unescape_node(self._jid[0]), | ||||||
|  |                             self._jid[1], | ||||||
|  |                             self._jid[2]) | ||||||
|  |  | ||||||
|  |     def regenerate(self): | ||||||
|  |         """No-op | ||||||
|  |  | ||||||
|  |         .. deprecated:: 1.1.10 | ||||||
|  |         """ | ||||||
|  |         pass | ||||||
|  |  | ||||||
|  |     def reset(self, data): | ||||||
|  |         """Start fresh from a new JID string. | ||||||
|  |  | ||||||
|  |         :param string data: A string of the form ``'[user@]domain[/resource]'``. | ||||||
|  |  | ||||||
|  |         .. deprecated:: 1.1.10 | ||||||
|  |         """ | ||||||
|  |         self._jid = JID(data)._jid | ||||||
|  |  | ||||||
|  |     # pylint: disable=R0911 | ||||||
|  |     def __getattr__(self, name): | ||||||
|  |         """Retrieve the given JID component. | ||||||
|  |  | ||||||
|  |         :param name: one of: user, server, domain, resource, | ||||||
|  |                      full, or bare. | ||||||
|  |         """ | ||||||
|  |         if name == 'resource': | ||||||
|  |             return self._jid[2] or '' | ||||||
|  |         elif name in ('user', 'username', 'local', 'node'): | ||||||
|  |             return self._jid[0] or '' | ||||||
|  |         elif name in ('server', 'domain', 'host'): | ||||||
|  |             return self._jid[1] or '' | ||||||
|  |         elif name in ('full', 'jid'): | ||||||
|  |             return _format_jid(*self._jid) | ||||||
|  |         elif name == 'bare': | ||||||
|  |             return _format_jid(self._jid[0], self._jid[1]) | ||||||
|  |         elif name == '_jid': | ||||||
|  |             return getattr(super(JID, self), '_jid') | ||||||
|  |         else: | ||||||
|  |             return None | ||||||
|  |  | ||||||
|  |     # pylint: disable=W0212 | ||||||
|  |     def __setattr__(self, name, value): | ||||||
|  |         """Update the given JID component. | ||||||
|  |  | ||||||
|  |         :param name: one of: ``user``, ``username``, ``local``, | ||||||
|  |                              ``node``, ``server``, ``domain``, ``host``, | ||||||
|  |                              ``resource``, ``full``, ``jid``, or ``bare``. | ||||||
|  |         :param value: The new string value of the JID component. | ||||||
|  |         """ | ||||||
|  |         if name == 'resource': | ||||||
|  |             self._jid = JID(self, resource=value)._jid | ||||||
|  |         elif name in ('user', 'username', 'local', 'node'): | ||||||
|  |             self._jid = JID(self, local=value)._jid | ||||||
|  |         elif name in ('server', 'domain', 'host'): | ||||||
|  |             self._jid = JID(self, domain=value)._jid | ||||||
|  |         elif name in ('full', 'jid'): | ||||||
|  |             self._jid = JID(value)._jid | ||||||
|  |         elif name == 'bare': | ||||||
|  |             parsed = JID(value)._jid | ||||||
|  |             self._jid = (parsed[0], parsed[1], self._jid[2]) | ||||||
|  |         elif name == '_jid': | ||||||
|  |             super(JID, self).__setattr__('_jid', value) | ||||||
|  |  | ||||||
|  |     def __str__(self): | ||||||
|  |         """Use the full JID as the string value.""" | ||||||
|  |         return _format_jid(*self._jid) | ||||||
|  |  | ||||||
|  |     def __repr__(self): | ||||||
|  |         """Use the full JID as the representation.""" | ||||||
|  |         return self.__str__() | ||||||
|  |  | ||||||
|  |     # pylint: disable=W0212 | ||||||
|  |     def __eq__(self, other): | ||||||
|  |         """Two JIDs are equal if they have the same full JID value.""" | ||||||
|  |         if isinstance(other, UnescapedJID): | ||||||
|  |             return False | ||||||
|  |  | ||||||
|  |         other = JID(other) | ||||||
|  |         return self._jid == other._jid | ||||||
|  |  | ||||||
|  |     # pylint: disable=W0212 | ||||||
|  |     def __ne__(self, other): | ||||||
|  |         """Two JIDs are considered unequal if they are not equal.""" | ||||||
|  |         return not self == other | ||||||
|  |  | ||||||
|  |     def __hash__(self): | ||||||
|  |         """Hash a JID based on the string version of its full JID.""" | ||||||
|  |         return hash(self.__str__()) | ||||||
|  |  | ||||||
|  |     def __copy__(self): | ||||||
|  |         """Generate a duplicate JID.""" | ||||||
|  |         return JID(self) | ||||||
| @@ -32,23 +32,36 @@ __all__ = [ | |||||||
| #   'xep_0078',  # Non-SASL auth. Don't automatically load | #   'xep_0078',  # Non-SASL auth. Don't automatically load | ||||||
|     'xep_0080',  # User Location |     'xep_0080',  # User Location | ||||||
|     'xep_0082',  # XMPP Date and Time Profiles |     'xep_0082',  # XMPP Date and Time Profiles | ||||||
|  |     'xep_0084',  # User Avatar | ||||||
|     'xep_0085',  # Chat State Notifications |     'xep_0085',  # Chat State Notifications | ||||||
|     'xep_0086',  # Legacy Error Codes |     'xep_0086',  # Legacy Error Codes | ||||||
|     'xep_0092',  # Software Version |     'xep_0092',  # Software Version | ||||||
|  |     'xep_0106',  # JID Escaping | ||||||
|     'xep_0107',  # User Mood |     'xep_0107',  # User Mood | ||||||
|     'xep_0108',  # User Activity |     'xep_0108',  # User Activity | ||||||
|     'xep_0115',  # Entity Capabilities |     'xep_0115',  # Entity Capabilities | ||||||
|     'xep_0118',  # User Tune |     'xep_0118',  # User Tune | ||||||
|     'xep_0128',  # Extended Service Discovery |     'xep_0128',  # Extended Service Discovery | ||||||
|  |     'xep_0131',  # Standard Headers and Internet Metadata | ||||||
|  |     'xep_0133',  # Service Administration | ||||||
|     'xep_0153',  # vCard-Based Avatars |     'xep_0153',  # vCard-Based Avatars | ||||||
|     'xep_0163',  # Personal Eventing Protocol |     'xep_0163',  # Personal Eventing Protocol | ||||||
|     'xep_0172',  # User Nickname |     'xep_0172',  # User Nickname | ||||||
|     'xep_0184',  # Message Receipts |     'xep_0184',  # Message Receipts | ||||||
|  |     'xep_0186',  # Invisible Command | ||||||
|  |     'xep_0191',  # Blocking Command | ||||||
|     'xep_0198',  # Stream Management |     'xep_0198',  # Stream Management | ||||||
|     'xep_0199',  # Ping |     'xep_0199',  # Ping | ||||||
|     'xep_0202',  # Entity Time |     'xep_0202',  # Entity Time | ||||||
|     'xep_0203',  # Delayed Delivery |     '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_0224',  # Attention | ||||||
|     'xep_0231',  # Bits of Binary |     'xep_0231',  # Bits of Binary | ||||||
|     'xep_0249',  # Direct MUC Invitations |     'xep_0249',  # Direct MUC Invitations | ||||||
|  |     'xep_0256',  # Last Activity in Presence | ||||||
|  |     'xep_0258',  # Security Labels in XMPP | ||||||
|  |     'xep_0270',  # XMPP Compliance Suites 2010 | ||||||
|  |     'xep_0302',  # XMPP Compliance Suites 2012 | ||||||
| ] | ] | ||||||
|   | |||||||
| @@ -14,6 +14,7 @@ | |||||||
| """ | """ | ||||||
|  |  | ||||||
| import sys | import sys | ||||||
|  | import copy | ||||||
| import logging | import logging | ||||||
| import threading | import threading | ||||||
|  |  | ||||||
| @@ -31,10 +32,10 @@ log = logging.getLogger(__name__) | |||||||
| PLUGIN_REGISTRY = {} | PLUGIN_REGISTRY = {} | ||||||
|  |  | ||||||
| #: In order to do cascading plugin disabling, reverse dependencies | #: In order to do cascading plugin disabling, reverse dependencies | ||||||
| #: must be tracked.  | #: must be tracked. | ||||||
| PLUGIN_DEPENDENTS = {} | 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() | REGISTRY_LOCK = threading.RLock() | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -75,7 +76,7 @@ def load_plugin(name, module=None): | |||||||
|                      plugins are in packages matching their name, |                      plugins are in packages matching their name, | ||||||
|                      even though the plugin class name does not |                      even though the plugin class name does not | ||||||
|                      have to match. |                      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. |                        for the plugin. | ||||||
|     """ |     """ | ||||||
|     try: |     try: | ||||||
| @@ -84,7 +85,7 @@ def load_plugin(name, module=None): | |||||||
|                 module = 'sleekxmpp.plugins.%s' % name |                 module = 'sleekxmpp.plugins.%s' % name | ||||||
|                 __import__(module) |                 __import__(module) | ||||||
|                 mod = sys.modules[module] |                 mod = sys.modules[module] | ||||||
|             except: |             except ImportError: | ||||||
|                 module = 'sleekxmpp.features.%s' % name |                 module = 'sleekxmpp.features.%s' % name | ||||||
|                 __import__(module) |                 __import__(module) | ||||||
|                 mod = sys.modules[module] |                 mod = sys.modules[module] | ||||||
| @@ -103,11 +104,11 @@ def load_plugin(name, module=None): | |||||||
|                 # we can work around dependency issues. |                 # we can work around dependency issues. | ||||||
|                 plugin.old_style = True |                 plugin.old_style = True | ||||||
|             register_plugin(plugin, name) |             register_plugin(plugin, name) | ||||||
|     except: |     except ImportError: | ||||||
|         log.exception("Unable to load plugin: %s", name) |         log.exception("Unable to load plugin: %s", name) | ||||||
|  |  | ||||||
|  |  | ||||||
| class PluginManager(object):  | class PluginManager(object): | ||||||
|     def __init__(self, xmpp, config=None): |     def __init__(self, xmpp, config=None): | ||||||
|         #: We will track all enabled plugins in a set so that we |         #: We will track all enabled plugins in a set so that we | ||||||
|         #: can enable plugins in batches and pull in dependencies |         #: can enable plugins in batches and pull in dependencies | ||||||
| @@ -167,8 +168,7 @@ class PluginManager(object): | |||||||
|                 self._plugins[name] = plugin |                 self._plugins[name] = plugin | ||||||
|                 for dep in plugin.dependencies: |                 for dep in plugin.dependencies: | ||||||
|                     self.enable(dep, enabled=enabled) |                     self.enable(dep, enabled=enabled) | ||||||
|                 plugin.plugin_init() |                 plugin._init() | ||||||
|                 log.debug("Loaded Plugin: %s", plugin.description) |  | ||||||
|  |  | ||||||
|         if top_level: |         if top_level: | ||||||
|             for name in enabled: |             for name in enabled: | ||||||
| @@ -181,7 +181,7 @@ class PluginManager(object): | |||||||
|  |  | ||||||
|     def enable_all(self, names=None, config=None): |     def enable_all(self, names=None, config=None): | ||||||
|         """Enable all registered plugins. |         """Enable all registered plugins. | ||||||
|          |  | ||||||
|         :param list names: A list of plugin names to enable. If |         :param list names: A list of plugin names to enable. If | ||||||
|                            none are provided, all registered plugins |                            none are provided, all registered plugins | ||||||
|                            will be enabled. |                            will be enabled. | ||||||
| @@ -229,7 +229,7 @@ class PluginManager(object): | |||||||
|                     raise PluginNotFound(name) |                     raise PluginNotFound(name) | ||||||
|                 for dep in PLUGIN_DEPENDENTS[name]: |                 for dep in PLUGIN_DEPENDENTS[name]: | ||||||
|                     self.disable(dep, _disabled) |                     self.disable(dep, _disabled) | ||||||
|                 plugin.plugin_end() |                 plugin._end() | ||||||
|                 if name in self._enabled: |                 if name in self._enabled: | ||||||
|                     self._enabled.remove(name) |                     self._enabled.remove(name) | ||||||
|                 del self._plugins[name] |                 del self._plugins[name] | ||||||
| @@ -273,6 +273,14 @@ class BasePlugin(object): | |||||||
|     #: be initialized as needed if this plugin is enabled. |     #: be initialized as needed if this plugin is enabled. | ||||||
|     dependencies = set() |     dependencies = set() | ||||||
|  |  | ||||||
|  |     #: The basic, standard configuration for the plugin, which may | ||||||
|  |     #: be overridden when initializing the plugin. The configuration | ||||||
|  |     #: fields included here may be accessed directly as attributes of | ||||||
|  |     #: the plugin. For example, including the configuration field 'foo' | ||||||
|  |     #: would mean accessing `plugin.foo` returns the current value of | ||||||
|  |     #: `plugin.config['foo']`. | ||||||
|  |     default_config = {} | ||||||
|  |  | ||||||
|     def __init__(self, xmpp, config=None): |     def __init__(self, xmpp, config=None): | ||||||
|         self.xmpp = xmpp |         self.xmpp = xmpp | ||||||
|         if self.xmpp: |         if self.xmpp: | ||||||
| @@ -280,7 +288,54 @@ class BasePlugin(object): | |||||||
|  |  | ||||||
|         #: A plugin's behaviour may be configurable, in which case those |         #: A plugin's behaviour may be configurable, in which case those | ||||||
|         #: configuration settings will be provided as a dictionary. |         #: configuration settings will be provided as a dictionary. | ||||||
|         self.config = config if config is not None else {} |         self.config = copy.copy(self.default_config) | ||||||
|  |         if config: | ||||||
|  |             self.config.update(config) | ||||||
|  |  | ||||||
|  |     def __getattr__(self, key): | ||||||
|  |         """Provide direct access to configuration fields. | ||||||
|  |  | ||||||
|  |         If the standard configuration includes the option `'foo'`, then | ||||||
|  |         accessing `self.foo` should be the same as `self.config['foo']`. | ||||||
|  |         """ | ||||||
|  |         if key in self.default_config: | ||||||
|  |             return self.config.get(key, None) | ||||||
|  |         else: | ||||||
|  |             return object.__getattribute__(self, key) | ||||||
|  |  | ||||||
|  |     def __setattr__(self, key, value): | ||||||
|  |         """Provide direct assignment to configuration fields. | ||||||
|  |  | ||||||
|  |         If the standard configuration includes the option `'foo'`, then | ||||||
|  |         assigning to `self.foo` should be the same as assigning to | ||||||
|  |         `self.config['foo']`. | ||||||
|  |         """ | ||||||
|  |         if key in self.default_config: | ||||||
|  |             self.config[key] = value | ||||||
|  |         else: | ||||||
|  |             super(BasePlugin, self).__setattr__(key, value) | ||||||
|  |  | ||||||
|  |     def _init(self): | ||||||
|  |         """Initialize plugin state, such as registering event handlers. | ||||||
|  |  | ||||||
|  |         Also sets up required event handlers. | ||||||
|  |         """ | ||||||
|  |         if self.xmpp is not None: | ||||||
|  |             self.xmpp.add_event_handler('session_bind', self.session_bind) | ||||||
|  |             if self.xmpp.session_bind_event.is_set(): | ||||||
|  |                 self.session_bind(self.xmpp.boundjid.full) | ||||||
|  |         self.plugin_init() | ||||||
|  |         log.debug('Loaded Plugin: %s', self.description) | ||||||
|  |  | ||||||
|  |     def _end(self): | ||||||
|  |         """Cleanup plugin state, and prepare for plugin removal. | ||||||
|  |  | ||||||
|  |         Also removes required event handlers. | ||||||
|  |         """ | ||||||
|  |         if self.xmpp is not None: | ||||||
|  |             self.xmpp.del_event_handler('session_bind', self.session_bind) | ||||||
|  |         self.plugin_end() | ||||||
|  |         log.debug('Disabled Plugin: %s' % self.description) | ||||||
|  |  | ||||||
|     def plugin_init(self): |     def plugin_init(self): | ||||||
|         """Initialize plugin state, such as registering event handlers.""" |         """Initialize plugin state, such as registering event handlers.""" | ||||||
| @@ -290,9 +345,13 @@ class BasePlugin(object): | |||||||
|         """Cleanup plugin state, and prepare for plugin removal.""" |         """Cleanup plugin state, and prepare for plugin removal.""" | ||||||
|         pass |         pass | ||||||
|  |  | ||||||
|  |     def session_bind(self, jid): | ||||||
|  |         """Initialize plugin state based on the bound JID.""" | ||||||
|  |         pass | ||||||
|  |  | ||||||
|     def post_init(self): |     def post_init(self): | ||||||
|         """Initialize any cross-plugin state. |         """Initialize any cross-plugin state. | ||||||
|         |  | ||||||
|         Only needed if the plugin has circular dependencies. |         Only needed if the plugin has circular dependencies. | ||||||
|         """ |         """ | ||||||
|         pass |         pass | ||||||
|   | |||||||
| @@ -27,7 +27,7 @@ class XEP_0004(BasePlugin): | |||||||
|     stanza = stanza |     stanza = stanza | ||||||
|  |  | ||||||
|     def plugin_init(self): |     def plugin_init(self): | ||||||
|         self.xmpp.registerHandler( |         self.xmpp.register_handler( | ||||||
|             Callback('Data Form', |             Callback('Data Form', | ||||||
|                  StanzaPath('message/form'), |                  StanzaPath('message/form'), | ||||||
|                  self.handle_form)) |                  self.handle_form)) | ||||||
| @@ -36,6 +36,11 @@ class XEP_0004(BasePlugin): | |||||||
|         register_stanza_plugin(Form, FormField, iterable=True) |         register_stanza_plugin(Form, FormField, iterable=True) | ||||||
|         register_stanza_plugin(Message, Form) |         register_stanza_plugin(Message, Form) | ||||||
|  |  | ||||||
|  |     def plugin_end(self): | ||||||
|  |         self.xmpp.remove_handler('Data Form') | ||||||
|  |         self.xmpp['xep_0030'].del_feature(feature='jabber:x:data') | ||||||
|  |  | ||||||
|  |     def session_bind(self, jid): | ||||||
|         self.xmpp['xep_0030'].add_feature('jabber:x:data') |         self.xmpp['xep_0030'].add_feature('jabber:x:data') | ||||||
|  |  | ||||||
|     def make_form(self, ftype='form', title='', instructions=''): |     def make_form(self, ftype='form', title='', instructions=''): | ||||||
|   | |||||||
| @@ -37,13 +37,11 @@ class XEP_0012(BasePlugin): | |||||||
|  |  | ||||||
|         self._last_activities = {} |         self._last_activities = {} | ||||||
|  |  | ||||||
|         self.xmpp.registerHandler( |         self.xmpp.register_handler( | ||||||
|             Callback('Last Activity', |             Callback('Last Activity', | ||||||
|                  StanzaPath('iq@type=get/last_activity'), |                  StanzaPath('iq@type=get/last_activity'), | ||||||
|                  self._handle_get_last_activity)) |                  self._handle_get_last_activity)) | ||||||
|  |  | ||||||
|         self.xmpp.plugin['xep_0030'].add_feature('jabber:iq:last') |  | ||||||
|  |  | ||||||
|         self.api.register(self._default_get_last_activity, |         self.api.register(self._default_get_last_activity, | ||||||
|                 'get_last_activity', |                 'get_last_activity', | ||||||
|                 default=True) |                 default=True) | ||||||
| @@ -54,6 +52,13 @@ class XEP_0012(BasePlugin): | |||||||
|                 'del_last_activity', |                 'del_last_activity', | ||||||
|                 default=True) |                 default=True) | ||||||
|  |  | ||||||
|  |     def plugin_end(self): | ||||||
|  |         self.xmpp.remove_handler('Last Activity') | ||||||
|  |         self.xmpp['xep_0030'].del_feature(feature='jabber:iq:last') | ||||||
|  |  | ||||||
|  |     def session_bind(self, jid): | ||||||
|  |         self.xmpp['xep_0030'].add_feature('jabber:iq:last') | ||||||
|  |  | ||||||
|     def begin_idle(self, jid=None, status=None): |     def begin_idle(self, jid=None, status=None): | ||||||
|         self.set_last_activity(jid, 0, status) |         self.set_last_activity(jid, 0, status) | ||||||
|  |  | ||||||
|   | |||||||
| @@ -24,7 +24,7 @@ def _extract_data(data, kind): | |||||||
|         if not begin_headers and 'BEGIN PGP %s' % kind in line: |         if not begin_headers and 'BEGIN PGP %s' % kind in line: | ||||||
|             begin_headers = True |             begin_headers = True | ||||||
|             continue |             continue | ||||||
|         if begin_headers and line == '': |         if begin_headers and line.stripped() == '': | ||||||
|             begin_data = True |             begin_data = True | ||||||
|             continue |             continue | ||||||
|         if 'END PGP %s' % kind in line: |         if 'END PGP %s' % kind in line: | ||||||
| @@ -40,14 +40,15 @@ class XEP_0027(BasePlugin): | |||||||
|     description = 'XEP-0027: Current Jabber OpenPGP Usage' |     description = 'XEP-0027: Current Jabber OpenPGP Usage' | ||||||
|     dependencies = set() |     dependencies = set() | ||||||
|     stanza = stanza |     stanza = stanza | ||||||
|  |     default_config = { | ||||||
|  |         'gpg_binary': 'gpg', | ||||||
|  |         'gpg_home': '', | ||||||
|  |         'use_agent': True, | ||||||
|  |         'keyring': None, | ||||||
|  |         'key_server': 'pgp.mit.edu' | ||||||
|  |     } | ||||||
|  |  | ||||||
|     def plugin_init(self): |     def plugin_init(self): | ||||||
|         self.gpg_binary = self.config.get('gpg_binary', 'gpg') |  | ||||||
|         self.gpg_home = self.config.get('gpg_home', '') |  | ||||||
|         self.use_agent = self.config.get('use_agent', True) |  | ||||||
|         self.keyring = self.config.get('keyring', None) |  | ||||||
|         self.key_server = self.config.get('key_server', 'pgp.mit.edu') |  | ||||||
|  |  | ||||||
|         self.gpg = GPG(gnupghome=self.gpg_home, |         self.gpg = GPG(gnupghome=self.gpg_home, | ||||||
|                        gpgbinary=self.gpg_binary, |                        gpgbinary=self.gpg_binary, | ||||||
|                        use_agent=self.use_agent, |                        use_agent=self.use_agent, | ||||||
| @@ -79,9 +80,17 @@ class XEP_0027(BasePlugin): | |||||||
|                     StanzaPath('message/encrypted'), |                     StanzaPath('message/encrypted'), | ||||||
|                     self._handle_encrypted_message)) |                     self._handle_encrypted_message)) | ||||||
|  |  | ||||||
|  |     def plugin_end(self): | ||||||
|  |         self.xmpp.remove_handler('Encrypted Message') | ||||||
|  |         self.xmpp.remove_handler('Signed Presence') | ||||||
|  |         self.xmpp.del_filter('out', self._sign_presence) | ||||||
|  |         self.xmpp.del_event_handler('unverified_signed_presence', | ||||||
|  |                 self._handle_unverified_signed_presence) | ||||||
|  |  | ||||||
|     def _sign_presence(self, stanza): |     def _sign_presence(self, stanza): | ||||||
|         if isinstance(stanza, Presence): |         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'] |                 stanza['signed'] = stanza['status'] | ||||||
|         return stanza |         return stanza | ||||||
|  |  | ||||||
|   | |||||||
| @@ -51,6 +51,3 @@ class Encrypted(ElementBase): | |||||||
|         if self.xml.text: |         if self.xml.text: | ||||||
|             return xmpp['xep_0027'].decrypt(self.xml.text, parent['to']) |             return xmpp['xep_0027'].decrypt(self.xml.text, parent['to']) | ||||||
|         return None |         return None | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|   | |||||||
| @@ -88,6 +88,10 @@ class XEP_0030(BasePlugin): | |||||||
|     description = 'XEP-0030: Service Discovery' |     description = 'XEP-0030: Service Discovery' | ||||||
|     dependencies = set() |     dependencies = set() | ||||||
|     stanza = stanza |     stanza = stanza | ||||||
|  |     default_config = { | ||||||
|  |         'use_cache': True, | ||||||
|  |         'wrap_results': False | ||||||
|  |     } | ||||||
|  |  | ||||||
|     def plugin_init(self): |     def plugin_init(self): | ||||||
|         """ |         """ | ||||||
| @@ -108,9 +112,6 @@ class XEP_0030(BasePlugin): | |||||||
|  |  | ||||||
|         self.static = StaticDisco(self.xmpp, self) |         self.static = StaticDisco(self.xmpp, self) | ||||||
|  |  | ||||||
|         self.use_cache = self.config.get('use_cache', True) |  | ||||||
|         self.wrap_results = self.config.get('wrap_results', False) |  | ||||||
|  |  | ||||||
|         self._disco_ops = [ |         self._disco_ops = [ | ||||||
|                 'get_info', 'set_info', 'set_identities', 'set_features', |                 'get_info', 'set_info', 'set_identities', 'set_features', | ||||||
|                 'get_items', 'set_items', 'del_items', 'add_identity', |                 'get_items', 'set_items', 'del_items', 'add_identity', | ||||||
| @@ -339,8 +340,8 @@ class XEP_0030(BasePlugin): | |||||||
|         if local: |         if local: | ||||||
|             log.debug("Looking up local disco#info data " + \ |             log.debug("Looking up local disco#info data " + \ | ||||||
|                       "for %s, node %s.", jid, node) |                       "for %s, node %s.", jid, node) | ||||||
|             info = self.api['get_info'](jid, node,  |             info = self.api['get_info'](jid, node, | ||||||
|                     kwargs.get('ifrom', None),  |                     kwargs.get('ifrom', None), | ||||||
|                     kwargs) |                     kwargs) | ||||||
|             info = self._fix_default_info(info) |             info = self._fix_default_info(info) | ||||||
|             return self._wrap(kwargs.get('ifrom', None), jid, info) |             return self._wrap(kwargs.get('ifrom', None), jid, info) | ||||||
| @@ -348,8 +349,8 @@ class XEP_0030(BasePlugin): | |||||||
|         if cached: |         if cached: | ||||||
|             log.debug("Looking up cached disco#info data " + \ |             log.debug("Looking up cached disco#info data " + \ | ||||||
|                       "for %s, node %s.", jid, node) |                       "for %s, node %s.", jid, node) | ||||||
|             info = self.api['get_cached_info'](jid, node,  |             info = self.api['get_cached_info'](jid, node, | ||||||
|                     kwargs.get('ifrom', None),  |                     kwargs.get('ifrom', None), | ||||||
|                     kwargs) |                     kwargs) | ||||||
|             if info is not None: |             if info is not None: | ||||||
|                 return self._wrap(kwargs.get('ifrom', None), jid, info) |                 return self._wrap(kwargs.get('ifrom', None), jid, info) | ||||||
| @@ -405,8 +406,8 @@ class XEP_0030(BasePlugin): | |||||||
|                         Otherwise the parameter is ignored. |                         Otherwise the parameter is ignored. | ||||||
|         """ |         """ | ||||||
|         if local or jid is None: |         if local or jid is None: | ||||||
|             items = self.api['get_items'](jid, node,  |             items = self.api['get_items'](jid, node, | ||||||
|                     kwargs.get('ifrom', None),  |                     kwargs.get('ifrom', None), | ||||||
|                     kwargs) |                     kwargs) | ||||||
|             return self._wrap(kwargs.get('ifrom', None), jid, items) |             return self._wrap(kwargs.get('ifrom', None), jid, items) | ||||||
|  |  | ||||||
| @@ -622,11 +623,7 @@ class XEP_0030(BasePlugin): | |||||||
|         if iq['type'] == 'get': |         if iq['type'] == 'get': | ||||||
|             log.debug("Received disco info query from " + \ |             log.debug("Received disco info query from " + \ | ||||||
|                       "<%s> to <%s>.", iq['from'], iq['to']) |                       "<%s> to <%s>.", iq['from'], iq['to']) | ||||||
|             if self.xmpp.is_component: |             info = self.api['get_info'](iq['to'], | ||||||
|                 jid = iq['to'].full |  | ||||||
|             else: |  | ||||||
|                 jid = iq['to'].bare |  | ||||||
|             info = self.api['get_info'](jid, |  | ||||||
|                                         iq['disco_info']['node'], |                                         iq['disco_info']['node'], | ||||||
|                                         iq['from'], |                                         iq['from'], | ||||||
|                                         iq) |                                         iq) | ||||||
| @@ -649,7 +646,7 @@ class XEP_0030(BasePlugin): | |||||||
|                     ito = iq['to'].full |                     ito = iq['to'].full | ||||||
|                 else: |                 else: | ||||||
|                     ito = None |                     ito = None | ||||||
|                 self.api['cache_info'](iq['from'].full, |                 self.api['cache_info'](iq['from'], | ||||||
|                                        iq['disco_info']['node'], |                                        iq['disco_info']['node'], | ||||||
|                                        ito, |                                        ito, | ||||||
|                                        iq) |                                        iq) | ||||||
| @@ -667,13 +664,9 @@ class XEP_0030(BasePlugin): | |||||||
|         if iq['type'] == 'get': |         if iq['type'] == 'get': | ||||||
|             log.debug("Received disco items query from " + \ |             log.debug("Received disco items query from " + \ | ||||||
|                       "<%s> to <%s>.", iq['from'], iq['to']) |                       "<%s> to <%s>.", iq['from'], iq['to']) | ||||||
|             if self.xmpp.is_component: |             items = self.api['get_items'](iq['to'], | ||||||
|                 jid = iq['to'].full |  | ||||||
|             else: |  | ||||||
|                 jid = iq['to'].bare |  | ||||||
|             items = self.api['get_items'](jid, |  | ||||||
|                                           iq['disco_items']['node'], |                                           iq['disco_items']['node'], | ||||||
|                                           iq['from'].full, |                                           iq['from'], | ||||||
|                                           iq) |                                           iq) | ||||||
|             if isinstance(items, Iq): |             if isinstance(items, Iq): | ||||||
|                 items.send() |                 items.send() | ||||||
|   | |||||||
| @@ -237,7 +237,7 @@ class StaticDisco(object): | |||||||
|         with self.lock: |         with self.lock: | ||||||
|             if not self.node_exists(jid, node): |             if not self.node_exists(jid, node): | ||||||
|                 if not node: |                 if not node: | ||||||
|                     return DiscoInfo() |                     return DiscoItems() | ||||||
|                 else: |                 else: | ||||||
|                     raise XMPPError(condition='item-not-found') |                     raise XMPPError(condition='item-not-found') | ||||||
|             else: |             else: | ||||||
| @@ -424,9 +424,6 @@ class StaticDisco(object): | |||||||
|         The data parameter is not used. |         The data parameter is not used. | ||||||
|         """ |         """ | ||||||
|         with self.lock: |         with self.lock: | ||||||
|             if isinstance(jid, JID): |  | ||||||
|                 jid = jid.full |  | ||||||
|  |  | ||||||
|             if not self.node_exists(jid, node, ifrom): |             if not self.node_exists(jid, node, ifrom): | ||||||
|                 return None |                 return None | ||||||
|             else: |             else: | ||||||
|   | |||||||
| @@ -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 | ||||||
							
								
								
									
										37
									
								
								sleekxmpp/plugins/xep_0033/addresses.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								sleekxmpp/plugins/xep_0033/addresses.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,37 @@ | |||||||
|  | """ | ||||||
|  |     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): | ||||||
|  |         register_stanza_plugin(Message, Addresses) | ||||||
|  |         register_stanza_plugin(Presence, Addresses) | ||||||
|  |  | ||||||
|  |     def plugin_end(self): | ||||||
|  |         self.xmpp['xep_0030'].del_feature(feature=Addresses.namespace) | ||||||
|  |  | ||||||
|  |     def session_bind(self, jid): | ||||||
|  |         self.xmpp['xep_0030'].add_feature(Addresses.namespace) | ||||||
|  |  | ||||||
							
								
								
									
										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) | ||||||
| @@ -137,7 +137,7 @@ class XEP_0045(BasePlugin): | |||||||
|     def handle_groupchat_invite(self, inv): |     def handle_groupchat_invite(self, inv): | ||||||
|         """ Handle an invite into a muc. |         """ Handle an invite into a muc. | ||||||
|         """ |         """ | ||||||
|         logging.debug("MUC invite to %s from %s: %s", inv['from'], inv["from"], inv) |         logging.debug("MUC invite to %s from %s: %s", inv['to'], inv["from"], inv) | ||||||
|         if inv['from'] not in self.rooms.keys(): |         if inv['from'] not in self.rooms.keys(): | ||||||
|             self.xmpp.event("groupchat_invite", inv) |             self.xmpp.event("groupchat_invite", inv) | ||||||
|  |  | ||||||
|   | |||||||
| @@ -20,18 +20,19 @@ class XEP_0047(BasePlugin): | |||||||
|     description = 'XEP-0047: In-band Bytestreams' |     description = 'XEP-0047: In-band Bytestreams' | ||||||
|     dependencies = set(['xep_0030']) |     dependencies = set(['xep_0030']) | ||||||
|     stanza = stanza |     stanza = stanza | ||||||
|  |     default_config = { | ||||||
|  |         'max_block_size': 8192, | ||||||
|  |         'window_size': 1, | ||||||
|  |         'auto_accept': True, | ||||||
|  |         'accept_stream': None | ||||||
|  |     } | ||||||
|  |  | ||||||
|     def plugin_init(self): |     def plugin_init(self): | ||||||
|         self.streams = {} |         self.streams = {} | ||||||
|         self.pending_streams = {3: 5} |         self.pending_streams = {} | ||||||
|         self.pending_close_streams = {} |         self.pending_close_streams = {} | ||||||
|         self._stream_lock = threading.Lock() |         self._stream_lock = threading.Lock() | ||||||
|  |  | ||||||
|         self.max_block_size = self.config.get('max_block_size', 8192) |  | ||||||
|         self.window_size = self.config.get('window_size', 1) |  | ||||||
|         self.auto_accept = self.config.get('auto_accept', True) |  | ||||||
|         self.accept_stream = self.config.get('accept_stream', None) |  | ||||||
|  |  | ||||||
|         register_stanza_plugin(Iq, Open) |         register_stanza_plugin(Iq, Open) | ||||||
|         register_stanza_plugin(Iq, Close) |         register_stanza_plugin(Iq, Close) | ||||||
|         register_stanza_plugin(Iq, Data) |         register_stanza_plugin(Iq, Data) | ||||||
| @@ -51,6 +52,13 @@ class XEP_0047(BasePlugin): | |||||||
|             StanzaPath('iq@type=set/ibb_data'), |             StanzaPath('iq@type=set/ibb_data'), | ||||||
|             self._handle_data)) |             self._handle_data)) | ||||||
|  |  | ||||||
|  |     def plugin_end(self): | ||||||
|  |         self.xmpp.remove_handler('IBB Open') | ||||||
|  |         self.xmpp.remove_handler('IBB Close') | ||||||
|  |         self.xmpp.remove_handler('IBB Data') | ||||||
|  |         self.xmpp['xep_0030'].del_feature(feature='http://jabber.org/protocol/ibb') | ||||||
|  |  | ||||||
|  |     def session_bind(self, jid): | ||||||
|         self.xmpp['xep_0030'].add_feature('http://jabber.org/protocol/ibb') |         self.xmpp['xep_0030'].add_feature('http://jabber.org/protocol/ibb') | ||||||
|  |  | ||||||
|     def _accept_stream(self, iq): |     def _accept_stream(self, iq): | ||||||
|   | |||||||
| @@ -1,11 +1,8 @@ | |||||||
| import socket | import socket | ||||||
| import threading | import threading | ||||||
| import logging | import logging | ||||||
| try: |  | ||||||
|     import queue |  | ||||||
| except ImportError: |  | ||||||
|     import Queue as queue |  | ||||||
|  |  | ||||||
|  | from sleekxmpp.util import Queue | ||||||
| from sleekxmpp.exceptions import XMPPError | from sleekxmpp.exceptions import XMPPError | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -33,7 +30,7 @@ class IBBytestream(object): | |||||||
|         self.stream_in_closed = threading.Event() |         self.stream_in_closed = threading.Event() | ||||||
|         self.stream_out_closed = threading.Event() |         self.stream_out_closed = threading.Event() | ||||||
|  |  | ||||||
|         self.recv_queue = queue.Queue() |         self.recv_queue = Queue() | ||||||
|  |  | ||||||
|         self.send_window = threading.BoundedSemaphore(value=self.window_size) |         self.send_window = threading.BoundedSemaphore(value=self.window_size) | ||||||
|         self.window_ids = set() |         self.window_ids = set() | ||||||
|   | |||||||
| @@ -82,12 +82,18 @@ class XEP_0050(BasePlugin): | |||||||
|     description = 'XEP-0050: Ad-Hoc Commands' |     description = 'XEP-0050: Ad-Hoc Commands' | ||||||
|     dependencies = set(['xep_0030', 'xep_0004']) |     dependencies = set(['xep_0030', 'xep_0004']) | ||||||
|     stanza = stanza |     stanza = stanza | ||||||
|  |     default_config = { | ||||||
|  |         'threaded': True, | ||||||
|  |         'session_db': None | ||||||
|  |     } | ||||||
|  |  | ||||||
|     def plugin_init(self): |     def plugin_init(self): | ||||||
|         """Start the XEP-0050 plugin.""" |         """Start the XEP-0050 plugin.""" | ||||||
|         self.threaded = self.config.get('threaded', True) |         self.sessions = self.session_db | ||||||
|  |         if self.sessions is None: | ||||||
|  |             self.sessions = {} | ||||||
|  |  | ||||||
|         self.commands = {} |         self.commands = {} | ||||||
|         self.sessions = self.config.get('session_db', {}) |  | ||||||
|  |  | ||||||
|         self.xmpp.register_handler( |         self.xmpp.register_handler( | ||||||
|                 Callback("Ad-Hoc Execute", |                 Callback("Ad-Hoc Execute", | ||||||
| @@ -110,6 +116,20 @@ class XEP_0050(BasePlugin): | |||||||
|                                     self._handle_command_complete, |                                     self._handle_command_complete, | ||||||
|                                     threaded=self.threaded) |                                     threaded=self.threaded) | ||||||
|  |  | ||||||
|  |     def plugin_end(self): | ||||||
|  |         self.xmpp.del_event_handler('command_execute', | ||||||
|  |                                     self._handle_command_start) | ||||||
|  |         self.xmpp.del_event_handler('command_next', | ||||||
|  |                                     self._handle_command_next) | ||||||
|  |         self.xmpp.del_event_handler('command_cancel', | ||||||
|  |                                     self._handle_command_cancel) | ||||||
|  |         self.xmpp.del_event_handler('command_complete', | ||||||
|  |                                     self._handle_command_complete) | ||||||
|  |         self.xmpp.remove_handler('Ad-Hoc Execute') | ||||||
|  |         self.xmpp['xep_0030'].del_feature(feature=Command.namespace) | ||||||
|  |         self.xmpp['xep_0030'].set_items(node=Command.namespace, items=tuple()) | ||||||
|  |  | ||||||
|  |     def session_bind(self, jid): | ||||||
|         self.xmpp['xep_0030'].add_feature(Command.namespace) |         self.xmpp['xep_0030'].add_feature(Command.namespace) | ||||||
|         self.xmpp['xep_0030'].set_items(node=Command.namespace, items=tuple()) |         self.xmpp['xep_0030'].set_items(node=Command.namespace, items=tuple()) | ||||||
|  |  | ||||||
|   | |||||||
| @@ -110,14 +110,14 @@ class Command(ElementBase): | |||||||
|         """ |         """ | ||||||
|         Return the set of allowable next actions. |         Return the set of allowable next actions. | ||||||
|         """ |         """ | ||||||
|         actions = [] |         actions = set() | ||||||
|         actions_xml = self.find('{%s}actions' % self.namespace) |         actions_xml = self.find('{%s}actions' % self.namespace) | ||||||
|         if actions_xml is not None: |         if actions_xml is not None: | ||||||
|             for action in self.next_actions: |             for action in self.next_actions: | ||||||
|                 action_xml = actions_xml.find('{%s}%s' % (self.namespace, |                 action_xml = actions_xml.find('{%s}%s' % (self.namespace, | ||||||
|                                                           action)) |                                                           action)) | ||||||
|                 if action_xml is not None: |                 if action_xml is not None: | ||||||
|                     actions.append(action) |                     actions.add(action) | ||||||
|         return actions |         return actions | ||||||
|  |  | ||||||
|     def del_actions(self): |     def del_actions(self): | ||||||
|   | |||||||
| @@ -72,6 +72,7 @@ class Nickname(ElementBase): | |||||||
|     name = 'NICKNAME' |     name = 'NICKNAME' | ||||||
|     namespace = 'vcard-temp' |     namespace = 'vcard-temp' | ||||||
|     plugin_attrib = name |     plugin_attrib = name | ||||||
|  |     plugin_multi_attrib = 'nicknames' | ||||||
|     interfaces = set([name]) |     interfaces = set([name]) | ||||||
|     is_extension = True |     is_extension = True | ||||||
|  |  | ||||||
| @@ -94,6 +95,7 @@ class Email(ElementBase): | |||||||
|     name = 'EMAIL' |     name = 'EMAIL' | ||||||
|     namespace = 'vcard-temp' |     namespace = 'vcard-temp' | ||||||
|     plugin_attrib = name |     plugin_attrib = name | ||||||
|  |     plugin_multi_attrib = 'emails' | ||||||
|     interfaces = set(['HOME', 'WORK', 'INTERNET', 'PREF', 'X400', 'USERID']) |     interfaces = set(['HOME', 'WORK', 'INTERNET', 'PREF', 'X400', 'USERID']) | ||||||
|     sub_interfaces = set(['USERID']) |     sub_interfaces = set(['USERID']) | ||||||
|     bool_interfaces = set(['HOME', 'WORK', 'INTERNET', 'PREF', 'X400']) |     bool_interfaces = set(['HOME', 'WORK', 'INTERNET', 'PREF', 'X400']) | ||||||
| @@ -103,8 +105,9 @@ class Address(ElementBase): | |||||||
|     name = 'ADR' |     name = 'ADR' | ||||||
|     namespace = 'vcard-temp' |     namespace = 'vcard-temp' | ||||||
|     plugin_attrib = name |     plugin_attrib = name | ||||||
|     interfaces = set(['HOME', 'WORK', 'POSTAL', 'PARCEL', 'DOM', 'INTL',  |     plugin_multi_attrib = 'addresses' | ||||||
|                       'PREF', 'POBOX', 'EXTADD', 'STREET', 'LOCALITY',  |     interfaces = set(['HOME', 'WORK', 'POSTAL', 'PARCEL', 'DOM', 'INTL', | ||||||
|  |                       'PREF', 'POBOX', 'EXTADD', 'STREET', 'LOCALITY', | ||||||
|                       'REGION', 'PCODE', 'CTRY']) |                       'REGION', 'PCODE', 'CTRY']) | ||||||
|     sub_interfaces = set(['POBOX', 'EXTADD', 'STREET', 'LOCALITY', |     sub_interfaces = set(['POBOX', 'EXTADD', 'STREET', 'LOCALITY', | ||||||
|                           'REGION', 'PCODE', 'CTRY']) |                           'REGION', 'PCODE', 'CTRY']) | ||||||
| @@ -115,12 +118,13 @@ class Telephone(ElementBase): | |||||||
|     name = 'TEL' |     name = 'TEL' | ||||||
|     namespace = 'vcard-temp' |     namespace = 'vcard-temp' | ||||||
|     plugin_attrib = name |     plugin_attrib = name | ||||||
|  |     plugin_multi_attrib = 'telephone_numbers' | ||||||
|     interfaces = set(['HOME', 'WORK', 'VOICE', 'FAX', 'PAGER', 'MSG', |     interfaces = set(['HOME', 'WORK', 'VOICE', 'FAX', 'PAGER', 'MSG', | ||||||
|                       'CELL', 'VIDEO', 'BBS', 'MODEM', 'ISDN', 'PCS', |                       'CELL', 'VIDEO', 'BBS', 'MODEM', 'ISDN', 'PCS', | ||||||
|                       'PREF', 'NUMBER']) |                       'PREF', 'NUMBER']) | ||||||
|     sub_interfaces = set(['NUMBER']) |     sub_interfaces = set(['NUMBER']) | ||||||
|     bool_interfaces = set(['HOME', 'WORK', 'VOICE', 'FAX', 'PAGER',  |     bool_interfaces = set(['HOME', 'WORK', 'VOICE', 'FAX', 'PAGER', | ||||||
|                            'MSG', 'CELL', 'VIDEO', 'BBS', 'MODEM',  |                            'MSG', 'CELL', 'VIDEO', 'BBS', 'MODEM', | ||||||
|                            'ISDN', 'PCS', 'PREF']) |                            'ISDN', 'PCS', 'PREF']) | ||||||
|  |  | ||||||
|     def setup(self, xml=None): |     def setup(self, xml=None): | ||||||
| @@ -138,9 +142,10 @@ class Label(ElementBase): | |||||||
|     name = 'LABEL' |     name = 'LABEL' | ||||||
|     namespace = 'vcard-temp' |     namespace = 'vcard-temp' | ||||||
|     plugin_attrib = name |     plugin_attrib = name | ||||||
|  |     plugin_multi_attrib = 'labels' | ||||||
|     interfaces = set(['HOME', 'WORK', 'POSTAL', 'PARCEL', 'DOM', 'INT', |     interfaces = set(['HOME', 'WORK', 'POSTAL', 'PARCEL', 'DOM', 'INT', | ||||||
|                       'PREF', 'lines']) |                       'PREF', 'lines']) | ||||||
|     bool_interfaces = set(['HOME', 'WORK', 'POSTAL', 'PARCEL', 'DOM',  |     bool_interfaces = set(['HOME', 'WORK', 'POSTAL', 'PARCEL', 'DOM', | ||||||
|                            'INT', 'PREF']) |                            'INT', 'PREF']) | ||||||
|  |  | ||||||
|     def add_line(self, value): |     def add_line(self, value): | ||||||
| @@ -171,6 +176,7 @@ class Geo(ElementBase): | |||||||
|     name = 'GEO' |     name = 'GEO' | ||||||
|     namespace = 'vcard-temp' |     namespace = 'vcard-temp' | ||||||
|     plugin_attrib = name |     plugin_attrib = name | ||||||
|  |     plugin_multi_attrib = 'geolocations' | ||||||
|     interfaces = set(['LAT', 'LON']) |     interfaces = set(['LAT', 'LON']) | ||||||
|     sub_interfaces = interfaces |     sub_interfaces = interfaces | ||||||
|  |  | ||||||
| @@ -179,6 +185,7 @@ class Org(ElementBase): | |||||||
|     name = 'ORG' |     name = 'ORG' | ||||||
|     namespace = 'vcard-temp' |     namespace = 'vcard-temp' | ||||||
|     plugin_attrib = name |     plugin_attrib = name | ||||||
|  |     plugin_multi_attrib = 'organizations' | ||||||
|     interfaces = set(['ORGNAME', 'ORGUNIT', 'orgunits']) |     interfaces = set(['ORGNAME', 'ORGUNIT', 'orgunits']) | ||||||
|     sub_interfaces = set(['ORGNAME', 'ORGUNIT']) |     sub_interfaces = set(['ORGNAME', 'ORGUNIT']) | ||||||
|  |  | ||||||
| @@ -210,6 +217,7 @@ class Photo(ElementBase): | |||||||
|     name = 'PHOTO' |     name = 'PHOTO' | ||||||
|     namespace = 'vcard-temp' |     namespace = 'vcard-temp' | ||||||
|     plugin_attrib = name |     plugin_attrib = name | ||||||
|  |     plugin_multi_attrib = 'photos' | ||||||
|     interfaces = set(['TYPE', 'EXTVAL']) |     interfaces = set(['TYPE', 'EXTVAL']) | ||||||
|     sub_interfaces = interfaces |     sub_interfaces = interfaces | ||||||
|  |  | ||||||
| @@ -218,14 +226,16 @@ class Logo(ElementBase): | |||||||
|     name = 'LOGO' |     name = 'LOGO' | ||||||
|     namespace = 'vcard-temp' |     namespace = 'vcard-temp' | ||||||
|     plugin_attrib = name |     plugin_attrib = name | ||||||
|  |     plugin_multi_attrib = 'logos' | ||||||
|     interfaces = set(['TYPE', 'EXTVAL']) |     interfaces = set(['TYPE', 'EXTVAL']) | ||||||
|     sub_interfaces = interfaces |     sub_interfaces = interfaces | ||||||
|  |  | ||||||
|  |  | ||||||
| class Sound(ElementBase): | class Sound(ElementBase): | ||||||
|     name = 'LOGO' |     name = 'SOUND' | ||||||
|     namespace = 'vcard-temp' |     namespace = 'vcard-temp' | ||||||
|     plugin_attrib = name |     plugin_attrib = name | ||||||
|  |     plugin_multi_attrib = 'sounds' | ||||||
|     interfaces = set(['PHONETC', 'EXTVAL']) |     interfaces = set(['PHONETC', 'EXTVAL']) | ||||||
|     sub_interfaces = interfaces |     sub_interfaces = interfaces | ||||||
|  |  | ||||||
| @@ -264,6 +274,7 @@ class Classification(ElementBase): | |||||||
|     name = 'CLASS' |     name = 'CLASS' | ||||||
|     namespace = 'vcard-temp' |     namespace = 'vcard-temp' | ||||||
|     plugin_attrib = name |     plugin_attrib = name | ||||||
|  |     plugin_multi_attrib = 'classifications' | ||||||
|     interfaces = set(['PUBLIC', 'PRIVATE', 'CONFIDENTIAL']) |     interfaces = set(['PUBLIC', 'PRIVATE', 'CONFIDENTIAL']) | ||||||
|     bool_interfaces = interfaces |     bool_interfaces = interfaces | ||||||
|  |  | ||||||
| @@ -272,6 +283,7 @@ class Categories(ElementBase): | |||||||
|     name = 'CATEGORIES' |     name = 'CATEGORIES' | ||||||
|     namespace = 'vcard-temp' |     namespace = 'vcard-temp' | ||||||
|     plugin_attrib = name |     plugin_attrib = name | ||||||
|  |     plugin_multi_attrib = 'categories' | ||||||
|     interfaces = set([name]) |     interfaces = set([name]) | ||||||
|     is_extension = True |     is_extension = True | ||||||
|  |  | ||||||
| @@ -301,6 +313,7 @@ class Birthday(ElementBase): | |||||||
|     name = 'BDAY' |     name = 'BDAY' | ||||||
|     namespace = 'vcard-temp' |     namespace = 'vcard-temp' | ||||||
|     plugin_attrib = name |     plugin_attrib = name | ||||||
|  |     plugin_multi_attrib = 'birthdays' | ||||||
|     interfaces = set([name]) |     interfaces = set([name]) | ||||||
|     is_extension = True |     is_extension = True | ||||||
|  |  | ||||||
| @@ -319,6 +332,7 @@ class Rev(ElementBase): | |||||||
|     name = 'REV' |     name = 'REV' | ||||||
|     namespace = 'vcard-temp' |     namespace = 'vcard-temp' | ||||||
|     plugin_attrib = name |     plugin_attrib = name | ||||||
|  |     plugin_multi_attrib = 'revision_dates' | ||||||
|     interfaces = set([name]) |     interfaces = set([name]) | ||||||
|     is_extension = True |     is_extension = True | ||||||
|  |  | ||||||
| @@ -337,6 +351,7 @@ class Title(ElementBase): | |||||||
|     name = 'TITLE' |     name = 'TITLE' | ||||||
|     namespace = 'vcard-temp' |     namespace = 'vcard-temp' | ||||||
|     plugin_attrib = name |     plugin_attrib = name | ||||||
|  |     plugin_multi_attrib = 'titles' | ||||||
|     interfaces = set([name]) |     interfaces = set([name]) | ||||||
|     is_extension = True |     is_extension = True | ||||||
|  |  | ||||||
| @@ -351,6 +366,7 @@ class Role(ElementBase): | |||||||
|     name = 'ROLE' |     name = 'ROLE' | ||||||
|     namespace = 'vcard-temp' |     namespace = 'vcard-temp' | ||||||
|     plugin_attrib = name |     plugin_attrib = name | ||||||
|  |     plugin_multi_attrib = 'roles' | ||||||
|     interfaces = set([name]) |     interfaces = set([name]) | ||||||
|     is_extension = True |     is_extension = True | ||||||
|  |  | ||||||
| @@ -365,6 +381,7 @@ class Note(ElementBase): | |||||||
|     name = 'NOTE' |     name = 'NOTE' | ||||||
|     namespace = 'vcard-temp' |     namespace = 'vcard-temp' | ||||||
|     plugin_attrib = name |     plugin_attrib = name | ||||||
|  |     plugin_multi_attrib = 'notes' | ||||||
|     interfaces = set([name]) |     interfaces = set([name]) | ||||||
|     is_extension = True |     is_extension = True | ||||||
|  |  | ||||||
| @@ -379,6 +396,7 @@ class Desc(ElementBase): | |||||||
|     name = 'DESC' |     name = 'DESC' | ||||||
|     namespace = 'vcard-temp' |     namespace = 'vcard-temp' | ||||||
|     plugin_attrib = name |     plugin_attrib = name | ||||||
|  |     plugin_multi_attrib = 'descriptions' | ||||||
|     interfaces = set([name]) |     interfaces = set([name]) | ||||||
|     is_extension = True |     is_extension = True | ||||||
|  |  | ||||||
| @@ -393,6 +411,7 @@ class URL(ElementBase): | |||||||
|     name = 'URL' |     name = 'URL' | ||||||
|     namespace = 'vcard-temp' |     namespace = 'vcard-temp' | ||||||
|     plugin_attrib = name |     plugin_attrib = name | ||||||
|  |     plugin_multi_attrib = 'urls' | ||||||
|     interfaces = set([name]) |     interfaces = set([name]) | ||||||
|     is_extension = True |     is_extension = True | ||||||
|  |  | ||||||
| @@ -407,6 +426,7 @@ class UID(ElementBase): | |||||||
|     name = 'UID' |     name = 'UID' | ||||||
|     namespace = 'vcard-temp' |     namespace = 'vcard-temp' | ||||||
|     plugin_attrib = name |     plugin_attrib = name | ||||||
|  |     plugin_multi_attrib = 'uids' | ||||||
|     interfaces = set([name]) |     interfaces = set([name]) | ||||||
|     is_extension = True |     is_extension = True | ||||||
|  |  | ||||||
| @@ -421,6 +441,7 @@ class ProdID(ElementBase): | |||||||
|     name = 'PRODID' |     name = 'PRODID' | ||||||
|     namespace = 'vcard-temp' |     namespace = 'vcard-temp' | ||||||
|     plugin_attrib = name |     plugin_attrib = name | ||||||
|  |     plugin_multi_attrib = 'product_ids' | ||||||
|     interfaces = set([name]) |     interfaces = set([name]) | ||||||
|     is_extension = True |     is_extension = True | ||||||
|  |  | ||||||
| @@ -435,6 +456,7 @@ class Mailer(ElementBase): | |||||||
|     name = 'MAILER' |     name = 'MAILER' | ||||||
|     namespace = 'vcard-temp' |     namespace = 'vcard-temp' | ||||||
|     plugin_attrib = name |     plugin_attrib = name | ||||||
|  |     plugin_multi_attrib = 'mailers' | ||||||
|     interfaces = set([name]) |     interfaces = set([name]) | ||||||
|     is_extension = True |     is_extension = True | ||||||
|  |  | ||||||
| @@ -449,6 +471,7 @@ class SortString(ElementBase): | |||||||
|     name = 'SORT-STRING' |     name = 'SORT-STRING' | ||||||
|     namespace = 'vcard-temp' |     namespace = 'vcard-temp' | ||||||
|     plugin_attrib = 'SORT_STRING' |     plugin_attrib = 'SORT_STRING' | ||||||
|  |     plugin_multi_attrib = 'sort_strings' | ||||||
|     interfaces = set([name]) |     interfaces = set([name]) | ||||||
|     is_extension = True |     is_extension = True | ||||||
|  |  | ||||||
| @@ -463,6 +486,7 @@ class Agent(ElementBase): | |||||||
|     name = 'AGENT' |     name = 'AGENT' | ||||||
|     namespace = 'vcard-temp' |     namespace = 'vcard-temp' | ||||||
|     plugin_attrib = name |     plugin_attrib = name | ||||||
|  |     plugin_multi_attrib = 'agents' | ||||||
|     interfaces = set(['EXTVAL']) |     interfaces = set(['EXTVAL']) | ||||||
|     sub_interfaces = interfaces |     sub_interfaces = interfaces | ||||||
|  |  | ||||||
| @@ -471,6 +495,7 @@ class JabberID(ElementBase): | |||||||
|     name = 'JABBERID' |     name = 'JABBERID' | ||||||
|     namespace = 'vcard-temp' |     namespace = 'vcard-temp' | ||||||
|     plugin_attrib = name |     plugin_attrib = name | ||||||
|  |     plugin_multi_attrib = 'jids' | ||||||
|     interfaces = set([name]) |     interfaces = set([name]) | ||||||
|     is_extension = True |     is_extension = True | ||||||
|  |  | ||||||
| @@ -485,11 +510,12 @@ class TimeZone(ElementBase): | |||||||
|     name = 'TZ' |     name = 'TZ' | ||||||
|     namespace = 'vcard-temp' |     namespace = 'vcard-temp' | ||||||
|     plugin_attrib = name |     plugin_attrib = name | ||||||
|  |     plugin_multi_attrib = 'timezones' | ||||||
|     interfaces = set([name]) |     interfaces = set([name]) | ||||||
|     is_extension = True |     is_extension = True | ||||||
|  |  | ||||||
|     def set_tz(self, value): |     def set_tz(self, value): | ||||||
|         time = xep_0082.time(offset=value)  |         time = xep_0082.time(offset=value) | ||||||
|         if time[-1] == 'Z': |         if time[-1] == 'Z': | ||||||
|             self.xml.text = 'Z' |             self.xml.text = 'Z' | ||||||
|         else: |         else: | ||||||
|   | |||||||
| @@ -37,7 +37,6 @@ class XEP_0054(BasePlugin): | |||||||
|         """ |         """ | ||||||
|         register_stanza_plugin(Iq, VCardTemp) |         register_stanza_plugin(Iq, VCardTemp) | ||||||
|  |  | ||||||
|         self.xmpp['xep_0030'].add_feature('vcard-temp') |  | ||||||
|  |  | ||||||
|         self.api.register(self._set_vcard, 'set_vcard', default=True) |         self.api.register(self._set_vcard, 'set_vcard', default=True) | ||||||
|         self.api.register(self._get_vcard, 'get_vcard', default=True) |         self.api.register(self._get_vcard, 'get_vcard', default=True) | ||||||
| @@ -50,10 +49,17 @@ class XEP_0054(BasePlugin): | |||||||
|                     StanzaPath('iq/vcard_temp'), |                     StanzaPath('iq/vcard_temp'), | ||||||
|                     self._handle_get_vcard)) |                     self._handle_get_vcard)) | ||||||
|  |  | ||||||
|  |     def plugin_end(self): | ||||||
|  |         self.xmpp.remove_handler('VCardTemp') | ||||||
|  |         self.xmpp['xep_0030'].del_feature(feature='vcard-temp') | ||||||
|  |  | ||||||
|  |     def session_bind(self, jid): | ||||||
|  |         self.xmpp['xep_0030'].add_feature('vcard-temp') | ||||||
|  |  | ||||||
|     def make_vcard(self): |     def make_vcard(self): | ||||||
|         return VCardTemp() |         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): |                   block=True, callback=None, timeout=None): | ||||||
|         if self.xmpp.is_component and jid.domain == self.xmpp.boundjid.domain: |         if self.xmpp.is_component and jid.domain == self.xmpp.boundjid.domain: | ||||||
|             local = True |             local = True | ||||||
| @@ -84,12 +90,12 @@ class XEP_0054(BasePlugin): | |||||||
|         iq.enable('vcard_temp') |         iq.enable('vcard_temp') | ||||||
|  |  | ||||||
|         vcard = iq.send(block=block, callback=callback, timeout=timeout) |         vcard = iq.send(block=block, callback=callback, timeout=timeout) | ||||||
|          |  | ||||||
|         if block: |         if block: | ||||||
|             self.api['set_vcard'](vcard['from'], args=vcard['vcard_temp']) |             self.api['set_vcard'](vcard['from'], args=vcard['vcard_temp']) | ||||||
|             return vcard |             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): |                       callback=None, timeout=None): | ||||||
|         if self.xmpp.is_component: |         if self.xmpp.is_component: | ||||||
|             self.api['set_vcard'](jid, None, ifrom, vcard) |             self.api['set_vcard'](jid, None, ifrom, vcard) | ||||||
| @@ -107,7 +113,7 @@ class XEP_0054(BasePlugin): | |||||||
|             self.api['set_vcard'](jid=iq['from'], args=iq['vcard_temp']) |             self.api['set_vcard'](jid=iq['from'], args=iq['vcard_temp']) | ||||||
|             return |             return | ||||||
|         elif iq['type'] == 'get': |         elif iq['type'] == 'get': | ||||||
|             vcard = self.api['get_vard'](iq['from'].bare) |             vcard = self.api['get_vcard'](iq['from'].bare) | ||||||
|             if isinstance(vcard, Iq): |             if isinstance(vcard, Iq): | ||||||
|                 vcard.send() |                 vcard.send() | ||||||
|             else: |             else: | ||||||
|   | |||||||
| @@ -47,6 +47,7 @@ class ResultIterator(): | |||||||
|         self.start = start |         self.start = start | ||||||
|         self.interface = interface |         self.interface = interface | ||||||
|         self.reverse = reverse |         self.reverse = reverse | ||||||
|  |         self._stop = False | ||||||
|  |  | ||||||
|     def __iter__(self): |     def __iter__(self): | ||||||
|         return self |         return self | ||||||
| @@ -62,6 +63,8 @@ class ResultIterator(): | |||||||
|               results will be the items before the current page |               results will be the items before the current page | ||||||
|               of items. |               of items. | ||||||
|         """ |         """ | ||||||
|  |         if self._stop: | ||||||
|  |             raise StopIteration | ||||||
|         self.query[self.interface]['rsm']['before'] = self.reverse |         self.query[self.interface]['rsm']['before'] = self.reverse | ||||||
|         self.query['id'] = self.query.stream.new_id() |         self.query['id'] = self.query.stream.new_id() | ||||||
|         self.query[self.interface]['rsm']['max'] = str(self.amount) |         self.query[self.interface]['rsm']['max'] = str(self.amount) | ||||||
| @@ -84,7 +87,7 @@ class ResultIterator(): | |||||||
|                 first = int(r[self.interface]['rsm']['first_index']) |                 first = int(r[self.interface]['rsm']['first_index']) | ||||||
|                 num_items = len(r[self.interface]['substanzas']) |                 num_items = len(r[self.interface]['substanzas']) | ||||||
|                 if first + num_items == count: |                 if first + num_items == count: | ||||||
|                     raise StopIteration |                     self._stop = True | ||||||
|  |  | ||||||
|             if self.reverse: |             if self.reverse: | ||||||
|                 self.start = r[self.interface]['rsm']['first'] |                 self.start = r[self.interface]['rsm']['first'] | ||||||
| @@ -111,10 +114,15 @@ class XEP_0059(BasePlugin): | |||||||
|         """ |         """ | ||||||
|         Start the XEP-0059 plugin. |         Start the XEP-0059 plugin. | ||||||
|         """ |         """ | ||||||
|         self.xmpp['xep_0030'].add_feature(Set.namespace) |  | ||||||
|         register_stanza_plugin(self.xmpp['xep_0030'].stanza.DiscoItems, |         register_stanza_plugin(self.xmpp['xep_0030'].stanza.DiscoItems, | ||||||
|                                self.stanza.Set) |                                self.stanza.Set) | ||||||
|  |  | ||||||
|  |     def plugin_end(self): | ||||||
|  |         self.xmpp['xep_0030'].del_feature(feature=Set.namespace) | ||||||
|  |  | ||||||
|  |     def session_bind(self, jid): | ||||||
|  |         self.xmpp['xep_0030'].add_feature(Set.namespace) | ||||||
|  |  | ||||||
|     def iterate(self, stanza, interface): |     def iterate(self, stanza, interface): | ||||||
|         """ |         """ | ||||||
|         Create a new result set iterator for a given stanza query. |         Create a new result set iterator for a given stanza query. | ||||||
|   | |||||||
| @@ -74,7 +74,7 @@ class Set(ElementBase): | |||||||
|         if fi is not None: |         if fi is not None: | ||||||
|             if val: |             if val: | ||||||
|                 fi.attrib['index'] = val |                 fi.attrib['index'] = val | ||||||
|             else: |             elif 'index' in fi.attrib: | ||||||
|                 del fi.attrib['index'] |                 del fi.attrib['index'] | ||||||
|         elif val: |         elif val: | ||||||
|             fi = ET.Element("{%s}first" % (self.namespace)) |             fi = ET.Element("{%s}first" % (self.namespace)) | ||||||
| @@ -93,7 +93,7 @@ class Set(ElementBase): | |||||||
|  |  | ||||||
|     def set_before(self, val): |     def set_before(self, val): | ||||||
|         b = self.xml.find("{%s}before" % (self.namespace)) |         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) |             self._set_sub_text('{%s}before' % self.namespace, '', True) | ||||||
|         else: |         else: | ||||||
|             self._set_sub_text('{%s}before' % self.namespace, val) |             self._set_sub_text('{%s}before' % self.namespace, val) | ||||||
|   | |||||||
| @@ -26,7 +26,7 @@ class XEP_0060(BasePlugin): | |||||||
|  |  | ||||||
|     name = 'xep_0060' |     name = 'xep_0060' | ||||||
|     description = 'XEP-0060: Publish-Subscribe' |     description = 'XEP-0060: Publish-Subscribe' | ||||||
|     dependencies = set(['xep_0030', 'xep_0004']) |     dependencies = set(['xep_0030', 'xep_0004', 'xep_0082', 'xep_0131']) | ||||||
|     stanza = stanza |     stanza = stanza | ||||||
|  |  | ||||||
|     def plugin_init(self): |     def plugin_init(self): | ||||||
| @@ -53,6 +53,15 @@ class XEP_0060(BasePlugin): | |||||||
|                     StanzaPath('message/pubsub_event/subscription'), |                     StanzaPath('message/pubsub_event/subscription'), | ||||||
|                     self._handle_event_subscription)) |                     self._handle_event_subscription)) | ||||||
|  |  | ||||||
|  |         self.xmpp['xep_0131'].supported_headers.add('SubID') | ||||||
|  |  | ||||||
|  |     def plugin_end(self): | ||||||
|  |         self.xmpp.remove_handler('Pubsub Event: Items') | ||||||
|  |         self.xmpp.remove_handler('Pubsub Event: Purge') | ||||||
|  |         self.xmpp.remove_handler('Pubsub Event: Delete') | ||||||
|  |         self.xmpp.remove_handler('Pubsub Event: Configuration') | ||||||
|  |         self.xmpp.remove_handler('Pubsub Event: Subscription') | ||||||
|  |  | ||||||
|     def _handle_event_items(self, msg): |     def _handle_event_items(self, msg): | ||||||
|         """Raise events for publish and retraction notifications.""" |         """Raise events for publish and retraction notifications.""" | ||||||
|         node = msg['pubsub_event']['items']['node'] |         node = msg['pubsub_event']['items']['node'] | ||||||
|   | |||||||
| @@ -77,12 +77,12 @@ class Item(ElementBase): | |||||||
|         self.append(value) |         self.append(value) | ||||||
|  |  | ||||||
|     def get_payload(self): |     def get_payload(self): | ||||||
|         childs = self.xml.getchildren() |         childs = list(self.xml) | ||||||
|         if len(childs) > 0: |         if len(childs) > 0: | ||||||
|             return childs[0] |             return childs[0] | ||||||
|  |  | ||||||
|     def del_payload(self): |     def del_payload(self): | ||||||
|         for child in self.xml.getchildren(): |         for child in self.xml: | ||||||
|             self.xml.remove(child) |             self.xml.remove(child) | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -254,12 +254,12 @@ class PubsubState(ElementBase): | |||||||
|         self.xml.append(value) |         self.xml.append(value) | ||||||
|  |  | ||||||
|     def get_payload(self): |     def get_payload(self): | ||||||
|         childs = self.xml.getchildren() |         childs = list(self.xml) | ||||||
|         if len(childs) > 0: |         if len(childs) > 0: | ||||||
|             return childs[0] |             return childs[0] | ||||||
|  |  | ||||||
|     def del_payload(self): |     def del_payload(self): | ||||||
|         for child in self.xml.getchildren(): |         for child in self.xml: | ||||||
|             self.xml.remove(child) |             self.xml.remove(child) | ||||||
|  |  | ||||||
|  |  | ||||||
|   | |||||||
| @@ -33,7 +33,7 @@ class PubsubErrorCondition(ElementBase): | |||||||
|  |  | ||||||
|     def get_condition(self): |     def get_condition(self): | ||||||
|         """Return the condition element's name.""" |         """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: |             if "{%s}" % self.condition_ns in child.tag: | ||||||
|                 cond = child.tag.split('}', 1)[-1] |                 cond = child.tag.split('}', 1)[-1] | ||||||
|                 if cond in self.conditions: |                 if cond in self.conditions: | ||||||
| @@ -55,7 +55,7 @@ class PubsubErrorCondition(ElementBase): | |||||||
|  |  | ||||||
|     def del_condition(self): |     def del_condition(self): | ||||||
|         """Remove the condition element.""" |         """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: |             if "{%s}" % self.condition_ns in child.tag: | ||||||
|                 tag = child.tag.split('}', 1)[-1] |                 tag = child.tag.split('}', 1)[-1] | ||||||
|                 if tag in self.conditions: |                 if tag in self.conditions: | ||||||
|   | |||||||
| @@ -31,12 +31,12 @@ class EventItem(ElementBase): | |||||||
|         self.xml.append(value) |         self.xml.append(value) | ||||||
|  |  | ||||||
|     def get_payload(self): |     def get_payload(self): | ||||||
|         childs = self.xml.getchildren() |         childs = list(self.xml) | ||||||
|         if len(childs) > 0: |         if len(childs) > 0: | ||||||
|             return childs[0] |             return childs[0] | ||||||
|  |  | ||||||
|     def del_payload(self): |     def del_payload(self): | ||||||
|         for child in self.xml.getchildren(): |         for child in self.xml: | ||||||
|             self.xml.remove(child) |             self.xml.remove(child) | ||||||
|  |  | ||||||
|  |  | ||||||
|   | |||||||
| @@ -62,6 +62,12 @@ class XEP_0066(BasePlugin): | |||||||
|                          StanzaPath('iq@type=set/oob_transfer'), |                          StanzaPath('iq@type=set/oob_transfer'), | ||||||
|                          self._handle_transfer)) |                          self._handle_transfer)) | ||||||
|  |  | ||||||
|  |     def plugin_end(self): | ||||||
|  |         self.xmpp.remove_handler('OOB Transfer') | ||||||
|  |         self.xmpp['xep_0030'].del_feature(feature=stanza.OOBTransfer.namespace) | ||||||
|  |         self.xmpp['xep_0030'].del_feature(feature=stanza.OOB.namespace) | ||||||
|  |  | ||||||
|  |     def session_bind(self, jid): | ||||||
|         self.xmpp['xep_0030'].add_feature(stanza.OOBTransfer.namespace) |         self.xmpp['xep_0030'].add_feature(stanza.OOBTransfer.namespace) | ||||||
|         self.xmpp['xep_0030'].add_feature(stanza.OOB.namespace) |         self.xmpp['xep_0030'].add_feature(stanza.OOB.namespace) | ||||||
|  |  | ||||||
|   | |||||||
| @@ -27,24 +27,28 @@ class XEP_0077(BasePlugin): | |||||||
|     description = 'XEP-0077: In-Band Registration' |     description = 'XEP-0077: In-Band Registration' | ||||||
|     dependencies = set(['xep_0004', 'xep_0066']) |     dependencies = set(['xep_0004', 'xep_0066']) | ||||||
|     stanza = stanza |     stanza = stanza | ||||||
|  |     default_config = { | ||||||
|  |         'create_account': True, | ||||||
|  |         'order': 50 | ||||||
|  |     } | ||||||
|  |  | ||||||
|     def plugin_init(self): |     def plugin_init(self): | ||||||
|         self.create_account = self.config.get('create_account', True) |  | ||||||
|  |  | ||||||
|         register_stanza_plugin(StreamFeatures, RegisterFeature) |         register_stanza_plugin(StreamFeatures, RegisterFeature) | ||||||
|         register_stanza_plugin(Iq, Register) |         register_stanza_plugin(Iq, Register) | ||||||
|  |  | ||||||
|         if self.xmpp.is_component: |         if not self.xmpp.is_component: | ||||||
|             pass |  | ||||||
|         else: |  | ||||||
|             self.xmpp.register_feature('register', |             self.xmpp.register_feature('register', | ||||||
|                 self._handle_register_feature, |                 self._handle_register_feature, | ||||||
|                 restart=False, |                 restart=False, | ||||||
|                 order=self.config.get('order', 50)) |                 order=self.order) | ||||||
|  |  | ||||||
|         register_stanza_plugin(Register, self.xmpp['xep_0004'].stanza.Form) |         register_stanza_plugin(Register, self.xmpp['xep_0004'].stanza.Form) | ||||||
|         register_stanza_plugin(Register, self.xmpp['xep_0066'].stanza.OOB) |         register_stanza_plugin(Register, self.xmpp['xep_0066'].stanza.OOB) | ||||||
|  |  | ||||||
|  |     def plugin_end(self): | ||||||
|  |         if not self.xmpp.is_component: | ||||||
|  |             self.xmpp.unregister_feature('register', self.order) | ||||||
|  |  | ||||||
|     def _handle_register_feature(self, features): |     def _handle_register_feature(self, features): | ||||||
|         if 'mechanisms' in self.xmpp.features: |         if 'mechanisms' in self.xmpp.features: | ||||||
|             # We have already logged in with an account |             # We have already logged in with an account | ||||||
|   | |||||||
| @@ -34,16 +34,22 @@ class XEP_0078(BasePlugin): | |||||||
|     description = 'XEP-0078: Non-SASL Authentication' |     description = 'XEP-0078: Non-SASL Authentication' | ||||||
|     dependencies = set() |     dependencies = set() | ||||||
|     stanza = stanza |     stanza = stanza | ||||||
|  |     default_config = { | ||||||
|  |         'order': 15 | ||||||
|  |     } | ||||||
|  |  | ||||||
|     def plugin_init(self): |     def plugin_init(self): | ||||||
|         self.xmpp.register_feature('auth', |         self.xmpp.register_feature('auth', | ||||||
|                 self._handle_auth, |                 self._handle_auth, | ||||||
|                 restart=False, |                 restart=False, | ||||||
|                 order=self.config.get('order', 15)) |                 order=self.order) | ||||||
|  |  | ||||||
|         register_stanza_plugin(Iq, stanza.IqAuth) |         register_stanza_plugin(Iq, stanza.IqAuth) | ||||||
|         register_stanza_plugin(StreamFeatures, stanza.AuthFeature) |         register_stanza_plugin(StreamFeatures, stanza.AuthFeature) | ||||||
|  |  | ||||||
|  |     def plugin_end(self): | ||||||
|  |         self.xmpp.unregister_feature('auth', self.order) | ||||||
|  |  | ||||||
|     def _handle_auth(self, features): |     def _handle_auth(self, features): | ||||||
|         # If we can or have already authenticated with SASL, do nothing. |         # If we can or have already authenticated with SASL, do nothing. | ||||||
|         if 'mechanisms' in features['features']: |         if 'mechanisms' in features['features']: | ||||||
|   | |||||||
| @@ -39,5 +39,3 @@ class AuthFeature(ElementBase): | |||||||
|     interfaces = set() |     interfaces = set() | ||||||
|     plugin_tag_map = {} |     plugin_tag_map = {} | ||||||
|     plugin_attrib_map = {} |     plugin_attrib_map = {} | ||||||
|  |  | ||||||
|  |  | ||||||
|   | |||||||
| @@ -28,8 +28,11 @@ class XEP_0080(BasePlugin): | |||||||
|     dependencies = set(['xep_0163']) |     dependencies = set(['xep_0163']) | ||||||
|     stanza = stanza |     stanza = stanza | ||||||
|  |  | ||||||
|     def plugin_init(self): |     def plugin_end(self): | ||||||
|         """Start the XEP-0080 plugin.""" |         self.xmpp['xep_0163'].remove_interest(Geoloc.namespace) | ||||||
|  |         self.xmpp['xep_0030'].del_feature(feature=Geoloc.namespace) | ||||||
|  |  | ||||||
|  |     def session_bind(self, jid): | ||||||
|         self.xmpp['xep_0163'].register_pep('user_location', Geoloc) |         self.xmpp['xep_0163'].register_pep('user_location', Geoloc) | ||||||
|  |  | ||||||
|     def publish_location(self, **kwargs): |     def publish_location(self, **kwargs): | ||||||
| @@ -40,33 +43,33 @@ class XEP_0080(BasePlugin): | |||||||
|             accuracy    -- Horizontal GPS error in meters. |             accuracy    -- Horizontal GPS error in meters. | ||||||
|             alt         -- Altitude in meters above or below sea level. |             alt         -- Altitude in meters above or below sea level. | ||||||
|             area        -- A named area such as a campus or neighborhood. |             area        -- A named area such as a campus or neighborhood. | ||||||
|             bearing     -- GPS bearing (direction in which the entity is  |             bearing     -- GPS bearing (direction in which the entity is | ||||||
|                            heading to reach its next waypoint), measured in  |                            heading to reach its next waypoint), measured in | ||||||
|                            decimal degrees relative to true north. |                            decimal degrees relative to true north. | ||||||
|             building    -- A specific building on a street or in an area. |             building    -- A specific building on a street or in an area. | ||||||
|             country     -- The nation where the user is located. |             country     -- The nation where the user is located. | ||||||
|             countrycode -- The ISO 3166 two-letter country code. |             countrycode -- The ISO 3166 two-letter country code. | ||||||
|             datum       -- GPS datum. |             datum       -- GPS datum. | ||||||
|             description -- A natural-language name for or description of  |             description -- A natural-language name for or description of | ||||||
|                            the location. |                            the location. | ||||||
|             error       -- Horizontal GPS error in arc minutes. Obsoleted by |             error       -- Horizontal GPS error in arc minutes. Obsoleted by | ||||||
|                            the accuracy parameter. |                            the accuracy parameter. | ||||||
|             floor       -- A particular floor in a building. |             floor       -- A particular floor in a building. | ||||||
|             lat         -- Latitude in decimal degrees North. |             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. |                            as a town or city. | ||||||
|             lon         -- Longitude in decimal degrees East. |             lon         -- Longitude in decimal degrees East. | ||||||
|             postalcode  -- A code used for postal delivery. |             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. |                            as a state or province. | ||||||
|             room        -- A particular room in a building. |             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. |                            in meters per second. | ||||||
|             street      -- A thoroughfare within the locality, or a crossing |             street      -- A thoroughfare within the locality, or a crossing | ||||||
|                            of two thoroughfares. |                            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. |                            information about the location. | ||||||
|             timestamp   -- UTC timestamp specifying the moment when the  |             timestamp   -- UTC timestamp specifying the moment when the | ||||||
|                            reading was taken. |                            reading was taken. | ||||||
|             uri         -- A URI or URL pointing to information about |             uri         -- A URI or URL pointing to information about | ||||||
|                            the location. |                            the location. | ||||||
| @@ -115,7 +118,7 @@ class XEP_0080(BasePlugin): | |||||||
|                         be executed when a reply stanza is received. |                         be executed when a reply stanza is received. | ||||||
|         """ |         """ | ||||||
|         geoloc = Geoloc() |         geoloc = Geoloc() | ||||||
|         return self.xmpp['xep_0163'].publish(geoloc,  |         return self.xmpp['xep_0163'].publish(geoloc, | ||||||
|                 ifrom=ifrom, |                 ifrom=ifrom, | ||||||
|                 block=block, |                 block=block, | ||||||
|                 callback=callback, |                 callback=callback, | ||||||
|   | |||||||
| @@ -31,33 +31,33 @@ class Geoloc(ElementBase): | |||||||
|         accuracy    -- Horizontal GPS error in meters. |         accuracy    -- Horizontal GPS error in meters. | ||||||
|         alt         -- Altitude in meters above or below sea level. |         alt         -- Altitude in meters above or below sea level. | ||||||
|         area        -- A named area such as a campus or neighborhood. |         area        -- A named area such as a campus or neighborhood. | ||||||
|         bearing     -- GPS bearing (direction in which the entity is  |         bearing     -- GPS bearing (direction in which the entity is | ||||||
|                        heading to reach its next waypoint), measured in  |                        heading to reach its next waypoint), measured in | ||||||
|                        decimal degrees relative to true north. |                        decimal degrees relative to true north. | ||||||
|         building    -- A specific building on a street or in an area. |         building    -- A specific building on a street or in an area. | ||||||
|         country     -- The nation where the user is located. |         country     -- The nation where the user is located. | ||||||
|         countrycode -- The ISO 3166 two-letter country code. |         countrycode -- The ISO 3166 two-letter country code. | ||||||
|         datum       -- GPS datum. |         datum       -- GPS datum. | ||||||
|         description -- A natural-language name for or description of  |         description -- A natural-language name for or description of | ||||||
|                        the location. |                        the location. | ||||||
|         error       -- Horizontal GPS error in arc minutes. Obsoleted by |         error       -- Horizontal GPS error in arc minutes. Obsoleted by | ||||||
|                        the accuracy parameter. |                        the accuracy parameter. | ||||||
|         floor       -- A particular floor in a building. |         floor       -- A particular floor in a building. | ||||||
|         lat         -- Latitude in decimal degrees North. |         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. |                        as a town or city. | ||||||
|         lon         -- Longitude in decimal degrees East. |         lon         -- Longitude in decimal degrees East. | ||||||
|         postalcode  -- A code used for postal delivery. |         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. |                        as a state or province. | ||||||
|         room        -- A particular room in a building. |         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. |                        in meters per second. | ||||||
|         street      -- A thoroughfare within the locality, or a crossing |         street      -- A thoroughfare within the locality, or a crossing | ||||||
|                        of two thoroughfares. |                        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. |                        information about the location. | ||||||
|         timestamp   -- UTC timestamp specifying the moment when the  |         timestamp   -- UTC timestamp specifying the moment when the | ||||||
|                        reading was taken. |                        reading was taken. | ||||||
|         uri         -- A URI or URL pointing to information about |         uri         -- A URI or URL pointing to information about | ||||||
|                        the location. |                        the location. | ||||||
| @@ -65,10 +65,10 @@ class Geoloc(ElementBase): | |||||||
|  |  | ||||||
|     namespace = 'http://jabber.org/protocol/geoloc' |     namespace = 'http://jabber.org/protocol/geoloc' | ||||||
|     name = 'geoloc' |     name = 'geoloc' | ||||||
|     interfaces = set(('accuracy', 'alt', 'area', 'bearing', 'building',  |     interfaces = set(('accuracy', 'alt', 'area', 'bearing', 'building', | ||||||
|                       'country', 'countrycode', 'datum', 'dscription',  |                       'country', 'countrycode', 'datum', 'dscription', | ||||||
|                       'error', 'floor', 'lat', 'locality', 'lon',  |                       'error', 'floor', 'lat', 'locality', 'lon', | ||||||
|                       'postalcode', 'region', 'room', 'speed', 'street',  |                       'postalcode', 'region', 'room', 'speed', 'street', | ||||||
|                       'text', 'timestamp', 'uri')) |                       'text', 'timestamp', 'uri')) | ||||||
|     sub_interfaces = interfaces |     sub_interfaces = interfaces | ||||||
|     plugin_attrib = name |     plugin_attrib = name | ||||||
| @@ -88,7 +88,7 @@ class Geoloc(ElementBase): | |||||||
|         """ |         """ | ||||||
|         self._set_sub_text('accuracy', text=str(accuracy)) |         self._set_sub_text('accuracy', text=str(accuracy)) | ||||||
|         return self |         return self | ||||||
|      |  | ||||||
|     def get_accuracy(self): |     def get_accuracy(self): | ||||||
|         """ |         """ | ||||||
|         Return the value of the <accuracy> element as an integer. |         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)) |         self._set_sub_text('alt', text=str(alt)) | ||||||
|         return self |         return self | ||||||
|      |  | ||||||
|     def get_alt(self): |     def get_alt(self): | ||||||
|         """ |         """ | ||||||
|         Return the value of the <alt> element as an integer. |         Return the value of the <alt> element as an integer. | ||||||
| @@ -130,8 +130,8 @@ class Geoloc(ElementBase): | |||||||
|         Set the value of the <bearing> element. |         Set the value of the <bearing> element. | ||||||
|  |  | ||||||
|         Arguments: |         Arguments: | ||||||
|             bearing -- GPS bearing (direction in which the entity is heading  |             bearing -- GPS bearing (direction in which the entity is heading | ||||||
|                        to reach its next waypoint), measured in decimal  |                        to reach its next waypoint), measured in decimal | ||||||
|                        degrees relative to true north |                        degrees relative to true north | ||||||
|         """ |         """ | ||||||
|         self._set_sub_text('bearing', text=str(bearing)) |         self._set_sub_text('bearing', text=str(bearing)) | ||||||
| @@ -155,7 +155,7 @@ class Geoloc(ElementBase): | |||||||
|         Set the value of the <error> element. |         Set the value of the <error> element. | ||||||
|  |  | ||||||
|         Arguments: |         Arguments: | ||||||
|             error -- Horizontal GPS error in arc minutes; this  |             error -- Horizontal GPS error in arc minutes; this | ||||||
|                      element is deprecated in favor of <accuracy/> |                      element is deprecated in favor of <accuracy/> | ||||||
|         """ |         """ | ||||||
|         self._set_sub_text('error', text=str(error)) |         self._set_sub_text('error', text=str(error)) | ||||||
| @@ -183,7 +183,7 @@ class Geoloc(ElementBase): | |||||||
|         """ |         """ | ||||||
|         self._set_sub_text('lat', text=str(lat)) |         self._set_sub_text('lat', text=str(lat)) | ||||||
|         return self |         return self | ||||||
|      |  | ||||||
|     def get_lat(self): |     def get_lat(self): | ||||||
|         """ |         """ | ||||||
|         Return the value of the <lat> element as a float. |         Return the value of the <lat> element as a float. | ||||||
| @@ -196,7 +196,7 @@ class Geoloc(ElementBase): | |||||||
|                 return float(p) |                 return float(p) | ||||||
|             except ValueError: |             except ValueError: | ||||||
|                 return None |                 return None | ||||||
|    |  | ||||||
|     def set_lon(self, lon): |     def set_lon(self, lon): | ||||||
|         """ |         """ | ||||||
|         Set the value of the <lon> element. |         Set the value of the <lon> element. | ||||||
| @@ -225,12 +225,12 @@ class Geoloc(ElementBase): | |||||||
|         Set the value of the <speed> element. |         Set the value of the <speed> element. | ||||||
|  |  | ||||||
|         Arguments: |         Arguments: | ||||||
|             speed -- The speed at which the entity is moving,  |             speed -- The speed at which the entity is moving, | ||||||
|                      in meters per second |                      in meters per second | ||||||
|         """ |         """ | ||||||
|         self._set_sub_text('speed', text=str(speed)) |         self._set_sub_text('speed', text=str(speed)) | ||||||
|         return self |         return self | ||||||
|      |  | ||||||
|     def get_speed(self): |     def get_speed(self): | ||||||
|         """ |         """ | ||||||
|         Return the value of the <speed> element as a float. |         Return the value of the <speed> element as a float. | ||||||
|   | |||||||
| @@ -42,6 +42,7 @@ def format_date(time_obj): | |||||||
|         time_obj = time_obj.date() |         time_obj = time_obj.date() | ||||||
|     return time_obj.isoformat() |     return time_obj.isoformat() | ||||||
|  |  | ||||||
|  |  | ||||||
| def format_time(time_obj): | def format_time(time_obj): | ||||||
|     """ |     """ | ||||||
|     Return a formatted string version of a time object. |     Return a formatted string version of a time object. | ||||||
| @@ -60,6 +61,7 @@ def format_time(time_obj): | |||||||
|         return '%sZ' % timestamp |         return '%sZ' % timestamp | ||||||
|     return timestamp |     return timestamp | ||||||
|  |  | ||||||
|  |  | ||||||
| def format_datetime(time_obj): | def format_datetime(time_obj): | ||||||
|     """ |     """ | ||||||
|     Return a formatted string version of a datetime object. |     Return a formatted string version of a datetime object. | ||||||
| @@ -76,6 +78,7 @@ def format_datetime(time_obj): | |||||||
|         return '%sZ' % timestamp |         return '%sZ' % timestamp | ||||||
|     return timestamp |     return timestamp | ||||||
|  |  | ||||||
|  |  | ||||||
| def date(year=None, month=None, day=None, obj=False): | def date(year=None, month=None, day=None, obj=False): | ||||||
|     """ |     """ | ||||||
|     Create a date only timestamp for the given instant. |     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 |         day = today.day | ||||||
|     value = dt.date(year, month, day) |     value = dt.date(year, month, day) | ||||||
|     if obj: |     if obj: | ||||||
|         return value  |         return value | ||||||
|     return format_date(value) |     return format_date(value) | ||||||
|  |  | ||||||
|  |  | ||||||
| def time(hour=None, min=None, sec=None, micro=None, offset=None, obj=False): | def time(hour=None, min=None, sec=None, micro=None, offset=None, obj=False): | ||||||
|     """ |     """ | ||||||
|     Create a time only timestamp for the given instant. |     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 value | ||||||
|     return format_time(value) |     return format_time(value) | ||||||
|  |  | ||||||
|  |  | ||||||
| def datetime(year=None, month=None, day=None, hour=None, | def datetime(year=None, month=None, day=None, hour=None, | ||||||
|              min=None, sec=None, micro=None, offset=None, |              min=None, sec=None, micro=None, offset=None, | ||||||
|              separators=True, obj=False): |              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, |     value = dt.datetime(year, month, day, hour, | ||||||
|                        min, sec, micro, offset) |                        min, sec, micro, offset) | ||||||
|     if obj: |     if obj: | ||||||
|         return value  |         return value | ||||||
|     return format_datetime(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) | ||||||
							
								
								
									
										108
									
								
								sleekxmpp/plugins/xep_0084/avatar.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										108
									
								
								sleekxmpp/plugins/xep_0084/avatar.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,108 @@ | |||||||
|  | """ | ||||||
|  |     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): | ||||||
|  |         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 plugin_end(self): | ||||||
|  |         self.xmpp['xep_0030'].del_feature(feature=MetaData.namespace) | ||||||
|  |         self.xmpp['xep_0163'].remove_interest(MetaData.namespace) | ||||||
|  |  | ||||||
|  |     def session_bind(self, jid): | ||||||
|  |         self.xmpp['xep_0163'].register_pep('avatar_metadata', MetaData) | ||||||
|  |  | ||||||
|  |     def generate_id(self, data): | ||||||
|  |         return hashlib.sha1(data).hexdigest() | ||||||
|  |  | ||||||
|  |     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, | ||||||
|  |                 id=self.generate_id(data), | ||||||
|  |                 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', '')) | ||||||
|  |  | ||||||
|  |         if pointers is not None: | ||||||
|  |             for pointer in pointers: | ||||||
|  |                 metadata.add_pointer(pointer) | ||||||
|  |  | ||||||
|  |         return self.xmpp['xep_0163'].publish(metadata, | ||||||
|  |                 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': '%s' % 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) | ||||||
| @@ -43,6 +43,10 @@ class XEP_0085(BasePlugin): | |||||||
|         register_stanza_plugin(Message, stanza.Inactive) |         register_stanza_plugin(Message, stanza.Inactive) | ||||||
|         register_stanza_plugin(Message, stanza.Paused) |         register_stanza_plugin(Message, stanza.Paused) | ||||||
|  |  | ||||||
|  |     def plugin_end(self): | ||||||
|  |         self.xmpp.remove_handler('Chat State') | ||||||
|  |  | ||||||
|  |     def session_bind(self, jid): | ||||||
|         self.xmpp.plugin['xep_0030'].add_feature(ChatState.namespace) |         self.xmpp.plugin['xep_0030'].add_feature(ChatState.namespace) | ||||||
|  |  | ||||||
|     def _handle_chat_state(self, msg): |     def _handle_chat_state(self, msg): | ||||||
|   | |||||||
| @@ -37,7 +37,10 @@ class XEP_0086(BasePlugin): | |||||||
|     description = 'XEP-0086: Error Condition Mappings' |     description = 'XEP-0086: Error Condition Mappings' | ||||||
|     dependencies = set() |     dependencies = set() | ||||||
|     stanza = stanza |     stanza = stanza | ||||||
|  |     default_config = { | ||||||
|  |         'override': True | ||||||
|  |     } | ||||||
|  |  | ||||||
|     def plugin_init(self): |     def plugin_init(self): | ||||||
|         register_stanza_plugin(Error, LegacyError, |         register_stanza_plugin(Error, LegacyError, | ||||||
|                                overrides=self.config.get('override', True)) |                                overrides=self.override) | ||||||
|   | |||||||
| @@ -47,28 +47,28 @@ class LegacyError(ElementBase): | |||||||
|     interfaces = set(('condition',)) |     interfaces = set(('condition',)) | ||||||
|     overrides = ['set_condition'] |     overrides = ['set_condition'] | ||||||
|  |  | ||||||
|     error_map = {'bad-request': ('modify','400'), |     error_map = {'bad-request': ('modify', '400'), | ||||||
|                  'conflict': ('cancel','409'), |                  'conflict': ('cancel', '409'), | ||||||
|                  'feature-not-implemented': ('cancel','501'), |                  'feature-not-implemented': ('cancel', '501'), | ||||||
|                  'forbidden': ('auth','403'), |                  'forbidden': ('auth', '403'), | ||||||
|                  'gone': ('modify','302'), |                  'gone': ('modify', '302'), | ||||||
|                  'internal-server-error': ('wait','500'), |                  'internal-server-error': ('wait', '500'), | ||||||
|                  'item-not-found': ('cancel','404'), |                  'item-not-found': ('cancel', '404'), | ||||||
|                  'jid-malformed': ('modify','400'), |                  'jid-malformed': ('modify', '400'), | ||||||
|                  'not-acceptable': ('modify','406'), |                  'not-acceptable': ('modify', '406'), | ||||||
|                  'not-allowed': ('cancel','405'), |                  'not-allowed': ('cancel', '405'), | ||||||
|                  'not-authorized': ('auth','401'), |                  'not-authorized': ('auth', '401'), | ||||||
|                  'payment-required': ('auth','402'), |                  'payment-required': ('auth', '402'), | ||||||
|                  'recipient-unavailable': ('wait','404'), |                  'recipient-unavailable': ('wait', '404'), | ||||||
|                  'redirect': ('modify','302'), |                  'redirect': ('modify', '302'), | ||||||
|                  'registration-required': ('auth','407'), |                  'registration-required': ('auth', '407'), | ||||||
|                  'remote-server-not-found': ('cancel','404'), |                  'remote-server-not-found': ('cancel', '404'), | ||||||
|                  'remote-server-timeout': ('wait','504'), |                  'remote-server-timeout': ('wait', '504'), | ||||||
|                  'resource-constraint': ('wait','500'), |                  'resource-constraint': ('wait', '500'), | ||||||
|                  'service-unavailable': ('cancel','503'), |                  'service-unavailable': ('cancel', '503'), | ||||||
|                  'subscription-required': ('auth','407'), |                  'subscription-required': ('auth', '407'), | ||||||
|                  'undefined-condition': (None,'500'), |                  'undefined-condition': (None, '500'), | ||||||
|                  'unexpected-request': ('wait','400')} |                  'unexpected-request': ('wait', '400')} | ||||||
|  |  | ||||||
|     def setup(self, xml): |     def setup(self, xml): | ||||||
|         """Don't create XML for the plugin.""" |         """Don't create XML for the plugin.""" | ||||||
|   | |||||||
| @@ -30,16 +30,18 @@ class XEP_0092(BasePlugin): | |||||||
|     description = 'XEP-0092: Software Version' |     description = 'XEP-0092: Software Version' | ||||||
|     dependencies = set(['xep_0030']) |     dependencies = set(['xep_0030']) | ||||||
|     stanza = stanza |     stanza = stanza | ||||||
|  |     default_config = { | ||||||
|  |         'software_name': 'SleekXMPP', | ||||||
|  |         'version': sleekxmpp.__version__, | ||||||
|  |         'os': '' | ||||||
|  |     } | ||||||
|  |  | ||||||
|     def plugin_init(self): |     def plugin_init(self): | ||||||
|         """ |         """ | ||||||
|         Start the XEP-0092 plugin. |         Start the XEP-0092 plugin. | ||||||
|         """ |         """ | ||||||
|         self.name = self.config.get('name', 'SleekXMPP') |         if 'name' in self.config: | ||||||
|         self.version = self.config.get('version', sleekxmpp.__version__) |             self.software_name = self.config['name'] | ||||||
|         self.os = self.config.get('os', '') |  | ||||||
|  |  | ||||||
|         self.getVersion = self.get_version |  | ||||||
|  |  | ||||||
|         self.xmpp.register_handler( |         self.xmpp.register_handler( | ||||||
|                 Callback('Software Version', |                 Callback('Software Version', | ||||||
| @@ -48,6 +50,11 @@ class XEP_0092(BasePlugin): | |||||||
|  |  | ||||||
|         register_stanza_plugin(Iq, Version) |         register_stanza_plugin(Iq, Version) | ||||||
|  |  | ||||||
|  |     def plugin_end(self): | ||||||
|  |         self.xmpp.remove_handler('Software Version') | ||||||
|  |         self.xmpp['xep_0030'].del_feature(feature='jabber:iq:version') | ||||||
|  |  | ||||||
|  |     def session_bind(self, jid): | ||||||
|         self.xmpp.plugin['xep_0030'].add_feature('jabber:iq:version') |         self.xmpp.plugin['xep_0030'].add_feature('jabber:iq:version') | ||||||
|  |  | ||||||
|     def _handle_version(self, iq): |     def _handle_version(self, iq): | ||||||
| @@ -58,7 +65,7 @@ class XEP_0092(BasePlugin): | |||||||
|             iq -- The Iq stanza containing the software version query. |             iq -- The Iq stanza containing the software version query. | ||||||
|         """ |         """ | ||||||
|         iq.reply() |         iq.reply() | ||||||
|         iq['software_version']['name'] = self.name |         iq['software_version']['name'] = self.software_name | ||||||
|         iq['software_version']['version'] = self.version |         iq['software_version']['version'] = self.version | ||||||
|         iq['software_version']['os'] = self.os |         iq['software_version']['os'] = self.os | ||||||
|         iq.send() |         iq.send() | ||||||
| @@ -79,5 +86,10 @@ class XEP_0092(BasePlugin): | |||||||
|         result = iq.send() |         result = iq.send() | ||||||
|  |  | ||||||
|         if result and result['type'] != 'error': |         if result and result['type'] != 'error': | ||||||
|             return result['software_version'].values |             values = result['software_version'].values | ||||||
|  |             del values['lang'] | ||||||
|  |             return values | ||||||
|         return False |         return False | ||||||
|  |  | ||||||
|  |  | ||||||
|  | XEP_0092.getVersion = XEP_0092.get_version | ||||||
|   | |||||||
							
								
								
									
										26
									
								
								sleekxmpp/plugins/xep_0106.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								sleekxmpp/plugins/xep_0106.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | |||||||
|  | """ | ||||||
|  |     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 import BasePlugin, register_plugin | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class XEP_0106(BasePlugin): | ||||||
|  |  | ||||||
|  |     name = 'xep_0106' | ||||||
|  |     description = 'XEP-0106: JID Escaping' | ||||||
|  |     dependencies = set(['xep_0030']) | ||||||
|  |  | ||||||
|  |     def session_bind(self, jid): | ||||||
|  |         self.xmpp['xep_0030'].add_feature(feature='jid\\20escaping') | ||||||
|  |  | ||||||
|  |     def plugin_end(self): | ||||||
|  |         self.xmpp['xep_0030'].del_feature(feature='jid\\20escaping') | ||||||
|  |  | ||||||
|  |  | ||||||
|  | register_plugin(XEP_0106) | ||||||
| @@ -32,9 +32,15 @@ class XEP_0107(BasePlugin): | |||||||
|  |  | ||||||
|     def plugin_init(self): |     def plugin_init(self): | ||||||
|         register_stanza_plugin(Message, UserMood) |         register_stanza_plugin(Message, UserMood) | ||||||
|  |  | ||||||
|  |     def plugin_end(self): | ||||||
|  |         self.xmpp['xep_0030'].del_feature(feature=UserMood.namespace) | ||||||
|  |         self.xmpp['xep_0163'].remove_interest(UserMood.namespace) | ||||||
|  |  | ||||||
|  |     def session_bind(self, jid): | ||||||
|         self.xmpp['xep_0163'].register_pep('user_mood', 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): |                      ifrom=None, block=True, callback=None, timeout=None): | ||||||
|         """ |         """ | ||||||
|         Publish the user's current mood. |         Publish the user's current mood. | ||||||
| @@ -79,7 +85,7 @@ class XEP_0107(BasePlugin): | |||||||
|                         be executed when a reply stanza is received. |                         be executed when a reply stanza is received. | ||||||
|         """ |         """ | ||||||
|         mood = UserMood() |         mood = UserMood() | ||||||
|         return self.xmpp['xep_0163'].publish(mood,  |         return self.xmpp['xep_0163'].publish(mood, | ||||||
|                 node=UserMood.namespace, |                 node=UserMood.namespace, | ||||||
|                 ifrom=ifrom, |                 ifrom=ifrom, | ||||||
|                 block=block, |                 block=block, | ||||||
|   | |||||||
| @@ -21,7 +21,7 @@ class UserActivity(ElementBase): | |||||||
|                    'talking', 'traveling', 'undefined', 'working']) |                    'talking', 'traveling', 'undefined', 'working']) | ||||||
|     specific = set(['at_the_spa', 'brushing_teeth', 'buying_groceries', |     specific = set(['at_the_spa', 'brushing_teeth', 'buying_groceries', | ||||||
|                     'cleaning', 'coding', 'commuting', 'cooking', 'cycling', |                     'cleaning', 'coding', 'commuting', 'cooking', 'cycling', | ||||||
|                     'dancing', 'day_off', 'doing_maintenance',  |                     'dancing', 'day_off', 'doing_maintenance', | ||||||
|                     'doing_the_dishes', 'doing_the_laundry', 'driving', |                     'doing_the_dishes', 'doing_the_laundry', 'driving', | ||||||
|                     'fishing', 'gaming', 'gardening', 'getting_a_haircut', |                     'fishing', 'gaming', 'gardening', 'getting_a_haircut', | ||||||
|                     'going_out', 'hanging_out', 'having_a_beer', |                     'going_out', 'hanging_out', 'having_a_beer', | ||||||
| @@ -31,11 +31,11 @@ class UserActivity(ElementBase): | |||||||
|                     'jogging', 'on_a_bus', 'on_a_plane', 'on_a_train', |                     'jogging', 'on_a_bus', 'on_a_plane', 'on_a_train', | ||||||
|                     'on_a_trip', 'on_the_phone', 'on_vacation', |                     'on_a_trip', 'on_the_phone', 'on_vacation', | ||||||
|                     'on_video_phone', 'other', 'partying', 'playing_sports', |                     'on_video_phone', 'other', 'partying', 'playing_sports', | ||||||
|                     'praying', 'reading', 'rehearsing', 'running',  |                     'praying', 'reading', 'rehearsing', 'running', | ||||||
|                     'running_an_errand', 'scheduled_holiday', 'shaving', |                     'running_an_errand', 'scheduled_holiday', 'shaving', | ||||||
|                     'shopping', 'skiing', 'sleeping', 'smoking', |                     'shopping', 'skiing', 'sleeping', 'smoking', | ||||||
|                     'socializing', 'studying', 'sunbathing', 'swimming', |                     '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', |                     'walking', 'walking_the_dog', 'watching_a_movie', | ||||||
|                     'watching_tv', 'working_out', 'writing']) |                     'watching_tv', 'working_out', 'writing']) | ||||||
|  |  | ||||||
| @@ -46,7 +46,7 @@ class UserActivity(ElementBase): | |||||||
|         if isinstance(value, tuple) or isinstance(value, list): |         if isinstance(value, tuple) or isinstance(value, list): | ||||||
|             general = value[0] |             general = value[0] | ||||||
|             specific = value[1] |             specific = value[1] | ||||||
|             |  | ||||||
|         if general in self.general: |         if general in self.general: | ||||||
|             gen_xml = ET.Element('{%s}%s' % (self.namespace, general)) |             gen_xml = ET.Element('{%s}%s' % (self.namespace, general)) | ||||||
|             if specific: |             if specific: | ||||||
|   | |||||||
| @@ -26,10 +26,14 @@ class XEP_0108(BasePlugin): | |||||||
|     dependencies = set(['xep_0163']) |     dependencies = set(['xep_0163']) | ||||||
|     stanza = stanza |     stanza = stanza | ||||||
|  |  | ||||||
|     def plugin_init(self): |     def plugin_end(self): | ||||||
|  |         self.xmpp['xep_0030'].del_feature(feature=UserActivity.namespace) | ||||||
|  |         self.xmpp['xep_0163'].remove_interest(UserActivity.namespace) | ||||||
|  |  | ||||||
|  |     def session_bind(self, jid): | ||||||
|         self.xmpp['xep_0163'].register_pep('user_activity', UserActivity) |         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): |                      ifrom=None, block=True, callback=None, timeout=None): | ||||||
|         """ |         """ | ||||||
|         Publish the user's current activity. |         Publish the user's current activity. | ||||||
| @@ -76,7 +80,7 @@ class XEP_0108(BasePlugin): | |||||||
|                         be executed when a reply stanza is received. |                         be executed when a reply stanza is received. | ||||||
|         """ |         """ | ||||||
|         activity = UserActivity() |         activity = UserActivity() | ||||||
|         return self.xmpp['xep_0163'].publish(activity,  |         return self.xmpp['xep_0163'].publish(activity, | ||||||
|                 node=UserActivity.namespace, |                 node=UserActivity.namespace, | ||||||
|                 ifrom=ifrom, |                 ifrom=ifrom, | ||||||
|                 block=block, |                 block=block, | ||||||
|   | |||||||
| @@ -33,16 +33,17 @@ class XEP_0115(BasePlugin): | |||||||
|     description = 'XEP-0115: Entity Capabilities' |     description = 'XEP-0115: Entity Capabilities' | ||||||
|     dependencies = set(['xep_0030', 'xep_0128', 'xep_0004']) |     dependencies = set(['xep_0030', 'xep_0128', 'xep_0004']) | ||||||
|     stanza = stanza |     stanza = stanza | ||||||
|  |     default_config = { | ||||||
|  |         'hash': 'sha-1', | ||||||
|  |         'caps_node': None, | ||||||
|  |         'broadcast': True | ||||||
|  |     } | ||||||
|  |  | ||||||
|     def plugin_init(self): |     def plugin_init(self): | ||||||
|         self.hashes = {'sha-1': hashlib.sha1,  |         self.hashes = {'sha-1': hashlib.sha1, | ||||||
|                        'sha1': hashlib.sha1, |                        'sha1': hashlib.sha1, | ||||||
|                        'md5': hashlib.md5} |                        'md5': hashlib.md5} | ||||||
|  |  | ||||||
|         self.hash = self.config.get('hash', 'sha-1') |  | ||||||
|         self.caps_node = self.config.get('caps_node', None) |  | ||||||
|         self.broadcast = self.config.get('broadcast', True) |  | ||||||
|  |  | ||||||
|         if self.caps_node is None: |         if self.caps_node is None: | ||||||
|             ver = sleekxmpp.__version__ |             ver = sleekxmpp.__version__ | ||||||
|             self.caps_node = 'http://sleekxmpp.com/ver/%s' % ver |             self.caps_node = 'http://sleekxmpp.com/ver/%s' % ver | ||||||
| @@ -73,16 +74,15 @@ class XEP_0115(BasePlugin): | |||||||
|                     restart=False, |                     restart=False, | ||||||
|                     order=10010) |                     order=10010) | ||||||
|  |  | ||||||
|         self.xmpp['xep_0030'].add_feature(stanza.Capabilities.namespace) |  | ||||||
|  |  | ||||||
|         disco = self.xmpp['xep_0030'] |         disco = self.xmpp['xep_0030'] | ||||||
|         self.static = StaticCaps(self.xmpp, disco.static) |         self.static = StaticCaps(self.xmpp, disco.static) | ||||||
|  |  | ||||||
|         self.api.settings['client_bare'] = False |  | ||||||
|         self.api.settings['component_bare'] = False |  | ||||||
|         for op in self._disco_ops: |         for op in self._disco_ops: | ||||||
|             self.api.register(getattr(self.static, op), op, default=True) |             self.api.register(getattr(self.static, op), op, default=True) | ||||||
|  |  | ||||||
|  |         for op in ('supports', 'has_identity'): | ||||||
|  |             self.xmpp['xep_0030'].api.register(getattr(self.static, op), op) | ||||||
|  |  | ||||||
|         self._run_node_handler = disco._run_node_handler |         self._run_node_handler = disco._run_node_handler | ||||||
|  |  | ||||||
|         disco.cache_caps = self.cache_caps |         disco.cache_caps = self.cache_caps | ||||||
| @@ -90,13 +90,31 @@ class XEP_0115(BasePlugin): | |||||||
|         disco.assign_verstring = self.assign_verstring |         disco.assign_verstring = self.assign_verstring | ||||||
|         disco.get_verstring = self.get_verstring |         disco.get_verstring = self.get_verstring | ||||||
|  |  | ||||||
|  |     def plugin_end(self): | ||||||
|  |         self.xmpp['xep_0030'].del_feature(feature=stanza.Capabilities.namespace) | ||||||
|  |         self.xmpp.del_filter('out', self._filter_add_caps) | ||||||
|  |         self.xmpp.del_event_handler('entity_caps', self._process_caps) | ||||||
|  |         self.xmpp.remove_handler('Entity Capabilities') | ||||||
|  |         if not self.xmpp.is_component: | ||||||
|  |             self.xmpp.unregister_feature('caps', 10010) | ||||||
|  |         for op in ('supports', 'has_identity'): | ||||||
|  |             self.xmpp['xep_0030'].restore_defaults(op) | ||||||
|  |  | ||||||
|  |     def session_bind(self, jid): | ||||||
|  |         self.xmpp['xep_0030'].add_feature(stanza.Capabilities.namespace) | ||||||
|  |  | ||||||
|     def _filter_add_caps(self, stanza): |     def _filter_add_caps(self, stanza): | ||||||
|         if isinstance(stanza, Presence) and self.broadcast: |         if not isinstance(stanza, Presence) or not self.broadcast: | ||||||
|             ver = self.get_verstring(stanza['from']) |             return stanza | ||||||
|             if ver: |  | ||||||
|                 stanza['caps']['node'] = self.caps_node |         if stanza['type'] not in ('available', 'chat', 'away', 'dnd', 'xa'): | ||||||
|                 stanza['caps']['hash'] = self.hash |             return stanza | ||||||
|                 stanza['caps']['ver'] = ver |  | ||||||
|  |         ver = self.get_verstring(stanza['from']) | ||||||
|  |         if ver: | ||||||
|  |             stanza['caps']['node'] = self.caps_node | ||||||
|  |             stanza['caps']['hash'] = self.hash | ||||||
|  |             stanza['caps']['ver'] = ver | ||||||
|         return stanza |         return stanza | ||||||
|  |  | ||||||
|     def _handle_caps(self, presence): |     def _handle_caps(self, presence): | ||||||
| @@ -124,7 +142,7 @@ class XEP_0115(BasePlugin): | |||||||
|         existing_verstring = self.get_verstring(pres['from'].full) |         existing_verstring = self.get_verstring(pres['from'].full) | ||||||
|         if str(existing_verstring) == str(pres['caps']['ver']): |         if str(existing_verstring) == str(pres['caps']['ver']): | ||||||
|             return |             return | ||||||
|       |  | ||||||
|         if pres['caps']['hash'] not in self.hashes: |         if pres['caps']['hash'] not in self.hashes: | ||||||
|             try: |             try: | ||||||
|                 log.debug("Unknown caps hash: %s", pres['caps']['hash']) |                 log.debug("Unknown caps hash: %s", pres['caps']['hash']) | ||||||
| @@ -132,7 +150,7 @@ class XEP_0115(BasePlugin): | |||||||
|                 return |                 return | ||||||
|             except XMPPError: |             except XMPPError: | ||||||
|                 return |                 return | ||||||
|     |  | ||||||
|         log.debug("New caps verification string: %s", pres['caps']['ver']) |         log.debug("New caps verification string: %s", pres['caps']['ver']) | ||||||
|         try: |         try: | ||||||
|             node = '%s#%s' % (pres['caps']['node'], pres['caps']['ver']) |             node = '%s#%s' % (pres['caps']['node'], pres['caps']['ver']) | ||||||
| @@ -140,7 +158,7 @@ class XEP_0115(BasePlugin): | |||||||
|  |  | ||||||
|             if isinstance(caps, Iq): |             if isinstance(caps, Iq): | ||||||
|                 caps = caps['disco_info'] |                 caps = caps['disco_info'] | ||||||
|                      |  | ||||||
|             if self._validate_caps(caps, pres['caps']['hash'], |             if self._validate_caps(caps, pres['caps']['hash'], | ||||||
|                                          pres['caps']['ver']): |                                          pres['caps']['ver']): | ||||||
|                 self.assign_verstring(pres['from'], pres['caps']['ver']) |                 self.assign_verstring(pres['from'], pres['caps']['ver']) | ||||||
| @@ -173,7 +191,8 @@ class XEP_0115(BasePlugin): | |||||||
|                     form_types.append(f_type) |                     form_types.append(f_type) | ||||||
|                     deduped_form_types.add(f_type) |                     deduped_form_types.add(f_type) | ||||||
|                     if len(form_types) != len(deduped_form_types): |                     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 |                         return False | ||||||
|  |  | ||||||
|                     if len(f_type) > 1: |                     if len(f_type) > 1: | ||||||
| @@ -183,7 +202,8 @@ class XEP_0115(BasePlugin): | |||||||
|                             return False |                             return False | ||||||
|  |  | ||||||
|                     if stanza['fields']['FORM_TYPE']['type'] != 'hidden': |                     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) |                         caps.xml.remove(stanza.xml) | ||||||
|                 else: |                 else: | ||||||
|                     log.debug("No FORM_TYPE found, ignoring form for caps") |                     log.debug("No FORM_TYPE found, ignoring form for caps") | ||||||
| @@ -212,7 +232,7 @@ class XEP_0115(BasePlugin): | |||||||
|  |  | ||||||
|         identities = sorted(('/'.join(i) for i in identities)) |         identities = sorted(('/'.join(i) for i in identities)) | ||||||
|         features = sorted(info['features']) |         features = sorted(info['features']) | ||||||
|   |  | ||||||
|         S += '<'.join(identities) + '<' |         S += '<'.join(identities) + '<' | ||||||
|         S += '<'.join(features) + '<' |         S += '<'.join(features) + '<' | ||||||
|  |  | ||||||
| @@ -254,7 +274,7 @@ class XEP_0115(BasePlugin): | |||||||
|                 info = info['disco_info'] |                 info = info['disco_info'] | ||||||
|             ver = self.generate_verstring(info, self.hash) |             ver = self.generate_verstring(info, self.hash) | ||||||
|             self.xmpp['xep_0030'].set_info( |             self.xmpp['xep_0030'].set_info( | ||||||
|                     jid=jid,  |                     jid=jid, | ||||||
|                     node='%s#%s' % (self.caps_node, ver), |                     node='%s#%s' % (self.caps_node, ver), | ||||||
|                     info=info) |                     info=info) | ||||||
|             self.cache_caps(ver, info) |             self.cache_caps(ver, info) | ||||||
|   | |||||||
| @@ -69,7 +69,7 @@ class StaticCaps(object): | |||||||
|                 return True |                 return True | ||||||
|  |  | ||||||
|         try: |         try: | ||||||
|             info = self.disco.get_info(jid=jid, node=node,  |             info = self.disco.get_info(jid=jid, node=node, | ||||||
|                                        ifrom=ifrom, **data) |                                        ifrom=ifrom, **data) | ||||||
|             info = self.disco._wrap(ifrom, jid, info, True) |             info = self.disco._wrap(ifrom, jid, info, True) | ||||||
|             return feature in info['disco_info']['features'] |             return feature in info['disco_info']['features'] | ||||||
| @@ -99,7 +99,7 @@ class StaticCaps(object): | |||||||
|                         be skipped, even if a result has already been |                         be skipped, even if a result has already been | ||||||
|                         cached. Defaults to false. |                         cached. Defaults to false. | ||||||
|         """ |         """ | ||||||
|         identity = (data.get('category', None),  |         identity = (data.get('category', None), | ||||||
|                     data.get('itype', None), |                     data.get('itype', None), | ||||||
|                     data.get('lang', None)) |                     data.get('lang', None)) | ||||||
|  |  | ||||||
| @@ -114,7 +114,7 @@ class StaticCaps(object): | |||||||
|                 return True |                 return True | ||||||
|  |  | ||||||
|         try: |         try: | ||||||
|             info = self.disco.get_info(jid=jid, node=node,  |             info = self.disco.get_info(jid=jid, node=node, | ||||||
|                                        ifrom=ifrom, **data) |                                        ifrom=ifrom, **data) | ||||||
|             info = self.disco._wrap(ifrom, jid, info, True) |             info = self.disco._wrap(ifrom, jid, info, True) | ||||||
|             return identity in map(trunc, info['disco_info']['identities']) |             return identity in map(trunc, info['disco_info']['identities']) | ||||||
|   | |||||||
| @@ -14,7 +14,7 @@ class UserTune(ElementBase): | |||||||
|     name = 'tune' |     name = 'tune' | ||||||
|     namespace = 'http://jabber.org/protocol/tune' |     namespace = 'http://jabber.org/protocol/tune' | ||||||
|     plugin_attrib = 'tune' |     plugin_attrib = 'tune' | ||||||
|     interfaces = set(['artist', 'length', 'rating', 'source',  |     interfaces = set(['artist', 'length', 'rating', 'source', | ||||||
|                       'title', 'track', 'uri']) |                       'title', 'track', 'uri']) | ||||||
|     sub_interfaces = interfaces |     sub_interfaces = interfaces | ||||||
|  |  | ||||||
|   | |||||||
| @@ -26,11 +26,15 @@ class XEP_0118(BasePlugin): | |||||||
|     dependencies = set(['xep_0163']) |     dependencies = set(['xep_0163']) | ||||||
|     stanza = stanza |     stanza = stanza | ||||||
|  |  | ||||||
|     def plugin_init(self): |     def plugin_end(self): | ||||||
|  |         self.xmpp['xep_0030'].del_feature(feature=UserTune.namespace) | ||||||
|  |         self.xmpp['xep_0163'].remove_interest(UserTune.namespace) | ||||||
|  |  | ||||||
|  |     def session_bind(self, jid): | ||||||
|         self.xmpp['xep_0163'].register_pep('user_tune', UserTune) |         self.xmpp['xep_0163'].register_pep('user_tune', UserTune) | ||||||
|  |  | ||||||
|     def publish_tune(self, artist=None, length=None, rating=None, source=None, |     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): |                      ifrom=None, block=True, callback=None, timeout=None): | ||||||
|         """ |         """ | ||||||
|         Publish the user's current tune. |         Publish the user's current tune. | ||||||
| @@ -61,7 +65,7 @@ class XEP_0118(BasePlugin): | |||||||
|         tune['title'] = title |         tune['title'] = title | ||||||
|         tune['track'] = track |         tune['track'] = track | ||||||
|         tune['uri'] = uri |         tune['uri'] = uri | ||||||
|         return self.xmpp['xep_0163'].publish(tune,  |         return self.xmpp['xep_0163'].publish(tune, | ||||||
|                 node=UserTune.namespace, |                 node=UserTune.namespace, | ||||||
|                 options=options, |                 options=options, | ||||||
|                 ifrom=ifrom, |                 ifrom=ifrom, | ||||||
| @@ -84,7 +88,7 @@ class XEP_0118(BasePlugin): | |||||||
|                         be executed when a reply stanza is received. |                         be executed when a reply stanza is received. | ||||||
|         """ |         """ | ||||||
|         tune = UserTune() |         tune = UserTune() | ||||||
|         return self.xmpp['xep_0163'].publish(tune,  |         return self.xmpp['xep_0163'].publish(tune, | ||||||
|                 node=UserTune.namespace, |                 node=UserTune.namespace, | ||||||
|                 ifrom=ifrom, |                 ifrom=ifrom, | ||||||
|                 block=block, |                 block=block, | ||||||
|   | |||||||
| @@ -51,8 +51,6 @@ class XEP_0128(BasePlugin): | |||||||
|  |  | ||||||
|         register_stanza_plugin(DiscoInfo, Form, iterable=True) |         register_stanza_plugin(DiscoInfo, Form, iterable=True) | ||||||
|  |  | ||||||
|     def post_init(self): |  | ||||||
|         """Handle cross-plugin dependencies.""" |  | ||||||
|         self.disco = self.xmpp['xep_0030'] |         self.disco = self.xmpp['xep_0030'] | ||||||
|         self.static = StaticExtendedDisco(self.disco.static) |         self.static = StaticExtendedDisco(self.disco.static) | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										16
									
								
								sleekxmpp/plugins/xep_0131/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								sleekxmpp/plugins/xep_0131/__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_0131 import stanza | ||||||
|  | from sleekxmpp.plugins.xep_0131.stanza import Headers | ||||||
|  | from sleekxmpp.plugins.xep_0131.headers import XEP_0131 | ||||||
|  |  | ||||||
|  |  | ||||||
|  | register_plugin(XEP_0131) | ||||||
							
								
								
									
										41
									
								
								sleekxmpp/plugins/xep_0131/headers.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								sleekxmpp/plugins/xep_0131/headers.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,41 @@ | |||||||
|  | """ | ||||||
|  |     SleekXMPP: The Sleek XMPP Library | ||||||
|  |     Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout | ||||||
|  |     This file is part of SleekXMPP. | ||||||
|  |  | ||||||
|  |     See the file LICENSE for copying permission. | ||||||
|  | """ | ||||||
|  |  | ||||||
|  | from sleekxmpp import Message, Presence | ||||||
|  | from sleekxmpp.xmlstream import register_stanza_plugin | ||||||
|  | from sleekxmpp.plugins import BasePlugin | ||||||
|  | from sleekxmpp.plugins.xep_0131 import stanza | ||||||
|  | from sleekxmpp.plugins.xep_0131.stanza import Headers | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class XEP_0131(BasePlugin): | ||||||
|  |  | ||||||
|  |     name = 'xep_0131' | ||||||
|  |     description = 'XEP-0131: Stanza Headers and Internet Metadata' | ||||||
|  |     dependencies = set(['xep_0030']) | ||||||
|  |     stanza = stanza | ||||||
|  |     default_config = { | ||||||
|  |         'supported_headers': set() | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     def plugin_init(self): | ||||||
|  |         register_stanza_plugin(Message, Headers) | ||||||
|  |         register_stanza_plugin(Presence, Headers) | ||||||
|  |  | ||||||
|  |     def plugin_end(self): | ||||||
|  |         self.xmpp['xep_0030'].del_feature(feature=Headers.namespace) | ||||||
|  |         for header in self.supported_headers: | ||||||
|  |             self.xmpp['xep_0030'].del_feature( | ||||||
|  |                     feature='%s#%s' % (Headers.namespace, header)) | ||||||
|  |  | ||||||
|  |     def session_bind(self, jid): | ||||||
|  |         self.xmpp['xep_0030'].add_feature(Headers.namespace) | ||||||
|  |         for header in self.supported_headers: | ||||||
|  |             self.xmpp['xep_0030'].add_feature('%s#%s' % ( | ||||||
|  |                 Headers.namespace, | ||||||
|  |                 header)) | ||||||
							
								
								
									
										51
									
								
								sleekxmpp/plugins/xep_0131/stanza.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								sleekxmpp/plugins/xep_0131/stanza.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,51 @@ | |||||||
|  | """ | ||||||
|  |     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.thirdparty import OrderedDict | ||||||
|  | from sleekxmpp.xmlstream import ET, ElementBase | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class Headers(ElementBase): | ||||||
|  |     name = 'headers' | ||||||
|  |     namespace = 'http://jabber.org/protocol/shim' | ||||||
|  |     plugin_attrib = 'headers' | ||||||
|  |     interfaces = set(['headers']) | ||||||
|  |     is_extension = True | ||||||
|  |  | ||||||
|  |     def get_headers(self): | ||||||
|  |         result = OrderedDict() | ||||||
|  |         headers = self.xml.findall('{%s}header' % self.namespace) | ||||||
|  |         for header in headers: | ||||||
|  |             name = header.attrib.get('name', '') | ||||||
|  |             value = header.text | ||||||
|  |             if name in result: | ||||||
|  |                 if not isinstance(result[name], set): | ||||||
|  |                     result[name] = [result[name]] | ||||||
|  |                 else: | ||||||
|  |                     result[name] = [] | ||||||
|  |                 result[name].add(value) | ||||||
|  |             else: | ||||||
|  |                 result[name] = value | ||||||
|  |         return result | ||||||
|  |  | ||||||
|  |     def set_headers(self, values): | ||||||
|  |         self.del_headers() | ||||||
|  |         for name in values: | ||||||
|  |             vals = values[name] | ||||||
|  |             if not isinstance(vals, (list, set)): | ||||||
|  |                 vals = [values[name]] | ||||||
|  |             for value in vals: | ||||||
|  |                 header = ET.Element('{%s}header' % self.namespace) | ||||||
|  |                 header.attrib['name'] = name | ||||||
|  |                 header.text = value | ||||||
|  |                 self.xml.append(header) | ||||||
|  |  | ||||||
|  |     def del_headers(self): | ||||||
|  |         headers = self.xml.findall('{%s}header' % self.namespace) | ||||||
|  |         for header in headers: | ||||||
|  |             self.xml.remove(header) | ||||||
							
								
								
									
										54
									
								
								sleekxmpp/plugins/xep_0133.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								sleekxmpp/plugins/xep_0133.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,54 @@ | |||||||
|  | """ | ||||||
|  |     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 import BasePlugin, register_plugin | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class XEP_0133(BasePlugin): | ||||||
|  |  | ||||||
|  |     name = 'xep_0133' | ||||||
|  |     description = 'XEP-0133: Service Administration' | ||||||
|  |     dependencies = set(['xep_0030', 'xep_0004', 'xep_0050']) | ||||||
|  |     commands = set(['add-user', 'delete-user', 'disable-user', | ||||||
|  |                     'reenable-user', 'end-user-session', 'get-user-password', | ||||||
|  |                     'change-user-password', 'get-user-roster', | ||||||
|  |                     'get-user-lastlogin', 'user-stats', 'edit-blacklist', | ||||||
|  |                     'edit-whitelist', 'get-registered-users-num', | ||||||
|  |                     'get-disabled-users-num', 'get-online-users-num', | ||||||
|  |                     'get-active-users-num', 'get-idle-users-num', | ||||||
|  |                     'get-registered-users-list', 'get-disabled-users-list', | ||||||
|  |                     'get-online-users-list', 'get-online-users', | ||||||
|  |                     'get-active-users', 'get-idle-userslist', 'announce', | ||||||
|  |                     'set-motd', 'edit-motd', 'delete-motd', 'set-welcome', | ||||||
|  |                     'delete-welcome', 'edit-admin', 'restart', 'shutdown']) | ||||||
|  |  | ||||||
|  |     def get_commands(self, jid=None, **kwargs): | ||||||
|  |         if jid is None: | ||||||
|  |             jid = self.xmpp.boundjid.server | ||||||
|  |         return self.xmpp['xep_0050'].get_commands(jid, **kwargs) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def create_command(name): | ||||||
|  |     def admin_command(self, jid=None, session=None, ifrom=None, block=False): | ||||||
|  |         if jid is None: | ||||||
|  |             jid = self.xmpp.boundjid.server | ||||||
|  |         self.xmpp['xep_0050'].start_command( | ||||||
|  |                 jid=jid, | ||||||
|  |                 node='http://jabber.org/protocol/admin#%s' % name, | ||||||
|  |                 session=session, | ||||||
|  |                 ifrom=ifrom, | ||||||
|  |                 block=block) | ||||||
|  |     return admin_command | ||||||
|  |  | ||||||
|  |  | ||||||
|  | for cmd in XEP_0133.commands: | ||||||
|  |     setattr(XEP_0133, cmd.replace('-', '_'), create_command(cmd)) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | register_plugin(XEP_0133) | ||||||
| @@ -8,7 +8,9 @@ | |||||||
|  |  | ||||||
| import hashlib | import hashlib | ||||||
| import logging | import logging | ||||||
|  | import threading | ||||||
|  |  | ||||||
|  | from sleekxmpp import JID | ||||||
| from sleekxmpp.stanza import Presence | from sleekxmpp.stanza import Presence | ||||||
| from sleekxmpp.xmlstream import register_stanza_plugin | from sleekxmpp.xmlstream import register_stanza_plugin | ||||||
| from sleekxmpp.xmlstream.matcher import StanzaPath | from sleekxmpp.xmlstream.matcher import StanzaPath | ||||||
| @@ -30,11 +32,14 @@ class XEP_0153(BasePlugin): | |||||||
|     def plugin_init(self): |     def plugin_init(self): | ||||||
|         self._hashes = {} |         self._hashes = {} | ||||||
|  |  | ||||||
|  |         self._allow_advertising = threading.Event() | ||||||
|  |  | ||||||
|         register_stanza_plugin(Presence, VCardTempUpdate) |         register_stanza_plugin(Presence, VCardTempUpdate) | ||||||
|  |  | ||||||
|         self.xmpp.add_filter('out', self._update_presence) |         self.xmpp.add_filter('out', self._update_presence) | ||||||
|  |  | ||||||
|         self.xmpp.add_event_handler('session_start', self._start) |         self.xmpp.add_event_handler('session_start', self._start) | ||||||
|  |         self.xmpp.add_event_handler('session_end', self._end) | ||||||
|  |  | ||||||
|         self.xmpp.add_event_handler('presence_available', self._recv_presence) |         self.xmpp.add_event_handler('presence_available', self._recv_presence) | ||||||
|         self.xmpp.add_event_handler('presence_dnd', self._recv_presence) |         self.xmpp.add_event_handler('presence_dnd', self._recv_presence) | ||||||
| @@ -44,59 +49,82 @@ class XEP_0153(BasePlugin): | |||||||
|  |  | ||||||
|         self.api.register(self._set_hash, 'set_hash', default=True) |         self.api.register(self._set_hash, 'set_hash', default=True) | ||||||
|         self.api.register(self._get_hash, 'get_hash', default=True) |         self.api.register(self._get_hash, 'get_hash', default=True) | ||||||
|  |         self.api.register(self._reset_hash, 'reset_hash', default=True) | ||||||
|  |  | ||||||
|     def set_avatar(self, jid=None, avatar=None, mtype=None, block=True,  |     def plugin_end(self): | ||||||
|  |         self.xmpp.del_filter('out', self._update_presence) | ||||||
|  |         self.xmpp.del_event_handler('session_start', self._start) | ||||||
|  |         self.xmpp.del_event_handler('session_end', self._end) | ||||||
|  |         self.xmpp.del_event_handler('presence_available', self._recv_presence) | ||||||
|  |         self.xmpp.del_event_handler('presence_dnd', self._recv_presence) | ||||||
|  |         self.xmpp.del_event_handler('presence_xa', self._recv_presence) | ||||||
|  |         self.xmpp.del_event_handler('presence_chat', self._recv_presence) | ||||||
|  |         self.xmpp.del_event_handler('presence_away', self._recv_presence) | ||||||
|  |  | ||||||
|  |     def set_avatar(self, jid=None, avatar=None, mtype=None, block=True, | ||||||
|                    timeout=None, callback=None): |                    timeout=None, callback=None): | ||||||
|  |         if jid is None: | ||||||
|  |             jid = self.xmpp.boundjid.bare | ||||||
|  |  | ||||||
|         vcard = self.xmpp['xep_0054'].get_vcard(jid, cached=True) |         vcard = self.xmpp['xep_0054'].get_vcard(jid, cached=True) | ||||||
|         vcard = vcard['vcard_temp'] |         vcard = vcard['vcard_temp'] | ||||||
|         vcard['PHOTO']['TYPE'] = mtype |         vcard['PHOTO']['TYPE'] = mtype | ||||||
|         vcard['PHOTO']['BINVAL'] = avatar |         vcard['PHOTO']['BINVAL'] = avatar | ||||||
|  |  | ||||||
|         self.xmpp['xep_0054'].publish_vcard(jid=jid, vcard=vcard) |         self.xmpp['xep_0054'].publish_vcard(jid=jid, vcard=vcard) | ||||||
|         self._reset_hash(jid) |  | ||||||
|  |         self.api['reset_hash'](jid) | ||||||
|  |         self.xmpp.roster[jid].send_last_presence() | ||||||
|  |  | ||||||
|     def _start(self, event): |     def _start(self, event): | ||||||
|         self.xmpp['xep_0054'].get_vcard() |         vcard = self.xmpp['xep_0054'].get_vcard() | ||||||
|  |         self._allow_advertising.set() | ||||||
|  |  | ||||||
|  |     def _end(self, event): | ||||||
|  |         self._allow_advertising.clear() | ||||||
|  |  | ||||||
|     def _update_presence(self, stanza): |     def _update_presence(self, stanza): | ||||||
|         if not isinstance(stanza, Presence): |         if not isinstance(stanza, Presence): | ||||||
|             return stanza |             return stanza | ||||||
|  |  | ||||||
|  |         if stanza['type'] not in ('available', 'dnd', 'chat', 'away', 'xa'): | ||||||
|  |             return stanza | ||||||
|  |  | ||||||
|         current_hash = self.api['get_hash'](stanza['from']) |         current_hash = self.api['get_hash'](stanza['from']) | ||||||
|         stanza['vcard_temp_update']['photo'] = current_hash |         stanza['vcard_temp_update']['photo'] = current_hash | ||||||
|         return stanza |         return stanza | ||||||
|  |  | ||||||
|     def _reset_hash(self, jid=None): |     def _reset_hash(self, jid, node, ifrom, args): | ||||||
|         own_jid = (jid.bare == self.xmpp.boundjid.bare) |         own_jid = (jid.bare == self.xmpp.boundjid.bare) | ||||||
|         if self.xmpp.is_component: |         if self.xmpp.is_component: | ||||||
|             own_jid = (jid.domain == self.xmpp.boundjid.domain) |             own_jid = (jid.domain == self.xmpp.boundjid.domain) | ||||||
|       |  | ||||||
|         if jid is not None: |  | ||||||
|             jid = jid.bare |  | ||||||
|         self.api['set_hash'](jid, args=None) |         self.api['set_hash'](jid, args=None) | ||||||
|         if own_jid: |         if own_jid: | ||||||
|             self.xmpp.roster[jid].send_last_presence() |             self.xmpp.roster[jid].send_last_presence() | ||||||
|  |  | ||||||
|         iq = self.xmpp['xep_0054'].get_vcard( |         iq = self.xmpp['xep_0054'].get_vcard(jid=jid.bare, ifrom=ifrom) | ||||||
|                 jid=jid,  |  | ||||||
|                 ifrom=self.xmpp.boundjid) |  | ||||||
|         data = iq['vcard_temp']['PHOTO']['BINVAL'] |         data = iq['vcard_temp']['PHOTO']['BINVAL'] | ||||||
|         if not data: |         if not data: | ||||||
|             new_hash = '' |             new_hash = '' | ||||||
|         else: |         else: | ||||||
|             new_hash = hashlib.sha1(data).hexdigest() |             new_hash = hashlib.sha1(data).hexdigest() | ||||||
|  |  | ||||||
|         self.api['set_hash'](jid, args=new_hash) |         self.api['set_hash'](jid, args=new_hash) | ||||||
|         if own_jid: |  | ||||||
|             self.xmpp.roster[jid].send_last_presence() |  | ||||||
|  |  | ||||||
|     def _recv_presence(self, pres): |     def _recv_presence(self, pres): | ||||||
|         if not pres.match('presence/vcard_temp_update'): |         if not pres.match('presence/vcard_temp_update'): | ||||||
|             self.api['set_hash'](pres['from'], args=None) |             self.api['set_hash'](pres['from'], args=None) | ||||||
|             return |             return | ||||||
|  |  | ||||||
|         data = pres['vcard_temp_update']['photo'] |         data = pres['vcard_temp_update']['photo'] | ||||||
|         if data is None: |         if data is None: | ||||||
|             return |             return | ||||||
|         elif data == '' or data != self.api['get_hash'](pres['to']): |         elif data == '' or data != self.api['get_hash'](pres['to']): | ||||||
|             self._reset_hash(pres['from']) |             ifrom = pres['to'] if self.xmpp.is_component else None | ||||||
|  |             self.api['reset_hash'](pres['from'], ifrom=ifrom) | ||||||
|  |             self.xmpp.event('vcard_avatar_update', pres) | ||||||
|  |  | ||||||
|     # ================================================================= |     # ================================================================= | ||||||
|  |  | ||||||
|   | |||||||
| @@ -56,7 +56,7 @@ class XEP_0163(BasePlugin): | |||||||
|             jid       -- Optionally specify the JID. |             jid       -- Optionally specify the JID. | ||||||
|         """ |         """ | ||||||
|         if not isinstance(namespace, set) and not isinstance(namespace, list): |         if not isinstance(namespace, set) and not isinstance(namespace, list): | ||||||
|            namespace = [namespace] |             namespace = [namespace] | ||||||
|  |  | ||||||
|         for ns in namespace: |         for ns in namespace: | ||||||
|             self.xmpp['xep_0030'].add_feature('%s+notify' % ns, |             self.xmpp['xep_0030'].add_feature('%s+notify' % ns, | ||||||
| @@ -74,8 +74,8 @@ class XEP_0163(BasePlugin): | |||||||
|                          be a list of such namespaces. |                          be a list of such namespaces. | ||||||
|             jid       -- Optionally specify the JID. |             jid       -- Optionally specify the JID. | ||||||
|         """ |         """ | ||||||
|         if not isinstance(namespace, set) and not isinstance(namespace, list): |         if not isinstance(namespace, (set, list)): | ||||||
|            namespace = [namespace] |             namespace = [namespace] | ||||||
|  |  | ||||||
|         for ns in namespace: |         for ns in namespace: | ||||||
|             self.xmpp['xep_0030'].del_feature(jid=jid, |             self.xmpp['xep_0030'].del_feature(jid=jid, | ||||||
| @@ -109,6 +109,7 @@ class XEP_0163(BasePlugin): | |||||||
|             node = stanza.namespace |             node = stanza.namespace | ||||||
|  |  | ||||||
|         return self.xmpp['xep_0060'].publish(ifrom, node, |         return self.xmpp['xep_0060'].publish(ifrom, node, | ||||||
|  |                 id=id, | ||||||
|                 payload=stanza.xml, |                 payload=stanza.xml, | ||||||
|                 options=options, |                 options=options, | ||||||
|                 ifrom=ifrom, |                 ifrom=ifrom, | ||||||
|   | |||||||
| @@ -34,6 +34,12 @@ class XEP_0172(BasePlugin): | |||||||
|     def plugin_init(self): |     def plugin_init(self): | ||||||
|         register_stanza_plugin(Message, UserNick) |         register_stanza_plugin(Message, UserNick) | ||||||
|         register_stanza_plugin(Presence, UserNick) |         register_stanza_plugin(Presence, UserNick) | ||||||
|  |  | ||||||
|  |     def plugin_end(self): | ||||||
|  |         self.xmpp['xep_0030'].del_feature(feature=UserNick.namespace) | ||||||
|  |         self.xmpp['xep_0163'].remove_interest(UserNick.namespace) | ||||||
|  |  | ||||||
|  |     def session_bind(self, jid): | ||||||
|         self.xmpp['xep_0163'].register_pep('user_nick', UserNick) |         self.xmpp['xep_0163'].register_pep('user_nick', UserNick) | ||||||
|  |  | ||||||
|     def publish_nick(self, nick=None, options=None, ifrom=None, block=True, |     def publish_nick(self, nick=None, options=None, ifrom=None, block=True, | ||||||
| @@ -78,7 +84,7 @@ class XEP_0172(BasePlugin): | |||||||
|                         be executed when a reply stanza is received. |                         be executed when a reply stanza is received. | ||||||
|         """ |         """ | ||||||
|         nick = UserNick() |         nick = UserNick() | ||||||
|         return self.xmpp['xep_0163'].publish(nick,  |         return self.xmpp['xep_0163'].publish(nick, | ||||||
|                 node=UserNick.namespace, |                 node=UserNick.namespace, | ||||||
|                 ifrom=ifrom, |                 ifrom=ifrom, | ||||||
|                 block=block, |                 block=block, | ||||||
|   | |||||||
| @@ -26,13 +26,14 @@ class XEP_0184(BasePlugin): | |||||||
|     description = 'XEP-0184: Message Delivery Receipts' |     description = 'XEP-0184: Message Delivery Receipts' | ||||||
|     dependencies = set(['xep_0030']) |     dependencies = set(['xep_0030']) | ||||||
|     stanza = stanza |     stanza = stanza | ||||||
|  |     default_config = { | ||||||
|  |         'auto_ack': True, | ||||||
|  |         'auto_request': False | ||||||
|  |     } | ||||||
|  |  | ||||||
|     ack_types = ('normal', 'chat', 'headline') |     ack_types = ('normal', 'chat', 'headline') | ||||||
|  |  | ||||||
|     def plugin_init(self): |     def plugin_init(self): | ||||||
|         self.auto_ack = self.config.get('auto_ack', True) |  | ||||||
|         self.auto_request = self.config.get('auto_request', False) |  | ||||||
|  |  | ||||||
|         register_stanza_plugin(Message, Request) |         register_stanza_plugin(Message, Request) | ||||||
|         register_stanza_plugin(Message, Received) |         register_stanza_plugin(Message, Received) | ||||||
|  |  | ||||||
| @@ -48,6 +49,13 @@ class XEP_0184(BasePlugin): | |||||||
|                     StanzaPath('message/request_receipt'), |                     StanzaPath('message/request_receipt'), | ||||||
|                     self._handle_receipt_request)) |                     self._handle_receipt_request)) | ||||||
|  |  | ||||||
|  |     def plugin_end(self): | ||||||
|  |         self.xmpp['xep_0030'].del_feature('urn:xmpp:receipts') | ||||||
|  |         self.xmpp.del_filter('out', self._filter_add_receipt_request) | ||||||
|  |         self.xmpp.remove_handler('Message Receipt') | ||||||
|  |         self.xmpp.remove_handler('Message Receipt Request') | ||||||
|  |  | ||||||
|  |     def session_bind(self, jid): | ||||||
|         self.xmpp['xep_0030'].add_feature('urn:xmpp:receipts') |         self.xmpp['xep_0030'].add_feature('urn:xmpp:receipts') | ||||||
|  |  | ||||||
|     def ack(self, msg): |     def ack(self, msg): | ||||||
| @@ -61,7 +69,7 @@ class XEP_0184(BasePlugin): | |||||||
|         ack['to'] = msg['from'] |         ack['to'] = msg['from'] | ||||||
|         ack['from'] = msg['to'] |         ack['from'] = msg['to'] | ||||||
|         ack['receipt'] = msg['id'] |         ack['receipt'] = msg['id'] | ||||||
|         ack['id'] = self.xmpp.new_id() |         ack['id'] = msg['id'] | ||||||
|         ack.send() |         ack.send() | ||||||
|  |  | ||||||
|     def _handle_receipt_received(self, msg): |     def _handle_receipt_received(self, msg): | ||||||
| @@ -100,13 +108,13 @@ class XEP_0184(BasePlugin): | |||||||
|  |  | ||||||
|         if not isinstance(stanza, Message): |         if not isinstance(stanza, Message): | ||||||
|             return stanza |             return stanza | ||||||
|                  |  | ||||||
|         if stanza['request_receipt']: |         if stanza['request_receipt']: | ||||||
|             return stanza |             return stanza | ||||||
|  |  | ||||||
|         if not stanza['type'] in self.ack_types: |         if not stanza['type'] in self.ack_types: | ||||||
|             return stanza |             return stanza | ||||||
|                      |  | ||||||
|         if stanza['receipt']: |         if stanza['receipt']: | ||||||
|             return stanza |             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() | ||||||
							
								
								
									
										15
									
								
								sleekxmpp/plugins/xep_0191/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								sleekxmpp/plugins/xep_0191/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,15 @@ | |||||||
|  | """ | ||||||
|  |     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_0191.stanza import Block, Unblock, BlockList | ||||||
|  | from sleekxmpp.plugins.xep_0191.blocking import XEP_0191 | ||||||
|  |  | ||||||
|  |  | ||||||
|  | register_plugin(XEP_0191) | ||||||
							
								
								
									
										83
									
								
								sleekxmpp/plugins/xep_0191/blocking.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										83
									
								
								sleekxmpp/plugins/xep_0191/blocking.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,83 @@ | |||||||
|  | """ | ||||||
|  |     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.handler import Callback | ||||||
|  | from sleekxmpp.xmlstream.matcher import StanzaPath | ||||||
|  | from sleekxmpp.xmlstream import register_stanza_plugin, JID | ||||||
|  | from sleekxmpp.plugins.xep_0191 import stanza, Block, Unblock, BlockList | ||||||
|  |  | ||||||
|  |  | ||||||
|  | log = logging.getLogger(__name__) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class XEP_0191(BasePlugin): | ||||||
|  |  | ||||||
|  |     name = 'xep_0191' | ||||||
|  |     description = 'XEP-0191: Blocking Command' | ||||||
|  |     dependencies = set(['xep_0030']) | ||||||
|  |     stanza = stanza | ||||||
|  |  | ||||||
|  |     def plugin_init(self): | ||||||
|  |         register_stanza_plugin(Iq, BlockList) | ||||||
|  |         register_stanza_plugin(Iq, Block) | ||||||
|  |         register_stanza_plugin(Iq, Unblock) | ||||||
|  |  | ||||||
|  |         self.xmpp.register_handler( | ||||||
|  |                 Callback('Blocked Contact', | ||||||
|  |                     StanzaPath('iq@type=set/block'), | ||||||
|  |                     self._handle_blocked)) | ||||||
|  |  | ||||||
|  |         self.xmpp.register_handler( | ||||||
|  |                 Callback('Unblocked Contact', | ||||||
|  |                     StanzaPath('iq@type=set/unblock'), | ||||||
|  |                     self._handle_unblocked)) | ||||||
|  |  | ||||||
|  |     def plugin_end(self): | ||||||
|  |         self.xmpp.remove_handler('Blocked Contact') | ||||||
|  |         self.xmpp.remove_handler('Unblocked Contact') | ||||||
|  |  | ||||||
|  |     def get_blocked(self, ifrom=None, block=True, timeout=None, callback=None): | ||||||
|  |         iq = self.xmpp.Iq() | ||||||
|  |         iq['type'] = 'get' | ||||||
|  |         iq['from'] = 'ifrom' | ||||||
|  |         iq.enable('blocklist') | ||||||
|  |         return iq.send(block=block, timeout=timeout, callback=callback) | ||||||
|  |  | ||||||
|  |     def block(self, jids, ifrom=None, block=True, timeout=None, callback=None): | ||||||
|  |         iq = self.xmpp.Iq() | ||||||
|  |         iq['type'] = 'set' | ||||||
|  |         iq['from'] = ifrom | ||||||
|  |  | ||||||
|  |         if not isinstance(jids, (set, list)): | ||||||
|  |             jids = [jids] | ||||||
|  |  | ||||||
|  |         iq['block']['items'] = jids | ||||||
|  |         return iq.send(block=block, timeout=timeout, callback=callback) | ||||||
|  |  | ||||||
|  |     def unblock(self, jids=None, ifrom=None, block=True, timeout=None, callback=None): | ||||||
|  |         iq = self.xmpp.Iq() | ||||||
|  |         iq['type'] = 'set' | ||||||
|  |         iq['from'] = ifrom | ||||||
|  |  | ||||||
|  |         if jids is None: | ||||||
|  |             jids = [] | ||||||
|  |         if not isinstance(jids, (set, list)): | ||||||
|  |             jids = [jids] | ||||||
|  |  | ||||||
|  |         iq['unblock']['items'] = jids | ||||||
|  |         return iq.send(block=block, timeout=timeout, callback=callback) | ||||||
|  |  | ||||||
|  |     def _handle_blocked(self, iq): | ||||||
|  |         self.xmpp.event('blocked', iq) | ||||||
|  |  | ||||||
|  |     def _handle_unblocked(self, iq): | ||||||
|  |         self.xmpp.event('unblocked', iq) | ||||||
							
								
								
									
										50
									
								
								sleekxmpp/plugins/xep_0191/stanza.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								sleekxmpp/plugins/xep_0191/stanza.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,50 @@ | |||||||
|  | """ | ||||||
|  |     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 ET, ElementBase, JID | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class BlockList(ElementBase): | ||||||
|  |     name = 'blocklist' | ||||||
|  |     namespace = 'urn:xmpp:blocking' | ||||||
|  |     plugin_attrib = 'blocklist' | ||||||
|  |     interfaces = set(['items']) | ||||||
|  |  | ||||||
|  |     def get_items(self): | ||||||
|  |         result = set() | ||||||
|  |         items = self.xml.findall('{%s}item' % self.namespace) | ||||||
|  |         if items is not None: | ||||||
|  |             for item in items: | ||||||
|  |                 jid = JID(item.attrib.get('jid', '')) | ||||||
|  |                 if jid: | ||||||
|  |                     result.add(jid) | ||||||
|  |         return result | ||||||
|  |  | ||||||
|  |     def set_items(self, values): | ||||||
|  |         self.del_items() | ||||||
|  |         for jid in values: | ||||||
|  |             if jid: | ||||||
|  |                 item = ET.Element('{%s}item' % self.namespace) | ||||||
|  |                 item.attrib['jid'] = JID(jid).full | ||||||
|  |                 self.xml.append(item) | ||||||
|  |  | ||||||
|  |     def del_items(self): | ||||||
|  |         items = self.xml.findall('{%s}item' % self.namespace) | ||||||
|  |         if items is not None: | ||||||
|  |             for item in items: | ||||||
|  |                 self.xml.remove(item) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class Block(BlockList): | ||||||
|  |     name = 'block' | ||||||
|  |     plugin_attrib = 'block' | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class Unblock(BlockList): | ||||||
|  |     name = 'unblock' | ||||||
|  |     plugin_attrib = 'unblock' | ||||||
| @@ -82,7 +82,6 @@ class Resumed(StanzaBase): | |||||||
|         self._set_attr('h', str(val)) |         self._set_attr('h', str(val)) | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class Failed(StanzaBase, Error): | class Failed(StanzaBase, Error): | ||||||
|     name = 'failed' |     name = 'failed' | ||||||
|     namespace = 'urn:xmpp:sm:3' |     namespace = 'urn:xmpp:sm:3' | ||||||
| @@ -106,7 +105,7 @@ class StreamManagement(ElementBase): | |||||||
|         self.del_required() |         self.del_required() | ||||||
|         if val: |         if val: | ||||||
|             self._set_sub_text('required', '', keep=True) |             self._set_sub_text('required', '', keep=True) | ||||||
|          |  | ||||||
|     def del_required(self): |     def del_required(self): | ||||||
|         self._del_sub('required') |         self._del_sub('required') | ||||||
|  |  | ||||||
| @@ -117,7 +116,7 @@ class StreamManagement(ElementBase): | |||||||
|         self.del_optional() |         self.del_optional() | ||||||
|         if val: |         if val: | ||||||
|             self._set_sub_text('optional', '', keep=True) |             self._set_sub_text('optional', '', keep=True) | ||||||
|          |  | ||||||
|     def del_optional(self): |     def del_optional(self): | ||||||
|         self._del_sub('optional') |         self._del_sub('optional') | ||||||
|  |  | ||||||
|   | |||||||
| @@ -21,7 +21,7 @@ from sleekxmpp.plugins.xep_0198 import stanza | |||||||
| log = logging.getLogger(__name__) | log = logging.getLogger(__name__) | ||||||
|  |  | ||||||
|  |  | ||||||
| MAX_SEQ = 2**32 | MAX_SEQ = 2 ** 32 | ||||||
|  |  | ||||||
|  |  | ||||||
| class XEP_0198(BasePlugin): | class XEP_0198(BasePlugin): | ||||||
| @@ -34,6 +34,32 @@ class XEP_0198(BasePlugin): | |||||||
|     description = 'XEP-0198: Stream Management' |     description = 'XEP-0198: Stream Management' | ||||||
|     dependencies = set() |     dependencies = set() | ||||||
|     stanza = stanza |     stanza = stanza | ||||||
|  |     default_config = { | ||||||
|  |         #: The last ack number received from the server. | ||||||
|  |         'last_ack': 0, | ||||||
|  |  | ||||||
|  |         #: The number of stanzas to wait between sending ack requests to | ||||||
|  |         #: the server. Setting this to ``1`` will send an ack request after | ||||||
|  |         #: every sent stanza. Defaults to ``5``. | ||||||
|  |         'window': 5, | ||||||
|  |  | ||||||
|  |         #: The stream management ID for the stream. Knowing this value is | ||||||
|  |         #: required in order to do stream resumption. | ||||||
|  |         'sm_id': None, | ||||||
|  |  | ||||||
|  |         #: A counter of handled incoming stanzas, mod 2^32. | ||||||
|  |         'handled': 0, | ||||||
|  |  | ||||||
|  |         #: A counter of unacked outgoing stanzas, mod 2^32. | ||||||
|  |         'seq': 0, | ||||||
|  |  | ||||||
|  |         #: Control whether or not the ability to resume the stream will be | ||||||
|  |         #: requested when enabling stream management. Defaults to ``True``. | ||||||
|  |         'allow_resume': True, | ||||||
|  |  | ||||||
|  |         'order': 10100, | ||||||
|  |         'resume_order': 9000 | ||||||
|  |     } | ||||||
|  |  | ||||||
|     def plugin_init(self): |     def plugin_init(self): | ||||||
|         """Start the XEP-0198 plugin.""" |         """Start the XEP-0198 plugin.""" | ||||||
| @@ -43,33 +69,12 @@ class XEP_0198(BasePlugin): | |||||||
|         if self.xmpp.is_component: |         if self.xmpp.is_component: | ||||||
|             return |             return | ||||||
|  |  | ||||||
|         #: The stream management ID for the stream. Knowing this value is |  | ||||||
|         #: required in order to do stream resumption. |  | ||||||
|         self.sm_id = self.config.get('sm_id', None) |  | ||||||
|  |  | ||||||
|         #: A counter of handled incoming stanzas, mod 2^32. |  | ||||||
|         self.handled = self.config.get('handled', 0) |  | ||||||
|  |  | ||||||
|         #: A counter of unacked outgoing stanzas, mod 2^32. |  | ||||||
|         self.seq = self.config.get('seq', 0) |  | ||||||
|  |  | ||||||
|         #: The last ack number received from the server. |  | ||||||
|         self.last_ack = self.config.get('last_ack', 0) |  | ||||||
|  |  | ||||||
|         #: The number of stanzas to wait between sending ack requests to |  | ||||||
|         #: the server. Setting this to ``1`` will send an ack request after |  | ||||||
|         #: every sent stanza. Defaults to ``5``. |  | ||||||
|         self.window = self.config.get('window', 5) |  | ||||||
|         self.window_counter = self.window |         self.window_counter = self.window | ||||||
|         self.window_counter_lock = threading.Lock() |         self.window_counter_lock = threading.Lock() | ||||||
|  |  | ||||||
|         #: Control whether or not the ability to resume the stream will be |  | ||||||
|         #: requested when enabling stream management. Defaults to ``True``. |  | ||||||
|         self.allow_resume = self.config.get('allow_resume', True) |  | ||||||
|  |  | ||||||
|         self.enabled = threading.Event() |         self.enabled = threading.Event() | ||||||
|         self.unacked_queue = collections.deque() |         self.unacked_queue = collections.deque() | ||||||
|      |  | ||||||
|         self.seq_lock = threading.Lock() |         self.seq_lock = threading.Lock() | ||||||
|         self.handled_lock = threading.Lock() |         self.handled_lock = threading.Lock() | ||||||
|         self.ack_lock = threading.Lock() |         self.ack_lock = threading.Lock() | ||||||
| @@ -92,11 +97,11 @@ class XEP_0198(BasePlugin): | |||||||
|         self.xmpp.register_feature('sm', |         self.xmpp.register_feature('sm', | ||||||
|                 self._handle_sm_feature, |                 self._handle_sm_feature, | ||||||
|                 restart=True, |                 restart=True, | ||||||
|                 order=self.config.get('order', 10100)) |                 order=self.order) | ||||||
|         self.xmpp.register_feature('sm', |         self.xmpp.register_feature('sm', | ||||||
|                 self._handle_sm_feature, |                 self._handle_sm_feature, | ||||||
|                 restart=True, |                 restart=True, | ||||||
|                 order=self.config.get('resume_order', 9000)) |                 order=self.resume_order) | ||||||
|  |  | ||||||
|         self.xmpp.register_handler( |         self.xmpp.register_handler( | ||||||
|                 Callback('Stream Management Enabled', |                 Callback('Stream Management Enabled', | ||||||
| @@ -133,6 +138,27 @@ class XEP_0198(BasePlugin): | |||||||
|  |  | ||||||
|         self.xmpp.add_event_handler('session_end', self.session_end) |         self.xmpp.add_event_handler('session_end', self.session_end) | ||||||
|  |  | ||||||
|  |     def plugin_end(self): | ||||||
|  |         if self.xmpp.is_component: | ||||||
|  |             return | ||||||
|  |  | ||||||
|  |         self.xmpp.unregister_feature('sm', self.order) | ||||||
|  |         self.xmpp.unregister_feature('sm', self.resume_order) | ||||||
|  |         self.xmpp.del_event_handler('session_end', self.session_end) | ||||||
|  |         self.xmpp.del_filter('in', self._handle_incoming) | ||||||
|  |         self.xmpp.del_filter('out_sync', self._handle_outgoing) | ||||||
|  |         self.xmpp.remove_handler('Stream Management Enabled') | ||||||
|  |         self.xmpp.remove_handler('Stream Management Resumed') | ||||||
|  |         self.xmpp.remove_handler('Stream Management Failed') | ||||||
|  |         self.xmpp.remove_handler('Stream Management Ack') | ||||||
|  |         self.xmpp.remove_handler('Stream Management Request Ack') | ||||||
|  |         self.xmpp.remove_stanza(stanza.Enable) | ||||||
|  |         self.xmpp.remove_stanza(stanza.Enabled) | ||||||
|  |         self.xmpp.remove_stanza(stanza.Resume) | ||||||
|  |         self.xmpp.remove_stanza(stanza.Resumed) | ||||||
|  |         self.xmpp.remove_stanza(stanza.Ack) | ||||||
|  |         self.xmpp.remove_stanza(stanza.RequestAck) | ||||||
|  |  | ||||||
|     def session_end(self, event): |     def session_end(self, event): | ||||||
|         """Reset stream management state.""" |         """Reset stream management state.""" | ||||||
|         self.enabled.clear() |         self.enabled.clear() | ||||||
| @@ -197,7 +223,7 @@ class XEP_0198(BasePlugin): | |||||||
|  |  | ||||||
|     def _handle_enabled(self, stanza): |     def _handle_enabled(self, stanza): | ||||||
|         """Save the SM-ID, if provided. |         """Save the SM-ID, if provided. | ||||||
|          |  | ||||||
|         Raises an :term:`sm_enabled` event. |         Raises an :term:`sm_enabled` event. | ||||||
|         """ |         """ | ||||||
|         self.xmpp.features.add('stream_management') |         self.xmpp.features.add('stream_management') | ||||||
| @@ -231,7 +257,7 @@ class XEP_0198(BasePlugin): | |||||||
|  |  | ||||||
|     def _handle_ack(self, ack): |     def _handle_ack(self, ack): | ||||||
|         """Process a server ack by freeing acked stanzas from the queue. |         """Process a server ack by freeing acked stanzas from the queue. | ||||||
|          |  | ||||||
|         Raises a :term:`stanza_acked` event for each acked stanza. |         Raises a :term:`stanza_acked` event for each acked stanza. | ||||||
|         """ |         """ | ||||||
|         if ack['h'] == self.last_ack: |         if ack['h'] == self.last_ack: | ||||||
| @@ -243,10 +269,10 @@ class XEP_0198(BasePlugin): | |||||||
|             log.debug("Ack: %s, Last Ack: %s, " + \ |             log.debug("Ack: %s, Last Ack: %s, " + \ | ||||||
|                       "Unacked: %s, Num Acked: %s, " + \ |                       "Unacked: %s, Num Acked: %s, " + \ | ||||||
|                       "Remaining: %s", |                       "Remaining: %s", | ||||||
|                 ack['h'],  |                 ack['h'], | ||||||
|                 self.last_ack,  |                 self.last_ack, | ||||||
|                 num_unacked, |                 num_unacked, | ||||||
|                 num_acked,  |                 num_acked, | ||||||
|                 num_unacked - num_acked) |                 num_unacked - num_acked) | ||||||
|             for x in range(num_acked): |             for x in range(num_acked): | ||||||
|                 seq, stanza = self.unacked_queue.popleft() |                 seq, stanza = self.unacked_queue.popleft() | ||||||
|   | |||||||
| @@ -51,15 +51,16 @@ class XEP_0199(BasePlugin): | |||||||
|     description = 'XEP-0199: XMPP Ping' |     description = 'XEP-0199: XMPP Ping' | ||||||
|     dependencies = set(['xep_0030']) |     dependencies = set(['xep_0030']) | ||||||
|     stanza = stanza |     stanza = stanza | ||||||
|  |     default_config = { | ||||||
|  |         'keepalive': False, | ||||||
|  |         'frequency': 300, | ||||||
|  |         'timeout': 30 | ||||||
|  |     } | ||||||
|  |  | ||||||
|     def plugin_init(self): |     def plugin_init(self): | ||||||
|         """ |         """ | ||||||
|         Start the XEP-0199 plugin. |         Start the XEP-0199 plugin. | ||||||
|         """ |         """ | ||||||
|         self.keepalive = self.config.get('keepalive', False) |  | ||||||
|         self.frequency = float(self.config.get('frequency', 300)) |  | ||||||
|         self.timeout = self.config.get('timeout', 30) |  | ||||||
|  |  | ||||||
|         register_stanza_plugin(Iq, Ping) |         register_stanza_plugin(Iq, Ping) | ||||||
|  |  | ||||||
|         self.xmpp.register_handler( |         self.xmpp.register_handler( | ||||||
| @@ -74,6 +75,16 @@ class XEP_0199(BasePlugin): | |||||||
|             self.xmpp.add_event_handler('session_end', |             self.xmpp.add_event_handler('session_end', | ||||||
|                                         self._handle_session_end) |                                         self._handle_session_end) | ||||||
|  |  | ||||||
|  |     def plugin_end(self): | ||||||
|  |         self.xmpp['xep_0030'].del_feature(feature=Ping.namespace) | ||||||
|  |         self.xmpp.remove_handler('Ping') | ||||||
|  |         if self.keepalive: | ||||||
|  |             self.xmpp.del_event_handler('session_start', | ||||||
|  |                                         self._handle_keepalive) | ||||||
|  |             self.xmpp.del_event_handler('session_end', | ||||||
|  |                                         self._handle_session_end) | ||||||
|  |  | ||||||
|  |     def session_bind(self, jid): | ||||||
|         self.xmpp['xep_0030'].add_feature(Ping.namespace) |         self.xmpp['xep_0030'].add_feature(Ping.namespace) | ||||||
|  |  | ||||||
|     def _handle_keepalive(self, event): |     def _handle_keepalive(self, event): | ||||||
|   | |||||||
| @@ -30,18 +30,23 @@ class XEP_0202(BasePlugin): | |||||||
|     description = 'XEP-0202: Entity Time' |     description = 'XEP-0202: Entity Time' | ||||||
|     dependencies = set(['xep_0030', 'xep_0082']) |     dependencies = set(['xep_0030', 'xep_0082']) | ||||||
|     stanza = stanza |     stanza = stanza | ||||||
|  |     default_config = { | ||||||
|  |         #: As a default, respond to time requests with the | ||||||
|  |         #: local time returned by XEP-0082. However, a | ||||||
|  |         #: custom function can be supplied which accepts | ||||||
|  |         #: the JID of the entity to query for the time. | ||||||
|  |         'local_time': None, | ||||||
|  |         'tz_offset': 0 | ||||||
|  |     } | ||||||
|  |  | ||||||
|     def plugin_init(self): |     def plugin_init(self): | ||||||
|         """Start the XEP-0203 plugin.""" |         """Start the XEP-0203 plugin.""" | ||||||
|         self.tz_offset = self.config.get('tz_offset', 0) |  | ||||||
|  |  | ||||||
|         # As a default, respond to time requests with the |  | ||||||
|         # local time returned by XEP-0082. However, a |  | ||||||
|         # 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) |  | ||||||
|         if not self.local_time: |         if not self.local_time: | ||||||
|             self.local_time = lambda x: xep_0082.datetime(offset=self.tz_offset) |             def default_local_time(jid): | ||||||
|  |                 return xep_0082.datetime(offset=self.tz_offset) | ||||||
|  |  | ||||||
|  |             self.local_time = default_local_time | ||||||
|  |  | ||||||
|         self.xmpp.registerHandler( |         self.xmpp.registerHandler( | ||||||
|             Callback('Entity Time', |             Callback('Entity Time', | ||||||
| @@ -49,6 +54,11 @@ class XEP_0202(BasePlugin): | |||||||
|                  self._handle_time_request)) |                  self._handle_time_request)) | ||||||
|         register_stanza_plugin(Iq, stanza.EntityTime) |         register_stanza_plugin(Iq, stanza.EntityTime) | ||||||
|  |  | ||||||
|  |     def plugin_end(self): | ||||||
|  |         self.xmpp['xep_0030'].del_feature(feature='urn:xmpp:time') | ||||||
|  |         self.xmpp.remove_handler('Entity Time') | ||||||
|  |  | ||||||
|  |     def session_bind(self, jid): | ||||||
|         self.xmpp['xep_0030'].add_feature('urn:xmpp:time') |         self.xmpp['xep_0030'].add_feature('urn:xmpp:time') | ||||||
|  |  | ||||||
|     def _handle_time_request(self, iq): |     def _handle_time_request(self, iq): | ||||||
|   | |||||||
| @@ -13,9 +13,7 @@ from sleekxmpp.plugins.xep_0203.stanza import Delay | |||||||
| from sleekxmpp.plugins.xep_0203.delay import XEP_0203 | from sleekxmpp.plugins.xep_0203.delay import XEP_0203 | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| register_plugin(XEP_0203) | register_plugin(XEP_0203) | ||||||
|  |  | ||||||
|  |  | ||||||
| # Retain some backwards compatibility | # Retain some backwards compatibility | ||||||
| xep_0203 = XEP_0203 | 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 Public 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) | ||||||
| @@ -39,6 +39,11 @@ class XEP_0224(BasePlugin): | |||||||
|                     StanzaPath('message/attention'), |                     StanzaPath('message/attention'), | ||||||
|                     self._handle_attention)) |                     self._handle_attention)) | ||||||
|  |  | ||||||
|  |     def plugin_end(self): | ||||||
|  |         self.xmpp['xep_0030'].del_feature(feature=stanza.Attention.namespace) | ||||||
|  |         self.xmpp.remove_handler('Attention') | ||||||
|  |  | ||||||
|  |     def session_bind(self, jid): | ||||||
|         self.xmpp['xep_0030'].add_feature(stanza.Attention.namespace) |         self.xmpp['xep_0030'].add_feature(stanza.Attention.namespace) | ||||||
|  |  | ||||||
|     def request_attention(self, to, mfrom=None, mbody=''): |     def request_attention(self, to, mfrom=None, mbody=''): | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| """ | """ | ||||||
|     SleekXMPP: The Sleek XMPP Library |     SleekXMPP: The Sleek XMPP Library | ||||||
|     Copyright (C) 2012 Nathanael C. Fritz,  |     Copyright (C) 2012 Nathanael C. Fritz, | ||||||
|                        Emmanuel Gil Peyrot <linkmauve@linkmauve.fr> |                        Emmanuel Gil Peyrot <linkmauve@linkmauve.fr> | ||||||
|     This file is part of SleekXMPP. |     This file is part of SleekXMPP. | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| """ | """ | ||||||
|     SleekXMPP: The Sleek XMPP Library |     SleekXMPP: The Sleek XMPP Library | ||||||
|     Copyright (C) 2012 Nathanael C. Fritz,  |     Copyright (C) 2012 Nathanael C. Fritz, | ||||||
|                        Emmanuel Gil Peyrot <linkmauve@linkmauve.fr> |                        Emmanuel Gil Peyrot <linkmauve@linkmauve.fr> | ||||||
|     This file is part of SleekXMPP. |     This file is part of SleekXMPP. | ||||||
|  |  | ||||||
| @@ -35,8 +35,6 @@ class XEP_0231(BasePlugin): | |||||||
|     def plugin_init(self): |     def plugin_init(self): | ||||||
|         self._cids = {} |         self._cids = {} | ||||||
|  |  | ||||||
|         self.xmpp['xep_0030'].add_feature('urn:xmpp:bob') |  | ||||||
|  |  | ||||||
|         register_stanza_plugin(Iq, BitsOfBinary) |         register_stanza_plugin(Iq, BitsOfBinary) | ||||||
|  |  | ||||||
|         self.xmpp.register_handler( |         self.xmpp.register_handler( | ||||||
| @@ -58,6 +56,14 @@ class XEP_0231(BasePlugin): | |||||||
|         self.api.register(self._set_bob, 'set_bob', default=True) |         self.api.register(self._set_bob, 'set_bob', default=True) | ||||||
|         self.api.register(self._del_bob, 'del_bob', default=True) |         self.api.register(self._del_bob, 'del_bob', default=True) | ||||||
|  |  | ||||||
|  |     def plugin_end(self): | ||||||
|  |         self.xmpp['xep_0030'].del_feature(feature='urn:xmpp:bob') | ||||||
|  |         self.xmpp.remove_handler('Bits of Binary - Iq') | ||||||
|  |         self.xmpp.remove_handler('Bits of Binary - Message') | ||||||
|  |         self.xmpp.remove_handler('Bits of Binary - Presence') | ||||||
|  |  | ||||||
|  |     def session_bind(self, jid): | ||||||
|  |         self.xmpp['xep_0030'].add_feature('urn:xmpp:bob') | ||||||
|  |  | ||||||
|     def set_bob(self, data, mtype, cid=None, max_age=None): |     def set_bob(self, data, mtype, cid=None, max_age=None): | ||||||
|         if cid is None: |         if cid is None: | ||||||
| @@ -73,7 +79,7 @@ class XEP_0231(BasePlugin): | |||||||
|  |  | ||||||
|         return cid |         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): |                 block=True, timeout=None, callback=None): | ||||||
|         if cached: |         if cached: | ||||||
|             data = self.api['get_bob'](None, None, ifrom, args=cid) |             data = self.api['get_bob'](None, None, ifrom, args=cid) | ||||||
| @@ -112,7 +118,7 @@ class XEP_0231(BasePlugin): | |||||||
|             iq.send() |             iq.send() | ||||||
|  |  | ||||||
|     def _handle_bob(self, stanza): |     def _handle_bob(self, stanza): | ||||||
|         self.api['set_bob'](stanza['from'], None,  |         self.api['set_bob'](stanza['from'], None, | ||||||
|                             stanza['to'], args=stanza['bob']) |                             stanza['to'], args=stanza['bob']) | ||||||
|         self.xmpp.event('bob', stanza) |         self.xmpp.event('bob', stanza) | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| """ | """ | ||||||
|     SleekXMPP: The Sleek XMPP Library |     SleekXMPP: The Sleek XMPP Library | ||||||
|     Copyright (C) 2012 Nathanael C. Fritz,  |     Copyright (C) 2012 Nathanael C. Fritz, | ||||||
|                        Emmanuel Gil Peyrot <linkmauve@linkmauve.fr> |                        Emmanuel Gil Peyrot <linkmauve@linkmauve.fr> | ||||||
|     This file is part of SleekXMPP. |     This file is part of SleekXMPP. | ||||||
|  |  | ||||||
|   | |||||||
| @@ -39,6 +39,11 @@ class XEP_0249(BasePlugin): | |||||||
|  |  | ||||||
|         register_stanza_plugin(Message, Invite) |         register_stanza_plugin(Message, Invite) | ||||||
|  |  | ||||||
|  |     def plugin_end(self): | ||||||
|  |         self.xmpp['xep_0030'].del_feature(feature=Invite.namespace) | ||||||
|  |         self.xmpp.remove_handler('Direct MUC Invitations') | ||||||
|  |  | ||||||
|  |     def session_bind(self, jid): | ||||||
|         self.xmpp['xep_0030'].add_feature(Invite.namespace) |         self.xmpp['xep_0030'].add_feature(Invite.namespace) | ||||||
|  |  | ||||||
|     def _handle_invite(self, msg): |     def _handle_invite(self, msg): | ||||||
|   | |||||||
							
								
								
									
										73
									
								
								sleekxmpp/plugins/xep_0256.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										73
									
								
								sleekxmpp/plugins/xep_0256.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,73 @@ | |||||||
|  | """ | ||||||
|  |     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 Presence | ||||||
|  | from sleekxmpp.exceptions import XMPPError | ||||||
|  | from sleekxmpp.plugins import BasePlugin, register_plugin | ||||||
|  | from sleekxmpp.xmlstream import register_stanza_plugin | ||||||
|  |  | ||||||
|  | from sleekxmpp.plugins.xep_0012 import stanza, LastActivity | ||||||
|  |  | ||||||
|  |  | ||||||
|  | log = logging.getLogger(__name__) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class XEP_0256(BasePlugin): | ||||||
|  |  | ||||||
|  |     name = 'xep_0256' | ||||||
|  |     description = 'XEP-0256: Last Activity in Presence' | ||||||
|  |     dependencies = set(['xep_0012']) | ||||||
|  |     stanza = stanza | ||||||
|  |     default_config = { | ||||||
|  |         'auto_last_activity': False | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     def plugin_init(self): | ||||||
|  |         register_stanza_plugin(Presence, LastActivity) | ||||||
|  |  | ||||||
|  |         self.xmpp.add_filter('out', self._initial_presence_activity) | ||||||
|  |         self.xmpp.add_event_handler('connected', self._reset_presence_activity) | ||||||
|  |  | ||||||
|  |         self._initial_presence = set() | ||||||
|  |  | ||||||
|  |     def plugin_end(self): | ||||||
|  |         self.xmpp.del_filter('out', self._initial_presence_activity) | ||||||
|  |         self.xmpp.del_event_handler('connected', self._reset_presence_activity) | ||||||
|  |  | ||||||
|  |     def _reset_presence_activity(self, e): | ||||||
|  |         self._initial_presence = set() | ||||||
|  |  | ||||||
|  |     def _initial_presence_activity(self, stanza): | ||||||
|  |         if isinstance(stanza, Presence): | ||||||
|  |             use_last_activity = False | ||||||
|  |  | ||||||
|  |             if self.auto_last_activity and  stanza['show'] in ('xa', 'away'): | ||||||
|  |                 use_last_activity = True | ||||||
|  |  | ||||||
|  |             if stanza['from'] not in self._initial_presence: | ||||||
|  |                 self._initial_presence.add(stanza['from']) | ||||||
|  |                 use_last_activity = True | ||||||
|  |  | ||||||
|  |             if use_last_activity: | ||||||
|  |                 plugin = self.xmpp['xep_0012'] | ||||||
|  |                 try: | ||||||
|  |                     result = plugin.api['get_last_activity'](stanza['from'], | ||||||
|  |                                                              None, | ||||||
|  |                                                              stanza['to']) | ||||||
|  |                     seconds = result['last_activity']['seconds'] | ||||||
|  |                 except XMPPError: | ||||||
|  |                     seconds = None | ||||||
|  |  | ||||||
|  |                 if seconds is not None: | ||||||
|  |                     stanza['last_activity']['seconds'] = seconds | ||||||
|  |         return stanza | ||||||
|  |  | ||||||
|  |  | ||||||
|  | register_plugin(XEP_0256) | ||||||
							
								
								
									
										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) | ||||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user