#!/usr/bin/env python # -*- coding: utf-8 -*- """ SleekXMPP: The Sleek XMPP Library Copyright (C) 2010 Nathanael C. Fritz This file is part of SleekXMPP. See the file LICENSE for copying permission. """ import sys import logging import time from optparse import OptionParser import sleekxmpp from sleekxmpp.componentxmpp import ComponentXMPP from sleekxmpp.stanza.roster import Roster from sleekxmpp.xmlstream import ElementBase from sleekxmpp.xmlstream.stanzabase import ET, registerStanzaPlugin # Python versions before 3.0 do not use UTF-8 encoding # by default. To ensure that Unicode is handled properly # throughout SleekXMPP, we will set the default encoding # ourselves to UTF-8. if sys.version_info < (3, 0): reload(sys) sys.setdefaultencoding('utf8') class Config(ElementBase): """ In order to make loading and manipulating an XML config file easier, we will create a custom stanza object for our config XML file contents. See the documentation on stanza objects for more information on how to create and use stanza objects and stanza plugins. We will reuse the IQ roster query stanza to store roster information since it already exists. Example config XML: <config xmlns="sleekxmpp:config"> <jid>component.localhost</jid> <secret>ssshh</secret> <server>localhost</server> <port>8888</port> <query xmlns="jabber:iq:roster"> <item jid="user@example.com" subscription="both" /> </query> </config> """ name = "config" namespace = "sleekxmpp:config" interfaces = set(('jid', 'secret', 'server', 'port')) sub_interfaces = interfaces registerStanzaPlugin(Config, Roster) class ConfigComponent(ComponentXMPP): """ A simple SleekXMPP component that uses an external XML file to store its configuration data. To make testing that the component works, it will also echo messages sent to it. """ def __init__(self, config): """ Create a ConfigComponent. Arguments: config -- The XML contents of the config file. config_file -- The XML config file object itself. """ ComponentXMPP.__init__(self, config['jid'], config['secret'], config['server'], config['port']) # Store the roster information. self.roster = config['roster']['items'] # The session_start event will be triggered when # the component 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 # broadcast any needed initial presence stanzas. 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) def start(self, event): """ Process the session_start event. The typical action for the session_start event in a component is to broadcast presence stanzas to all subscribers to the component. Note that the component does not have a roster provided by the XMPP server. In this case, we have possibly saved a roster in the component's configuration file. Since the component may use any number of JIDs, you should also include the JID that is sending the presence. Arguments: event -- An empty dictionary. The session_start event does not provide any additional data. """ for jid in self.roster: if self.roster[jid]['subscription'] != 'none': self.sendPresence(pfrom=self.jid, pto=jid) 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. Since a component may send messages from any number of JIDs, it is best to always include a from JID. Arguments: msg -- The received message stanza. See the documentation for stanza objects and the Message stanza to see how it may be used. """ # The reply method will use the messages 'to' JID as the # outgoing reply's 'from' JID. 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) # Component name and secret options. optp.add_option("-c", "--config", help="path to config file", dest="config", default="config.xml") opts, args = optp.parse_args() # Setup logging. logging.basicConfig(level=opts.loglevel, format='%(levelname)-8s %(message)s') # Load configuration data. config_file = open(opts.config, 'r+') config_data = "\n".join([line for line in config_file]) config = Config(xml=ET.fromstring(config_data)) config_file.close() # Setup the ConfigComponent and register plugins. Note that while plugins # may have interdependencies, the order in which you register them does # not matter. xmpp = ConfigComponent(config) xmpp.registerPlugin('xep_0030') # Service Discovery xmpp.registerPlugin('xep_0004') # Data Forms xmpp.registerPlugin('xep_0060') # PubSub xmpp.registerPlugin('xep_0199') # XMPP Ping # Connect to the XMPP server and start processing XMPP stanzas. if xmpp.connect(): xmpp.process(threaded=False) print("Done") else: print("Unable to connect.")