summaryrefslogtreecommitdiff
path: root/sleekxmpp
diff options
context:
space:
mode:
authorLance Stout <lancestout@gmail.com>2012-06-22 23:17:15 -0700
committerLance Stout <lancestout@gmail.com>2012-06-22 23:17:15 -0700
commit5d6019a962fd1a30ed04520892836cdecc7fe19f (patch)
tree6ee7b956a813af2f0507867dc861f4aead3479e2 /sleekxmpp
parenteb5df1aa3722e6e85b9683805b4088f4340918aa (diff)
parent85ef2d8d0bcc92cd20d857d01bcf1bba56bc8edf (diff)
downloadslixmpp-5d6019a962fd1a30ed04520892836cdecc7fe19f.tar.gz
slixmpp-5d6019a962fd1a30ed04520892836cdecc7fe19f.tar.bz2
slixmpp-5d6019a962fd1a30ed04520892836cdecc7fe19f.tar.xz
slixmpp-5d6019a962fd1a30ed04520892836cdecc7fe19f.zip
Merge branch 'master' into develop
Diffstat (limited to 'sleekxmpp')
-rw-r--r--sleekxmpp/basexmpp.py22
-rw-r--r--sleekxmpp/plugins/__init__.py3
-rw-r--r--sleekxmpp/plugins/xep_0256.py67
-rw-r--r--sleekxmpp/plugins/xep_0270.py20
-rw-r--r--sleekxmpp/plugins/xep_0302.py21
-rw-r--r--sleekxmpp/stanza/message.py44
-rw-r--r--sleekxmpp/stanza/presence.py17
-rw-r--r--sleekxmpp/xmlstream/stanzabase.py87
8 files changed, 230 insertions, 51 deletions
diff --git a/sleekxmpp/basexmpp.py b/sleekxmpp/basexmpp.py
index aae80168..275275d1 100644
--- a/sleekxmpp/basexmpp.py
+++ b/sleekxmpp/basexmpp.py
@@ -134,6 +134,7 @@ class BaseXMPP(XMLStream):
Callback('Presence',
MatchXPath("{%s}presence" % self.default_ns),
self._handle_presence))
+
self.register_handler(
Callback('Stream Error',
MatchXPath("{%s}error" % self.stream_ns),
@@ -658,6 +659,27 @@ class BaseXMPP(XMLStream):
def _handle_stream_error(self, error):
self.event('stream_error', error)
+ if error['condition'] == 'see-other-host':
+ other_host = error['see_other_host']
+
+ host = other_host
+ port = 5222
+
+ if '[' in other_host and ']' in other_host:
+ host = other_host.split(']')[0][1:]
+ elif ':' in other_host:
+ host = other_host.split(':')[0]
+
+ port_sec = other_host.split(']')[-1]
+ if ':' in port_sec:
+ port = int(port_sec.split(':')[1])
+
+ self.address = (host, port)
+ self.default_domain = host
+ self.dns_records = None
+ self.reconnect_delay = None
+ self.reconnect()
+
def _handle_message(self, msg):
"""Process incoming message stanzas."""
if not self.is_component and not msg['to'].bare:
diff --git a/sleekxmpp/plugins/__init__.py b/sleekxmpp/plugins/__init__.py
index e37eb825..fef7072c 100644
--- a/sleekxmpp/plugins/__init__.py
+++ b/sleekxmpp/plugins/__init__.py
@@ -57,5 +57,8 @@ __all__ = [
'xep_0224', # Attention
'xep_0231', # Bits of Binary
'xep_0249', # Direct MUC Invitations
+ 'xep_0256', # Last Activity in Presence
'xep_0258', # Security Labels in XMPP
+ 'xep_0270', # XMPP Compliance Suites 2010
+ 'xep_0302', # XMPP Compliance Suites 2012
]
diff --git a/sleekxmpp/plugins/xep_0256.py b/sleekxmpp/plugins/xep_0256.py
new file mode 100644
index 00000000..265a5da8
--- /dev/null
+++ b/sleekxmpp/plugins/xep_0256.py
@@ -0,0 +1,67 @@
+"""
+ SleekXMPP: The Sleek XMPP Library
+ Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
+ This file is part of SleekXMPP.
+
+ See the file LICENSE for copying permission.
+"""
+
+import logging
+
+from sleekxmpp import Presence
+from sleekxmpp.plugins import BasePlugin, register_plugin
+from sleekxmpp.xmlstream import register_stanza_plugin
+
+from sleekxmpp.plugins.xep_0012 import stanza, LastActivity
+
+
+log = logging.getLogger(__name__)
+
+
+class XEP_0256(BasePlugin):
+
+ name = 'xep_0256'
+ description = 'XEP-0256: Last Activity in Presence'
+ dependencies = set(['xep_0012'])
+ stanza = stanza
+
+ def plugin_init(self):
+ self.auto_last_activity = self.config.get('auto_last_activity', False)
+
+ register_stanza_plugin(Presence, LastActivity)
+
+ self.xmpp.add_filter('out', self._initial_presence_activity)
+ self.xmpp.add_event_handler('connected', self._reset_presence_activity)
+
+ self._initial_presence = set()
+
+ def _reset_presence_activity(self, e):
+ self._initial_presence = set()
+
+ def _initial_presence_activity(self, stanza):
+ if isinstance(stanza, Presence):
+ use_last_activity = False
+
+ if self.auto_last_activity and stanza['show'] in ('xa', 'away'):
+ use_last_activity = True
+
+ if stanza['from'] not in self._initial_presence:
+ self._initial_presence.add(stanza['from'])
+ use_last_activity = True
+
+ if use_last_activity:
+ plugin = self.xmpp['xep_0012']
+ try:
+ result = plugin.api['get_last_activity'](stanza['from'],
+ None,
+ stanza['to'])
+ seconds = result['last_activity']['seconds']
+ except XMPPError:
+ seconds = None
+
+ if seconds is not None:
+ stanza['last_activity']['seconds'] = seconds
+ return stanza
+
+
+register_plugin(XEP_0256)
diff --git a/sleekxmpp/plugins/xep_0270.py b/sleekxmpp/plugins/xep_0270.py
new file mode 100644
index 00000000..eadd45ad
--- /dev/null
+++ b/sleekxmpp/plugins/xep_0270.py
@@ -0,0 +1,20 @@
+"""
+ SleekXMPP: The Sleek XMPP Library
+ Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
+ This file is part of SleekXMPP.
+
+ See the file LICENSE for copying permission.
+"""
+
+from sleekxmpp.plugins import BasePlugin, register_plugin
+
+
+class XEP_0270(BasePlugin):
+
+ name = 'xep_0270'
+ description = 'XEP-0270: XMPP Compliance Suites 2010'
+ dependencies = set(['xep_0030', 'xep_0115', 'xep_0054',
+ 'xep_0163', 'xep_0045', 'xep_0085'])
+
+
+register_plugin(XEP_0270)
diff --git a/sleekxmpp/plugins/xep_0302.py b/sleekxmpp/plugins/xep_0302.py
new file mode 100644
index 00000000..dee60f91
--- /dev/null
+++ b/sleekxmpp/plugins/xep_0302.py
@@ -0,0 +1,21 @@
+"""
+ SleekXMPP: The Sleek XMPP Library
+ Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
+ This file is part of SleekXMPP.
+
+ See the file LICENSE for copying permission.
+"""
+
+from sleekxmpp.plugins import BasePlugin, register_plugin
+
+
+class XEP_0302(BasePlugin):
+
+ name = 'xep_0302'
+ description = 'XEP-0302: XMPP Compliance Suites 2012'
+ dependencies = set(['xep_0030', 'xep_0115', 'xep_0054',
+ 'xep_0163', 'xep_0045', 'xep_0085',
+ 'xep_0184', 'xep_0198'])
+
+
+register_plugin(XEP_0302)
diff --git a/sleekxmpp/stanza/message.py b/sleekxmpp/stanza/message.py
index 407802bd..02133682 100644
--- a/sleekxmpp/stanza/message.py
+++ b/sleekxmpp/stanza/message.py
@@ -7,7 +7,7 @@
"""
from sleekxmpp.stanza.rootstanza import RootStanza
-from sleekxmpp.xmlstream import StanzaBase
+from sleekxmpp.xmlstream import StanzaBase, ET
class Message(RootStanza):
@@ -54,13 +54,14 @@ class Message(RootStanza):
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'))
+ namespace = 'jabber:client'
plugin_attrib = name
- types = set((None, 'normal', 'chat', 'headline', 'error', 'groupchat'))
+ 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 get_type(self):
"""
@@ -72,6 +73,31 @@ class Message(RootStanza):
"""
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'
@@ -96,10 +122,16 @@ class Message(RootStanza):
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:
diff --git a/sleekxmpp/stanza/presence.py b/sleekxmpp/stanza/presence.py
index f2dd0968..7951f861 100644
--- a/sleekxmpp/stanza/presence.py
+++ b/sleekxmpp/stanza/presence.py
@@ -60,16 +60,17 @@ class Presence(RootStanza):
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'))
+ namespace = 'jabber:client'
plugin_attrib = name
-
- types = set(('available', 'unavailable', 'error', 'probe', 'subscribe',
- 'subscribed', 'unsubscribe', 'unsubscribed'))
- showtypes = set(('dnd', 'chat', 'xa', 'away'))
+ 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 exception(self, e):
"""
diff --git a/sleekxmpp/xmlstream/stanzabase.py b/sleekxmpp/xmlstream/stanzabase.py
index 88276ddf..4af441cc 100644
--- a/sleekxmpp/xmlstream/stanzabase.py
+++ b/sleekxmpp/xmlstream/stanzabase.py
@@ -421,12 +421,6 @@ class ElementBase(object):
#: ``'{namespace}elementname'``.
self.tag = self.tag_name()
- if 'lang' not in self.interfaces:
- if isinstance(self.interfaces, tuple):
- self.interfaces += ('lang',)
- else:
- self.interfaces.add('lang')
-
#: A :class:`weakref.weakref` to the parent stanza, if there is one.
#: If not, then :attr:`parent` is ``None``.
self.parent = None
@@ -574,6 +568,7 @@ class ElementBase(object):
.. versionadded:: 1.0-Beta1
"""
values = {}
+ values['lang'] = self['lang']
for interface in self.interfaces:
values[interface] = self[interface]
if interface in self.lang_interfaces:
@@ -629,6 +624,8 @@ class ElementBase(object):
sub.values = subdict
self.iterables.append(sub)
break
+ elif interface == 'lang':
+ self[interface] = value
elif interface in self.interfaces:
self[full_interface] = value
elif interface in self.plugin_attrib_map:
@@ -678,7 +675,7 @@ class ElementBase(object):
if attrib == 'substanzas':
return self.iterables
- elif attrib in self.interfaces:
+ elif attrib in self.interfaces or attrib == 'lang':
get_method = "get_%s" % attrib.lower()
get_method2 = "get%s" % attrib.title()
@@ -752,7 +749,7 @@ class ElementBase(object):
if lang and attrib in self.lang_interfaces:
kwargs['lang'] = lang
- if attrib in self.interfaces:
+ if attrib in self.interfaces or attrib == 'lang':
if value is not None:
set_method = "set_%s" % attrib.lower()
set_method2 = "set%s" % attrib.title()
@@ -838,7 +835,7 @@ class ElementBase(object):
if lang and attrib in self.lang_interfaces:
kwargs['lang'] = lang
- if attrib in self.interfaces:
+ if attrib in self.interfaces or attrib == 'lang':
del_method = "del_%s" % attrib.lower()
del_method2 = "del%s" % attrib.title()
@@ -973,10 +970,6 @@ class ElementBase(object):
:param keep: Indicates if the element should be kept if its text is
removed. Defaults to False.
"""
- path = self._fix_ns(name, split=True)
- element = self.xml.find(name)
- parent = self.xml
-
default_lang = self.get_lang()
if lang is None:
lang = default_lang
@@ -984,32 +977,51 @@ class ElementBase(object):
if not text and not keep:
return self._del_sub(name, lang=lang)
- if element is None:
- # We need to add the element. If the provided name was
- # an XPath expression, some of the intermediate elements
- # may already exist. If so, we want to use those instead
- # of generating new elements.
- last_xml = self.xml
- walked = []
- for ename in path:
- walked.append(ename)
- element = self.xml.find("/".join(walked))
- if element is None:
- element = ET.Element(ename)
- if lang:
- element.attrib['{%s}lang' % XML_NS] = lang
- last_xml.append(element)
- parent = last_xml
- last_xml = element
- element = last_xml
+ path = self._fix_ns(name, split=True)
+ name = path[-1]
+ parent = self.xml
- if lang:
- if element.attrib.get('{%s}lang' % XML_NS, default_lang) != lang:
- element = ET.Element(ename)
- element.attrib['{%s}lang' % XML_NS] = lang
- parent.append(element)
+ # The first goal is to find the parent of the subelement, or, if
+ # we can't find that, the closest grandparent element.
+ missing_path = []
+ search_order = path[:-1]
+ while search_order:
+ parent = self.xml.find('/'.join(search_order))
+ ename = search_order.pop()
+ if parent is not None:
+ break
+ else:
+ missing_path.append(ename)
+ missing_path.reverse()
+
+ # Find all existing elements that match the desired
+ # element path (there may be multiples due to different
+ # languages values).
+ if parent is not None:
+ elements = self.xml.findall('/'.join(path))
+ else:
+ parent = self.xml
+ elements = []
+
+ # Insert the remaining grandparent elements that don't exist yet.
+ for ename in missing_path:
+ element = ET.Element(ename)
+ parent.append(element)
+ parent = element
+
+ # Re-use an existing element with the proper language, if one exists.
+ for element in elements:
+ elang = element.attrib.get('{%s}lang' % XML_NS, default_lang)
+ if not lang and elang == default_lang or lang and lang == elang:
+ element.text = text
+ return element
+ # No useable element exists, so create a new one.
+ element = ET.Element(name)
element.text = text
+ if lang and lang != default_lang:
+ element.attrib['{%s}lang' % XML_NS] = lang
+ parent.append(element)
return element
def _set_all_sub_text(self, name, values, keep=False, lang=None):
@@ -1184,6 +1196,7 @@ class ElementBase(object):
out = []
out += [x for x in self.interfaces]
out += [x for x in self.loaded_plugins]
+ out.append('lang')
if self.iterables:
out.append('substanzas')
return out
@@ -1263,7 +1276,7 @@ class ElementBase(object):
"""
return "{%s}%s" % (cls.namespace, cls.name)
- def get_lang(self):
+ def get_lang(self, lang=None):
result = self.xml.attrib.get('{%s}lang' % XML_NS, '')
if not result and self.parent and self.parent():
return self.parent()['lang']