summaryrefslogtreecommitdiff
path: root/slixmpp/stanza
diff options
context:
space:
mode:
authorFlorent Le Coz <louiz@louiz.org>2014-07-17 14:19:04 +0200
committerFlorent Le Coz <louiz@louiz.org>2014-07-17 14:19:04 +0200
commit5ab77c745270d7d5c016c1dc7ef2a82533a4b16e (patch)
tree259377cc666f8b9c7954fc4e7b8f7a912bcfe101 /slixmpp/stanza
parente5582694c07236e6830c20361840360a1dde37f3 (diff)
downloadslixmpp-5ab77c745270d7d5c016c1dc7ef2a82533a4b16e.tar.gz
slixmpp-5ab77c745270d7d5c016c1dc7ef2a82533a4b16e.tar.bz2
slixmpp-5ab77c745270d7d5c016c1dc7ef2a82533a4b16e.tar.xz
slixmpp-5ab77c745270d7d5c016c1dc7ef2a82533a4b16e.zip
Rename to slixmpp
Diffstat (limited to 'slixmpp/stanza')
-rw-r--r--slixmpp/stanza/__init__.py15
-rw-r--r--slixmpp/stanza/atom.py26
-rw-r--r--slixmpp/stanza/error.py174
-rw-r--r--slixmpp/stanza/htmlim.py21
-rw-r--r--slixmpp/stanza/iq.py281
-rw-r--r--slixmpp/stanza/message.py199
-rw-r--r--slixmpp/stanza/nick.py23
-rw-r--r--slixmpp/stanza/presence.py191
-rw-r--r--slixmpp/stanza/rootstanza.py86
-rw-r--r--slixmpp/stanza/roster.py158
-rw-r--r--slixmpp/stanza/stream_error.py83
-rw-r--r--slixmpp/stanza/stream_features.py57
12 files changed, 1314 insertions, 0 deletions
diff --git a/slixmpp/stanza/__init__.py b/slixmpp/stanza/__init__.py
new file mode 100644
index 00000000..6cd6a2c5
--- /dev/null
+++ b/slixmpp/stanza/__init__.py
@@ -0,0 +1,15 @@
+"""
+ Slixmpp: The Slick XMPP Library
+ Copyright (C) 2010 Nathanael C. Fritz
+ This file is part of Slixmpp.
+
+ See the file LICENSE for copying permission.
+"""
+
+
+from slixmpp.stanza.error import Error
+from slixmpp.stanza.iq import Iq
+from slixmpp.stanza.message import Message
+from slixmpp.stanza.presence import Presence
+from slixmpp.stanza.stream_features import StreamFeatures
+from slixmpp.stanza.stream_error import StreamError
diff --git a/slixmpp/stanza/atom.py b/slixmpp/stanza/atom.py
new file mode 100644
index 00000000..2c105685
--- /dev/null
+++ b/slixmpp/stanza/atom.py
@@ -0,0 +1,26 @@
+"""
+ Slixmpp: The Slick XMPP Library
+ Copyright (C) 2010 Nathanael C. Fritz
+ This file is part of Slixmpp.
+
+ See the file LICENSE for copying permission.
+"""
+
+from slixmpp.xmlstream import ElementBase
+
+
+class AtomEntry(ElementBase):
+
+ """
+ A simple Atom feed entry.
+
+ Stanza Interface:
+ title -- The title of the Atom feed entry.
+ summary -- The summary of the Atom feed entry.
+ """
+
+ namespace = 'http://www.w3.org/2005/Atom'
+ name = 'entry'
+ plugin_attrib = 'entry'
+ interfaces = set(('title', 'summary'))
+ sub_interfaces = set(('title', 'summary'))
diff --git a/slixmpp/stanza/error.py b/slixmpp/stanza/error.py
new file mode 100644
index 00000000..efdcda0e
--- /dev/null
+++ b/slixmpp/stanza/error.py
@@ -0,0 +1,174 @@
+"""
+ Slixmpp: The Slick XMPP Library
+ Copyright (C) 2010 Nathanael C. Fritz
+ This file is part of Slixmpp.
+
+ See the file LICENSE for copying permission.
+"""
+
+from slixmpp.xmlstream import ElementBase, ET
+
+
+class Error(ElementBase):
+
+ """
+ XMPP stanzas of type 'error' should include an <error> stanza that
+ describes the nature of the error and how it should be handled.
+
+ Use the 'XEP-0086: Error Condition Mappings' plugin to include error
+ codes used in older XMPP versions.
+
+ Example error stanza:
+ <error type="cancel" code="404">
+ <item-not-found xmlns="urn:ietf:params:xml:ns:xmpp-stanzas" />
+ <text xmlns="urn:ietf:params:xml:ns:xmpp-stanzas">
+ The item was not found.
+ </text>
+ </error>
+
+ Stanza Interface:
+ code -- The error code used in older XMPP versions.
+ condition -- The name of the condition element.
+ text -- Human readable description of the error.
+ type -- Error type indicating how the error should be handled.
+
+ Attributes:
+ conditions -- The set of allowable error condition elements.
+ condition_ns -- The namespace for the condition element.
+ types -- A set of values indicating how the error
+ should be treated.
+
+ Methods:
+ setup -- Overrides ElementBase.setup.
+ get_condition -- Retrieve the name of the condition element.
+ set_condition -- Add a condition element.
+ del_condition -- Remove the condition element.
+ get_text -- Retrieve the contents of the <text> element.
+ set_text -- Set the contents of the <text> element.
+ del_text -- Remove the <text> element.
+ """
+
+ namespace = 'jabber:client'
+ name = 'error'
+ plugin_attrib = 'error'
+ interfaces = set(('code', 'condition', 'text', 'type',
+ 'gone', 'redirect', 'by'))
+ sub_interfaces = set(('text',))
+ plugin_attrib_map = {}
+ plugin_tag_map = {}
+ conditions = set(('bad-request', 'conflict', 'feature-not-implemented',
+ 'forbidden', 'gone', 'internal-server-error',
+ 'item-not-found', 'jid-malformed', 'not-acceptable',
+ 'not-allowed', 'not-authorized', 'payment-required',
+ 'recipient-unavailable', 'redirect',
+ 'registration-required', 'remote-server-not-found',
+ 'remote-server-timeout', 'resource-constraint',
+ 'service-unavailable', 'subscription-required',
+ 'undefined-condition', 'unexpected-request'))
+ condition_ns = 'urn:ietf:params:xml:ns:xmpp-stanzas'
+ types = set(('cancel', 'continue', 'modify', 'auth', 'wait'))
+
+ def setup(self, xml=None):
+ """
+ Populate the stanza object using an optional XML object.
+
+ Overrides ElementBase.setup.
+
+ Sets a default error type and condition, and changes the
+ parent stanza's type to 'error'.
+
+ Arguments:
+ xml -- Use an existing XML object for the stanza's values.
+ """
+ if ElementBase.setup(self, xml):
+ #If we had to generate XML then set default values.
+ self['type'] = 'cancel'
+ self['condition'] = 'feature-not-implemented'
+ if self.parent is not None:
+ self.parent()['type'] = 'error'
+
+ def get_condition(self):
+ """Return the condition element's name."""
+ for child in self.xml:
+ if "{%s}" % self.condition_ns in child.tag:
+ cond = child.tag.split('}', 1)[-1]
+ if cond in self.conditions:
+ return cond
+ return ''
+
+ def set_condition(self, value):
+ """
+ Set the tag name of the condition element.
+
+ Arguments:
+ value -- The tag name of the condition element.
+ """
+ if value in self.conditions:
+ del self['condition']
+ self.xml.append(ET.Element("{%s}%s" % (self.condition_ns, value)))
+ return self
+
+ def del_condition(self):
+ """Remove the condition element."""
+ for child in self.xml:
+ if "{%s}" % self.condition_ns in child.tag:
+ tag = child.tag.split('}', 1)[-1]
+ if tag in self.conditions:
+ self.xml.remove(child)
+ return self
+
+ def get_text(self):
+ """Retrieve the contents of the <text> element."""
+ return self._get_sub_text('{%s}text' % self.condition_ns)
+
+ def set_text(self, value):
+ """
+ Set the contents of the <text> element.
+
+ Arguments:
+ value -- The new contents for the <text> element.
+ """
+ self._set_sub_text('{%s}text' % self.condition_ns, text=value)
+ return self
+
+ def del_text(self):
+ """Remove the <text> element."""
+ self._del_sub('{%s}text' % self.condition_ns)
+ return self
+
+ def get_gone(self):
+ return self._get_sub_text('{%s}gone' % self.condition_ns, '')
+
+ def get_redirect(self):
+ return self._get_sub_text('{%s}redirect' % self.condition_ns, '')
+
+ def set_gone(self, value):
+ if value:
+ del self['condition']
+ return self._set_sub_text('{%s}gone' % self.condition_ns, value)
+ elif self['condition'] == 'gone':
+ del self['condition']
+
+ def set_redirect(self, value):
+ if value:
+ del self['condition']
+ ns = self.condition_ns
+ return self._set_sub_text('{%s}redirect' % ns, value)
+ elif self['condition'] == 'redirect':
+ del self['condition']
+
+ def del_gone(self):
+ self._del_sub('{%s}gone' % self.condition_ns)
+
+ def del_redirect(self):
+ self._del_sub('{%s}redirect' % self.condition_ns)
+
+
+# To comply with PEP8, method names now use underscores.
+# Deprecated method names are re-mapped for backwards compatibility.
+Error.getCondition = Error.get_condition
+Error.setCondition = Error.set_condition
+Error.delCondition = Error.del_condition
+Error.getText = Error.get_text
+Error.setText = Error.set_text
+Error.delText = Error.del_text
diff --git a/slixmpp/stanza/htmlim.py b/slixmpp/stanza/htmlim.py
new file mode 100644
index 00000000..a2842a3b
--- /dev/null
+++ b/slixmpp/stanza/htmlim.py
@@ -0,0 +1,21 @@
+"""
+ Slixmpp: The Slick XMPP Library
+ Copyright (C) 2010 Nathanael C. Fritz
+ This file is part of Slixmpp.
+
+ See the file LICENSE for copying permission.
+"""
+
+from slixmpp.stanza import Message
+from slixmpp.xmlstream import register_stanza_plugin
+from slixmpp.plugins.xep_0071 import XHTML_IM as HTMLIM
+
+
+register_stanza_plugin(Message, HTMLIM)
+
+
+# To comply with PEP8, method names now use underscores.
+# Deprecated method names are re-mapped for backwards compatibility.
+HTMLIM.setBody = HTMLIM.set_body
+HTMLIM.getBody = HTMLIM.get_body
+HTMLIM.delBody = HTMLIM.del_body
diff --git a/slixmpp/stanza/iq.py b/slixmpp/stanza/iq.py
new file mode 100644
index 00000000..4be819b0
--- /dev/null
+++ b/slixmpp/stanza/iq.py
@@ -0,0 +1,281 @@
+"""
+ Slixmpp: The Slick XMPP Library
+ Copyright (C) 2010 Nathanael C. Fritz
+ This file is part of Slixmpp.
+
+ See the file LICENSE for copying permission.
+"""
+
+from slixmpp.stanza.rootstanza import RootStanza
+from slixmpp.xmlstream import StanzaBase, ET
+from slixmpp.xmlstream.handler import Waiter, Callback
+from slixmpp.xmlstream.matcher import MatchIDSender, MatcherId
+from slixmpp.exceptions import IqTimeout, IqError
+
+
+class Iq(RootStanza):
+
+ """
+ XMPP <iq> stanzas, or info/query stanzas, are XMPP's method of
+ requesting and modifying information, similar to HTTP's GET and
+ POST methods.
+
+ Each <iq> stanza must have an 'id' value which associates the
+ stanza with the response stanza. XMPP entities must always
+ be given a response <iq> stanza with a type of 'result' after
+ sending a stanza of type 'get' or 'set'.
+
+ Most uses cases for <iq> stanzas will involve adding a <query>
+ element whose namespace indicates the type of information
+ desired. However, some custom XMPP applications use <iq> stanzas
+ as a carrier stanza for an application-specific protocol instead.
+
+ Example <iq> Stanzas:
+ <iq to="user@example.com" type="get" id="314">
+ <query xmlns="http://jabber.org/protocol/disco#items" />
+ </iq>
+
+ <iq to="user@localhost" type="result" id="17">
+ <query xmlns='jabber:iq:roster'>
+ <item jid='otheruser@example.net'
+ name='John Doe'
+ subscription='both'>
+ <group>Friends</group>
+ </item>
+ </query>
+ </iq>
+
+ Stanza Interface:
+ query -- The namespace of the <query> element if one exists.
+
+ Attributes:
+ types -- May be one of: get, set, result, or error.
+
+ Methods:
+ __init__ -- Overrides StanzaBase.__init__.
+ unhandled -- Send error if there are no handlers.
+ set_payload -- Overrides StanzaBase.set_payload.
+ set_query -- Add or modify a <query> element.
+ get_query -- Return the namespace of the <query> element.
+ del_query -- Remove the <query> element.
+ reply -- Overrides StanzaBase.reply
+ send -- Overrides StanzaBase.send
+ """
+
+ namespace = 'jabber:client'
+ name = 'iq'
+ interfaces = set(('type', 'to', 'from', 'id', 'query'))
+ types = set(('get', 'result', 'set', 'error'))
+ plugin_attrib = name
+
+ def __init__(self, *args, **kwargs):
+ """
+ Initialize a new <iq> stanza with an 'id' value.
+
+ Overrides StanzaBase.__init__.
+ """
+ StanzaBase.__init__(self, *args, **kwargs)
+ if self['id'] == '':
+ if self.stream is not None:
+ self['id'] = self.stream.new_id()
+ else:
+ self['id'] = '0'
+
+ def unhandled(self):
+ """
+ Send a feature-not-implemented error if the stanza is not handled.
+
+ Overrides StanzaBase.unhandled.
+ """
+ if self['type'] in ('get', 'set'):
+ self.reply()
+ self['error']['condition'] = 'feature-not-implemented'
+ self['error']['text'] = 'No handlers registered for this request.'
+ self.send()
+
+ def set_payload(self, value):
+ """
+ Set the XML contents of the <iq> stanza.
+
+ Arguments:
+ value -- An XML object to use as the <iq> stanza's contents
+ """
+ self.clear()
+ StanzaBase.set_payload(self, value)
+ return self
+
+ def set_query(self, value):
+ """
+ Add or modify a <query> element.
+
+ Query elements are differentiated by their namespace.
+
+ Arguments:
+ value -- The namespace of the <query> element.
+ """
+ query = self.xml.find("{%s}query" % value)
+ if query is None and value:
+ plugin = self.plugin_tag_map.get('{%s}query' % value, None)
+ if plugin:
+ self.enable(plugin.plugin_attrib)
+ else:
+ self.clear()
+ query = ET.Element("{%s}query" % value)
+ self.xml.append(query)
+ return self
+
+ def get_query(self):
+ """Return the namespace of the <query> element."""
+ for child in self.xml:
+ if child.tag.endswith('query'):
+ ns = child.tag.split('}')[0]
+ if '{' in ns:
+ ns = ns[1:]
+ return ns
+ return ''
+
+ def del_query(self):
+ """Remove the <query> element."""
+ for child in self.xml:
+ if child.tag.endswith('query'):
+ self.xml.remove(child)
+ return self
+
+ def reply(self, clear=True):
+ """
+ Send a reply <iq> stanza.
+
+ Overrides StanzaBase.reply
+
+ Sets the 'type' to 'result' in addition to the default
+ StanzaBase.reply behavior.
+
+ Arguments:
+ clear -- Indicates if existing content should be
+ removed before replying. Defaults to True.
+ """
+ self['type'] = 'result'
+ StanzaBase.reply(self, clear)
+ return self
+
+ def send(self, block=True, timeout=None, callback=None, now=False, timeout_callback=None):
+ """
+ Send an <iq> stanza over the XML stream.
+
+ The send call can optionally block until a response is received or
+ a timeout occurs. Be aware that using blocking in non-threaded event
+ handlers can drastically impact performance. Otherwise, a callback
+ handler can be provided that will be executed when the Iq stanza's
+ result reply is received. Be aware though that that the callback
+ handler will not be executed in its own thread.
+
+ Using both block and callback is not recommended, and only the
+ callback argument will be used in that case.
+
+ Overrides StanzaBase.send
+
+ Arguments:
+ 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 slixmpp.xmlstream.RESPONSE_TIMEOUT
+ callback -- Optional reference to a stream handler function. Will
+ be executed when a reply stanza is received.
+ now -- Indicates if the send queue should be skipped and send
+ the stanza immediately. Used during stream
+ initialization. Defaults to False.
+ timeout_callback -- Optional reference to a stream handler function.
+ Will be executed when the timeout expires before a
+ response has been received with the originally-sent IQ
+ stanza. Only called if there is a callback parameter
+ (and therefore are in async mode).
+ """
+ if timeout is None:
+ timeout = self.stream.response_timeout
+
+ if self.stream.session_bind_event.is_set():
+ matcher = MatchIDSender({
+ 'id': self['id'],
+ 'self': self.stream.boundjid,
+ 'peer': self['to']
+ })
+ else:
+ matcher = MatcherId(self['id'])
+
+ if callback is not None and self['type'] in ('get', 'set'):
+ handler_name = 'IqCallback_%s' % self['id']
+ if timeout_callback:
+ self.callback = callback
+ self.timeout_callback = timeout_callback
+ self.stream.schedule('IqTimeout_%s' % self['id'],
+ timeout,
+ self._fire_timeout,
+ repeat=False)
+ handler = Callback(handler_name,
+ matcher,
+ self._handle_result,
+ once=True)
+ else:
+ handler = Callback(handler_name,
+ matcher,
+ callback,
+ once=True)
+ self.stream.register_handler(handler)
+ StanzaBase.send(self, now=now)
+ return handler_name
+ elif block and self['type'] in ('get', 'set'):
+ waitfor = Waiter('IqWait_%s' % self['id'], matcher)
+ self.stream.register_handler(waitfor)
+ StanzaBase.send(self, now=now)
+ result = waitfor.wait(timeout)
+ if not result:
+ raise IqTimeout(self)
+ if result['type'] == 'error':
+ raise IqError(result)
+ return result
+ else:
+ return StanzaBase.send(self, now=now)
+
+ def _handle_result(self, iq):
+ # we got the IQ, so don't fire the timeout
+ self.stream.scheduler.remove('IqTimeout_%s' % self['id'])
+ self.callback(iq)
+
+ def _fire_timeout(self):
+ # don't fire the handler for the IQ, if it finally does come in
+ self.stream.remove_handler('IqCallback_%s' % self['id'])
+ self.timeout_callback(self)
+
+ def _set_stanza_values(self, values):
+ """
+ Set multiple stanza interface values using a dictionary.
+
+ Stanza plugin values may be set usind nested dictionaries.
+
+ If the interface 'query' is given, then it will be set
+ last to avoid duplication of the <query /> element.
+
+ Overrides ElementBase._set_stanza_values.
+
+ Arguments:
+ values -- A dictionary mapping stanza interface with values.
+ Plugin interfaces may accept a nested dictionary that
+ will be used recursively.
+ """
+ query = values.get('query', '')
+ if query:
+ del values['query']
+ StanzaBase._set_stanza_values(self, values)
+ self['query'] = query
+ else:
+ StanzaBase._set_stanza_values(self, values)
+ return self
+
+
+# To comply with PEP8, method names now use underscores.
+# Deprecated method names are re-mapped for backwards compatibility.
+Iq.setPayload = Iq.set_payload
+Iq.getQuery = Iq.get_query
+Iq.setQuery = Iq.set_query
+Iq.delQuery = Iq.del_query
diff --git a/slixmpp/stanza/message.py b/slixmpp/stanza/message.py
new file mode 100644
index 00000000..9495a629
--- /dev/null
+++ b/slixmpp/stanza/message.py
@@ -0,0 +1,199 @@
+"""
+ Slixmpp: The Slick XMPP Library
+ Copyright (C) 2010 Nathanael C. Fritz
+ This file is part of Slixmpp.
+
+ See the file LICENSE for copying permission.
+"""
+
+from slixmpp.stanza.rootstanza import RootStanza
+from slixmpp.xmlstream import StanzaBase, ET
+
+
+class Message(RootStanza):
+
+ """
+ XMPP's <message> stanzas are a "push" mechanism to send information
+ to other XMPP entities without requiring a response.
+
+ Chat clients will typically use <message> stanzas that have a type
+ of either "chat" or "groupchat".
+
+ When handling a message event, be sure to check if the message is
+ an error response.
+
+ Example <message> stanzas:
+ <message to="user1@example.com" from="user2@example.com">
+ <body>Hi!</body>
+ </message>
+
+ <message type="groupchat" to="room@conference.example.com">
+ <body>Hi everyone!</body>
+ </message>
+
+ Stanza Interface:
+ body -- The main contents of the message.
+ subject -- An optional description of the message's contents.
+ mucroom -- (Read-only) The name of the MUC room that sent the message.
+ mucnick -- (Read-only) The MUC nickname of message's sender.
+
+ Attributes:
+ types -- May be one of: normal, chat, headline, groupchat, or error.
+
+ Methods:
+ setup -- Overrides StanzaBase.setup.
+ chat -- Set the message type to 'chat'.
+ normal -- Set the message type to 'normal'.
+ reply -- Overrides StanzaBase.reply
+ get_type -- Overrides StanzaBase interface
+ get_mucroom -- Return the name of the MUC room of the message.
+ set_mucroom -- Dummy method to prevent assignment.
+ del_mucroom -- Dummy method to prevent deletion.
+ get_mucnick -- Return the MUC nickname of the message's sender.
+ set_mucnick -- Dummy method to prevent assignment.
+ del_mucnick -- Dummy method to prevent deletion.
+ """
+
+ name = 'message'
+ namespace = 'jabber:client'
+ plugin_attrib = name
+ interfaces = set(['type', 'to', 'from', 'id', 'body', 'subject',
+ 'thread', 'parent_thread', 'mucroom', 'mucnick'])
+ sub_interfaces = set(['body', 'subject', 'thread'])
+ lang_interfaces = sub_interfaces
+ types = set(['normal', 'chat', 'headline', 'error', 'groupchat'])
+
+ def __init__(self, *args, **kwargs):
+ """
+ Initialize a new <message /> stanza with an optional 'id' value.
+
+ Overrides StanzaBase.__init__.
+ """
+ StanzaBase.__init__(self, *args, **kwargs)
+ if self['id'] == '':
+ if self.stream is not None and self.stream.use_message_ids:
+ self['id'] = self.stream.new_id()
+
+ def get_type(self):
+ """
+ Return the message type.
+
+ Overrides default stanza interface behavior.
+
+ Returns 'normal' if no type attribute is present.
+ """
+ return self._get_attr('type', 'normal')
+
+ def get_parent_thread(self):
+ """Return the message thread's parent thread."""
+ thread = self.xml.find('{%s}thread' % self.namespace)
+ if thread is not None:
+ return thread.attrib.get('parent', '')
+ return ''
+
+ def set_parent_thread(self, value):
+ """Add or change the message thread's parent thread."""
+ thread = self.xml.find('{%s}thread' % self.namespace)
+ if value:
+ if thread is None:
+ thread = ET.Element('{%s}thread' % self.namespace)
+ self.xml.append(thread)
+ thread.attrib['parent'] = value
+ else:
+ if thread is not None and 'parent' in thread.attrib:
+ del thread.attrib['parent']
+
+ def del_parent_thread(self):
+ """Delete the message thread's parent reference."""
+ thread = self.xml.find('{%s}thread' % self.namespace)
+ if thread is not None and 'parent' in thread.attrib:
+ del thread.attrib['parent']
+
+ def chat(self):
+ """Set the message type to 'chat'."""
+ self['type'] = 'chat'
+ return self
+
+ def normal(self):
+ """Set the message type to 'normal'."""
+ self['type'] = 'normal'
+ return self
+
+ def reply(self, body=None, clear=True):
+ """
+ Create a message reply.
+
+ Overrides StanzaBase.reply.
+
+ Sets proper 'to' attribute if the message is from a MUC, and
+ adds a message body if one is given.
+
+ Arguments:
+ body -- Optional text content for the message.
+ clear -- Indicates if existing content should be removed
+ before replying. Defaults to True.
+ """
+ thread = self['thread']
+ parent = self['parent_thread']
+
+ StanzaBase.reply(self, clear)
+ if self['type'] == 'groupchat':
+ self['to'] = self['to'].bare
+
+ self['thread'] = thread
+ self['parent_thread'] = parent
+
+ del self['id']
+
+ if body is not None:
+ self['body'] = body
+ return self
+
+ def get_mucroom(self):
+ """
+ Return the name of the MUC room where the message originated.
+
+ Read-only stanza interface.
+ """
+ if self['type'] == 'groupchat':
+ return self['from'].bare
+ else:
+ return ''
+
+ def get_mucnick(self):
+ """
+ Return the nickname of the MUC user that sent the message.
+
+ Read-only stanza interface.
+ """
+ if self['type'] == 'groupchat':
+ return self['from'].resource
+ else:
+ return ''
+
+ def set_mucroom(self, value):
+ """Dummy method to prevent modification."""
+ pass
+
+ def del_mucroom(self):
+ """Dummy method to prevent deletion."""
+ pass
+
+ def set_mucnick(self, value):
+ """Dummy method to prevent modification."""
+ pass
+
+ def del_mucnick(self):
+ """Dummy method to prevent deletion."""
+ pass
+
+
+# To comply with PEP8, method names now use underscores.
+# Deprecated method names are re-mapped for backwards compatibility.
+Message.getType = Message.get_type
+Message.getMucroom = Message.get_mucroom
+Message.setMucroom = Message.set_mucroom
+Message.delMucroom = Message.del_mucroom
+Message.getMucnick = Message.get_mucnick
+Message.setMucnick = Message.set_mucnick
+Message.delMucnick = Message.del_mucnick
diff --git a/slixmpp/stanza/nick.py b/slixmpp/stanza/nick.py
new file mode 100644
index 00000000..bc7bf5ed
--- /dev/null
+++ b/slixmpp/stanza/nick.py
@@ -0,0 +1,23 @@
+"""
+ Slixmpp: The Slick XMPP Library
+ Copyright (C) 2010 Nathanael C. Fritz
+ This file is part of Slixmpp.
+
+ See the file LICENSE for copying permission.
+"""
+
+# The nickname stanza has been moved to its own plugin, but the existing
+# references are kept for backwards compatibility.
+
+from slixmpp.stanza import Message, Presence
+from slixmpp.xmlstream import register_stanza_plugin
+from slixmpp.plugins.xep_0172 import UserNick as Nick
+
+register_stanza_plugin(Message, Nick)
+register_stanza_plugin(Presence, Nick)
+
+# To comply with PEP8, method names now use underscores.
+# Deprecated method names are re-mapped for backwards compatibility.
+Nick.setNick = Nick.set_nick
+Nick.getNick = Nick.get_nick
+Nick.delNick = Nick.del_nick
diff --git a/slixmpp/stanza/presence.py b/slixmpp/stanza/presence.py
new file mode 100644
index 00000000..3da20b7d
--- /dev/null
+++ b/slixmpp/stanza/presence.py
@@ -0,0 +1,191 @@
+"""
+ Slixmpp: The Slick XMPP Library
+ Copyright (C) 2010 Nathanael C. Fritz
+ This file is part of Slixmpp.
+
+ See the file LICENSE for copying permission.
+"""
+
+from slixmpp.stanza.rootstanza import RootStanza
+from slixmpp.xmlstream import StanzaBase
+
+
+class Presence(RootStanza):
+
+ """
+ XMPP's <presence> stanza allows entities to know the status of other
+ clients and components. Since it is currently the only multi-cast
+ stanza in XMPP, many extensions add more information to <presence>
+ stanzas to broadcast to every entry in the roster, such as
+ capabilities, music choices, or locations (XEP-0115: Entity Capabilities
+ and XEP-0163: Personal Eventing Protocol).
+
+ Since <presence> stanzas are broadcast when an XMPP entity changes
+ its status, the bulk of the traffic in an XMPP network will be from
+ <presence> stanzas. Therefore, do not include more information than
+ necessary in a status message or within a <presence> stanza in order
+ to help keep the network running smoothly.
+
+ Example <presence> stanzas:
+ <presence />
+
+ <presence from="user@example.com">
+ <show>away</show>
+ <status>Getting lunch.</status>
+ <priority>5</priority>
+ </presence>
+
+ <presence type="unavailable" />
+
+ <presence to="user@otherhost.com" type="subscribe" />
+
+ Stanza Interface:
+ priority -- A value used by servers to determine message routing.
+ show -- The type of status, such as away or available for chat.
+ status -- Custom, human readable status message.
+
+ Attributes:
+ types -- One of: available, unavailable, error, probe,
+ subscribe, subscribed, unsubscribe,
+ and unsubscribed.
+ showtypes -- One of: away, chat, dnd, and xa.
+
+ Methods:
+ setup -- Overrides StanzaBase.setup
+ reply -- Overrides StanzaBase.reply
+ set_show -- Set the value of the <show> element.
+ get_type -- Get the value of the type attribute or <show> element.
+ set_type -- Set the value of the type attribute or <show> element.
+ get_priority -- Get the value of the <priority> element.
+ set_priority -- Set the value of the <priority> element.
+ """
+
+ name = 'presence'
+ namespace = 'jabber:client'
+ plugin_attrib = name
+ interfaces = set(['type', 'to', 'from', 'id', 'show',
+ 'status', 'priority'])
+ sub_interfaces = set(['show', 'status', 'priority'])
+ lang_interfaces = set(['status'])
+
+ types = set(['available', 'unavailable', 'error', 'probe', 'subscribe',
+ 'subscribed', 'unsubscribe', 'unsubscribed'])
+ showtypes = set(['dnd', 'chat', 'xa', 'away'])
+
+ def __init__(self, *args, **kwargs):
+ """
+ Initialize a new <presence /> stanza with an optional 'id' value.
+
+ Overrides StanzaBase.__init__.
+ """
+ StanzaBase.__init__(self, *args, **kwargs)
+ if self['id'] == '':
+ if self.stream is not None and self.stream.use_presence_ids:
+ self['id'] = self.stream.new_id()
+
+ def exception(self, e):
+ """
+ Override exception passback for presence.
+ """
+ pass
+
+ def set_show(self, show):
+ """
+ Set the value of the <show> element.
+
+ Arguments:
+ show -- Must be one of: away, chat, dnd, or xa.
+ """
+ if show is None:
+ self._del_sub('show')
+ elif show in self.showtypes:
+ self._set_sub_text('show', text=show)
+ return self
+
+ def get_type(self):
+ """
+ Return the value of the <presence> stanza's type attribute, or
+ the value of the <show> element.
+ """
+ out = self._get_attr('type')
+ if not out:
+ out = self['show']
+ if not out or out is None:
+ out = 'available'
+ return out
+
+ def set_type(self, value):
+ """
+ Set the type attribute's value, and the <show> element
+ if applicable.
+
+ Arguments:
+ value -- Must be in either self.types or self.showtypes.
+ """
+ if value in self.types:
+ self['show'] = None
+ if value == 'available':
+ value = ''
+ self._set_attr('type', value)
+ elif value in self.showtypes:
+ self['show'] = value
+ return self
+
+ def del_type(self):
+ """
+ Remove both the type attribute and the <show> element.
+ """
+ self._del_attr('type')
+ self._del_sub('show')
+
+ def set_priority(self, value):
+ """
+ Set the entity's priority value. Some server use priority to
+ determine message routing behavior.
+
+ Bot clients should typically use a priority of 0 if the same
+ JID is used elsewhere by a human-interacting client.
+
+ Arguments:
+ value -- An integer value greater than or equal to 0.
+ """
+ self._set_sub_text('priority', text=str(value))
+
+ def get_priority(self):
+ """
+ Return the value of the <presence> element as an integer.
+ """
+ p = self._get_sub_text('priority')
+ if not p:
+ p = 0
+ try:
+ return int(p)
+ except ValueError:
+ # The priority is not a number: we consider it 0 as a default
+ return 0
+
+ def reply(self, clear=True):
+ """
+ Set the appropriate presence reply type.
+
+ Overrides StanzaBase.reply.
+
+ Arguments:
+ clear -- Indicates if the stanza contents should be removed
+ before replying. Defaults to True.
+ """
+ if self['type'] == 'unsubscribe':
+ self['type'] = 'unsubscribed'
+ elif self['type'] == 'subscribe':
+ self['type'] = 'subscribed'
+ return StanzaBase.reply(self, clear)
+
+
+# To comply with PEP8, method names now use underscores.
+# Deprecated method names are re-mapped for backwards compatibility.
+Presence.setShow = Presence.set_show
+Presence.getType = Presence.get_type
+Presence.setType = Presence.set_type
+Presence.delType = Presence.get_type
+Presence.getPriority = Presence.get_priority
+Presence.setPriority = Presence.set_priority
diff --git a/slixmpp/stanza/rootstanza.py b/slixmpp/stanza/rootstanza.py
new file mode 100644
index 00000000..7bd0c32d
--- /dev/null
+++ b/slixmpp/stanza/rootstanza.py
@@ -0,0 +1,86 @@
+"""
+ Slixmpp: The Slick XMPP Library
+ Copyright (C) 2010 Nathanael C. Fritz
+ This file is part of Slixmpp.
+
+ See the file LICENSE for copying permission.
+"""
+
+import logging
+
+from slixmpp.exceptions import XMPPError, IqError, IqTimeout
+from slixmpp.stanza import Error
+from slixmpp.xmlstream import ET, StanzaBase, register_stanza_plugin
+
+
+log = logging.getLogger(__name__)
+
+
+class RootStanza(StanzaBase):
+
+ """
+ A top-level XMPP stanza in an XMLStream.
+
+ The RootStanza class provides a more XMPP specific exception
+ handler than provided by the generic StanzaBase class.
+
+ Methods:
+ exception -- Overrides StanzaBase.exception
+ """
+
+ def exception(self, e):
+ """
+ Create and send an error reply.
+
+ Typically called when an event handler raises an exception.
+ The error's type and text content are based on the exception
+ object's type and content.
+
+ Overrides StanzaBase.exception.
+
+ Arguments:
+ e -- Exception object
+ """
+ if isinstance(e, IqError):
+ # We received an Iq error reply, but it wasn't caught
+ # locally. Using the condition/text from that error
+ # response could leak too much information, so we'll
+ # only use a generic error here.
+ self.reply()
+ self['error']['condition'] = 'undefined-condition'
+ self['error']['text'] = 'External error'
+ self['error']['type'] = 'cancel'
+ log.warning('You should catch IqError exceptions')
+ self.send()
+ elif isinstance(e, IqTimeout):
+ self.reply()
+ self['error']['condition'] = 'remote-server-timeout'
+ self['error']['type'] = 'wait'
+ log.warning('You should catch IqTimeout exceptions')
+ self.send()
+ elif isinstance(e, XMPPError):
+ # We raised this deliberately
+ self.reply(clear=e.clear)
+ self['error']['condition'] = e.condition
+ self['error']['text'] = e.text
+ self['error']['type'] = e.etype
+ if e.extension is not None:
+ # Extended error tag
+ extxml = ET.Element("{%s}%s" % (e.extension_ns, e.extension),
+ e.extension_args)
+ self['error'].append(extxml)
+ self.send()
+ else:
+ # We probably didn't raise this on purpose, so send an error stanza
+ self.reply()
+ self['error']['condition'] = 'undefined-condition'
+ self['error']['text'] = "Slixmpp got into trouble."
+ self['error']['type'] = 'cancel'
+ self.send()
+ # log the error
+ log.exception('Error handling {%s}%s stanza',
+ self.namespace, self.name)
+ # Finally raise the exception to a global exception handler
+ self.stream.exception(e)
+
+register_stanza_plugin(RootStanza, Error)
diff --git a/slixmpp/stanza/roster.py b/slixmpp/stanza/roster.py
new file mode 100644
index 00000000..0cf11429
--- /dev/null
+++ b/slixmpp/stanza/roster.py
@@ -0,0 +1,158 @@
+"""
+ Slixmpp: The Slick XMPP Library
+ Copyright (C) 2010 Nathanael C. Fritz
+ This file is part of Slixmpp.
+
+ See the file LICENSE for copying permission.
+"""
+
+from slixmpp.stanza import Iq
+from slixmpp.xmlstream import JID
+from slixmpp.xmlstream import ET, ElementBase, register_stanza_plugin
+
+
+class Roster(ElementBase):
+
+ """
+ Example roster stanzas:
+ <iq type="set">
+ <query xmlns="jabber:iq:roster">
+ <item jid="user@example.com" subscription="both" name="User">
+ <group>Friends</group>
+ </item>
+ </query>
+ </iq>
+
+ Stanza Inteface:
+ items -- A dictionary of roster entries contained
+ in the stanza.
+
+ Methods:
+ get_items -- Return a dictionary of roster entries.
+ set_items -- Add <item> elements.
+ del_items -- Remove all <item> elements.
+ """
+
+ namespace = 'jabber:iq:roster'
+ name = 'query'
+ plugin_attrib = 'roster'
+ interfaces = set(('items', 'ver'))
+
+ def get_ver(self):
+ """
+ Ensure handling an empty ver attribute propery.
+
+ The ver attribute is special in that the presence of the
+ attribute with an empty value is important for boostrapping
+ roster versioning.
+ """
+ return self.xml.attrib.get('ver', None)
+
+ def set_ver(self, ver):
+ """
+ Ensure handling an empty ver attribute propery.
+
+ The ver attribute is special in that the presence of the
+ attribute with an empty value is important for boostrapping
+ roster versioning.
+ """
+ if ver is not None:
+ self.xml.attrib['ver'] = ver
+ else:
+ del self.xml.attrib['ver']
+
+ def set_items(self, items):
+ """
+ Set the roster entries in the <roster> stanza.
+
+ Uses a dictionary using JIDs as keys, where each entry is itself
+ a dictionary that contains:
+ name -- An alias or nickname for the JID.
+ subscription -- The subscription type. Can be one of 'to',
+ 'from', 'both', 'none', or 'remove'.
+ groups -- A list of group names to which the JID
+ has been assigned.
+
+ Arguments:
+ items -- A dictionary of roster entries.
+ """
+ self.del_items()
+ for jid in items:
+ item = RosterItem()
+ item.values = items[jid]
+ item['jid'] = jid
+ self.append(item)
+ return self
+
+ def get_items(self):
+ """
+ Return a dictionary of roster entries.
+
+ Each item is keyed using its JID, and contains:
+ name -- An assigned alias or nickname for the JID.
+ subscription -- The subscription type. Can be one of 'to',
+ 'from', 'both', 'none', or 'remove'.
+ groups -- A list of group names to which the JID has
+ been assigned.
+ """
+ items = {}
+ for item in self['substanzas']:
+ if isinstance(item, RosterItem):
+ items[item['jid']] = item.values
+ # Remove extra JID reference to keep everything
+ # backward compatible
+ del items[item['jid']]['jid']
+ del items[item['jid']]['lang']
+ return items
+
+ def del_items(self):
+ """
+ Remove all <item> elements from the roster stanza.
+ """
+ for item in self['substanzas']:
+ if isinstance(item, RosterItem):
+ self.xml.remove(item.xml)
+
+
+class RosterItem(ElementBase):
+ namespace = 'jabber:iq:roster'
+ name = 'item'
+ plugin_attrib = 'item'
+ interfaces = set(('jid', 'name', 'subscription', 'ask',
+ 'approved', 'groups'))
+
+ def get_jid(self):
+ return JID(self._get_attr('jid', ''))
+
+ def set_jid(self, jid):
+ self._set_attr('jid', str(jid))
+
+ def get_groups(self):
+ groups = []
+ for group in self.xml.findall('{%s}group' % self.namespace):
+ if group.text:
+ groups.append(group.text)
+ else:
+ groups.append('')
+ return groups
+
+ def set_groups(self, values):
+ self.del_groups()
+ for group in values:
+ group_xml = ET.Element('{%s}group' % self.namespace)
+ group_xml.text = group
+ self.xml.append(group_xml)
+
+ def del_groups(self):
+ for group in self.xml.findall('{%s}group' % self.namespace):
+ self.xml.remove(group)
+
+
+register_stanza_plugin(Iq, Roster)
+register_stanza_plugin(Roster, RosterItem, iterable=True)
+
+# To comply with PEP8, method names now use underscores.
+# Deprecated method names are re-mapped for backwards compatibility.
+Roster.setItems = Roster.set_items
+Roster.getItems = Roster.get_items
+Roster.delItems = Roster.del_items
diff --git a/slixmpp/stanza/stream_error.py b/slixmpp/stanza/stream_error.py
new file mode 100644
index 00000000..d8b8bb5a
--- /dev/null
+++ b/slixmpp/stanza/stream_error.py
@@ -0,0 +1,83 @@
+"""
+ Slixmpp: The Slick XMPP Library
+ Copyright (C) 2010 Nathanael C. Fritz
+ This file is part of Slixmpp.
+
+ See the file LICENSE for copying permission.
+"""
+
+from slixmpp.stanza.error import Error
+from slixmpp.xmlstream import StanzaBase
+
+
+class StreamError(Error, StanzaBase):
+
+ """
+ XMPP stanzas of type 'error' should include an <error> stanza that
+ describes the nature of the error and how it should be handled.
+
+ Use the 'XEP-0086: Error Condition Mappings' plugin to include error
+ codes used in older XMPP versions.
+
+ The stream:error stanza is used to provide more information for
+ error that occur with the underlying XML stream itself, and not
+ a particular stanza.
+
+ Note: The StreamError stanza is mostly the same as the normal
+ Error stanza, but with different namespaces and
+ condition names.
+
+ Example error stanza:
+ <stream:error>
+ <not-well-formed xmlns="urn:ietf:params:xml:ns:xmpp-streams" />
+ <text xmlns="urn:ietf:params:xml:ns:xmpp-streams">
+ XML was not well-formed.
+ </text>
+ </stream:error>
+
+ Stanza Interface:
+ condition -- The name of the condition element.
+ text -- Human readable description of the error.
+
+ Attributes:
+ conditions -- The set of allowable error condition elements.
+ condition_ns -- The namespace for the condition element.
+
+ Methods:
+ setup -- Overrides ElementBase.setup.
+ get_condition -- Retrieve the name of the condition element.
+ set_condition -- Add a condition element.
+ del_condition -- Remove the condition element.
+ get_text -- Retrieve the contents of the <text> element.
+ set_text -- Set the contents of the <text> element.
+ del_text -- Remove the <text> element.
+ """
+
+ namespace = 'http://etherx.jabber.org/streams'
+ interfaces = set(('condition', 'text', 'see_other_host'))
+ conditions = set((
+ 'bad-format', 'bad-namespace-prefix', 'conflict',
+ 'connection-timeout', 'host-gone', 'host-unknown',
+ 'improper-addressing', 'internal-server-error', 'invalid-from',
+ 'invalid-namespace', 'invalid-xml', 'not-authorized',
+ 'not-well-formed', 'policy-violation', 'remote-connection-failed',
+ 'reset', 'resource-constraint', 'restricted-xml', 'see-other-host',
+ 'system-shutdown', 'undefined-condition', 'unsupported-encoding',
+ 'unsupported-feature', 'unsupported-stanza-type',
+ 'unsupported-version'))
+ condition_ns = 'urn:ietf:params:xml:ns:xmpp-streams'
+
+ def get_see_other_host(self):
+ ns = self.condition_ns
+ return self._get_sub_text('{%s}see-other-host' % ns, '')
+
+ def set_see_other_host(self, value):
+ if value:
+ del self['condition']
+ ns = self.condition_ns
+ return self._set_sub_text('{%s}see-other-host' % ns, value)
+ elif self['condition'] == 'see-other-host':
+ del self['condition']
+
+ def del_see_other_host(self):
+ self._del_sub('{%s}see-other-host' % self.condition_ns)
diff --git a/slixmpp/stanza/stream_features.py b/slixmpp/stanza/stream_features.py
new file mode 100644
index 00000000..4d46b979
--- /dev/null
+++ b/slixmpp/stanza/stream_features.py
@@ -0,0 +1,57 @@
+"""
+ Slixmpp: The Slick XMPP Library
+ Copyright (C) 2010 Nathanael C. Fritz
+ This file is part of Slixmpp.
+
+ See the file LICENSE for copying permission.
+"""
+
+from slixmpp.thirdparty import OrderedDict
+from slixmpp.xmlstream import StanzaBase
+
+
+class StreamFeatures(StanzaBase):
+
+ """
+ """
+
+ name = 'features'
+ namespace = 'http://etherx.jabber.org/streams'
+ interfaces = set(('features', 'required', 'optional'))
+ sub_interfaces = interfaces
+ plugin_tag_map = {}
+ plugin_attrib_map = {}
+
+ def setup(self, xml):
+ StanzaBase.setup(self, xml)
+ self.values = self.values
+
+ def get_features(self):
+ """
+ """
+ features = OrderedDict()
+ for (name, lang), plugin in self.plugins.items():
+ features[name] = plugin
+ return features
+
+ def set_features(self, value):
+ """
+ """
+ pass
+
+ def del_features(self):
+ """
+ """
+ pass
+
+ def get_required(self):
+ """
+ """
+ features = self['features']
+ return [f for n, f in features.items() if f['required']]
+
+ def get_optional(self):
+ """
+ """
+ features = self['features']
+ return [f for n, f in features.items() if not f['required']]