diff options
Diffstat (limited to 'sleekxmpp/stanza')
| -rw-r--r-- | sleekxmpp/stanza/__init__.py | 13 | ||||
| -rw-r--r-- | sleekxmpp/stanza/atom.py | 26 | ||||
| -rw-r--r-- | sleekxmpp/stanza/error.py | 141 | ||||
| -rw-r--r-- | sleekxmpp/stanza/htmlim.py | 97 | ||||
| -rw-r--r-- | sleekxmpp/stanza/iq.py | 183 | ||||
| -rw-r--r-- | sleekxmpp/stanza/message.py | 165 | ||||
| -rw-r--r-- | sleekxmpp/stanza/nick.py | 89 | ||||
| -rw-r--r-- | sleekxmpp/stanza/presence.py | 186 | ||||
| -rw-r--r-- | sleekxmpp/stanza/rootstanza.py | 66 | ||||
| -rw-r--r-- | sleekxmpp/stanza/roster.py | 125 |
10 files changed, 1091 insertions, 0 deletions
diff --git a/sleekxmpp/stanza/__init__.py b/sleekxmpp/stanza/__init__.py new file mode 100644 index 0000000..8302c43 --- /dev/null +++ b/sleekxmpp/stanza/__init__.py @@ -0,0 +1,13 @@ +""" + SleekXMPP: The Sleek XMPP Library + Copyright (C) 2010 Nathanael C. Fritz + This file is part of SleekXMPP. + + See the file LICENSE for copying permission. +""" + + +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 new file mode 100644 index 0000000..244ef31 --- /dev/null +++ b/sleekxmpp/stanza/atom.py @@ -0,0 +1,26 @@ +""" + SleekXMPP: The Sleek XMPP Library + Copyright (C) 2010 Nathanael C. Fritz + This file is part of SleekXMPP. + + See the file LICENSE for copying permission. +""" + +from sleekxmpp.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/sleekxmpp/stanza/error.py b/sleekxmpp/stanza/error.py new file mode 100644 index 0000000..09229bc --- /dev/null +++ b/sleekxmpp/stanza/error.py @@ -0,0 +1,141 @@ +""" + SleekXMPP: The Sleek XMPP Library + Copyright (C) 2010 Nathanael C. Fritz + This file is part of SleekXMPP. + + See the file LICENSE for copying permission. +""" + +from sleekxmpp.xmlstream import ElementBase, ET, register_stanza_plugin + + +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')) + 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. + """ + # To comply with PEP8, method names now use underscores. + # Deprecated method names are re-mapped for backwards compatibility. + self.getCondition = self.get_condition + self.setCondition = self.set_condition + self.delCondition = self.del_condition + self.getText = self.get_text + self.setText = self.set_text + self.delText = self.del_text + + 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.getchildren(): + if "{%s}" % self.condition_ns in child.tag: + return child.tag.split('}', 1)[-1] + 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.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 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 diff --git a/sleekxmpp/stanza/htmlim.py b/sleekxmpp/stanza/htmlim.py new file mode 100644 index 0000000..4586828 --- /dev/null +++ b/sleekxmpp/stanza/htmlim.py @@ -0,0 +1,97 @@ +""" + SleekXMPP: The Sleek XMPP Library + Copyright (C) 2010 Nathanael C. Fritz + This file is part of SleekXMPP. + + See the file LICENSE for copying permission. +""" + +from sleekxmpp.stanza import Message +from sleekxmpp.xmlstream import ElementBase, ET, register_stanza_plugin + + +class HTMLIM(ElementBase): + + """ + 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: + setup -- Overrides ElementBase.setup. + get_body -- Return the HTML body contents. + set_body -- Set the HTML body contents. + del_body -- Remove the HTML body contents. + """ + + namespace = 'http://jabber.org/protocol/xhtml-im' + name = 'html' + interfaces = set(('body',)) + plugin_attrib = name + + def setup(self, xml=None): + """ + Populate the stanza object using an optional XML object. + + Overrides StanzaBase.setup. + + Arguments: + xml -- Use an existing XML object for the stanza's values. + """ + # To comply with PEP8, method names now use underscores. + # Deprecated method names are re-mapped for backwards compatibility. + self.setBody = self.set_body + self.getBody = self.get_body + self.delBody = self.del_body + + return ElementBase.setup(self, xml) + + def set_body(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 get_body(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 del_body(self): + """Remove the HTML body contents.""" + if self.parent is not None: + self.parent().xml.remove(self.xml) + + +register_stanza_plugin(Message, HTMLIM) diff --git a/sleekxmpp/stanza/iq.py b/sleekxmpp/stanza/iq.py new file mode 100644 index 0000000..614d14f --- /dev/null +++ b/sleekxmpp/stanza/iq.py @@ -0,0 +1,183 @@ +""" + SleekXMPP: The Sleek XMPP Library + Copyright (C) 2010 Nathanael C. Fritz + This file is part of SleekXMPP. + + See the file LICENSE for copying permission. +""" + +from sleekxmpp.stanza import Error +from sleekxmpp.stanza.rootstanza import RootStanza +from sleekxmpp.xmlstream import RESPONSE_TIMEOUT, StanzaBase, ET +from sleekxmpp.xmlstream.handler import Waiter +from sleekxmpp.xmlstream.matcher import MatcherId + + +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) + # To comply with PEP8, method names now use underscores. + # Deprecated method names are re-mapped for backwards compatibility. + self.setPayload = self.set_payload + self.getQuery = self.get_query + self.setQuery = self.set_query + self.delQuery = self.del_query + + 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 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: + 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.getchildren(): + 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.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 new file mode 100644 index 0000000..66c74d8 --- /dev/null +++ b/sleekxmpp/stanza/message.py @@ -0,0 +1,165 @@ +""" + SleekXMPP: The Sleek XMPP Library + Copyright (C) 2010 Nathanael C. Fritz + This file is part of SleekXMPP. + + See the file LICENSE for copying permission. +""" + +from sleekxmpp.stanza import Error +from sleekxmpp.stanza.rootstanza import RootStanza +from sleekxmpp.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. + """ + + 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 setup(self, xml=None): + """ + Populate the stanza object using an optional XML object. + + Overrides StanzaBase.setup. + + Arguments: + xml -- Use an existing XML object for the stanza's values. + """ + # To comply with PEP8, method names now use underscores. + # Deprecated method names are re-mapped for backwards compatibility. + self.getType = self.get_type + self.getMucroom = self.get_mucroom + self.setMucroom = self.set_mucroom + self.delMucroom = self.del_mucroom + self.getMucnick = self.get_mucnick + self.setMucnick = self.set_mucnick + self.delMucnick = self.del_mucnick + + return StanzaBase.setup(self, xml) + + 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 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 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 diff --git a/sleekxmpp/stanza/nick.py b/sleekxmpp/stanza/nick.py new file mode 100644 index 0000000..a9243d1 --- /dev/null +++ b/sleekxmpp/stanza/nick.py @@ -0,0 +1,89 @@ +""" + SleekXMPP: The Sleek XMPP Library + Copyright (C) 2010 Nathanael C. Fritz + This file is part of SleekXMPP. + + See the file LICENSE for copying permission. +""" + +from sleekxmpp.stanza import Message, Presence +from sleekxmpp.xmlstream import ElementBase, ET, register_stanza_plugin + + +class Nick(ElementBase): + + """ + 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: + setup -- Overrides ElementBase.setup. + get_nick -- Return the nickname in the <nick> element. + set_nick -- Add a <nick> element with the given nickname. + del_nick -- Remove the <nick> element. + """ + + namespace = 'http://jabber.org/nick/nick' + name = 'nick' + plugin_attrib = name + interfaces = set(('nick',)) + + def setup(self, xml=None): + """ + Populate the stanza object using an optional XML object. + + Overrides StanzaBase.setup. + + Arguments: + xml -- Use an existing XML object for the stanza's values. + """ + # To comply with PEP8, method names now use underscores. + # Deprecated method names are re-mapped for backwards compatibility. + self.setNick = self.set_nick + self.getNick = self.get_nick + self.delNick = self.del_nick + + return ElementBase.setup(self, xml) + + def set_nick(self, nick): + """ + Add a <nick> element with the given nickname. + + Arguments: + nick -- A human readable, informal name. + """ + self.xml.text = nick + + def get_nick(self): + """Return the nickname in the <nick> element.""" + return self.xml.text + + def del_nick(self): + """Remove the <nick> element.""" + if self.parent is not None: + self.parent().xml.remove(self.xml) + + +register_stanza_plugin(Message, Nick) +register_stanza_plugin(Presence, Nick) diff --git a/sleekxmpp/stanza/presence.py b/sleekxmpp/stanza/presence.py new file mode 100644 index 0000000..7dcd8f9 --- /dev/null +++ b/sleekxmpp/stanza/presence.py @@ -0,0 +1,186 @@ +""" + SleekXMPP: The Sleek XMPP Library + Copyright (C) 2010 Nathanael C. Fritz + This file is part of SleekXMPP. + + See the file LICENSE for copying permission. +""" + +from sleekxmpp.stanza import Error +from sleekxmpp.stanza.rootstanza import RootStanza +from sleekxmpp.xmlstream import StanzaBase, ET + + +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. + """ + + 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 setup(self, xml=None): + """ + Populate the stanza object using an optional XML object. + + Overrides ElementBase.setup. + + Arguments: + xml -- Use an existing XML object for the stanza's values. + """ + # To comply with PEP8, method names now use underscores. + # Deprecated method names are re-mapped for backwards compatibility. + self.setShow = self.set_show + self.getType = self.get_type + self.setType = self.set_type + self.delType = self.get_type + self.getPriority = self.get_priority + self.setPriority = self.set_priority + + return StanzaBase.setup(self, xml) + + 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): + """ + 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 new file mode 100644 index 0000000..2677ea9 --- /dev/null +++ b/sleekxmpp/stanza/rootstanza.py @@ -0,0 +1,66 @@ +""" + SleekXMPP: The Sleek XMPP Library + Copyright (C) 2010 Nathanael C. Fritz + This file is part of SleekXMPP. + + See the file LICENSE for copying permission. +""" + +import logging +import traceback +import sys + +from sleekxmpp.exceptions import XMPPError +from sleekxmpp.stanza import Error +from sleekxmpp.xmlstream import ET, StanzaBase, register_stanza_plugin + + +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 + """ + 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() + + +register_stanza_plugin(RootStanza, Error) diff --git a/sleekxmpp/stanza/roster.py b/sleekxmpp/stanza/roster.py new file mode 100644 index 0000000..8f154a2 --- /dev/null +++ b/sleekxmpp/stanza/roster.py @@ -0,0 +1,125 @@ +""" + SleekXMPP: The Sleek XMPP Library + Copyright (C) 2010 Nathanael C. Fritz + This file is part of SleekXMPP. + + See the file LICENSE for copying permission. +""" + +from sleekxmpp.stanza import Iq +from sleekxmpp.xmlstream import JID +from sleekxmpp.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',)) + + def setup(self, xml=None): + """ + Populate the stanza object using an optional XML object. + + Overrides StanzaBase.setup. + + Arguments: + xml -- Use an existing XML object for the stanza's values. + """ + # To comply with PEP8, method names now use underscores. + # Deprecated method names are re-mapped for backwards compatibility. + self.setItems = self.set_items + self.getItems = self.get_items + self.delItems = self.del_items + + return ElementBase.setup(self, xml) + + 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: + 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 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 = {} + 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 del_items(self): + """ + Remove all <item> elements from the roster stanza. + """ + for child in self.xml.getchildren(): + self.xml.remove(child) + + +register_stanza_plugin(Iq, Roster) |
