From 961668d420d2241541f4facc64265932c66ad81c Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Wed, 17 Aug 2011 21:21:37 -0700 Subject: [PATCH 1/5] Add guide for sending a message and then disconnecting. --- docs/api/basexmpp.rst | 8 ++ docs/api/xmlstream.rst | 8 ++ docs/features.rst | 2 + docs/getting_started/echobot.rst | 126 +--------------------------- docs/getting_started/sendlogout.rst | 95 ++++++++++++++++++++- docs/index.rst | 14 ++-- docs/plugin_arch.rst | 2 + examples/send_client.py | 53 +++++------- 8 files changed, 146 insertions(+), 162 deletions(-) create mode 100644 docs/api/basexmpp.rst create mode 100644 docs/api/xmlstream.rst create mode 100644 docs/features.rst create mode 100644 docs/plugin_arch.rst diff --git a/docs/api/basexmpp.rst b/docs/api/basexmpp.rst new file mode 100644 index 0000000..841df3d --- /dev/null +++ b/docs/api/basexmpp.rst @@ -0,0 +1,8 @@ +======== +basexmpp +======== + +.. module:: sleekxmpp.basexmpp + +.. autoclass:: BaseXMPP + :members: diff --git a/docs/api/xmlstream.rst b/docs/api/xmlstream.rst new file mode 100644 index 0000000..7835bf5 --- /dev/null +++ b/docs/api/xmlstream.rst @@ -0,0 +1,8 @@ +========= +xmlstream +========= + +.. module:: sleekxmpp.xmlstream + +.. autoclass:: XMLStream + :members: diff --git a/docs/features.rst b/docs/features.rst new file mode 100644 index 0000000..4d93d5c --- /dev/null +++ b/docs/features.rst @@ -0,0 +1,2 @@ +How to Use Stream Features +========================== diff --git a/docs/getting_started/echobot.rst b/docs/getting_started/echobot.rst index 9defca6..053a76f 100644 --- a/docs/getting_started/echobot.rst +++ b/docs/getting_started/echobot.rst @@ -1,3 +1,5 @@ +.. _echobot: + =============================== SleekXMPP Quickstart - Echo Bot =============================== @@ -386,127 +388,3 @@ can also be found in the SleekXMPP `examples directory `_ + or join the chat room at `sleek@conference.jabber.org + `_. A common use case for SleekXMPP is to send one-off messages from -time to time. +time to time. For example, one use case could be sending out a notice when +a shell script finishes a task. + +We will create our one-shot bot based on the pattern explained in :ref:`echobot`. To +start, we create a client class based on :class:`ClientXMPP ` and +register a handler for the :term:`session_start` event. We will also accept parameters +for the JID that will receive our message, and the string content of the message. + +.. code-block:: python + + import sleekxmpp + + + class SendMsgBot(sleekxmpp.ClientXMPP): + + def __init__(self, jid, password, recipient, msg): + super(SendMsgBot, self).__init__(jid, password) + + self.recipient = recipient + self.msg = msg + + self.add_event_handler('session_start', self.start) + + def start(self, event): + self.send_presence() + self.get_roster() + +Note that as in :ref:`echobot`, we need to include send an initial presence and request +the roster. Next, we want to send our message, and to do that we will use :meth:`send_message `. + +.. code-block:: python + + def start(self, event): + self.send_presence() + self.get_roster() + + self.send_message(mto=self.recipient, mbody=self.msg) + +Finally, we need to disconnect the client using :meth:`disconnect `. +Now, sent stanzas are placed in a queue to pass them to the send thread. If we were to call +:meth:`disconnect ` without any parameters, then it is possible +for the client to disconnect before the send queue is processed and the message is actually +sent on the wire. To ensure that our message is processed, we use +:meth:`disconnect(wait=True) `. + +.. code-block:: python + + def start(self, event): + self.send_presence() + self.get_roster() + + self.send_message(mto=self.recipient, mbody=self.msg) + + self.disconnect(wait=True) + +.. warning:: + + If you happen to be adding stanzas to the send queue faster than the send thread + can process them, then :meth:`disconnect(wait=True) ` + will block and not disconnect. + +Final Product +------------- + +.. compound:: + + The final step is to create a small runner script for initialising our ``SendMsgBot`` class and adding some + basic configuration options. By following the basic boilerplate pattern in :ref:`echobot`, we arrive + at the code below. To experiment with this example, you can use: + + .. code-block:: sh + + python send_client.py -d -j oneshot@example.com -t someone@example.net -m "This is a message" + + which will prompt for the password and then log in, send your message, and then disconnect. To test, open + your regular IM client with the account you wish to send messages to. When you run the ``send_client.py`` + example and instruct it to send your IM client account a message, you should receive the message you + gave. If the two JIDs you use also have a mutual presence subscription (they're on each other's buddy lists) + then you will also see the ``SendMsgBot`` client come online and then go offline. + +.. include:: ../../examples/send_client.py + :literal: diff --git a/docs/index.rst b/docs/index.rst index 95e4974..5da389b 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -67,7 +67,7 @@ SleekXMPP's design goals and philosphy are: Getting Started (with Examples) ------------------------------- .. toctree:: - :maxdepth: 2 + :maxdepth: 1 getting_started/echobot getting_started/sendlogout @@ -82,27 +82,29 @@ Getting Started (with Examples) Tutorials, FAQs, and How To Guides ---------------------------------- .. toctree:: - :maxdepth: 2 + :maxdepth: 1 xeps xmpp_tdg create_plugin + features sasl handlersmatchers Plugin Guides ~~~~~~~~~~~~~ .. toctree:: - :maxdepth: 2 + :maxdepth: 1 guide_xep_0030 SleekXMPP Architecture and Design --------------------------------- .. toctree:: - :maxdepth: 2 + :maxdepth: 3 - architecture.rst + architecture + plugin_arch API Reference ------------- @@ -111,6 +113,8 @@ API Reference event_index api/clientxmpp + api/basexmpp + api/xmlstream Additional Info --------------- diff --git a/docs/plugin_arch.rst b/docs/plugin_arch.rst new file mode 100644 index 0000000..0141b79 --- /dev/null +++ b/docs/plugin_arch.rst @@ -0,0 +1,2 @@ +Plugin Architecture +=================== diff --git a/examples/send_client.py b/examples/send_client.py index fd99e8c..b367351 100755 --- a/examples/send_client.py +++ b/examples/send_client.py @@ -29,13 +29,18 @@ if sys.version_info < (3, 0): class SendMsgBot(sleekxmpp.ClientXMPP): """ - A simple SleekXMPP bot that will echo messages it - receives, along with a short thank you message. + A basic SleekXMPP bot that will log in, send a message, + and then log out. """ - def __init__(self, jid, password): + def __init__(self, jid, password, recipient, message): sleekxmpp.ClientXMPP.__init__(self, jid, password) + # The message we wish to send, and the JID that + # will receive it. + self.recipient = recipient + self.msg = message + # 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 @@ -43,11 +48,6 @@ class SendMsgBot(sleekxmpp.ClientXMPP): # 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) - def start(self, event): """ Process the session_start event. @@ -63,27 +63,14 @@ class SendMsgBot(sleekxmpp.ClientXMPP): """ self.send_presence() self.get_roster() - msg = self.Message() - msg['to'] = 'user@example.com' - msg['type'] = 'chat' - msg['body'] = "Hello there!" - msg.send() - self.disconnect() - 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. + self.send_message(mto=self.recipient, + mbody=self.msg, + mtype='chat') - Arguments: - msg -- The received message stanza. See the documentation - for stanza objects and the Message stanza to see - how it may be used. - """ - #msg.reply("Thanks for sending\n%(body)s" % msg).send() - print "Msg rceived from %(body)s: %(jid)s" % msg + # Using wait=True ensures that the send queue will be + # emptied before ending the session. + self.disconnect(wait=True) if __name__ == '__main__': @@ -106,6 +93,10 @@ if __name__ == '__main__': help="JID to use") optp.add_option("-p", "--password", dest="password", help="password to use") + optp.add_option("-t", "--to", dest="to", + help="JID to send the message to") + optp.add_option("-m", "--message", dest="message", + help="message to send") opts, args = optp.parse_args() @@ -117,14 +108,16 @@ if __name__ == '__main__': opts.jid = raw_input("Username: ") if opts.password is None: opts.password = getpass.getpass("Password: ") + if opts.to is None: + opts.to = raw_input("Send To: ") + if opts.message is None: + opts.message = raw_input("Message: ") # Setup the EchoBot and register plugins. Note that while plugins may # have interdependencies, the order in which you register them does # not matter. - xmpp = SendMsgBot(opts.jid, opts.password) + xmpp = SendMsgBot(opts.jid, opts.password, opts.to, opts.message) 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 From 62230fc970f86b11bc74ee448e30cbe93f477e72 Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Wed, 17 Aug 2011 21:22:03 -0700 Subject: [PATCH 2/5] Return '' instead of None from form fields with no values. --- sleekxmpp/plugins/xep_0004/stanza/field.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sleekxmpp/plugins/xep_0004/stanza/field.py b/sleekxmpp/plugins/xep_0004/stanza/field.py index 43293bf..8131233 100644 --- a/sleekxmpp/plugins/xep_0004/stanza/field.py +++ b/sleekxmpp/plugins/xep_0004/stanza/field.py @@ -85,7 +85,7 @@ class FormField(ElementBase): return None elif self._type == 'boolean': return valsXML[0].text in self.true_values - elif self._type in self.multi_value_types: + elif self._type in self.multi_value_types or len(valsXML) > 1: values = [] for valXML in valsXML: if valXML.text is None: @@ -95,6 +95,8 @@ class FormField(ElementBase): values = "\n".join(values) return values else: + if valsXML[0].text is None: + return '' return valsXML[0].text def set_answer(self, answer): From 004eabf80959ebcaeddf2e15065bd4f50ad10974 Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Wed, 17 Aug 2011 21:30:47 -0700 Subject: [PATCH 3/5] Update plugins that use Iq stanzas to work with new exceptions. --- sleekxmpp/plugins/xep_0012.py | 5 +-- sleekxmpp/plugins/xep_0045.py | 42 +++++++++++++++++------- sleekxmpp/plugins/xep_0078/legacyauth.py | 39 ++++++++++++++-------- sleekxmpp/plugins/xep_0199/ping.py | 24 +++++++++----- sleekxmpp/roster/single.py | 6 ++-- 5 files changed, 75 insertions(+), 41 deletions(-) diff --git a/sleekxmpp/plugins/xep_0012.py b/sleekxmpp/plugins/xep_0012.py index d636d4d..8fe818b 100644 --- a/sleekxmpp/plugins/xep_0012.py +++ b/sleekxmpp/plugins/xep_0012.py @@ -112,7 +112,4 @@ class xep_0012(base.base_plugin): iq.attrib['from'] = self.xmpp.boundjid.full id = iq.get('id') result = iq.send() - if result and result is not None and result.get('type', 'error') != 'error': - return result['last_activity']['seconds'] - else: - return False + return result['last_activity']['seconds'] diff --git a/sleekxmpp/plugins/xep_0045.py b/sleekxmpp/plugins/xep_0045.py index 364fbbd..338ed15 100644 --- a/sleekxmpp/plugins/xep_0045.py +++ b/sleekxmpp/plugins/xep_0045.py @@ -188,8 +188,12 @@ class xep_0045(base.base_plugin): iq['from'] = ifrom query = ET.Element('{http://jabber.org/protocol/muc#owner}query') iq.append(query) - result = iq.send() - if result['type'] == 'error': + # For now, swallow errors to preserve existing API + try: + result = iq.send() + except IqError: + return False + except IqTimeout: return False xform = result.xml.find('{http://jabber.org/protocol/muc#owner}query/{jabber:x:data}x') if xform is None: return False @@ -209,8 +213,12 @@ class xep_0045(base.base_plugin): form = form.getXML('submit') query.append(form) iq.append(query) - result = iq.send() - if result['type'] == 'error': + # For now, swallow errors to preserve existing API + try: + result = iq.send() + except IqError: + return False + except IqTimeout: return False return True @@ -254,8 +262,12 @@ class xep_0045(base.base_plugin): destroy.append(xreason) query.append(destroy) iq.append(query) - r = iq.send() - if r is False or r['type'] == 'error': + # For now, swallow errors to preserve existing API + try: + r = iq.send() + except IqError: + return False + except IqTimeout: return False return True @@ -271,9 +283,13 @@ class xep_0045(base.base_plugin): query.append(item) iq = self.xmpp.makeIqSet(query) iq['to'] = room - result = iq.send() - if result is False or result['type'] != 'result': - raise ValueError + # For now, swallow errors to preserve existing API + try: + result = iq.send() + except IqError: + return False + except IqTimeout: + return False return True def invite(self, room, jid, reason='', mfrom=''): @@ -303,8 +319,12 @@ class xep_0045(base.base_plugin): iq = self.xmpp.makeIqGet('http://jabber.org/protocol/muc#owner') iq['to'] = room iq['from'] = ifrom - result = iq.send() - if result is None or result['type'] != 'result': + # For now, swallow errors to preserve existing API + try: + result = iq.send() + except IqError: + raise ValueError + except IqTimeout: raise ValueError form = result.xml.find('{http://jabber.org/protocol/muc#owner}query/{jabber:x:data}x') if form is None: diff --git a/sleekxmpp/plugins/xep_0078/legacyauth.py b/sleekxmpp/plugins/xep_0078/legacyauth.py index bdd2df6..edb8f31 100644 --- a/sleekxmpp/plugins/xep_0078/legacyauth.py +++ b/sleekxmpp/plugins/xep_0078/legacyauth.py @@ -56,11 +56,17 @@ class xep_0078(base_plugin): iq['type'] = 'get' iq['to'] = self.xmpp.boundjid.host iq['auth']['username'] = self.xmpp.boundjid.user - resp = iq.send(now=True) - if resp is None or resp['type'] != 'result': + try: + resp = iq.send(now=True) + except IqError: log.info("Authentication failed: %s" % resp['error']['condition']) - self.xmpp.event('failed_auth', resp, direct=True) + self.xmpp.event('failed_auth', direct=True) + self.xmpp.disconnect() + return True + except IqTimeout: + log.info("Authentication failed: %s" % 'timeout') + self.xmpp.event('failed_auth', direct=True) self.xmpp.disconnect() return True @@ -91,18 +97,23 @@ class xep_0078(base_plugin): iq['auth']['password'] = self.xmpp.password # Step 3: Send credentials - result = iq.send(now=True) - if result is not None and result.attrib['type'] == 'result': - self.xmpp.features.add('auth') - - self.xmpp.authenticated = True - log.debug("Established Session") - self.xmpp.sessionstarted = True - self.xmpp.session_started_event.set() - self.xmpp.event('session_start') - else: + try: + result = iq.send(now=True) + except IqError as err: log.info("Authentication failed") self.xmpp.disconnect() - self.xmpp.event("failed_auth") + self.xmpp.event("failed_auth", direct=True) + except IqTimeout: + log.info("Authentication failed") + self.xmpp.disconnect() + self.xmpp.event("failed_auth", direct=True) + + self.xmpp.features.add('auth') + + self.xmpp.authenticated = True + log.debug("Established Session") + self.xmpp.sessionstarted = True + self.xmpp.session_started_event.set() + self.xmpp.event('session_start') return True diff --git a/sleekxmpp/plugins/xep_0199/ping.py b/sleekxmpp/plugins/xep_0199/ping.py index 0fa22f8..b0304b0 100644 --- a/sleekxmpp/plugins/xep_0199/ping.py +++ b/sleekxmpp/plugins/xep_0199/ping.py @@ -11,6 +11,7 @@ import logging import sleekxmpp from sleekxmpp import Iq +from sleekxmpp.exceptions import IqError, IqTimeout from sleekxmpp.xmlstream import register_stanza_plugin from sleekxmpp.xmlstream.matcher import StanzaPath from sleekxmpp.xmlstream.handler import Callback @@ -89,8 +90,13 @@ class xep_0199(base_plugin): def scheduled_ping(): """Send ping request to the server.""" log.debug("Pinging...") - resp = self.send_ping(self.xmpp.boundjid.host, self.timeout) - if resp is None or resp is False: + try: + self.send_ping(self.xmpp.boundjid.host, self.timeout) + except IqError: + log.debug("Ping response was an error." + \ + "Requesting Reconnect.") + self.xmpp.reconnect() + except IqTimeout: log.debug("Did not recieve ping back in time." + \ "Requesting Reconnect.") self.xmpp.reconnect() @@ -142,9 +148,14 @@ class xep_0199(base_plugin): iq.enable('ping') start_time = time.clock() - resp = iq.send(block=block, - timeout=timeout, - callback=callback) + + try: + resp = iq.send(block=block, + timeout=timeout, + callback=callback) + except IqError as err: + resp = err.iq + end_time = time.clock() delay = end_time - start_time @@ -152,9 +163,6 @@ class xep_0199(base_plugin): if not block: return None - if not resp or resp['type'] == 'error': - return False - log.debug("Pong: %s %f" % (jid, delay)) return delay diff --git a/sleekxmpp/roster/single.py b/sleekxmpp/roster/single.py index deb1ac8..411c5d9 100644 --- a/sleekxmpp/roster/single.py +++ b/sleekxmpp/roster/single.py @@ -204,10 +204,8 @@ class RosterNode(object): iq['roster']['items'] = {jid: {'name': name, 'subscription': subscription, 'groups': groups}} - response = iq.send(block, timeout, callback) - if response in [False, None] or isinstance(response, Iq): - return response - return response and response['type'] == 'result' + + return iq.send(block, timeout, callback) def presence(self, jid, resource=None): """ From 3fc20e10f5ae009273cd2d751628b0dfc05f9e8d Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Thu, 18 Aug 2011 00:07:37 -0700 Subject: [PATCH 4/5] Add some convenience methods to rosters. Can now use len(self.client_roster) to get the number of JIDs in the roster, and self.client_roster.groups() to get a dict of groups and the JIDs in those groups. --- sleekxmpp/roster/single.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/sleekxmpp/roster/single.py b/sleekxmpp/roster/single.py index 411c5d9..c2a73d3 100644 --- a/sleekxmpp/roster/single.py +++ b/sleekxmpp/roster/single.py @@ -75,6 +75,10 @@ class RosterNode(object): self.add(key, save=True) return self._jids[key] + def __len__(self): + """Return the number of JIDs referenced by the roster.""" + return len(self._jids) + def keys(self): """Return a list of all subscribed JIDs.""" return self._jids.keys() @@ -83,6 +87,16 @@ class RosterNode(object): """Returns whether the roster has a JID.""" return jid in self._jids + def groups(self): + """Return a dictionary mapping group names to JIDs.""" + result = {} + for jid in self._jids: + for group in self._jids[jid]['groups']: + if group not in result: + result[group] = [] + result[group].append(jid) + return result + def __iter__(self): """Iterate over the roster items.""" return self._jids.__iter__() From 7d8aa4157bc7a602243996a45268b172629a6ae3 Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Thu, 18 Aug 2011 00:08:52 -0700 Subject: [PATCH 5/5] Add an example for dumping the roster to the command line. --- examples/roster_browser.py | 171 +++++++++++++++++++++++++++++++++++++ 1 file changed, 171 insertions(+) create mode 100644 examples/roster_browser.py diff --git a/examples/roster_browser.py b/examples/roster_browser.py new file mode 100644 index 0000000..d699d3c --- /dev/null +++ b/examples/roster_browser.py @@ -0,0 +1,171 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" + SleekXMPP: The Sleek XMPP Library + Copyright (C) 2011 Nathanael C. Fritz + This file is part of SleekXMPP. + + See the file LICENSE for copying permission. +""" + +import sys +import time +import logging +import getpass +import threading +from optparse import OptionParser + +import sleekxmpp +from sleekxmpp.exceptions import IqError, IqTimeout + + +# 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 RosterBrowser(sleekxmpp.ClientXMPP): + + """ + A basic script for dumping a client's roster to + the command line. + """ + + def __init__(self, jid, password): + sleekxmpp.ClientXMPP.__init__(self, jid, password) + # The session_start event will be triggered when + # the bot establishes its connection with the server + # and the XML streams are ready for use. We want to + # listen for this event so that we we can intialize + # our roster. We need threaded=True so that the + # session_start handler doesn't block event processing + # while we wait for presence stanzas to arrive. + self.add_event_handler("session_start", self.start, threaded=True) + self.add_event_handler("changed_status", self.wait_for_presences) + + 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 intial + presence stanza. + + Arguments: + event -- An empty dictionary. The session_start + event does not provide any additional + data. + """ + try: + self.get_roster() + except IqError as err: + print('Error: %' % err.iq['error']['condition']) + except IqTimeout: + print('Error: Request timed out') + self.send_presence() + + + print('Waiting for presence updates...\n') + self.presences_received.wait(5) + + print('Roster for %s' % self.boundjid.bare) + groups = self.client_roster.groups() + for group in groups: + print('\n%s' % group) + print('-' * 72) + for jid in groups[group]: + sub = self.client_roster[jid]['subscription'] + name = self.client_roster[jid]['name'] + if self.client_roster[jid]['name']: + print(' %s (%s) [%s]' % (name, jid, sub)) + else: + print(' %s [%s]' % (jid, sub)) + + connections = self.client_roster.presence(jid) + for res, pres in connections.items(): + show = 'available' + if pres['show']: + show = pres['show'] + print(' - %s (%s)' % (res, show)) + if pres['status']: + print(' %s' % pres['status']) + + self.disconnect() + + def wait_for_presences(self, pres): + """ + Track how many roster entries have received presence updates. + """ + 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 = input("Username: ") + if opts.password is None: + opts.password = getpass.getpass("Password: ") + + xmpp = RosterBrowser(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 pydns library installed, you will need + # to manually specify the name of the server if it does not match + # the one in the JID. For example, to use Google Talk you would + # need to use: + # + # if xmpp.connect(('talk.google.com', 5222)): + # ... + xmpp.process(threaded=False) + else: + print("Unable to connect.") +