summaryrefslogtreecommitdiff
path: root/sleekxmpp/stanza
diff options
context:
space:
mode:
Diffstat (limited to 'sleekxmpp/stanza')
-rw-r--r--sleekxmpp/stanza/__init__.py9
-rw-r--r--sleekxmpp/stanza/atom.py2
-rw-r--r--sleekxmpp/stanza/error.py179
-rw-r--r--sleekxmpp/stanza/htmlim.py99
-rw-r--r--sleekxmpp/stanza/iq.py238
-rw-r--r--sleekxmpp/stanza/message.py190
-rw-r--r--sleekxmpp/stanza/nick.py82
-rw-r--r--sleekxmpp/stanza/presence.py193
-rw-r--r--sleekxmpp/stanza/rootstanza.py80
-rw-r--r--sleekxmpp/stanza/roster.py146
10 files changed, 867 insertions, 351 deletions
diff --git a/sleekxmpp/stanza/__init__.py b/sleekxmpp/stanza/__init__.py
index c3d8a318..8302c43d 100644
--- a/sleekxmpp/stanza/__init__.py
+++ b/sleekxmpp/stanza/__init__.py
@@ -3,6 +3,11 @@
Copyright (C) 2010 Nathanael C. Fritz
This file is part of SleekXMPP.
- See the file license.txt for copying permission.
+ See the file LICENSE for copying permission.
"""
-__all__ = ['presence']
+
+
+from sleekxmpp.stanza.error import Error
+from sleekxmpp.stanza.iq import Iq
+from sleekxmpp.stanza.message import Message
+from sleekxmpp.stanza.presence import Presence
diff --git a/sleekxmpp/stanza/atom.py b/sleekxmpp/stanza/atom.py
index 5e82cb98..9df85a2b 100644
--- a/sleekxmpp/stanza/atom.py
+++ b/sleekxmpp/stanza/atom.py
@@ -1,4 +1,4 @@
-from .. xmlstream.stanzabase import ElementBase, ET, JID
+from .. xmlstream.stanzabase import registerStanzaPlugin, ElementBase, ET, JID
from xml.etree import cElementTree as ET
class AtomEntry(ElementBase):
diff --git a/sleekxmpp/stanza/error.py b/sleekxmpp/stanza/error.py
index ee46722a..6d18c297 100644
--- a/sleekxmpp/stanza/error.py
+++ b/sleekxmpp/stanza/error.py
@@ -3,60 +3,131 @@
Copyright (C) 2010 Nathanael C. Fritz
This file is part of SleekXMPP.
- See the file license.txt for copying permission.
+ See the file LICENSE for copying permission.
"""
-from .. xmlstream.stanzabase import ElementBase, ET
+
+from sleekxmpp.xmlstream.stanzabase import registerStanzaPlugin
+from sleekxmpp.xmlstream.stanzabase import ElementBase, ET
+
class Error(ElementBase):
- namespace = 'jabber:client'
- name = 'error'
- plugin_attrib = 'error'
- 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'))
- interfaces = set(('code', 'condition', 'text', 'type'))
- types = set(('cancel', 'continue', 'modify', 'auth', 'wait'))
- sub_interfaces = set(('text',))
- condition_ns = 'urn:ietf:params:xml:ns:xmpp-stanzas'
-
- def setup(self, xml=None):
- if ElementBase.setup(self, xml): #if we had to generate xml
- self['type'] = 'cancel'
- self['condition'] = 'feature-not-implemented'
- if self.parent is not None:
- self.parent()['type'] = 'error'
-
- def getCondition(self):
- for child in self.xml.getchildren():
- if "{%s}" % self.condition_ns in child.tag:
- return child.tag.split('}', 1)[-1]
- return ''
-
- def setCondition(self, value):
- if value in self.conditions:
- for child in self.xml.getchildren():
- if "{%s}" % self.condition_ns in child.tag:
- self.xml.remove(child)
- condition = ET.Element("{%s}%s" % (self.condition_ns, value))
- self.xml.append(condition)
- return self
-
- def delCondition(self):
- return self
-
- def getText(self):
- text = ''
- textxml = self.xml.find("{urn:ietf:params:xml:ns:xmpp-stanzas}text")
- if textxml is not None:
- text = textxml.text
- return text
-
- def setText(self, value):
- self.delText()
- textxml = ET.Element('{urn:ietf:params:xml:ns:xmpp-stanzas}text')
- textxml.text = value
- self.xml.append(textxml)
- return self
-
- def delText(self):
- textxml = self.xml.find("{urn:ietf:params:xml:ns:xmpp-stanzas}text")
- if textxml is not None:
- self.xml.remove(textxml)
+
+ """
+ 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.
+ getCondition -- Retrieve the name of the condition element.
+ setCondition -- Add a condition element.
+ delCondition -- Remove the condition element.
+ getText -- Retrieve the contents of the <text> element.
+ setText -- Set the contents of the <text> element.
+ delText -- Remove the <text> element.
+ """
+
+ namespace = 'jabber:client'
+ name = 'error'
+ plugin_attrib = 'error'
+ interfaces = set(('code', 'condition', 'text', 'type'))
+ sub_interfaces = set(('text',))
+ 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 getCondition(self):
+ """Return the condition element's name."""
+ for child in self.xml.getchildren():
+ if "{%s}" % self.condition_ns in child.tag:
+ return child.tag.split('}', 1)[-1]
+ return ''
+
+ def setCondition(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 delCondition(self):
+ """Remove the condition element."""
+ for child in self.xml.getchildren():
+ 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 getText(self):
+ """Retrieve the contents of the <text> element."""
+ return self._getSubText('{%s}text' % self.condition_ns)
+
+ def setText(self, value):
+ """
+ Set the contents of the <text> element.
+
+ Arguments:
+ value -- The new contents for the <text> element.
+ """
+ self._setSubText('{%s}text' % self.condition_ns, text=value)
+ return self
+
+ def delText(self):
+ """Remove the <text> element."""
+ self._delSub('{%s}text' % self.condition_ns)
+ return self
diff --git a/sleekxmpp/stanza/htmlim.py b/sleekxmpp/stanza/htmlim.py
index 60686e4a..c2f2f0c8 100644
--- a/sleekxmpp/stanza/htmlim.py
+++ b/sleekxmpp/stanza/htmlim.py
@@ -3,33 +3,78 @@
Copyright (C) 2010 Nathanael C. Fritz
This file is part of SleekXMPP.
- See the file license.txt for copying permission.
+ See the file LICENSE for copying permission.
"""
-from .. xmlstream.stanzabase import ElementBase, ET
+
+from sleekxmpp.stanza import Message
+from sleekxmpp.xmlstream.stanzabase import registerStanzaPlugin
+from sleekxmpp.xmlstream.stanzabase import ElementBase, ET
+
class HTMLIM(ElementBase):
- namespace = 'http://jabber.org/protocol/xhtml-im'
- name = 'html'
- plugin_attrib = 'html'
- interfaces = set(('html',))
- plugin_attrib_map = set()
- plugin_xml_map = set()
-
- def setHtml(self, html):
- if isinstance(html, str):
- html = ET.XML(html)
- if html.tag != '{http://www.w3.org/1999/xhtml}body':
- body = ET.Element('{http://www.w3.org/1999/xhtml}body')
- body.append(html)
- self.xml.append(body)
- else:
- self.xml.append(html)
-
- def getHtml(self):
- html = self.xml.find('{http://www.w3.org/1999/xhtml}body')
- if html is None: return ''
- return html
-
- def delHtml(self):
- if self.parent is not None:
- self.parent().xml.remove(self.xml)
+
+ """
+ XEP-0071: XHTML-IM defines a method for embedding XHTML content
+ within a <message> stanza so that lightweight markup can be used
+ to format the message contents and to create links.
+
+ Only a subset of XHTML is recommended for use with XHTML-IM.
+ See the full spec at 'http://xmpp.org/extensions/xep-0071.html'
+ for more information.
+
+ Example stanza:
+ <message to="user@example.com">
+ <body>Non-html message content.</body>
+ <html xmlns="http://jabber.org/protocol/xhtml-im">
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <p><b>HTML!</b></p>
+ </body>
+ </html>
+ </message>
+
+ Stanza Interface:
+ body -- The contents of the HTML body tag.
+
+ Methods:
+ getBody -- Return the HTML body contents.
+ setBody -- Set the HTML body contents.
+ delBody -- Remove the HTML body contents.
+ """
+
+ namespace = 'http://jabber.org/protocol/xhtml-im'
+ name = 'html'
+ interfaces = set(('body',))
+ plugin_attrib = name
+
+ def setBody(self, html):
+ """
+ Set the contents of the HTML body.
+
+ Arguments:
+ html -- Either a string or XML object. If the top level
+ element is not <body> with a namespace of
+ 'http://www.w3.org/1999/xhtml', it will be wrapped.
+ """
+ if isinstance(html, str):
+ html = ET.XML(html)
+ if html.tag != '{http://www.w3.org/1999/xhtml}body':
+ body = ET.Element('{http://www.w3.org/1999/xhtml}body')
+ body.append(html)
+ self.xml.append(body)
+ else:
+ self.xml.append(html)
+
+ def getBody(self):
+ """Return the contents of the HTML body."""
+ html = self.xml.find('{http://www.w3.org/1999/xhtml}body')
+ if html is None:
+ return ''
+ return html
+
+ def delBody(self):
+ """Remove the HTML body contents."""
+ if self.parent is not None:
+ self.parent().xml.remove(self.xml)
+
+
+registerStanzaPlugin(Message, HTMLIM)
diff --git a/sleekxmpp/stanza/iq.py b/sleekxmpp/stanza/iq.py
index ded7515f..c5ef8bb4 100644
--- a/sleekxmpp/stanza/iq.py
+++ b/sleekxmpp/stanza/iq.py
@@ -3,75 +3,175 @@
Copyright (C) 2010 Nathanael C. Fritz
This file is part of SleekXMPP.
- See the file license.txt for copying permission.
+ See the file LICENSE for copying permission.
"""
-from .. xmlstream.stanzabase import StanzaBase
-from xml.etree import cElementTree as ET
-from . error import Error
-from .. xmlstream.handler.waiter import Waiter
-from .. xmlstream.matcher.id import MatcherId
-from . rootstanza import RootStanza
+
+from sleekxmpp.stanza import Error
+from sleekxmpp.stanza.rootstanza import RootStanza
+from sleekxmpp.xmlstream import RESPONSE_TIMEOUT
+from sleekxmpp.xmlstream.stanzabase import StanzaBase, ET
+from sleekxmpp.xmlstream.handler import Waiter
+from sleekxmpp.xmlstream.matcher import MatcherId
+
class Iq(RootStanza):
- interfaces = set(('type', 'to', 'from', 'id','query'))
- types = set(('get', 'result', 'set', 'error'))
- name = 'iq'
- plugin_attrib = name
- namespace = 'jabber:client'
-
- def __init__(self, *args, **kwargs):
- StanzaBase.__init__(self, *args, **kwargs)
- if self['id'] == '':
- if self.stream is not None:
- self['id'] = self.stream.getNewId()
- else:
- self['id'] = '0'
-
- def unhandled(self):
- 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 setPayload(self, value):
- self.clear()
- StanzaBase.setPayload(self, value)
- return self
-
- def setQuery(self, value):
- query = self.xml.find("{%s}query" % value)
- if query is None and value:
- self.clear()
- query = ET.Element("{%s}query" % value)
- self.xml.append(query)
- return self
-
- def getQuery(self):
- for child in self.xml.getchildren():
- if child.tag.endswith('query'):
- ns =child.tag.split('}')[0]
- if '{' in ns:
- ns = ns[1:]
- return ns
- return ''
-
- def reply(self):
- self['type'] = 'result'
- StanzaBase.reply(self)
- return self
-
- def delQuery(self):
- for child in self.getchildren():
- if child.tag.endswith('query'):
- self.xml.remove(child)
- return self
-
- def send(self, block=True, timeout=10):
- if block and self['type'] in ('get', 'set'):
- waitfor = Waiter('IqWait_%s' % self['id'], MatcherId(self['id']))
- self.stream.registerHandler(waitfor)
- StanzaBase.send(self)
- return waitfor.wait(timeout)
- else:
- return StanzaBase.send(self)
+
+ """
+ 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.
+ setPayload -- Overrides StanzaBase.setPayload.
+ setQuery -- Add or modify a <query> element.
+ getQuery -- Return the namespace of the <query> element.
+ delQuery -- 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.getNewId()
+ 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 setPayload(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.setPayload(self, value)
+ return self
+
+ def setQuery(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:
+ self.clear()
+ query = ET.Element("{%s}query" % value)
+ self.xml.append(query)
+ return self
+
+ def getQuery(self):
+ """Return the namespace of the <query> element."""
+ for child in self.xml.getchildren():
+ if child.tag.endswith('query'):
+ ns = child.tag.split('}')[0]
+ if '{' in ns:
+ ns = ns[1:]
+ return ns
+ return ''
+
+ def delQuery(self):
+ """Remove the <query> element."""
+ for child in self.xml.getchildren():
+ if child.tag.endswith('query'):
+ self.xml.remove(child)
+ return self
+
+ def reply(self):
+ """
+ Send a reply <iq> stanza.
+
+ Overrides StanzaBase.reply
+
+ Sets the 'type' to 'result' in addition to the default
+ StanzaBase.reply behavior.
+ """
+ self['type'] = 'result'
+ StanzaBase.reply(self)
+ return self
+
+ def send(self, block=True, timeout=RESPONSE_TIMEOUT):
+ """
+ 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.
+
+ 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 sleekxmpp.xmlstream.RESPONSE_TIMEOUT
+ """
+ if block and self['type'] in ('get', 'set'):
+ waitfor = Waiter('IqWait_%s' % self['id'], MatcherId(self['id']))
+ self.stream.registerHandler(waitfor)
+ StanzaBase.send(self)
+ return waitfor.wait(timeout)
+ else:
+ return StanzaBase.send(self)
diff --git a/sleekxmpp/stanza/message.py b/sleekxmpp/stanza/message.py
index 38341809..560e1d47 100644
--- a/sleekxmpp/stanza/message.py
+++ b/sleekxmpp/stanza/message.py
@@ -3,61 +3,141 @@
Copyright (C) 2010 Nathanael C. Fritz
This file is part of SleekXMPP.
- See the file license.txt for copying permission.
+ See the file LICENSE for copying permission.
"""
-from .. xmlstream.stanzabase import StanzaBase
-from xml.etree import cElementTree as ET
-from . error import Error
-from . rootstanza import RootStanza
+
+from sleekxmpp.stanza import Error
+from sleekxmpp.stanza.rootstanza import RootStanza
+from sleekxmpp.xmlstream.stanzabase import StanzaBase, ET
+
class Message(RootStanza):
- interfaces = set(('type', 'to', 'from', 'id', 'body', 'subject', 'mucroom', 'mucnick'))
- types = set((None, 'normal', 'chat', 'headline', 'error', 'groupchat'))
- sub_interfaces = set(('body', 'subject'))
- name = 'message'
- plugin_attrib = name
- namespace = 'jabber:client'
-
- def getType(self):
- return self.xml.attrib.get('type', 'normal')
-
- def chat(self):
- self['type'] = 'chat'
- return self
-
- def normal(self):
- self['type'] = 'normal'
- return self
-
- def reply(self, body=None):
- StanzaBase.reply(self)
- if self['type'] == 'groupchat':
- self['to'] = self['to'].bare
- del self['id']
- if body is not None:
- self['body'] = body
- return self
-
- def getMucroom(self):
- if self['type'] == 'groupchat':
- return self['from'].bare
- else:
- return ''
-
- def setMucroom(self, value):
- pass
-
- def delMucroom(self):
- pass
-
- def getMucnick(self):
- if self['type'] == 'groupchat':
- return self['from'].resource
- else:
- return ''
-
- def setMucnick(self, value):
- pass
-
- def delMucnick(self):
- pass
+
+ """
+ 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:
+ chat -- Set the message type to 'chat'.
+ normal -- Set the message type to 'normal'.
+ reply -- Overrides StanzaBase.reply
+ getType -- Overrides StanzaBase interface
+ getMucroom -- Return the name of the MUC room of the message.
+ setMucroom -- Dummy method to prevent assignment.
+ delMucroom -- Dummy method to prevent deletion.
+ getMucnick -- Return the MUC nickname of the message's sender.
+ setMucnick -- Dummy method to prevent assignment.
+ delMucnick -- Dummy method to prevent deletion.
+ """
+
+ namespace = 'jabber:client'
+ name = 'message'
+ interfaces = set(('type', 'to', 'from', 'id', 'body', 'subject',
+ 'mucroom', 'mucnick'))
+ sub_interfaces = set(('body', 'subject'))
+ plugin_attrib = name
+ types = set((None, 'normal', 'chat', 'headline', 'error', 'groupchat'))
+
+ def getType(self):
+ """
+ Return the message type.
+
+ Overrides default stanza interface behavior.
+
+ Returns 'normal' if no type attribute is present.
+ """
+ return self._getAttr('type', 'normal')
+
+ def chat(self):
+ """Set the message type to 'chat'."""
+ self['type'] = 'chat'
+ return self
+
+ def normal(self):
+ """Set the message type to 'chat'."""
+ self['type'] = 'normal'
+ return self
+
+ def reply(self, body=None):
+ """
+ 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.
+ """
+ StanzaBase.reply(self)
+ if self['type'] == 'groupchat':
+ self['to'] = self['to'].bare
+
+ del self['id']
+
+ if body is not None:
+ self['body'] = body
+ return self
+
+ def getMucroom(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 getMucnick(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 setMucroom(self, value):
+ """Dummy method to prevent modification."""
+ pass
+
+ def delMucroom(self):
+ """Dummy method to prevent deletion."""
+ pass
+
+ def setMucnick(self, value):
+ """Dummy method to prevent modification."""
+ pass
+
+ def delMucnick(self):
+ """Dummy method to prevent deletion."""
+ pass
diff --git a/sleekxmpp/stanza/nick.py b/sleekxmpp/stanza/nick.py
index ac7e3604..de54b307 100644
--- a/sleekxmpp/stanza/nick.py
+++ b/sleekxmpp/stanza/nick.py
@@ -3,24 +3,70 @@
Copyright (C) 2010 Nathanael C. Fritz
This file is part of SleekXMPP.
- See the file license.txt for copying permission.
+ See the file LICENSE for copying permission.
"""
-from .. xmlstream.stanzabase import ElementBase, ET
+
+from sleekxmpp.stanza import Message, Presence
+from sleekxmpp.xmlstream.stanzabase import registerStanzaPlugin
+from sleekxmpp.xmlstream.stanzabase import ElementBase, ET
+
class Nick(ElementBase):
- namespace = 'http://jabber.org/nick/nick'
- name = 'nick'
- plugin_attrib = 'nick'
- interfaces = set(('nick'))
- plugin_attrib_map = set()
- plugin_xml_map = set()
-
- def setNick(self, nick):
- self.xml.text = nick
-
- def getNick(self):
- return self.xml.text
-
- def delNick(self):
- if self.parent is not None:
- self.parent().xml.remove(self.xml)
+
+ """
+ XEP-0172: User Nickname allows the addition of a <nick> element
+ in several stanza types, including <message> and <presence> stanzas.
+
+ The nickname contained in a <nick> should be the global, friendly or
+ informal name chosen by the owner of a bare JID. The <nick> element
+ may be included when establishing communications with new entities,
+ such as normal XMPP users or MUC services.
+
+ The nickname contained in a <nick> element will not necessarily be
+ the same as the nickname used in a MUC.
+
+ Example stanzas:
+ <message to="user@example.com">
+ <nick xmlns="http://jabber.org/nick/nick">The User</nick>
+ <body>...</body>
+ </message>
+
+ <presence to="otheruser@example.com" type="subscribe">
+ <nick xmlns="http://jabber.org/nick/nick">The User</nick>
+ </presence>
+
+ Stanza Interface:
+ nick -- A global, friendly or informal name chosen by a user.
+
+ Methods:
+ getNick -- Return the nickname in the <nick> element.
+ setNick -- Add a <nick> element with the given nickname.
+ delNick -- Remove the <nick> element.
+ """
+
+ namespace = 'http://jabber.org/nick/nick'
+ name = 'nick'
+ plugin_attrib = name
+ interfaces = set(('nick',))
+
+ def setNick(self, nick):
+ """
+ Add a <nick> element with the given nickname.
+
+ Arguments:
+ nick -- A human readable, informal name.
+ """
+ self.xml.text = nick
+
+ def getNick(self):
+ """Return the nickname in the <nick> element."""
+ return self.xml.text
+
+ def delNick(self):
+ """Remove the <nick> element."""
+ if self.parent is not None:
+ self.parent().xml.remove(self.xml)
+
+
+registerStanzaPlugin(Message, Nick)
+registerStanzaPlugin(Presence, Nick)
diff --git a/sleekxmpp/stanza/presence.py b/sleekxmpp/stanza/presence.py
index c66246c9..651bf34d 100644
--- a/sleekxmpp/stanza/presence.py
+++ b/sleekxmpp/stanza/presence.py
@@ -3,61 +3,144 @@
Copyright (C) 2010 Nathanael C. Fritz
This file is part of SleekXMPP.
- See the file license.txt for copying permission.
+ See the file LICENSE for copying permission.
"""
-from .. xmlstream.stanzabase import StanzaBase
-from xml.etree import cElementTree as ET
-from . error import Error
-from . rootstanza import RootStanza
+
+from sleekxmpp.stanza import Error
+from sleekxmpp.stanza.rootstanza import RootStanza
+from sleekxmpp.xmlstream.stanzabase import StanzaBase, ET
+
class Presence(RootStanza):
- interfaces = set(('type', 'to', 'from', 'id', 'status', 'priority'))
- types = set(('available', 'unavailable', 'error', 'probe', 'subscribe', 'subscribed', 'unsubscribe', 'unsubscribed'))
- showtypes = set(('dnd', 'chat', 'xa', 'away'))
- sub_interfaces = set(('status', 'priority'))
- name = 'presence'
- plugin_attrib = name
- namespace = 'jabber:client'
-
- def getShowElement(self):
- return self.xml.find("{%s}show" % self.namespace)
-
- def setType(self, value):
- show = self.getShowElement()
- if value in self.types:
- if show is not None:
- self.xml.remove(show)
- if value == 'available':
- value = ''
- self._setAttr('type', value)
- elif value in self.showtypes:
- if show is None:
- show = ET.Element("{%s}show" % self.namespace)
- self.xml.append(show)
- show.text = value
- return self
-
- def setPriority(self, value):
- self._setSubText('priority', text = str(value))
-
- def getPriority(self):
- p = self._getSubText('priority')
- if not p: p = 0
- return int(p)
-
- def getType(self):
- out = self._getAttr('type')
- if not out:
- show = self.getShowElement()
- if show is not None:
- out = show.text
- if not out or out is None:
- out = 'available'
- return out
-
- def reply(self):
- if self['type'] == 'unsubscribe':
- self['type'] = 'unsubscribed'
- elif self['type'] == 'subscribe':
- self['type'] = 'subscribed'
- return StanzaBase.reply(self)
+
+ """
+ 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:
+ reply -- Overrides StanzaBase.reply
+ setShow -- Set the value of the <show> element.
+ getType -- Get the value of the type attribute or <show> element.
+ setType -- Set the value of the type attribute or <show> element.
+ getPriority -- Get the value of the <priority> element.
+ setPriority -- Set the value of the <priority> element.
+ """
+
+ namespace = 'jabber:client'
+ name = 'presence'
+ interfaces = set(('type', 'to', 'from', 'id', 'show',
+ 'status', 'priority'))
+ sub_interfaces = set(('show', 'status', 'priority'))
+ plugin_attrib = name
+
+ types = set(('available', 'unavailable', 'error', 'probe', 'subscribe',
+ 'subscribed', 'unsubscribe', 'unsubscribed'))
+ showtypes = set(('dnd', 'chat', 'xa', 'away'))
+
+ def setShow(self, show):
+ """
+ Set the value of the <show> element.
+
+ Arguments:
+ show -- Must be one of: away, chat, dnd, or xa.
+ """
+ if show in self.showtypes:
+ self._setSubText('show', text=show)
+ return self
+
+ def setType(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._setAttr('type', value)
+ elif value in self.showtypes:
+ self['show'] = value
+ return self
+
+ def setPriority(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._setSubText('priority', text=str(value))
+
+ def getPriority(self):
+ """
+ Return the value of the <presence> element as an integer.
+ """
+ p = self._getSubText('priority')
+ if not p:
+ p = 0
+ return int(p)
+
+ def getType(self):
+ """
+ Return the value of the <presence> stanza's type attribute, or
+ the value of the <show> element.
+ """
+ out = self._getAttr('type')
+ if not out:
+ out = self['show']
+ if not out or out is None:
+ out = 'available'
+ return out
+
+ def reply(self):
+ """
+ Set the appropriate presence reply type.
+
+ Overrides StanzaBase.reply.
+ """
+ if self['type'] == 'unsubscribe':
+ self['type'] = 'unsubscribed'
+ elif self['type'] == 'subscribe':
+ self['type'] = 'subscribed'
+ return StanzaBase.reply(self)
diff --git a/sleekxmpp/stanza/rootstanza.py b/sleekxmpp/stanza/rootstanza.py
index 3b4822d8..eafc79a2 100644
--- a/sleekxmpp/stanza/rootstanza.py
+++ b/sleekxmpp/stanza/rootstanza.py
@@ -3,34 +3,64 @@
Copyright (C) 2010 Nathanael C. Fritz
This file is part of SleekXMPP.
- See the file license.txt for copying permission.
+ See the file LICENSE for copying permission.
"""
-from .. xmlstream.stanzabase import StanzaBase
-from xml.etree import cElementTree as ET
-from . error import Error
-from .. exceptions import XMPPError
+
+import logging
import traceback
import sys
+from sleekxmpp.exceptions import XMPPError
+from sleekxmpp.stanza import Error
+from sleekxmpp.xmlstream.stanzabase import ET, StanzaBase, registerStanzaPlugin
+
+
class RootStanza(StanzaBase):
- def exception(self, e): #called when a handler raises an exception
- self.reply()
- if isinstance(e, XMPPError): # we raised this deliberately
- self['error']['condition'] = e.condition
- self['error']['text'] = e.text
- if e.extension is not None: # extended error tag
- extxml = ET.Element("{%s}%s" % (e.extension_ns, e.extension), e.extension_args)
- self['error'].xml.append(extxml)
- self['error']['type'] = e.etype
- else: # we probably didn't raise this on purpose, so send back a traceback
- self['error']['condition'] = 'undefined-condition'
- if sys.version_info < (3,0):
- self['error']['text'] = "SleekXMPP got into trouble."
- else:
- self['error']['text'] = traceback.format_tb(e.__traceback__)
- self.send()
-
-# all jabber:client root stanzas should have the error plugin
-RootStanza.plugin_attrib_map['error'] = Error
-RootStanza.plugin_tag_map["{%s}%s" % (Error.namespace, Error.name)] = Error
+ """
+ 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
+ """
+ self.reply()
+ if isinstance(e, XMPPError):
+ # We raised this deliberately
+ self['error']['condition'] = e.condition
+ self['error']['text'] = e.text
+ 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['error']['type'] = e.etype
+ else:
+ # We probably didn't raise this on purpose, so send a traceback
+ self['error']['condition'] = 'undefined-condition'
+ if sys.version_info < (3, 0):
+ self['error']['text'] = "SleekXMPP got into trouble."
+ else:
+ self['error']['text'] = traceback.format_tb(e.__traceback__)
+ logging.exception('Error handling {%s}%s stanza' %
+ (self.namespace, self.name))
+ self.send()
+
+
+registerStanzaPlugin(RootStanza, Error)
diff --git a/sleekxmpp/stanza/roster.py b/sleekxmpp/stanza/roster.py
index 1fefc180..292c8956 100644
--- a/sleekxmpp/stanza/roster.py
+++ b/sleekxmpp/stanza/roster.py
@@ -3,51 +3,107 @@
Copyright (C) 2010 Nathanael C. Fritz
This file is part of SleekXMPP.
- See the file license.txt for copying permission.
+ See the file LICENSE for copying permission.
"""
-from .. xmlstream.stanzabase import ElementBase, ET, JID
-import logging
+
+from sleekxmpp.stanza import Iq
+from sleekxmpp.xmlstream import JID
+from sleekxmpp.xmlstream.stanzabase import registerStanzaPlugin
+from sleekxmpp.xmlstream.stanzabase import ET, ElementBase
+
class Roster(ElementBase):
- namespace = 'jabber:iq:roster'
- name = 'query'
- plugin_attrib = 'roster'
- interfaces = set(('items',))
- sub_interfaces = set()
-
- def setItems(self, items):
- self.delItems()
- for jid in items:
- ijid = str(jid)
- item = ET.Element('{jabber:iq:roster}item', {'jid': ijid})
- if 'subscription' in items[jid]:
- item.attrib['subscription'] = items[jid]['subscription']
- if 'name' in items[jid]:
- item.attrib['name'] = items[jid]['name']
- if 'groups' in items[jid]:
- for group in items[jid]['groups']:
- groupxml = ET.Element('{jabber:iq:roster}group')
- groupxml.text = group
- item.append(groupxml)
- self.xml.append(item)
- return self
-
- def getItems(self):
- items = {}
- itemsxml = self.xml.findall('{jabber:iq:roster}item')
- if itemsxml is not None:
- for itemxml in itemsxml:
- item = {}
- item['name'] = itemxml.get('name', '')
- item['subscription'] = itemxml.get('subscription', '')
- item['groups'] = []
- groupsxml = itemxml.findall('{jabber:iq:roster}group')
- if groupsxml is not None:
- for groupxml in groupsxml:
- item['groups'].append(groupxml.text)
- items[itemxml.get('jid')] = item
- return items
-
- def delItems(self):
- for child in self.xml.getchildren():
- self.xml.remove(child)
+
+ """
+ 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:
+ getItems -- Return a dictionary of roster entries.
+ setItems -- Add <item> elements.
+ delItems -- Remove all <item> elements.
+ """
+
+ namespace = 'jabber:iq:roster'
+ name = 'query'
+ plugin_attrib = 'roster'
+ interfaces = set(('items',))
+
+ def setItems(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.delItems()
+ for jid in items:
+ ijid = str(jid)
+ item = ET.Element('{jabber:iq:roster}item', {'jid': ijid})
+ if 'subscription' in items[jid]:
+ item.attrib['subscription'] = items[jid]['subscription']
+ if 'name' in items[jid]:
+ name = items[jid]['name']
+ if name is not None:
+ item.attrib['name'] = name
+ if 'groups' in items[jid]:
+ for group in items[jid]['groups']:
+ groupxml = ET.Element('{jabber:iq:roster}group')
+ groupxml.text = group
+ item.append(groupxml)
+ self.xml.append(item)
+ return self
+
+ def getItems(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 = {}
+ itemsxml = self.xml.findall('{jabber:iq:roster}item')
+ if itemsxml is not None:
+ for itemxml in itemsxml:
+ item = {}
+ item['name'] = itemxml.get('name', '')
+ item['subscription'] = itemxml.get('subscription', '')
+ item['groups'] = []
+ groupsxml = itemxml.findall('{jabber:iq:roster}group')
+ if groupsxml is not None:
+ for groupxml in groupsxml:
+ item['groups'].append(groupxml.text)
+ items[itemxml.get('jid')] = item
+ return items
+
+ def delItems(self):
+ """
+ Remove all <item> elements from the roster stanza.
+ """
+ for child in self.xml.getchildren():
+ self.xml.remove(child)
+
+
+registerStanzaPlugin(Iq, Roster)