summaryrefslogtreecommitdiff
path: root/sleekxmpp
diff options
context:
space:
mode:
Diffstat (limited to 'sleekxmpp')
-rw-r--r--sleekxmpp/__init__.py4
-rw-r--r--sleekxmpp/basexmpp.py49
-rw-r--r--sleekxmpp/clientxmpp.py14
-rw-r--r--sleekxmpp/componentxmpp.py4
-rw-r--r--sleekxmpp/exceptions.py21
-rw-r--r--sleekxmpp/features/__init__.py4
-rw-r--r--sleekxmpp/features/feature_mechanisms/mechanisms.py4
-rw-r--r--sleekxmpp/features/feature_mechanisms/stanza/mechanisms.py6
-rw-r--r--sleekxmpp/plugins/__init__.py4
-rw-r--r--sleekxmpp/plugins/xep_0004.py395
-rw-r--r--sleekxmpp/plugins/xep_0004/__init__.py11
-rw-r--r--sleekxmpp/plugins/xep_0004/dataforms.py60
-rw-r--r--sleekxmpp/plugins/xep_0004/stanza/__init__.py10
-rw-r--r--sleekxmpp/plugins/xep_0004/stanza/field.py167
-rw-r--r--sleekxmpp/plugins/xep_0004/stanza/form.py250
-rw-r--r--sleekxmpp/plugins/xep_0009/remote.py2
-rw-r--r--sleekxmpp/plugins/xep_0050/adhoc.py2
-rw-r--r--sleekxmpp/plugins/xep_0060/pubsub.py415
-rw-r--r--sleekxmpp/plugins/xep_0060/stanza/pubsub.py320
-rw-r--r--sleekxmpp/plugins/xep_0060/stanza/pubsub_owner.py187
-rw-r--r--sleekxmpp/plugins/xep_0078.py72
-rw-r--r--sleekxmpp/plugins/xep_0078/__init__.py12
-rw-r--r--sleekxmpp/plugins/xep_0078/legacyauth.py108
-rw-r--r--sleekxmpp/plugins/xep_0078/stanza.py43
-rw-r--r--sleekxmpp/plugins/xep_0082.py10
-rw-r--r--sleekxmpp/plugins/xep_0199/ping.py2
-rw-r--r--sleekxmpp/plugins/xep_0202/__init__.py1
-rw-r--r--sleekxmpp/plugins/xep_0202/stanza.py3
-rw-r--r--sleekxmpp/stanza/iq.py8
-rw-r--r--sleekxmpp/stanza/stream_features.py2
-rw-r--r--sleekxmpp/test/sleektest.py3
-rw-r--r--sleekxmpp/thirdparty/__init__.py1
-rw-r--r--sleekxmpp/thirdparty/mini_dateutil.py267
-rw-r--r--sleekxmpp/thirdparty/suelta/mechanisms/anonymous.py2
-rw-r--r--sleekxmpp/thirdparty/suelta/sasl.py4
-rw-r--r--sleekxmpp/xmlstream/stanzabase.py7
-rw-r--r--sleekxmpp/xmlstream/xmlstream.py21
37 files changed, 1432 insertions, 1063 deletions
diff --git a/sleekxmpp/__init__.py b/sleekxmpp/__init__.py
index a53cfb0e..d2c014d3 100644
--- a/sleekxmpp/__init__.py
+++ b/sleekxmpp/__init__.py
@@ -15,5 +15,5 @@ from sleekxmpp.xmlstream import XMLStream, RestartStream
from sleekxmpp.xmlstream.matcher import *
from sleekxmpp.xmlstream.stanzabase import StanzaBase, ET
-__version__ = '1.0beta6'
-__version_info__ = (1, 0, 0, 'beta6', 0)
+__version__ = '1.0rc1'
+__version_info__ = (1, 0, 0, 'rc1', 0)
diff --git a/sleekxmpp/basexmpp.py b/sleekxmpp/basexmpp.py
index c2267535..ecc30aa4 100644
--- a/sleekxmpp/basexmpp.py
+++ b/sleekxmpp/basexmpp.py
@@ -163,12 +163,41 @@ class BaseXMPP(XMLStream):
register_stanza_plugin(Message, Nick)
register_stanza_plugin(Message, HTMLIM)
- def process(self, *args, **kwargs):
+ def start_stream_handler(self, xml):
"""
- Ensure that plugin inter-dependencies are handled before starting
- event processing.
+ Save the stream ID once the streams have been established.
+
+ Overrides XMLStream.start_stream_handler.
+ Arguments:
+ xml -- The incoming stream's root element.
+ """
+ self.stream_id = xml.get('id', '')
+
+ def process(self, *args, **kwargs):
+ """
Overrides XMLStream.process.
+
+ Initialize the XML streams and begin processing events.
+
+ The number of threads used for processing stream events is determined
+ by HANDLER_THREADS.
+
+ Arguments:
+ block -- If block=False then event dispatcher will run
+ in a separate thread, allowing for the stream to be
+ used in the background for another application.
+ Otherwise, process(block=True) blocks the current thread.
+ Defaults to False.
+
+ **threaded is deprecated and included for API compatibility**
+ threaded -- If threaded=True then event dispatcher will run
+ in a separate thread, allowing for the stream to be
+ used in the background for another application.
+ Defaults to True.
+
+ Event handlers and the send queue will be threaded
+ regardless of these parameters.
"""
for name in self.plugin:
if not self.plugin[name].post_inited:
@@ -192,17 +221,23 @@ class BaseXMPP(XMLStream):
if not module:
try:
module = sleekxmpp.plugins
- module = __import__(str("%s.%s" % (module.__name__, plugin)),
- globals(), locals(), [str(plugin)])
+ module = __import__(
+ str("%s.%s" % (module.__name__, plugin)),
+ globals(), locals(), [str(plugin)])
except ImportError:
module = sleekxmpp.features
- module = __import__(str("%s.%s" % (module.__name__, plugin)),
- globals(), locals(), [str(plugin)])
+ module = __import__(
+ str("%s.%s" % (module.__name__, plugin)),
+ globals(), locals(), [str(plugin)])
if isinstance(module, str):
# We probably want to load a module from outside
# the sleekxmpp package, so leave out the globals().
module = __import__(module, fromlist=[plugin])
+ # Use the global plugin config cache, if applicable
+ if not pconfig:
+ pconfig = self.plugin_config.get(plugin, {})
+
# Load the plugin class from the module.
self.plugin[plugin] = getattr(module, plugin)(self, pconfig)
diff --git a/sleekxmpp/clientxmpp.py b/sleekxmpp/clientxmpp.py
index 8193d0a0..d7227486 100644
--- a/sleekxmpp/clientxmpp.py
+++ b/sleekxmpp/clientxmpp.py
@@ -40,9 +40,12 @@ log = logging.getLogger(__name__)
class ClientXMPP(BaseXMPP):
"""
- SleekXMPP's client class.
+ SleekXMPP's client class. ( Use only for good, not for evil.)
- Use only for good, not for evil.
+ Typical Use:
+ xmpp = ClientXMPP('user@server.tld/resource', 'password')
+ xmpp.process(block=False) // when block is True, it blocks the current
+ // thread. False by default.
Attributes:
@@ -251,12 +254,7 @@ class ClientXMPP(BaseXMPP):
iq.enable('roster')
response = iq.send(block, timeout, callback)
- if response == False:
- self.event('roster_timeout')
-
- if response in [False, None] or not isinstance(response, Iq):
- return response
- else:
+ if callback is None:
return self._handle_roster(response, request=True)
def _handle_connected(self, event=None):
diff --git a/sleekxmpp/componentxmpp.py b/sleekxmpp/componentxmpp.py
index 4d17d725..6c15986d 100644
--- a/sleekxmpp/componentxmpp.py
+++ b/sleekxmpp/componentxmpp.py
@@ -117,11 +117,13 @@ class ComponentXMPP(BaseXMPP):
Once the streams are established, attempt to handshake
with the server to be accepted as a component.
- Overrides XMLStream.start_stream_handler.
+ Overrides BaseXMPP.start_stream_handler.
Arguments:
xml -- The incoming stream's root element.
"""
+ BaseXMPP.start_stream_handler(self, xml)
+
# Construct a hash of the stream ID and the component secret.
sid = xml.get('id', '')
pre_hash = '%s%s' % (sid, self.secret)
diff --git a/sleekxmpp/exceptions.py b/sleekxmpp/exceptions.py
index 4727f0c6..8329a3c3 100644
--- a/sleekxmpp/exceptions.py
+++ b/sleekxmpp/exceptions.py
@@ -52,3 +52,24 @@ class XMPPError(Exception):
self.extension = extension
self.extension_ns = extension_ns
self.extension_args = extension_args
+
+
+class IqTimeout(Exception):
+
+ """
+ An exception which indicates that an IQ request response has not been
+ received within the alloted time window.
+ """
+
+ def __init__(self, iq):
+ self.iq = iq
+
+class IqError(Exception):
+
+ """
+ An exception raised when an Iq stanza of type 'error' is received
+ after making a blocking send call.
+ """
+
+ def __init__(self, iq):
+ self.iq = iq
diff --git a/sleekxmpp/features/__init__.py b/sleekxmpp/features/__init__.py
index 5c86cfea..5bfe173d 100644
--- a/sleekxmpp/features/__init__.py
+++ b/sleekxmpp/features/__init__.py
@@ -6,6 +6,4 @@
See the file LICENSE for copying permission.
"""
-__all__ = ['feature_starttls', 'feature_mechanisms',
- 'feature_bind', 'feature_session',
- 'sasl_plain', 'sasl_anonymous']
+__all__ = ['feature_starttls', 'feature_mechanisms', 'feature_bind']
diff --git a/sleekxmpp/features/feature_mechanisms/mechanisms.py b/sleekxmpp/features/feature_mechanisms/mechanisms.py
index d60818bb..a6cff0a0 100644
--- a/sleekxmpp/features/feature_mechanisms/mechanisms.py
+++ b/sleekxmpp/features/feature_mechanisms/mechanisms.py
@@ -29,6 +29,7 @@ class feature_mechanisms(base_plugin):
self.description = "SASL Stream Feature"
self.stanza = stanza
+ self.use_mech = self.config.get('use_mech', None)
def tls_active():
return 'starttls' in self.xmpp.features
@@ -49,7 +50,8 @@ class feature_mechanisms(base_plugin):
username=self.xmpp.boundjid.user,
sec_query=suelta.sec_query_allow,
request_values=sasl_callback,
- tls_active=tls_active)
+ tls_active=tls_active,
+ mech=self.use_mech)
register_stanza_plugin(StreamFeatures, stanza.Mechanisms)
diff --git a/sleekxmpp/features/feature_mechanisms/stanza/mechanisms.py b/sleekxmpp/features/feature_mechanisms/stanza/mechanisms.py
index 1189cd80..c09cafbd 100644
--- a/sleekxmpp/features/feature_mechanisms/stanza/mechanisms.py
+++ b/sleekxmpp/features/feature_mechanisms/stanza/mechanisms.py
@@ -42,9 +42,9 @@ class Mechanisms(ElementBase):
"""
self.del_mechanisms()
for val in values:
- mech = ET.Element('{%s}mechanism' % self.namespace)
- mech.text = val
- self.append(mech)
+ mech = ET.Element('{%s}mechanism' % self.namespace)
+ mech.text = val
+ self.append(mech)
def del_mechanisms(self):
"""
diff --git a/sleekxmpp/plugins/__init__.py b/sleekxmpp/plugins/__init__.py
index b48a4c03..c0b1121b 100644
--- a/sleekxmpp/plugins/__init__.py
+++ b/sleekxmpp/plugins/__init__.py
@@ -8,4 +8,6 @@
__all__ = ['xep_0004', 'xep_0009', 'xep_0012', 'xep_0030', 'xep_0033',
'xep_0045', 'xep_0050', 'xep_0060', 'xep_0066', 'xep_0082',
'xep_0085', 'xep_0086', 'xep_0092', 'xep_0128', 'xep_0199',
- 'xep_0202', 'xep_0203', 'xep_0224', 'xep_0249', 'gmail_notify']
+ 'xep_0203', 'xep_0224', 'xep_0249', 'gmail_notify']
+
+# Don't automatically load xep_0078
diff --git a/sleekxmpp/plugins/xep_0004.py b/sleekxmpp/plugins/xep_0004.py
deleted file mode 100644
index 5a49d70f..00000000
--- a/sleekxmpp/plugins/xep_0004.py
+++ /dev/null
@@ -1,395 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2010 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-import logging
-import copy
-from . import base
-from .. xmlstream.handler.callback import Callback
-from .. xmlstream.matcher.xpath import MatchXPath
-from .. xmlstream.stanzabase import registerStanzaPlugin, ElementBase, ET, JID
-from .. stanza.message import Message
-
-
-log = logging.getLogger(__name__)
-
-
-class Form(ElementBase):
- namespace = 'jabber:x:data'
- name = 'x'
- plugin_attrib = 'form'
- interfaces = set(('fields', 'instructions', 'items', 'reported', 'title', 'type', 'values'))
- sub_interfaces = set(('title',))
- form_types = set(('cancel', 'form', 'result', 'submit'))
-
- def __init__(self, *args, **kwargs):
- title = None
- if 'title' in kwargs:
- title = kwargs['title']
- del kwargs['title']
- ElementBase.__init__(self, *args, **kwargs)
- if title is not None:
- self['title'] = title
- self.field = FieldAccessor(self)
-
- def setup(self, xml=None):
- if ElementBase.setup(self, xml): #if we had to generate xml
- self['type'] = 'form'
-
- def addField(self, var='', ftype=None, label='', desc='', required=False, value=None, options=None, **kwargs):
- kwtype = kwargs.get('type', None)
- if kwtype is None:
- kwtype = ftype
-
- field = FormField(parent=self)
- field['var'] = var
- field['type'] = kwtype
- field['label'] = label
- field['desc'] = desc
- field['required'] = required
- field['value'] = value
- if options is not None:
- field['options'] = options
- return field
-
- def getXML(self, type='submit'):
- self['type'] = type
- log.warning("Form.getXML() is deprecated API compatibility with plugins/old_0004.py")
- return self.xml
-
- def fromXML(self, xml):
- log.warning("Form.fromXML() is deprecated API compatibility with plugins/old_0004.py")
- n = Form(xml=xml)
- return n
-
- def addItem(self, values):
- itemXML = ET.Element('{%s}item' % self.namespace)
- self.xml.append(itemXML)
- reported_vars = self['reported'].keys()
- for var in reported_vars:
- fieldXML = ET.Element('{%s}field' % FormField.namespace)
- itemXML.append(fieldXML)
- field = FormField(xml=fieldXML)
- field['var'] = var
- field['value'] = values.get(var, None)
-
- def addReported(self, var, ftype=None, label='', desc='', **kwargs):
- kwtype = kwargs.get('type', None)
- if kwtype is None:
- kwtype = ftype
- reported = self.xml.find('{%s}reported' % self.namespace)
- if reported is None:
- reported = ET.Element('{%s}reported' % self.namespace)
- self.xml.append(reported)
- fieldXML = ET.Element('{%s}field' % FormField.namespace)
- reported.append(fieldXML)
- field = FormField(xml=fieldXML)
- field['var'] = var
- field['type'] = kwtype
- field['label'] = label
- field['desc'] = desc
- return field
-
- def cancel(self):
- self['type'] = 'cancel'
-
- def delFields(self):
- fieldsXML = self.xml.findall('{%s}field' % FormField.namespace)
- for fieldXML in fieldsXML:
- self.xml.remove(fieldXML)
-
- def delInstructions(self):
- instsXML = self.xml.findall('{%s}instructions')
- for instXML in instsXML:
- self.xml.remove(instXML)
-
- def delItems(self):
- itemsXML = self.xml.find('{%s}item' % self.namespace)
- for itemXML in itemsXML:
- self.xml.remove(itemXML)
-
- def delReported(self):
- reportedXML = self.xml.find('{%s}reported' % self.namespace)
- if reportedXML is not None:
- self.xml.remove(reportedXML)
-
- def getFields(self, use_dict=False):
- fields = {} if use_dict else []
- fieldsXML = self.xml.findall('{%s}field' % FormField.namespace)
- for fieldXML in fieldsXML:
- field = FormField(xml=fieldXML)
- if use_dict:
- fields[field['var']] = field
- else:
- fields.append((field['var'], field))
- return fields
-
- def getInstructions(self):
- instructions = ''
- instsXML = self.xml.findall('{%s}instructions' % self.namespace)
- return "\n".join([instXML.text for instXML in instsXML])
-
- def getItems(self):
- items = []
- itemsXML = self.xml.findall('{%s}item' % self.namespace)
- for itemXML in itemsXML:
- item = {}
- fieldsXML = itemXML.findall('{%s}field' % FormField.namespace)
- for fieldXML in fieldsXML:
- field = FormField(xml=fieldXML)
- item[field['var']] = field['value']
- items.append(item)
- return items
-
- def getReported(self):
- fields = {}
- fieldsXML = self.xml.findall('{%s}reported/{%s}field' % (self.namespace,
- FormField.namespace))
- for fieldXML in fieldsXML:
- field = FormField(xml=fieldXML)
- fields[field['var']] = field
- return fields
-
- def getValues(self):
- values = {}
- fields = self.getFields(use_dict=True)
- for var in fields:
- values[var] = fields[var]['value']
- return values
-
- def reply(self):
- if self['type'] == 'form':
- self['type'] = 'submit'
- elif self['type'] == 'submit':
- self['type'] = 'result'
-
- def setFields(self, fields, default=None):
- del self['fields']
- for field_data in fields:
- var = field_data[0]
- field = field_data[1]
- field['var'] = var
-
- self.addField(**field)
-
- def setInstructions(self, instructions):
- del self['instructions']
- if instructions in [None, '']:
- return
- instructions = instructions.split('\n')
- for instruction in instructions:
- inst = ET.Element('{%s}instructions' % self.namespace)
- inst.text = instruction
- self.xml.append(inst)
-
- def setItems(self, items):
- for item in items:
- self.addItem(item)
-
- def setReported(self, reported, default=None):
- for var in reported:
- field = reported[var]
- field['var'] = var
- self.addReported(var, **field)
-
- def setValues(self, values):
- fields = self.getFields(use_dict=True)
- for field in values:
- fields[field]['value'] = values[field]
-
- def merge(self, other):
- new = copy.copy(self)
- if type(other) == dict:
- new.setValues(other)
- return new
- nfields = new.getFields(use_dict=True)
- ofields = other.getFields(use_dict=True)
- nfields.update(ofields)
- new.setFields([(x, nfields[x]) for x in nfields])
- return new
-
-class FieldAccessor(object):
- def __init__(self, form):
- self.form = form
-
- def __getitem__(self, key):
- return self.form.getFields(use_dict=True)[key]
-
- def __contains__(self, key):
- return key in self.form.getFields(use_dict=True)
-
- def has_key(self, key):
- return key in self.form.getFields(use_dict=True)
-
-
-class FormField(ElementBase):
- namespace = 'jabber:x:data'
- name = 'field'
- plugin_attrib = 'field'
- interfaces = set(('answer', 'desc', 'required', 'value', 'options', 'label', 'type', 'var'))
- sub_interfaces = set(('desc',))
- field_types = set(('boolean', 'fixed', 'hidden', 'jid-multi', 'jid-single', 'list-multi',
- 'list-single', 'text-multi', 'text-private', 'text-single'))
- multi_value_types = set(('hidden', 'jid-multi', 'list-multi', 'text-multi'))
- multi_line_types = set(('hidden', 'text-multi'))
- option_types = set(('list-multi', 'list-single'))
- true_values = set((True, '1', 'true'))
-
- def addOption(self, label='', value=''):
- if self['type'] in self.option_types:
- opt = FieldOption(parent=self)
- opt['label'] = label
- opt['value'] = value
- else:
- raise ValueError("Cannot add options to a %s field." % self['type'])
-
- def delOptions(self):
- optsXML = self.xml.findall('{%s}option' % self.namespace)
- for optXML in optsXML:
- self.xml.remove(optXML)
-
- def delRequired(self):
- reqXML = self.xml.find('{%s}required' % self.namespace)
- if reqXML is not None:
- self.xml.remove(reqXML)
-
- def delValue(self):
- valsXML = self.xml.findall('{%s}value' % self.namespace)
- for valXML in valsXML:
- self.xml.remove(valXML)
-
- def getAnswer(self):
- return self.getValue()
-
- def getOptions(self):
- options = []
- optsXML = self.xml.findall('{%s}option' % self.namespace)
- for optXML in optsXML:
- opt = FieldOption(xml=optXML)
- options.append({'label': opt['label'], 'value':opt['value']})
- return options
-
- def getRequired(self):
- reqXML = self.xml.find('{%s}required' % self.namespace)
- return reqXML is not None
-
- def getValue(self):
- valsXML = self.xml.findall('{%s}value' % self.namespace)
- if len(valsXML) == 0:
- return None
- elif self['type'] == 'boolean':
- return valsXML[0].text in self.true_values
- elif self['type'] in self.multi_value_types:
- values = []
- for valXML in valsXML:
- if valXML.text is None:
- valXML.text = ''
- values.append(valXML.text)
- if self['type'] == 'text-multi':
- values = "\n".join(values)
- return values
- else:
- return valsXML[0].text
-
- def setAnswer(self, answer):
- self.setValue(answer)
-
- def setFalse(self):
- self.setValue(False)
-
- def setOptions(self, options):
- for value in options:
- if isinstance(value, dict):
- self.addOption(**value)
- else:
- self.addOption(value=value)
-
- def setRequired(self, required):
- exists = self.getRequired()
- if not exists and required:
- self.xml.append(ET.Element('{%s}required' % self.namespace))
- elif exists and not required:
- self.delRequired()
-
- def setTrue(self):
- self.setValue(True)
-
- def setValue(self, value):
- self.delValue()
- valXMLName = '{%s}value' % self.namespace
-
- if self['type'] == 'boolean':
- if value in self.true_values:
- valXML = ET.Element(valXMLName)
- valXML.text = '1'
- self.xml.append(valXML)
- else:
- valXML = ET.Element(valXMLName)
- valXML.text = '0'
- self.xml.append(valXML)
- elif self['type'] in self.multi_value_types or self['type'] in ['', None]:
- if self['type'] in self.multi_line_types and isinstance(value, str):
- value = value.split('\n')
- if not isinstance(value, list):
- value = [value]
- for val in value:
- if self['type'] in ['', None] and val in self.true_values:
- val = '1'
- valXML = ET.Element(valXMLName)
- valXML.text = val
- self.xml.append(valXML)
- else:
- if isinstance(value, list):
- raise ValueError("Cannot add multiple values to a %s field." % self['type'])
- valXML = ET.Element(valXMLName)
- valXML.text = value
- self.xml.append(valXML)
-
-
-class FieldOption(ElementBase):
- namespace = 'jabber:x:data'
- name = 'option'
- plugin_attrib = 'option'
- interfaces = set(('label', 'value'))
- sub_interfaces = set(('value',))
-
-
-class xep_0004(base.base_plugin):
- """
- XEP-0004: Data Forms
- """
-
- def plugin_init(self):
- self.xep = '0004'
- self.description = 'Data Forms'
-
- self.xmpp.registerHandler(
- Callback('Data Form',
- MatchXPath('{%s}message/{%s}x' % (self.xmpp.default_ns,
- Form.namespace)),
- self.handle_form))
-
- registerStanzaPlugin(FormField, FieldOption)
- registerStanzaPlugin(Form, FormField)
- registerStanzaPlugin(Message, Form)
-
- def makeForm(self, ftype='form', title='', instructions=''):
- f = Form()
- f['type'] = ftype
- f['title'] = title
- f['instructions'] = instructions
- return f
-
- def post_init(self):
- base.base_plugin.post_init(self)
- self.xmpp.plugin['xep_0030'].add_feature('jabber:x:data')
-
- def handle_form(self, message):
- self.xmpp.event("message_xform", message)
-
- def buildForm(self, xml):
- return Form(xml=xml)
diff --git a/sleekxmpp/plugins/xep_0004/__init__.py b/sleekxmpp/plugins/xep_0004/__init__.py
new file mode 100644
index 00000000..aad4e15f
--- /dev/null
+++ b/sleekxmpp/plugins/xep_0004/__init__.py
@@ -0,0 +1,11 @@
+"""
+ SleekXMPP: The Sleek XMPP Library
+ Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout
+ This file is part of SleekXMPP.
+
+ See the file LICENSE for copying permission.
+"""
+
+from sleekxmpp.plugins.xep_0004.stanza import Form
+from sleekxmpp.plugins.xep_0004.stanza import FormField, FieldOption
+from sleekxmpp.plugins.xep_0004.dataforms import xep_0004
diff --git a/sleekxmpp/plugins/xep_0004/dataforms.py b/sleekxmpp/plugins/xep_0004/dataforms.py
new file mode 100644
index 00000000..5414be5c
--- /dev/null
+++ b/sleekxmpp/plugins/xep_0004/dataforms.py
@@ -0,0 +1,60 @@
+"""
+ SleekXMPP: The Sleek XMPP Library
+ Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout
+ This file is part of SleekXMPP.
+
+ See the file LICENSE for copying permission.
+"""
+
+import copy
+
+from sleekxmpp.thirdparty import OrderedDict
+
+from sleekxmpp import Message
+from sleekxmpp.xmlstream import register_stanza_plugin, ElementBase, ET
+from sleekxmpp.xmlstream.handler import Callback
+from sleekxmpp.xmlstream.matcher import StanzaPath
+from sleekxmpp.plugins.base import base_plugin
+from sleekxmpp.plugins.xep_0004 import stanza
+from sleekxmpp.plugins.xep_0004.stanza import Form, FormField, FieldOption
+
+
+class xep_0004(base_plugin):
+ """
+ XEP-0004: Data Forms
+ """
+
+ def plugin_init(self):
+ self.xep = '0004'
+ self.description = 'Data Forms'
+ self.stanza = stanza
+
+ self.xmpp.registerHandler(
+ Callback('Data Form',
+ StanzaPath('message/form'),
+ self.handle_form))
+
+ register_stanza_plugin(FormField, FieldOption, iterable=True)
+ register_stanza_plugin(Form, FormField, iterable=True)
+ register_stanza_plugin(Message, Form)
+
+ def make_form(self, ftype='form', title='', instructions=''):
+ f = Form()
+ f['type'] = ftype
+ f['title'] = title
+ f['instructions'] = instructions
+ return f
+
+ def post_init(self):
+ base_plugin.post_init(self)
+ self.xmpp.plugin['xep_0030'].add_feature('jabber:x:data')
+
+ def handle_form(self, message):
+ self.xmpp.event("message_xform", message)
+
+ def build_form(self, xml):
+ return Form(xml=xml)
+
+
+xep_0004.makeForm = xep_0004.make_form
+xep_0004.buildForm = xep_0004.build_form
diff --git a/sleekxmpp/plugins/xep_0004/stanza/__init__.py b/sleekxmpp/plugins/xep_0004/stanza/__init__.py
new file mode 100644
index 00000000..6ad35298
--- /dev/null
+++ b/sleekxmpp/plugins/xep_0004/stanza/__init__.py
@@ -0,0 +1,10 @@
+"""
+ SleekXMPP: The Sleek XMPP Library
+ Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout
+ This file is part of SleekXMPP.
+
+ See the file LICENSE for copying permission.
+"""
+
+from sleekxmpp.plugins.xep_0004.stanza.field import FormField, FieldOption
+from sleekxmpp.plugins.xep_0004.stanza.form import Form
diff --git a/sleekxmpp/plugins/xep_0004/stanza/field.py b/sleekxmpp/plugins/xep_0004/stanza/field.py
new file mode 100644
index 00000000..9bb92311
--- /dev/null
+++ b/sleekxmpp/plugins/xep_0004/stanza/field.py
@@ -0,0 +1,167 @@
+"""
+ SleekXMPP: The Sleek XMPP Library
+ Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout
+ This file is part of SleekXMPP.
+
+ See the file LICENSE for copying permission.
+"""
+
+from sleekxmpp.xmlstream import ElementBase, ET
+
+
+class FormField(ElementBase):
+ namespace = 'jabber:x:data'
+ name = 'field'
+ plugin_attrib = 'field'
+ interfaces = set(('answer', 'desc', 'required', 'value',
+ 'options', 'label', 'type', 'var'))
+ sub_interfaces = set(('desc',))
+ plugin_tag_map = {}
+ plugin_attrib_map = {}
+
+ field_types = set(('boolean', 'fixed', 'hidden', 'jid-multi',
+ 'jid-single', 'list-multi', 'list-single',
+ 'text-multi', 'text-private', 'text-single'))
+
+ true_values = set((True, '1', 'true'))
+ option_types = set(('list-multi', 'list-single'))
+ multi_line_types = set(('hidden', 'text-multi'))
+ multi_value_types = set(('hidden', 'jid-multi',
+ 'list-multi', 'text-multi'))
+
+ def add_option(self, label='', value=''):
+ if self['type'] in self.option_types:
+ opt = FieldOption(parent=self)
+ opt['label'] = label
+ opt['value'] = value
+ else:
+ raise ValueError("Cannot add options to " + \
+ "a %s field." % self['type'])
+
+ def del_options(self):
+ optsXML = self.xml.findall('{%s}option' % self.namespace)
+ for optXML in optsXML:
+ self.xml.remove(optXML)
+
+ def del_required(self):
+ reqXML = self.xml.find('{%s}required' % self.namespace)
+ if reqXML is not None:
+ self.xml.remove(reqXML)
+
+ def del_value(self):
+ valsXML = self.xml.findall('{%s}value' % self.namespace)
+ for valXML in valsXML:
+ self.xml.remove(valXML)
+
+ def get_answer(self):
+ return self['value']
+
+ def get_options(self):
+ options = []
+ optsXML = self.xml.findall('{%s}option' % self.namespace)
+ for optXML in optsXML:
+ opt = FieldOption(xml=optXML)
+ options.append({'label': opt['label'], 'value': opt['value']})
+ return options
+
+ def get_required(self):
+ reqXML = self.xml.find('{%s}required' % self.namespace)
+ return reqXML is not None
+
+ def get_value(self):
+ valsXML = self.xml.findall('{%s}value' % self.namespace)
+ if len(valsXML) == 0:
+ return None
+ elif self['type'] == 'boolean':
+ return valsXML[0].text in self.true_values
+ elif self['type'] in self.multi_value_types:
+ values = []
+ for valXML in valsXML:
+ if valXML.text is None:
+ valXML.text = ''
+ values.append(valXML.text)
+ if self['type'] == 'text-multi':
+ values = "\n".join(values)
+ return values
+ else:
+ return valsXML[0].text
+
+ def set_answer(self, answer):
+ self['value'] = answer
+
+ def set_false(self):
+ self['value'] = False
+
+ def set_options(self, options):
+ for value in options:
+ if isinstance(value, dict):
+ self.add_option(**value)
+ else:
+ self.add_option(value=value)
+
+ def set_required(self, required):
+ exists = self['required']
+ if not exists and required:
+ self.xml.append(ET.Element('{%s}required' % self.namespace))
+ elif exists and not required:
+ del self['required']
+
+ def set_true(self):
+ self['value'] = True
+
+ def set_value(self, value):
+ del self['value']
+ valXMLName = '{%s}value' % self.namespace
+
+ if self['type'] == 'boolean':
+ if value in self.true_values:
+ valXML = ET.Element(valXMLName)
+ valXML.text = '1'
+ self.xml.append(valXML)
+ else:
+ valXML = ET.Element(valXMLName)
+ valXML.text = '0'
+ self.xml.append(valXML)
+ elif self['type'] in self.multi_value_types or not self['type']:
+ if not isinstance(value, list):
+ if self['type'] in self.multi_line_types:
+ value = value.split('\n')
+ else:
+ value = [value]
+ for val in value:
+ if self['type'] in ['', None] and val in self.true_values:
+ val = '1'
+ valXML = ET.Element(valXMLName)
+ valXML.text = val
+ self.xml.append(valXML)
+ else:
+ if isinstance(value, list):
+ raise ValueError("Cannot add multiple values " + \
+ "to a %s field." % self['type'])
+ valXML = ET.Element(valXMLName)
+ valXML.text = value
+ self.xml.append(valXML)
+
+
+class FieldOption(ElementBase):
+ namespace = 'jabber:x:data'
+ name = 'option'
+ plugin_attrib = 'option'
+ interfaces = set(('label', 'value'))
+ sub_interfaces = set(('value',))
+
+
+FormField.addOption = FormField.add_option
+FormField.delOptions = FormField.del_options
+FormField.delRequired = FormField.del_required
+FormField.delValue = FormField.del_value
+FormField.getAnswer = FormField.get_answer
+FormField.getOptions = FormField.get_options
+FormField.getRequired = FormField.get_required
+FormField.getValue = FormField.get_value
+FormField.setAnswer = FormField.set_answer
+FormField.setFalse = FormField.set_false
+FormField.setOptions = FormField.set_options
+FormField.setRequired = FormField.set_required
+FormField.setTrue = FormField.set_true
+FormField.setValue = FormField.set_value
diff --git a/sleekxmpp/plugins/xep_0004/stanza/form.py b/sleekxmpp/plugins/xep_0004/stanza/form.py
new file mode 100644
index 00000000..d85266fc
--- /dev/null
+++ b/sleekxmpp/plugins/xep_0004/stanza/form.py
@@ -0,0 +1,250 @@
+"""
+ SleekXMPP: The Sleek XMPP Library
+ Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout
+ This file is part of SleekXMPP.
+
+ See the file LICENSE for copying permission.
+"""
+
+import copy
+import logging
+
+from sleekxmpp.thirdparty import OrderedDict
+
+from sleekxmpp.xmlstream import ElementBase, ET
+from sleekxmpp.plugins.xep_0004.stanza import FormField
+
+
+log = logging.getLogger(__name__)
+
+
+class Form(ElementBase):
+ namespace = 'jabber:x:data'
+ name = 'x'
+ plugin_attrib = 'form'
+ interfaces = set(('fields', 'instructions', 'items',
+ 'reported', 'title', 'type', 'values'))
+ sub_interfaces = set(('title',))
+ form_types = set(('cancel', 'form', 'result', 'submit'))
+
+ def __init__(self, *args, **kwargs):
+ title = None
+ if 'title' in kwargs:
+ title = kwargs['title']
+ del kwargs['title']
+ ElementBase.__init__(self, *args, **kwargs)
+ if title is not None:
+ self['title'] = title
+
+ def setup(self, xml=None):
+ if ElementBase.setup(self, xml):
+ # If we had to generate xml
+ self['type'] = 'form'
+
+ def set_type(self, ftype):
+ self._set_attr('type', ftype)
+ if ftype == 'submit':
+ fields = self['fields']
+ for var in fields:
+ field = fields[var]
+ del field['type']
+ del field['label']
+ del field['desc']
+ del field['required']
+ del field['options']
+ elif ftype == 'cancel':
+ del self['fields']
+
+ def add_field(self, var='', ftype=None, label='', desc='',
+ required=False, value=None, options=None, **kwargs):
+ kwtype = kwargs.get('type', None)
+ if kwtype is None:
+ kwtype = ftype
+
+ field = FormField(parent=self)
+ field['var'] = var
+ field['type'] = kwtype
+ field['value'] = value
+ if self['type'] in ('form', 'result'):
+ field['label'] = label
+ field['desc'] = desc
+ field['required'] = required
+ if options is not None:
+ field['options'] = options
+ else:
+ del field['type']
+ return field
+
+ def getXML(self, type='submit'):
+ self['type'] = type
+ log.warning("Form.getXML() is deprecated API compatibility " + \
+ "with plugins/old_0004.py")
+ return self.xml
+
+ def fromXML(self, xml):
+ log.warning("Form.fromXML() is deprecated API compatibility " + \
+ "with plugins/old_0004.py")
+ n = Form(xml=xml)
+ return n
+
+ def add_item(self, values):
+ itemXML = ET.Element('{%s}item' % self.namespace)
+ self.xml.append(itemXML)
+ reported_vars = self['reported'].keys()
+ for var in reported_vars:
+ fieldXML = ET.Element('{%s}field' % FormField.namespace)
+ itemXML.append(fieldXML)
+ field = FormField(xml=fieldXML)
+ field['var'] = var
+ field['value'] = values.get(var, None)
+
+ def add_reported(self, var, ftype=None, label='', desc='', **kwargs):
+ kwtype = kwargs.get('type', None)
+ if kwtype is None:
+ kwtype = ftype
+ reported = self.xml.find('{%s}reported' % self.namespace)
+ if reported is None:
+ reported = ET.Element('{%s}reported' % self.namespace)
+ self.xml.append(reported)
+ fieldXML = ET.Element('{%s}field' % FormField.namespace)
+ reported.append(fieldXML)
+ field = FormField(xml=fieldXML)
+ field['var'] = var
+ field['type'] = kwtype
+ field['label'] = label
+ field['desc'] = desc
+ return field
+
+ def cancel(self):
+ self['type'] = 'cancel'
+
+ def del_fields(self):
+ fieldsXML = self.xml.findall('{%s}field' % FormField.namespace)
+ for fieldXML in fieldsXML:
+ self.xml.remove(fieldXML)
+
+ def del_instructions(self):
+ instsXML = self.xml.findall('{%s}instructions')
+ for instXML in instsXML:
+ self.xml.remove(instXML)
+
+ def del_items(self):
+ itemsXML = self.xml.find('{%s}item' % self.namespace)
+ for itemXML in itemsXML:
+ self.xml.remove(itemXML)
+
+ def del_reported(self):
+ reportedXML = self.xml.find('{%s}reported' % self.namespace)
+ if reportedXML is not None:
+ self.xml.remove(reportedXML)
+
+ def get_fields(self, use_dict=False):
+ fields = OrderedDict()
+ fieldsXML = self.xml.findall('{%s}field' % FormField.namespace)
+ for fieldXML in fieldsXML:
+ field = FormField(xml=fieldXML)
+ fields[field['var']] = field
+ return fields
+
+ def get_instructions(self):
+ instructions = ''
+ instsXML = self.xml.findall('{%s}instructions' % self.namespace)
+ return "\n".join([instXML.text for instXML in instsXML])
+
+ def get_items(self):
+ items = []
+ itemsXML = self.xml.findall('{%s}item' % self.namespace)
+ for itemXML in itemsXML:
+ item = {}
+ fieldsXML = itemXML.findall('{%s}field' % FormField.namespace)
+ for fieldXML in fieldsXML:
+ field = FormField(xml=fieldXML)
+ item[field['var']] = field['value']
+ items.append(item)
+ return items
+
+ def get_reported(self):
+ fields = {}
+ xml = self.xml.findall('{%s}reported/{%s}field' % (self.namespace,
+ FormField.namespace))
+ for field in xml:
+ field = FormField(xml=field)
+ fields[field['var']] = field
+ return fields
+
+ def get_values(self):
+ values = {}
+ fields = self['fields']
+ for var in fields:
+ values[var] = fields[var]['value']
+ return values
+
+ def reply(self):
+ if self['type'] == 'form':
+ self['type'] = 'submit'
+ elif self['type'] == 'submit':
+ self['type'] = 'result'
+
+ def set_fields(self, fields):
+ del self['fields']
+ if not isinstance(fields, list):
+ fields = fields.items()
+ for var, field in fields:
+ field['var'] = var
+ self.add_field(**field)
+
+ def set_instructions(self, instructions):
+ del self['instructions']
+ if instructions in [None, '']:
+ return
+ instructions = instructions.split('\n')
+ for instruction in instructions:
+ inst = ET.Element('{%s}instructions' % self.namespace)
+ inst.text = instruction
+ self.xml.append(inst)
+
+ def set_items(self, items):
+ for item in items:
+ self.add_item(item)
+
+ def set_reported(self, reported):
+ for var in reported:
+ field = reported[var]
+ field['var'] = var
+ self.add_reported(var, **field)
+
+ def set_values(self, values):
+ fields = self['fields']
+ for field in values:
+ fields[field]['value'] = values[field]
+
+ def merge(self, other):
+ new = copy.copy(self)
+ if type(other) == dict:
+ new['values'] = other
+ return new
+ nfields = new['fields']
+ ofields = other['fields']
+ nfields.update(ofields)
+ new['fields'] = nfields
+ return new
+
+
+Form.setType = Form.set_type
+Form.addField = Form.add_field
+Form.addItem = Form.add_item
+Form.addReported = Form.add_reported
+Form.delFields = Form.del_fields
+Form.delInstructions = Form.del_instructions
+Form.delItems = Form.del_items
+Form.delReported = Form.del_reported
+Form.getFields = Form.get_fields
+Form.getInstructions = Form.get_instructions
+Form.getItems = Form.get_items
+Form.getReported = Form.get_reported
+Form.getValues = Form.get_values
+Form.setFields = Form.set_fields
+Form.setInstructions = Form.set_instructions
+Form.setItems = Form.set_items
+Form.setReported = Form.set_reported
+Form.setValues = Form.set_values
diff --git a/sleekxmpp/plugins/xep_0009/remote.py b/sleekxmpp/plugins/xep_0009/remote.py
index 8c534118..b5d10b85 100644
--- a/sleekxmpp/plugins/xep_0009/remote.py
+++ b/sleekxmpp/plugins/xep_0009/remote.py
@@ -463,7 +463,7 @@ class RemoteSession(object):
key = "%s.%s" % (endpoint, name)
log.debug("Registering call handler for %s (%s)." % (key, method))
with self._lock:
- if self._entries.has_key(key):
+ if key in self._entries:
raise KeyError("A handler for %s has already been regisered!" % endpoint)
self._entries[key] = JabberRPCEntry(endpoint, method)
return key
diff --git a/sleekxmpp/plugins/xep_0050/adhoc.py b/sleekxmpp/plugins/xep_0050/adhoc.py
index 72c6c513..dd1c88d6 100644
--- a/sleekxmpp/plugins/xep_0050/adhoc.py
+++ b/sleekxmpp/plugins/xep_0050/adhoc.py
@@ -589,5 +589,5 @@ class xep_0050(base_plugin):
elif iq['type'] == 'error':
self.terminate_command(session)
- if iq['command']['status'] == 'completed':
+ if iq['command']['status'] == 'completed':
self.terminate_command(session)
diff --git a/sleekxmpp/plugins/xep_0060/pubsub.py b/sleekxmpp/plugins/xep_0060/pubsub.py
index e199be07..55362068 100644
--- a/sleekxmpp/plugins/xep_0060/pubsub.py
+++ b/sleekxmpp/plugins/xep_0060/pubsub.py
@@ -11,303 +11,118 @@ log = logging.getLogger(__name__)
class xep_0060(base.base_plugin):
- """
- XEP-0060 Publish Subscribe
- """
-
- def plugin_init(self):
- self.xep = '0060'
- self.description = 'Publish-Subscribe'
-
- def create_node(self, jid, node, config=None, collection=False, ntype=None):
- pubsub = ET.Element('{http://jabber.org/protocol/pubsub}pubsub')
- create = ET.Element('create')
- create.set('node', node)
- pubsub.append(create)
- configure = ET.Element('configure')
- if collection:
- ntype = 'collection'
- #if config is None:
- # submitform = self.xmpp.plugin['xep_0004'].makeForm('submit')
- #else:
- if config is not None:
- submitform = config
- if 'FORM_TYPE' in submitform.field:
- submitform.field['FORM_TYPE'].setValue('http://jabber.org/protocol/pubsub#node_config')
- else:
- submitform.addField('FORM_TYPE', 'hidden', value='http://jabber.org/protocol/pubsub#node_config')
- if ntype:
- if 'pubsub#node_type' in submitform.field:
- submitform.field['pubsub#node_type'].setValue(ntype)
- else:
- submitform.addField('pubsub#node_type', value=ntype)
- else:
- if 'pubsub#node_type' in submitform.field:
- submitform.field['pubsub#node_type'].setValue('leaf')
- else:
- submitform.addField('pubsub#node_type', value='leaf')
- submitform['type'] = 'submit'
- configure.append(submitform.xml)
- pubsub.append(configure)
- iq = self.xmpp.makeIqSet(pubsub)
- iq.attrib['to'] = jid
- iq.attrib['from'] = self.xmpp.boundjid.full
- id = iq['id']
- result = iq.send()
- if result is False or result is None or result['type'] == 'error': return False
- return True
-
- def subscribe(self, jid, node, bare=True, subscribee=None):
- pubsub = ET.Element('{http://jabber.org/protocol/pubsub}pubsub')
- subscribe = ET.Element('subscribe')
- subscribe.attrib['node'] = node
- if subscribee is None:
- if bare:
- subscribe.attrib['jid'] = self.xmpp.boundjid.bare
- else:
- subscribe.attrib['jid'] = self.xmpp.boundjid.full
- else:
- subscribe.attrib['jid'] = subscribee
- pubsub.append(subscribe)
- iq = self.xmpp.makeIqSet(pubsub)
- iq.attrib['to'] = jid
- iq.attrib['from'] = self.xmpp.boundjid.full
- id = iq['id']
- result = iq.send()
- if result is False or result is None or result['type'] == 'error': return False
- return True
-
- def unsubscribe(self, jid, node, bare=True, subscribee=None):
- pubsub = ET.Element('{http://jabber.org/protocol/pubsub}pubsub')
- unsubscribe = ET.Element('unsubscribe')
- unsubscribe.attrib['node'] = node
- if subscribee is None:
- if bare:
- unsubscribe.attrib['jid'] = self.xmpp.boundjid.bare
- else:
- unsubscribe.attrib['jid'] = self.xmpp.boundjid.full
- else:
- unsubscribe.attrib['jid'] = subscribee
- pubsub.append(unsubscribe)
- iq = self.xmpp.makeIqSet(pubsub)
- iq.attrib['to'] = jid
- iq.attrib['from'] = self.xmpp.boundjid.full
- id = iq['id']
- result = iq.send()
- if result is False or result is None or result['type'] == 'error': return False
- return True
-
- def getNodeConfig(self, jid, node=None): # if no node, then grab default
- pubsub = ET.Element('{http://jabber.org/protocol/pubsub#owner}pubsub')
- if node is not None:
- configure = ET.Element('configure')
- configure.attrib['node'] = node
- else:
- configure = ET.Element('default')
- pubsub.append(configure)
- #TODO: Add configure support.
- iq = self.xmpp.makeIqGet()
- iq.append(pubsub)
- iq.attrib['to'] = jid
- iq.attrib['from'] = self.xmpp.boundjid.full
- id = iq['id']
- #self.xmpp.add_handler("<iq id='%s'/>" % id, self.handlerCreateNodeResponse)
- result = iq.send()
- if result is None or result == False or result['type'] == 'error':
- log.warning("got error instead of config")
- return False
- if node is not None:
- form = result.find('{http://jabber.org/protocol/pubsub#owner}pubsub/{http://jabber.org/protocol/pubsub#owner}configure/{jabber:x:data}x')
- else:
- form = result.find('{http://jabber.org/protocol/pubsub#owner}pubsub/{http://jabber.org/protocol/pubsub#owner}default/{jabber:x:data}x')
- if not form or form is None:
- log.error("No form found.")
- return False
- return Form(xml=form)
-
- def getNodeSubscriptions(self, jid, node):
- pubsub = ET.Element('{http://jabber.org/protocol/pubsub#owner}pubsub')
- subscriptions = ET.Element('subscriptions')
- subscriptions.attrib['node'] = node
- pubsub.append(subscriptions)
- iq = self.xmpp.makeIqGet()
- iq.append(pubsub)
- iq.attrib['to'] = jid
- iq.attrib['from'] = self.xmpp.boundjid.full
- id = iq['id']
- result = iq.send()
- if result is None or result == False or result['type'] == 'error':
- log.warning("got error instead of config")
- return False
- else:
- results = result.findall('{http://jabber.org/protocol/pubsub#owner}pubsub/{http://jabber.org/protocol/pubsub#owner}subscriptions/{http://jabber.org/protocol/pubsub#owner}subscription')
- if results is None:
- return False
- subs = {}
- for sub in results:
- subs[sub.get('jid')] = sub.get('subscription')
- return subs
-
- def getNodeAffiliations(self, jid, node):
- pubsub = ET.Element('{http://jabber.org/protocol/pubsub#owner}pubsub')
- affiliations = ET.Element('affiliations')
- affiliations.attrib['node'] = node
- pubsub.append(affiliations)
- iq = self.xmpp.makeIqGet()
- iq.append(pubsub)
- iq.attrib['to'] = jid
- iq.attrib['from'] = self.xmpp.boundjid.full
- id = iq['id']
- result = iq.send()
- if result is None or result == False or result['type'] == 'error':
- log.warning("got error instead of config")
- return False
- else:
- results = result.findall('{http://jabber.org/protocol/pubsub#owner}pubsub/{http://jabber.org/protocol/pubsub#owner}affiliations/{http://jabber.org/protocol/pubsub#owner}affiliation')
- if results is None:
- return False
- subs = {}
- for sub in results:
- subs[sub.get('jid')] = sub.get('affiliation')
- return subs
-
- def deleteNode(self, jid, node):
- pubsub = ET.Element('{http://jabber.org/protocol/pubsub#owner}pubsub')
- iq = self.xmpp.makeIqSet()
- delete = ET.Element('delete')
- delete.attrib['node'] = node
- pubsub.append(delete)
- iq.append(pubsub)
- iq.attrib['to'] = jid
- iq.attrib['from'] = self.xmpp.boundjid.full
- result = iq.send()
- if result is not None and result is not False and result['type'] != 'error':
- return True
- else:
- return False
-
-
- def setNodeConfig(self, jid, node, config):
- pubsub = ET.Element('{http://jabber.org/protocol/pubsub#owner}pubsub')
- configure = ET.Element('configure')
- configure.attrib['node'] = node
- config = config.getXML('submit')
- configure.append(config)
- pubsub.append(configure)
- iq = self.xmpp.makeIqSet(pubsub)
- iq.attrib['to'] = jid
- iq.attrib['from'] = self.xmpp.boundjid.full
- id = iq['id']
- result = iq.send()
- if result is None or result['type'] == 'error':
- return False
- return True
-
- def setItem(self, jid, node, items=[]):
- pubsub = ET.Element('{http://jabber.org/protocol/pubsub}pubsub')
- publish = ET.Element('publish')
- publish.attrib['node'] = node
- for pub_item in items:
- id, payload = pub_item
- item = ET.Element('item')
- if id is not None:
- item.attrib['id'] = id
- item.append(payload)
- publish.append(item)
- pubsub.append(publish)
- iq = self.xmpp.makeIqSet(pubsub)
- iq.attrib['to'] = jid
- iq.attrib['from'] = self.xmpp.boundjid.full
- id = iq['id']
- result = iq.send()
- if result is None or result is False or result['type'] == 'error': return False
- return True
-
- def addItem(self, jid, node, items=[]):
- return self.setItem(jid, node, items)
-
- def deleteItem(self, jid, node, item):
- pubsub = ET.Element('{http://jabber.org/protocol/pubsub}pubsub')
- retract = ET.Element('retract')
- retract.attrib['node'] = node
- itemn = ET.Element('item')
- itemn.attrib['id'] = item
- retract.append(itemn)
- pubsub.append(retract)
- iq = self.xmpp.makeIqSet(pubsub)
- iq.attrib['to'] = jid
- iq.attrib['from'] = self.xmpp.boundjid.full
- id = iq['id']
- result = iq.send()
- if result is None or result is False or result['type'] == 'error': return False
- return True
-
- def getNodes(self, jid):
- response = self.xmpp.plugin['xep_0030'].getItems(jid)
- items = response.findall('{http://jabber.org/protocol/disco#items}query/{http://jabber.org/protocol/disco#items}item')
- nodes = {}
- if items is not None and items is not False:
- for item in items:
- nodes[item.get('node')] = item.get('name')
- return nodes
-
- def getItems(self, jid, node):
- response = self.xmpp.plugin['xep_0030'].getItems(jid, node)
- items = response.findall('{http://jabber.org/protocol/disco#items}query/{http://jabber.org/protocol/disco#items}item')
- nodeitems = []
- if items is not None and items is not False:
- for item in items:
- nodeitems.append(item.get('node'))
- return nodeitems
-
- def addNodeToCollection(self, jid, child, parent=''):
- config = self.getNodeConfig(jid, child)
- if not config or config is None:
- self.lasterror = "Config Error"
- return False
- try:
- config.field['pubsub#collection'].setValue(parent)
- except KeyError:
- log.warning("pubsub#collection doesn't exist in config, trying to add it")
- config.addField('pubsub#collection', value=parent)
- if not self.setNodeConfig(jid, child, config):
- return False
- return True
-
- def modifyAffiliation(self, ps_jid, node, user_jid, affiliation):
- if affiliation not in ('owner', 'publisher', 'member', 'none', 'outcast'):
- raise TypeError
- pubsub = ET.Element('{http://jabber.org/protocol/pubsub#owner}pubsub')
- affs = ET.Element('affiliations')
- affs.attrib['node'] = node
- aff = ET.Element('affiliation')
- aff.attrib['jid'] = user_jid
- aff.attrib['affiliation'] = affiliation
- affs.append(aff)
- pubsub.append(affs)
- iq = self.xmpp.makeIqSet(pubsub)
- iq.attrib['to'] = ps_jid
- iq.attrib['from'] = self.xmpp.boundjid.full
- id = iq['id']
- result = iq.send()
- if result is None or result is False or result['type'] == 'error':
- return False
- return True
-
- def addNodeToCollection(self, jid, child, parent=''):
- config = self.getNodeConfig(jid, child)
- if not config or config is None:
- self.lasterror = "Config Error"
- return False
- try:
- config.field['pubsub#collection'].setValue(parent)
- except KeyError:
- log.warning("pubsub#collection doesn't exist in config, trying to add it")
- config.addField('pubsub#collection', value=parent)
- if not self.setNodeConfig(jid, child, config):
- return False
- return True
-
- def removeNodeFromCollection(self, jid, child):
- self.addNodeToCollection(jid, child, '')
-
+ """
+ XEP-0060 Publish Subscribe
+ """
+
+ def plugin_init(self):
+ self.xep = '0060'
+ self.description = 'Publish-Subscribe'
+
+ def create_node(self, jid, node, config=None, ntype=None):
+ iq = IQ(sto=jid, stype='set', sfrom=self.xmpp.jid)
+ iq['pubsub']['create']['node'] = node
+ if ntype is None:
+ ntype = 'leaf'
+ if config is not None:
+ if 'FORM_TYPE' in submitform.field:
+ config.field['FORM_TYPE'].setValue('http://jabber.org/protocol/pubsub#node_config')
+ else:
+ config.addField('FORM_TYPE', 'hidden', value='http://jabber.org/protocol/pubsub#node_config')
+ if 'pubsub#node_type' in submitform.field:
+ config.field['pubsub#node_type'].setValue(ntype)
+ else:
+ config.addField('pubsub#node_type', value=ntype)
+ iq['pubsub']['configure']['form'] = config
+ return iq.send()
+
+ def subscribe(self, jid, node, bare=True, subscribee=None):
+ iq = IQ(sto=jid, sfrom=self.xmpp.jid, stype='set')
+ iq['pubsub']['subscribe']['node'] = node
+ if subscribee is None:
+ if bare:
+ iq['pubsub']['subscribe']['jid'] = self.xmpp.jid.bare
+ else:
+ iq['pubsub']['subscribe']['jid'] = self.xmpp.jid.full
+ else:
+ iq['pubsub']['subscribe']['jid'] = subscribee
+ return iq.send()
+
+ def unsubscribe(self, jid, node, subid=None, bare=True, subscribee=None):
+ iq = IQ(sto=jid, sfrom=self.xmpp.jid, stype='set')
+ iq['pubsub']['unsubscribe']['node'] = node
+ if subscribee is None:
+ if bare:
+ iq['pubsub']['unsubscribe']['jid'] = self.xmpp.jid.bare
+ else:
+ iq['pubsub']['unsubscribe']['jid'] = self.xmpp.jid.full
+ else:
+ iq['pubsub']['unsubscribe']['jid'] = subscribee
+ if subid is not None:
+ iq['pubsub']['unsubscribe']['subid'] = subid
+ return iq.send()
+
+ def get_node_config(self, jid, node=None): # if no node, then grab default
+ iq = IQ(sto=jid, sfrom=self.xmpp.jid, stype='get')
+ if node is None:
+ iq['pubsub_owner']['default']
+ else:
+ iq['pubsub_owner']['configure']['node'] = node
+ return iq.send()
+
+ def get_node_subscriptions(self, jid, node):
+ iq = IQ(sto=jid, sfrom=self.xmpp.jid, stype='get')
+ iq['pubsub_owner']['subscriptions']['node'] = node
+ return iq.send()
+
+ def get_node_affiliations(self, jid, node):
+ iq = IQ(sto=jid, sfrom=self.xmpp.jid, stype='get')
+ iq['pubsub_owner']['affiliations']['node'] = node
+ return iq.send()
+
+ def delete_node(self, jid, node):
+ iq = IQ(sto=jid, sfrom=self.xmpp.jid, stype='get')
+ iq['pubsub_owner']['delete']['node'] = node
+ return iq.send()
+
+ def set_node_config(self, jid, node, config):
+ iq = IQ(sto=jid, sfrom=self.xmpp.jid, stype='set')
+ iq['pubsub_owner']['configure']['node'] = node
+ iq['pubsub_owner']['configure']['config'] = config
+ return iq.send()
+
+ def publish(self, jid, node, items=[]):
+ iq = IQ(sto=jid, sfrom=self.xmpp.jid, stype='set')
+ iq['pubsub']['publish']['node'] = node
+ for id, payload in items:
+ item = stanza.pubsub.Item()
+ if id is not None:
+ item['id'] = id
+ item['payload'] = payload
+ iq['pubsub']['publish'].append(item)
+ return iq.send()
+
+ def retract(self, jid, node, item):
+ iq = IQ(sto=jid, sfrom=self.xmpp.jid, stype='set')
+ iq['pubsub']['retract']['node'] = node
+ item = stanza.pubsub.Item()
+ item['id'] = item
+ iq['pubsub']['retract'].append(item)
+ return iq.send()
+
+ def get_nodes(self, jid):
+ return self.xmpp.plugin['xep_0030'].get_items(jid)
+
+ def getItems(self, jid, node):
+ return self.xmpp.plugin['xep_0030'].get_items(jid, node)
+
+ def modify_affiliation(self, jid, node, affiliation, user_jid=None):
+ iq = IQ(sto=jid, sfrom=self.xmpp.jid, stype='set')
+ iq['pubsub_owner']['affiliations']
+ aff = stanza.pubsub.Affiliation()
+ aff['node'] = node
+ if user_jid is not None:
+ aff['jid'] = user_jid
+ aff['affiliation'] = affiliation
+ iq['pubsub_owner']['affiliations'].append(aff)
+ return iq.send()
diff --git a/sleekxmpp/plugins/xep_0060/stanza/pubsub.py b/sleekxmpp/plugins/xep_0060/stanza/pubsub.py
index 96655942..d9e55052 100644
--- a/sleekxmpp/plugins/xep_0060/stanza/pubsub.py
+++ b/sleekxmpp/plugins/xep_0060/stanza/pubsub.py
@@ -9,236 +9,236 @@ from sleekxmpp.plugins.xep_0060.stanza.base import OptionalSetting
class Pubsub(ElementBase):
- namespace = 'http://jabber.org/protocol/pubsub'
- name = 'pubsub'
- plugin_attrib = 'pubsub'
- interfaces = set(tuple())
- plugin_attrib_map = {}
- plugin_tag_map = {}
+ namespace = 'http://jabber.org/protocol/pubsub'
+ name = 'pubsub'
+ plugin_attrib = 'pubsub'
+ interfaces = set(tuple())
+ plugin_attrib_map = {}
+ plugin_tag_map = {}
registerStanzaPlugin(Iq, Pubsub)
class Affiliation(ElementBase):
- namespace = 'http://jabber.org/protocol/pubsub'
- name = 'affiliation'
- plugin_attrib = name
- interfaces = set(('node', 'affiliation'))
- plugin_attrib_map = {}
- plugin_tag_map = {}
+ namespace = 'http://jabber.org/protocol/pubsub'
+ name = 'affiliation'
+ plugin_attrib = name
+ interfaces = set(('node', 'affiliation', 'jid'))
+ plugin_attrib_map = {}
+ plugin_tag_map = {}
+
+ def setJid(self, value):
+ self._setAttr('jid', str(value))
+
+ def getJid(self):
+ return JID(self._getAttr('jid'))
class Affiliations(ElementBase):
- namespace = 'http://jabber.org/protocol/pubsub'
- name = 'affiliations'
- plugin_attrib = 'affiliations'
- interfaces = set(tuple())
- plugin_attrib_map = {}
- plugin_tag_map = {}
- subitem = (Affiliation,)
-
- def append(self, affiliation):
- if not isinstance(affiliation, Affiliation):
- raise TypeError
- self.xml.append(affiliation.xml)
- return self.iterables.append(affiliation)
+ namespace = 'http://jabber.org/protocol/pubsub'
+ name = 'affiliations'
+ plugin_attrib = 'affiliations'
+ interfaces = set(tuple())
+ plugin_attrib_map = {}
+ plugin_tag_map = {}
+ subitem = (Affiliation,)
registerStanzaPlugin(Pubsub, Affiliations)
class Subscription(ElementBase):
- namespace = 'http://jabber.org/protocol/pubsub'
- name = 'subscription'
- plugin_attrib = name
- interfaces = set(('jid', 'node', 'subscription', 'subid'))
- plugin_attrib_map = {}
- plugin_tag_map = {}
+ namespace = 'http://jabber.org/protocol/pubsub'
+ name = 'subscription'
+ plugin_attrib = name
+ interfaces = set(('jid', 'node', 'subscription', 'subid'))
+ plugin_attrib_map = {}
+ plugin_tag_map = {}
- def setjid(self, value):
- self._setattr('jid', str(value))
+ def setjid(self, value):
+ self._setattr('jid', str(value))
- def getjid(self):
- return jid(self._getattr('jid'))
+ def getjid(self):
+ return jid(self._getattr('jid'))
registerStanzaPlugin(Pubsub, Subscription)
class Subscriptions(ElementBase):
- namespace = 'http://jabber.org/protocol/pubsub'
- name = 'subscriptions'
- plugin_attrib = 'subscriptions'
- interfaces = set(tuple())
- plugin_attrib_map = {}
- plugin_tag_map = {}
- subitem = (Subscription,)
+ namespace = 'http://jabber.org/protocol/pubsub'
+ name = 'subscriptions'
+ plugin_attrib = 'subscriptions'
+ interfaces = set(tuple())
+ plugin_attrib_map = {}
+ plugin_tag_map = {}
+ subitem = (Subscription,)
registerStanzaPlugin(Pubsub, Subscriptions)
class SubscribeOptions(ElementBase, OptionalSetting):
- namespace = 'http://jabber.org/protocol/pubsub'
- name = 'subscribe-options'
- plugin_attrib = 'suboptions'
- plugin_attrib_map = {}
- plugin_tag_map = {}
- interfaces = set(('required',))
+ namespace = 'http://jabber.org/protocol/pubsub'
+ name = 'subscribe-options'
+ plugin_attrib = 'suboptions'
+ plugin_attrib_map = {}
+ plugin_tag_map = {}
+ interfaces = set(('required',))
registerStanzaPlugin(Subscription, SubscribeOptions)
class Item(ElementBase):
- namespace = 'http://jabber.org/protocol/pubsub'
- name = 'item'
- plugin_attrib = name
- interfaces = set(('id', 'payload'))
- plugin_attrib_map = {}
- plugin_tag_map = {}
+ namespace = 'http://jabber.org/protocol/pubsub'
+ name = 'item'
+ plugin_attrib = name
+ interfaces = set(('id', 'payload'))
+ plugin_attrib_map = {}
+ plugin_tag_map = {}
- def setPayload(self, value):
- self.xml.append(value)
+ def setPayload(self, value):
+ self.xml.append(value)
- def getPayload(self):
- childs = self.xml.getchildren()
- if len(childs) > 0:
- return childs[0]
+ def getPayload(self):
+ childs = self.xml.getchildren()
+ if len(childs) > 0:
+ return childs[0]
- def delPayload(self):
- for child in self.xml.getchildren():
- self.xml.remove(child)
+ def delPayload(self):
+ for child in self.xml.getchildren():
+ self.xml.remove(child)
class Items(ElementBase):
- namespace = 'http://jabber.org/protocol/pubsub'
- name = 'items'
- plugin_attrib = 'items'
- interfaces = set(('node',))
- plugin_attrib_map = {}
- plugin_tag_map = {}
- subitem = (Item,)
+ namespace = 'http://jabber.org/protocol/pubsub'
+ name = 'items'
+ plugin_attrib = 'items'
+ interfaces = set(('node',))
+ plugin_attrib_map = {}
+ plugin_tag_map = {}
+ subitem = (Item,)
registerStanzaPlugin(Pubsub, Items)
class Create(ElementBase):
- namespace = 'http://jabber.org/protocol/pubsub'
- name = 'create'
- plugin_attrib = name
- interfaces = set(('node',))
- plugin_attrib_map = {}
- plugin_tag_map = {}
+ namespace = 'http://jabber.org/protocol/pubsub'
+ name = 'create'
+ plugin_attrib = name
+ interfaces = set(('node',))
+ plugin_attrib_map = {}
+ plugin_tag_map = {}
registerStanzaPlugin(Pubsub, Create)
#class Default(ElementBase):
-# namespace = 'http://jabber.org/protocol/pubsub'
-# name = 'default'
-# plugin_attrib = name
-# interfaces = set(('node', 'type'))
-# plugin_attrib_map = {}
-# plugin_tag_map = {}
+# namespace = 'http://jabber.org/protocol/pubsub'
+# name = 'default'
+# plugin_attrib = name
+# interfaces = set(('node', 'type'))
+# plugin_attrib_map = {}
+# plugin_tag_map = {}
#
-# def getType(self):
-# t = self._getAttr('type')
-# if not t: t == 'leaf'
-# return t
+# def getType(self):
+# t = self._getAttr('type')
+# if not t: t == 'leaf'
+# return t
#
#registerStanzaPlugin(Pubsub, Default)
class Publish(Items):
- namespace = 'http://jabber.org/protocol/pubsub'
- name = 'publish'
- plugin_attrib = name
- interfaces = set(('node',))
- plugin_attrib_map = {}
- plugin_tag_map = {}
- subitem = (Item,)
+ namespace = 'http://jabber.org/protocol/pubsub'
+ name = 'publish'
+ plugin_attrib = name
+ interfaces = set(('node',))
+ plugin_attrib_map = {}
+ plugin_tag_map = {}
+ subitem = (Item,)
registerStanzaPlugin(Pubsub, Publish)
class Retract(Items):
- namespace = 'http://jabber.org/protocol/pubsub'
- name = 'retract'
- plugin_attrib = name
- interfaces = set(('node', 'notify'))
- plugin_attrib_map = {}
- plugin_tag_map = {}
+ namespace = 'http://jabber.org/protocol/pubsub'
+ name = 'retract'
+ plugin_attrib = name
+ interfaces = set(('node', 'notify'))
+ plugin_attrib_map = {}
+ plugin_tag_map = {}
registerStanzaPlugin(Pubsub, Retract)
class Unsubscribe(ElementBase):
- namespace = 'http://jabber.org/protocol/pubsub'
- name = 'unsubscribe'
- plugin_attrib = name
- interfaces = set(('node', 'jid'))
- plugin_attrib_map = {}
- plugin_tag_map = {}
+ namespace = 'http://jabber.org/protocol/pubsub'
+ name = 'unsubscribe'
+ plugin_attrib = name
+ interfaces = set(('node', 'jid', 'subid'))
+ plugin_attrib_map = {}
+ plugin_tag_map = {}
- def setJid(self, value):
- self._setAttr('jid', str(value))
+ def setJid(self, value):
+ self._setAttr('jid', str(value))
- def getJid(self):
- return JID(self._getAttr('jid'))
+ def getJid(self):
+ return JID(self._getAttr('jid'))
registerStanzaPlugin(Pubsub, Unsubscribe)
class Subscribe(ElementBase):
- namespace = 'http://jabber.org/protocol/pubsub'
- name = 'subscribe'
- plugin_attrib = name
- interfaces = set(('node', 'jid'))
- plugin_attrib_map = {}
- plugin_tag_map = {}
+ namespace = 'http://jabber.org/protocol/pubsub'
+ name = 'subscribe'
+ plugin_attrib = name
+ interfaces = set(('node', 'jid'))
+ plugin_attrib_map = {}
+ plugin_tag_map = {}
- def setJid(self, value):
- self._setAttr('jid', str(value))
+ def setJid(self, value):
+ self._setAttr('jid', str(value))
- def getJid(self):
- return JID(self._getAttr('jid'))
+ def getJid(self):
+ return JID(self._getAttr('jid'))
registerStanzaPlugin(Pubsub, Subscribe)
class Configure(ElementBase):
- namespace = 'http://jabber.org/protocol/pubsub'
- name = 'configure'
- plugin_attrib = name
- interfaces = set(('node', 'type'))
- plugin_attrib_map = {}
- plugin_tag_map = {}
-
- def getType(self):
- t = self._getAttr('type')
- if not t: t == 'leaf'
- return t
+ namespace = 'http://jabber.org/protocol/pubsub'
+ name = 'configure'
+ plugin_attrib = name
+ interfaces = set(('node', 'type'))
+ plugin_attrib_map = {}
+ plugin_tag_map = {}
+
+ def getType(self):
+ t = self._getAttr('type')
+ if not t: t == 'leaf'
+ return t
registerStanzaPlugin(Pubsub, Configure)
registerStanzaPlugin(Configure, xep_0004.Form)
class Options(ElementBase):
- namespace = 'http://jabber.org/protocol/pubsub'
- name = 'options'
- plugin_attrib = 'options'
- interfaces = set(('jid', 'node', 'options'))
- plugin_attrib_map = {}
- plugin_tag_map = {}
-
- def __init__(self, *args, **kwargs):
- ElementBase.__init__(self, *args, **kwargs)
-
- def getOptions(self):
- config = self.xml.find('{jabber:x:data}x')
- form = xep_0004.Form()
- if config is not None:
- form.fromXML(config)
- return form
-
- def setOptions(self, value):
- self.xml.append(value.getXML())
- return self
-
- def delOptions(self):
- config = self.xml.find('{jabber:x:data}x')
- self.xml.remove(config)
-
- def setJid(self, value):
- self._setAttr('jid', str(value))
-
- def getJid(self):
- return JID(self._getAttr('jid'))
+ namespace = 'http://jabber.org/protocol/pubsub'
+ name = 'options'
+ plugin_attrib = 'options'
+ interfaces = set(('jid', 'node', 'options'))
+ plugin_attrib_map = {}
+ plugin_tag_map = {}
+
+ def __init__(self, *args, **kwargs):
+ ElementBase.__init__(self, *args, **kwargs)
+
+ def getOptions(self):
+ config = self.xml.find('{jabber:x:data}x')
+ form = xep_0004.Form()
+ if config is not None:
+ form.fromXML(config)
+ return form
+
+ def setOptions(self, value):
+ self.xml.append(value.getXML())
+ return self
+
+ def delOptions(self):
+ config = self.xml.find('{jabber:x:data}x')
+ self.xml.remove(config)
+
+ def setJid(self, value):
+ self._setAttr('jid', str(value))
+
+ def getJid(self):
+ return JID(self._getAttr('jid'))
registerStanzaPlugin(Pubsub, Options)
registerStanzaPlugin(Subscribe, Options)
diff --git a/sleekxmpp/plugins/xep_0060/stanza/pubsub_owner.py b/sleekxmpp/plugins/xep_0060/stanza/pubsub_owner.py
index a90780cc..201dc909 100644
--- a/sleekxmpp/plugins/xep_0060/stanza/pubsub_owner.py
+++ b/sleekxmpp/plugins/xep_0060/stanza/pubsub_owner.py
@@ -9,144 +9,147 @@ from sleekxmpp.plugins.xep_0060.stanza.base import OptionalSetting
from sleekxmpp.plugins.xep_0060.stanza.pubsub import Affiliations, Affiliation, Configure, Subscriptions
class PubsubOwner(ElementBase):
- namespace = 'http://jabber.org/protocol/pubsub#owner'
- name = 'pubsub'
- plugin_attrib = 'pubsub_owner'
- interfaces = set(tuple())
- plugin_attrib_map = {}
- plugin_tag_map = {}
+ namespace = 'http://jabber.org/protocol/pubsub#owner'
+ name = 'pubsub'
+ plugin_attrib = 'pubsub_owner'
+ interfaces = set(tuple())
+ plugin_attrib_map = {}
+ plugin_tag_map = {}
registerStanzaPlugin(Iq, PubsubOwner)
class DefaultConfig(ElementBase):
- namespace = 'http://jabber.org/protocol/pubsub#owner'
- name = 'default'
- plugin_attrib = 'default'
- interfaces = set(('node', 'type', 'config'))
- plugin_attrib_map = {}
- plugin_tag_map = {}
+ namespace = 'http://jabber.org/protocol/pubsub#owner'
+ name = 'default'
+ plugin_attrib = 'default'
+ interfaces = set(('node', 'type', 'config'))
+ plugin_attrib_map = {}
+ plugin_tag_map = {}
- def __init__(self, *args, **kwargs):
- ElementBase.__init__(self, *args, **kwargs)
+ def __init__(self, *args, **kwargs):
+ ElementBase.__init__(self, *args, **kwargs)
- def getType(self):
- t = self._getAttr('type')
- if not t: t = 'leaf'
- return t
+ def getType(self):
+ t = self._getAttr('type')
+ if not t: t = 'leaf'
+ return t
- def getConfig(self):
- return self['form']
+ def getConfig(self):
+ return self['form']
- def setConfig(self, value):
- self['form'].setStanzaValues(value.getStanzaValues())
- return self
+ def setConfig(self, value):
+ self['form'].setStanzaValues(value.getStanzaValues())
+ return self
registerStanzaPlugin(PubsubOwner, DefaultConfig)
registerStanzaPlugin(DefaultConfig, xep_0004.Form)
class OwnerAffiliations(Affiliations):
- namespace = 'http://jabber.org/protocol/pubsub#owner'
- interfaces = set(('node'))
- plugin_attrib_map = {}
- plugin_tag_map = {}
+ namespace = 'http://jabber.org/protocol/pubsub#owner'
+ interfaces = set(('node'))
+ plugin_attrib_map = {}
+ plugin_tag_map = {}
- def append(self, affiliation):
- if not isinstance(affiliation, OwnerAffiliation):
- raise TypeError
- self.xml.append(affiliation.xml)
- return self.affiliations.append(affiliation)
+ def append(self, affiliation):
+ if not isinstance(affiliation, OwnerAffiliation):
+ raise TypeError
+ self.xml.append(affiliation.xml)
+ return self.affiliations.append(affiliation)
registerStanzaPlugin(PubsubOwner, OwnerAffiliations)
class OwnerAffiliation(Affiliation):
- namespace = 'http://jabber.org/protocol/pubsub#owner'
- interfaces = set(('affiliation', 'jid'))
- plugin_attrib_map = {}
- plugin_tag_map = {}
+ namespace = 'http://jabber.org/protocol/pubsub#owner'
+ interfaces = set(('affiliation', 'jid'))
+ plugin_attrib_map = {}
+ plugin_tag_map = {}
class OwnerConfigure(Configure):
- namespace = 'http://jabber.org/protocol/pubsub#owner'
- interfaces = set(('node', 'config'))
- plugin_attrib_map = {}
- plugin_tag_map = {}
+ name = 'configure'
+ plugin_attrib = 'configure'
+ namespace = 'http://jabber.org/protocol/pubsub#owner'
+ interfaces = set(('node', 'config'))
+ plugin_attrib_map = {}
+ plugin_tag_map = {}
+
+ def getConfig(self):
+ return self['form']
+
+ def setConfig(self, value):
+ self['form'].setStanzaValues(value.getStanzaValues())
+ return self
registerStanzaPlugin(PubsubOwner, OwnerConfigure)
class OwnerDefault(OwnerConfigure):
- namespace = 'http://jabber.org/protocol/pubsub#owner'
- interfaces = set(('node', 'config'))
- plugin_attrib_map = {}
- plugin_tag_map = {}
+ namespace = 'http://jabber.org/protocol/pubsub#owner'
+ interfaces = set(('node', 'config'))
+ plugin_attrib_map = {}
+ plugin_tag_map = {}
- def getConfig(self):
- return self['form']
-
- def setConfig(self, value):
- self['form'].setStanzaValues(value.getStanzaValues())
- return self
registerStanzaPlugin(PubsubOwner, OwnerDefault)
registerStanzaPlugin(OwnerDefault, xep_0004.Form)
class OwnerDelete(ElementBase, OptionalSetting):
- namespace = 'http://jabber.org/protocol/pubsub#owner'
- name = 'delete'
- plugin_attrib = 'delete'
- plugin_attrib_map = {}
- plugin_tag_map = {}
- interfaces = set(('node',))
+ namespace = 'http://jabber.org/protocol/pubsub#owner'
+ name = 'delete'
+ plugin_attrib = 'delete'
+ plugin_attrib_map = {}
+ plugin_tag_map = {}
+ interfaces = set(('node',))
registerStanzaPlugin(PubsubOwner, OwnerDelete)
class OwnerPurge(ElementBase, OptionalSetting):
- namespace = 'http://jabber.org/protocol/pubsub#owner'
- name = 'purge'
- plugin_attrib = name
- plugin_attrib_map = {}
- plugin_tag_map = {}
+ namespace = 'http://jabber.org/protocol/pubsub#owner'
+ name = 'purge'
+ plugin_attrib = name
+ plugin_attrib_map = {}
+ plugin_tag_map = {}
registerStanzaPlugin(PubsubOwner, OwnerPurge)
class OwnerRedirect(ElementBase):
- namespace = 'http://jabber.org/protocol/pubsub#owner'
- name = 'redirect'
- plugin_attrib = name
- interfaces = set(('node', 'jid'))
- plugin_attrib_map = {}
- plugin_tag_map = {}
+ namespace = 'http://jabber.org/protocol/pubsub#owner'
+ name = 'redirect'
+ plugin_attrib = name
+ interfaces = set(('node', 'jid'))
+ plugin_attrib_map = {}
+ plugin_tag_map = {}
- def setJid(self, value):
- self._setAttr('jid', str(value))
+ def setJid(self, value):
+ self._setAttr('jid', str(value))
- def getJid(self):
- return JID(self._getAttr('jid'))
+ def getJid(self):
+ return JID(self._getAttr('jid'))
registerStanzaPlugin(OwnerDelete, OwnerRedirect)
class OwnerSubscriptions(Subscriptions):
- namespace = 'http://jabber.org/protocol/pubsub#owner'
- interfaces = set(('node',))
- plugin_attrib_map = {}
- plugin_tag_map = {}
+ namespace = 'http://jabber.org/protocol/pubsub#owner'
+ interfaces = set(('node',))
+ plugin_attrib_map = {}
+ plugin_tag_map = {}
- def append(self, subscription):
- if not isinstance(subscription, OwnerSubscription):
- raise TypeError
- self.xml.append(subscription.xml)
- return self.subscriptions.append(subscription)
+ def append(self, subscription):
+ if not isinstance(subscription, OwnerSubscription):
+ raise TypeError
+ self.xml.append(subscription.xml)
+ return self.subscriptions.append(subscription)
registerStanzaPlugin(PubsubOwner, OwnerSubscriptions)
class OwnerSubscription(ElementBase):
- namespace = 'http://jabber.org/protocol/pubsub#owner'
- name = 'subscription'
- plugin_attrib = name
- interfaces = set(('jid', 'subscription'))
- plugin_attrib_map = {}
- plugin_tag_map = {}
-
- def setJid(self, value):
- self._setAttr('jid', str(value))
-
- def getJid(self):
- return JID(self._getAttr('from'))
+ namespace = 'http://jabber.org/protocol/pubsub#owner'
+ name = 'subscription'
+ plugin_attrib = name
+ interfaces = set(('jid', 'subscription'))
+ plugin_attrib_map = {}
+ plugin_tag_map = {}
+
+ def setJid(self, value):
+ self._setAttr('jid', str(value))
+
+ def getJid(self):
+ return JID(self._getAttr('from'))
diff --git a/sleekxmpp/plugins/xep_0078.py b/sleekxmpp/plugins/xep_0078.py
deleted file mode 100644
index bb6a4632..00000000
--- a/sleekxmpp/plugins/xep_0078.py
+++ /dev/null
@@ -1,72 +0,0 @@
-"""
- 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 __future__ import with_statement
-from xml.etree import cElementTree as ET
-import logging
-import hashlib
-from . import base
-
-
-log = logging.getLogger(__name__)
-
-
-class xep_0078(base.base_plugin):
- """
- XEP-0078 NON-SASL Authentication
- """
- def plugin_init(self):
- self.description = "Non-SASL Authentication (broken)"
- self.xep = "0078"
- self.xmpp.add_event_handler("session_start", self.check_stream)
- #disabling until I fix conflict with PLAIN
- #self.xmpp.registerFeature("<auth xmlns='http://jabber.org/features/iq-auth'/>", self.auth)
- self.streamid = ''
-
- def check_stream(self, xml):
- self.streamid = xml.attrib['id']
- if xml.get('version', '0') != '1.0':
- self.auth()
-
- def auth(self, xml=None):
- log.debug("Starting jabber:iq:auth Authentication")
- auth_request = self.xmpp.makeIqGet()
- auth_request_query = ET.Element('{jabber:iq:auth}query')
- auth_request.attrib['to'] = self.xmpp.boundjid.host
- username = ET.Element('username')
- username.text = self.xmpp.username
- auth_request_query.append(username)
- auth_request.append(auth_request_query)
- result = auth_request.send()
- rquery = result.find('{jabber:iq:auth}query')
- attempt = self.xmpp.makeIqSet()
- query = ET.Element('{jabber:iq:auth}query')
- resource = ET.Element('resource')
- resource.text = self.xmpp.resource
- query.append(username)
- query.append(resource)
- if rquery.find('{jabber:iq:auth}digest') is None:
- log.warning("Authenticating via jabber:iq:auth Plain.")
- password = ET.Element('password')
- password.text = self.xmpp.password
- query.append(password)
- else:
- log.debug("Authenticating via jabber:iq:auth Digest")
- digest = ET.Element('digest')
- digest.text = hashlib.sha1(b"%s%s" % (self.streamid, self.xmpp.password)).hexdigest()
- query.append(digest)
- attempt.append(query)
- result = attempt.send()
- if result.attrib['type'] == 'result':
- with self.xmpp.lock:
- self.xmpp.authenticated = True
- self.xmpp.sessionstarted = True
- self.xmpp.event("session_start")
- else:
- log.info("Authentication failed")
- self.xmpp.disconnect()
- self.xmpp.event("failed_auth")
diff --git a/sleekxmpp/plugins/xep_0078/__init__.py b/sleekxmpp/plugins/xep_0078/__init__.py
new file mode 100644
index 00000000..5a2bda77
--- /dev/null
+++ b/sleekxmpp/plugins/xep_0078/__init__.py
@@ -0,0 +1,12 @@
+"""
+ SleekXMPP: The Sleek XMPP Library
+ Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout
+ This file is part of SleekXMPP.
+
+ See the file LICENSE for copying permission.
+"""
+
+from sleekxmpp.plugins.xep_0078 import stanza
+from sleekxmpp.plugins.xep_0078.stanza import IqAuth, AuthFeature
+from sleekxmpp.plugins.xep_0078.legacyauth import xep_0078
+
diff --git a/sleekxmpp/plugins/xep_0078/legacyauth.py b/sleekxmpp/plugins/xep_0078/legacyauth.py
new file mode 100644
index 00000000..bdd2df67
--- /dev/null
+++ b/sleekxmpp/plugins/xep_0078/legacyauth.py
@@ -0,0 +1,108 @@
+"""
+ SleekXMPP: The Sleek XMPP Library
+ Copyright (C) 2011 Nathanael C. Fritz
+ This file is part of SleekXMPP.
+
+ See the file LICENSE for copying permission.
+"""
+
+import logging
+import hashlib
+import random
+
+from sleekxmpp.stanza import Iq, StreamFeatures
+from sleekxmpp.xmlstream import ElementBase, ET, register_stanza_plugin
+from sleekxmpp.plugins.base import base_plugin
+from sleekxmpp.plugins.xep_0078 import stanza
+
+
+log = logging.getLogger(__name__)
+
+
+class xep_0078(base_plugin):
+
+ """
+ XEP-0078 NON-SASL Authentication
+
+ This XEP is OBSOLETE in favor of using SASL, so DO NOT use this plugin
+ unless you are forced to use an old XMPP server implementation.
+ """
+
+ def plugin_init(self):
+ self.xep = "0078"
+ self.description = "Non-SASL Authentication"
+ self.stanza = stanza
+
+ self.xmpp.register_feature('auth',
+ self._handle_auth,
+ restart=False,
+ order=self.config.get('order', 15))
+
+ register_stanza_plugin(Iq, stanza.IqAuth)
+ register_stanza_plugin(StreamFeatures, stanza.AuthFeature)
+
+
+ def _handle_auth(self, features):
+ # If we can or have already authenticated with SASL, do nothing.
+ if 'mechanisms' in features['features']:
+ return False
+ if self.xmpp.authenticated:
+ return False
+
+ log.debug("Starting jabber:iq:auth Authentication")
+
+ # Step 1: Request the auth form
+ iq = self.xmpp.Iq()
+ iq['type'] = 'get'
+ iq['to'] = self.xmpp.boundjid.host
+ iq['auth']['username'] = self.xmpp.boundjid.user
+ resp = iq.send(now=True)
+
+ if resp is None or resp['type'] != 'result':
+ log.info("Authentication failed: %s" % resp['error']['condition'])
+ self.xmpp.event('failed_auth', resp, direct=True)
+ self.xmpp.disconnect()
+ return True
+
+ # Step 2: Fill out auth form for either password or digest auth
+ iq = self.xmpp.Iq()
+ iq['type'] = 'set'
+ iq['auth']['username'] = self.xmpp.boundjid.user
+
+ # A resource is required, so create a random one if necessary
+ if self.xmpp.boundjid.resource:
+ iq['auth']['resource'] = self.xmpp.boundjid.resource
+ else:
+ iq['auth']['resource'] = '%s' % random.random()
+
+ if 'digest' in resp['auth']['fields']:
+ log.debug('Authenticating via jabber:iq:auth Digest')
+ if sys.version_info < (3, 0):
+ stream_id = bytes(self.xmpp.stream_id)
+ password = bytes(self.xmpp.password)
+ else:
+ stream_id = bytes(self.xmpp.stream_id, encoding='utf-8')
+ password = bytes(self.xmpp.password, encoding='utf-8')
+
+ digest = hashlib.sha1(b'%s%s' % (stream_id, password)).hexdigest()
+ iq['auth']['digest'] = digest
+ else:
+ log.warning('Authenticating via jabber:iq:auth Plain.')
+ iq['auth']['password'] = self.xmpp.password
+
+ # Step 3: Send credentials
+ result = iq.send(now=True)
+ if result is not None and result.attrib['type'] == 'result':
+ self.xmpp.features.add('auth')
+
+ self.xmpp.authenticated = True
+ log.debug("Established Session")
+ self.xmpp.sessionstarted = True
+ self.xmpp.session_started_event.set()
+ self.xmpp.event('session_start')
+ else:
+ log.info("Authentication failed")
+ self.xmpp.disconnect()
+ self.xmpp.event("failed_auth")
+
+ return True
diff --git a/sleekxmpp/plugins/xep_0078/stanza.py b/sleekxmpp/plugins/xep_0078/stanza.py
new file mode 100644
index 00000000..86ba09ad
--- /dev/null
+++ b/sleekxmpp/plugins/xep_0078/stanza.py
@@ -0,0 +1,43 @@
+"""
+ SleekXMPP: The Sleek XMPP Library
+ Copyright (C) 2011 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 IqAuth(ElementBase):
+ namespace = 'jabber:iq:auth'
+ name = 'query'
+ plugin_attrib = 'auth'
+ interfaces = set(('fields', 'username', 'password', 'resource', 'digest'))
+ sub_interfaces = set(('username', 'password', 'resource', 'digest'))
+ plugin_tag_map = {}
+ plugin_attrib_map = {}
+
+ def get_fields(self):
+ fields = set()
+ for field in self.sub_interfaces:
+ if self.xml.find('{%s}%s' % (self.namespace, field)) is not None:
+ fields.add(field)
+ return fields
+
+ def set_resource(self, value):
+ self._set_sub_text('resource', value, keep=True)
+
+ def set_password(self, value):
+ self._set_sub_text('password', value, keep=True)
+
+
+class AuthFeature(ElementBase):
+ namespace = 'http://jabber.org/features/iq-auth'
+ name = 'auth'
+ plugin_attrib = 'auth'
+ interfaces = set()
+ plugin_tag_map = {}
+ plugin_attrib_map = {}
+
+
diff --git a/sleekxmpp/plugins/xep_0082.py b/sleekxmpp/plugins/xep_0082.py
index 785ba36b..d3c4cc56 100644
--- a/sleekxmpp/plugins/xep_0082.py
+++ b/sleekxmpp/plugins/xep_0082.py
@@ -6,10 +6,11 @@
See the file LICENSE for copying permission.
"""
+import logging
import datetime as dt
-from dateutil import parser
-from dateutil.tz import tzoffset, tzutc
+
from sleekxmpp.plugins.base import base_plugin
+from sleekxmpp.thirdparty import tzutc, tzoffset, parse_iso
# =====================================================================
@@ -24,7 +25,8 @@ def parse(time_str):
Arguments:
time_str -- A formatted timestamp string.
"""
- return parser.parse(time_str)
+ return parse_iso(time_str)
+
def format_date(time_obj):
"""
@@ -45,7 +47,7 @@ def format_time(time_obj):
Return a formatted string version of a time object.
format:
- hh:mm:ss[.sss][TZD
+ hh:mm:ss[.sss][TZD]
arguments:
time_obj -- A time or datetime object.
diff --git a/sleekxmpp/plugins/xep_0199/ping.py b/sleekxmpp/plugins/xep_0199/ping.py
index d1e08e61..0fa22f8a 100644
--- a/sleekxmpp/plugins/xep_0199/ping.py
+++ b/sleekxmpp/plugins/xep_0199/ping.py
@@ -108,7 +108,7 @@ class xep_0199(base_plugin):
iq -- The ping request.
"""
log.debug("Pinged by %s" % iq['from'])
- iq.reply().enable('ping').send()
+ iq.reply().send()
def send_ping(self, jid, timeout=None, errorfalse=False,
ifrom=None, block=True, callback=None):
diff --git a/sleekxmpp/plugins/xep_0202/__init__.py b/sleekxmpp/plugins/xep_0202/__init__.py
index 82338d3a..a34b2376 100644
--- a/sleekxmpp/plugins/xep_0202/__init__.py
+++ b/sleekxmpp/plugins/xep_0202/__init__.py
@@ -6,6 +6,7 @@
See the file LICENSE for copying permission.
"""
+
from sleekxmpp.plugins.xep_0202 import stanza
from sleekxmpp.plugins.xep_0202.stanza import EntityTime
from sleekxmpp.plugins.xep_0202.time import xep_0202
diff --git a/sleekxmpp/plugins/xep_0202/stanza.py b/sleekxmpp/plugins/xep_0202/stanza.py
index bb27692a..b6ccc960 100644
--- a/sleekxmpp/plugins/xep_0202/stanza.py
+++ b/sleekxmpp/plugins/xep_0202/stanza.py
@@ -6,11 +6,12 @@
See the file LICENSE for copying permission.
"""
+import logging
import datetime as dt
-from dateutil.tz import tzoffset, tzutc
from sleekxmpp.xmlstream import ElementBase
from sleekxmpp.plugins import xep_0082
+from sleekxmpp.thirdparty import tzutc, tzoffset
class EntityTime(ElementBase):
diff --git a/sleekxmpp/stanza/iq.py b/sleekxmpp/stanza/iq.py
index 4a12a87e..f05dad17 100644
--- a/sleekxmpp/stanza/iq.py
+++ b/sleekxmpp/stanza/iq.py
@@ -11,6 +11,7 @@ from sleekxmpp.stanza.rootstanza import RootStanza
from sleekxmpp.xmlstream import StanzaBase, ET
from sleekxmpp.xmlstream.handler import Waiter, Callback
from sleekxmpp.xmlstream.matcher import MatcherId
+from sleekxmpp.exceptions import IqTimeout, IqError
class Iq(RootStanza):
@@ -197,7 +198,12 @@ class Iq(RootStanza):
waitfor = Waiter('IqWait_%s' % self['id'], MatcherId(self['id']))
self.stream.register_handler(waitfor)
StanzaBase.send(self, now=now)
- return waitfor.wait(timeout)
+ 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)
diff --git a/sleekxmpp/stanza/stream_features.py b/sleekxmpp/stanza/stream_features.py
index 5be2e55f..b800011f 100644
--- a/sleekxmpp/stanza/stream_features.py
+++ b/sleekxmpp/stanza/stream_features.py
@@ -19,6 +19,8 @@ class StreamFeatures(StanzaBase):
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)
diff --git a/sleekxmpp/test/sleektest.py b/sleekxmpp/test/sleektest.py
index 19309fc5..6e351727 100644
--- a/sleekxmpp/test/sleektest.py
+++ b/sleekxmpp/test/sleektest.py
@@ -16,6 +16,7 @@ import sleekxmpp
from sleekxmpp import ClientXMPP, ComponentXMPP
from sleekxmpp.stanza import Message, Iq, Presence
from sleekxmpp.test import TestSocket, TestLiveSocket
+from sleekxmpp.exceptions import XMPPError, IqTimeout, IqError
from sleekxmpp.xmlstream import ET, register_stanza_plugin
from sleekxmpp.xmlstream import ElementBase, StanzaBase
from sleekxmpp.xmlstream.tostring import tostring
@@ -344,9 +345,11 @@ class SleekTest(unittest.TestCase):
self.xmpp.socket.recv_data(header)
elif socket == 'live':
self.xmpp.socket_class = TestLiveSocket
+
def wait_for_session(x):
self.xmpp.socket.clear()
skip_queue.put('started')
+
self.xmpp.add_event_handler('session_start', wait_for_session)
self.xmpp.connect()
else:
diff --git a/sleekxmpp/thirdparty/__init__.py b/sleekxmpp/thirdparty/__init__.py
index 3eb6ad73..1c7bf651 100644
--- a/sleekxmpp/thirdparty/__init__.py
+++ b/sleekxmpp/thirdparty/__init__.py
@@ -4,3 +4,4 @@ except:
from sleekxmpp.thirdparty.ordereddict import OrderedDict
from sleekxmpp.thirdparty import suelta
+from sleekxmpp.thirdparty.mini_dateutil import tzutc, tzoffset, parse_iso
diff --git a/sleekxmpp/thirdparty/mini_dateutil.py b/sleekxmpp/thirdparty/mini_dateutil.py
new file mode 100644
index 00000000..6af5ffde
--- /dev/null
+++ b/sleekxmpp/thirdparty/mini_dateutil.py
@@ -0,0 +1,267 @@
+# This module is a very stripped down version of the dateutil
+# package for when dateutil has not been installed. As a replacement
+# for dateutil.parser.parse, the parsing methods from
+# http://blog.mfabrik.com/2008/06/30/relativity-of-time-shortcomings-in-python-datetime-and-workaround/
+
+#As such, the following copyrights and licenses applies:
+
+
+# dateutil - Extensions to the standard python 2.3+ datetime module.
+#
+# Copyright (c) 2003-2011 - Gustavo Niemeyer <gustavo@niemeyer.net>
+#
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * Neither the name of the copyright holder nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+# fixed_dateime
+#
+# Copyright (c) 2008, Red Innovation Ltd., Finland
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+# * Neither the name of Red Innovation nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY RED INNOVATION ``AS IS'' AND ANY
+# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL RED INNOVATION BE LIABLE FOR ANY
+# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+
+import re
+import datetime
+
+
+ZERO = datetime.timedelta(0)
+
+
+try:
+ from dateutil.parser import parse as parse_iso
+ from dateutil.tz import tzoffset, tzutc
+except:
+ # As a stopgap, define the two timezones here based
+ # on the dateutil code.
+
+ class tzutc(datetime.tzinfo):
+
+ def utcoffset(self, dt):
+ return ZERO
+
+ def dst(self, dt):
+ return ZERO
+
+ def tzname(self, dt):
+ return "UTC"
+
+ def __eq__(self, other):
+ return (isinstance(other, tzutc) or
+ (isinstance(other, tzoffset) and other._offset == ZERO))
+
+ def __ne__(self, other):
+ return not self.__eq__(other)
+
+ def __repr__(self):
+ return "%s()" % self.__class__.__name__
+
+ __reduce__ = object.__reduce__
+
+ class tzoffset(datetime.tzinfo):
+
+ def __init__(self, name, offset):
+ self._name = name
+ self._offset = datetime.timedelta(seconds=offset)
+
+ def utcoffset(self, dt):
+ return self._offset
+
+ def dst(self, dt):
+ return ZERO
+
+ def tzname(self, dt):
+ return self._name
+
+ def __eq__(self, other):
+ return (isinstance(other, tzoffset) and
+ self._offset == other._offset)
+
+ def __ne__(self, other):
+ return not self.__eq__(other)
+
+ def __repr__(self):
+ return "%s(%s, %s)" % (self.__class__.__name__,
+ repr(self._name),
+ self._offset.days*86400+self._offset.seconds)
+
+ __reduce__ = object.__reduce__
+
+
+ _fixed_offset_tzs = { }
+ UTC = tzutc()
+
+ def _get_fixed_offset_tz(offsetmins):
+ """For internal use only: Returns a tzinfo with
+ the given fixed offset. This creates only one instance
+ for each offset; the zones are kept in a dictionary"""
+
+ if offsetmins == 0:
+ return UTC
+
+ if not offsetmins in _fixed_offset_tzs:
+ if offsetmins < 0:
+ sign = '-'
+ absoff = -offsetmins
+ else:
+ sign = '+'
+ absoff = offsetmins
+
+ name = "UTC%s%02d:%02d" % (sign, int(absoff / 60), absoff % 60)
+ inst = tzoffset(offsetmins, name)
+ _fixed_offset_tzs[offsetmins] = inst
+
+ return _fixed_offset_tzs[offsetmins]
+
+
+ _iso8601_parser = re.compile("""
+ ^
+ (?P<year> [0-9]{4})?(?P<ymdsep>-?)?
+ (?P<month>[0-9]{2})?(?P=ymdsep)?
+ (?P<day> [0-9]{2})?
+
+ (?: # time part... optional... at least hour must be specified
+ (?:T|\s+)?
+ (?P<hour>[0-9]{2})
+ (?:
+ # minutes, separated with :, or none, from hours
+ (?P<hmssep>[:]?)
+ (?P<minute>[0-9]{2})
+ (?:
+ # same for seconds, separated with :, or none, from hours
+ (?P=hmssep)
+ (?P<second>[0-9]{2})
+ )?
+ )?
+
+ # fractions
+ (?: [,.] (?P<frac>[0-9]{1,10}))?
+
+ # timezone, Z, +-hh or +-hh:?mm. MUST BE, but complain if not there.
+ (
+ (?P<tzempty>Z)
+ |
+ (?P<tzh>[+-][0-9]{2})
+ (?: :? # optional separator
+ (?P<tzm>[0-9]{2})
+ )?
+ )?
+ )?
+ $
+ """, re.X) # """
+
+ def parse_iso(timestamp):
+ """Internal function for parsing a timestamp in
+ ISO 8601 format"""
+
+ timestamp = timestamp.strip()
+
+ m = _iso8601_parser.match(timestamp)
+ if not m:
+ raise ValueError("Not a proper ISO 8601 timestamp!: %s" % timestamp)
+
+ vals = m.groupdict()
+ def_vals = {'year': 1970, 'month': 1, 'day': 1}
+ for key in vals:
+ if vals[key] is None:
+ vals[key] = def_vals.get(key, 0)
+ elif key not in ['ymdsep', 'hmssep', 'tzempty']:
+ vals[key] = int(vals[key])
+
+ year = vals['year']
+ month = vals['month']
+ day = vals['day']
+
+ h, min, s, us = None, None, None, 0
+ frac = 0
+ if m.group('tzempty') == None and m.group('tzh') == None:
+ raise ValueError("Not a proper ISO 8601 timestamp: " +
+ "missing timezone (Z or +hh[:mm])!")
+
+ if m.group('frac'):
+ frac = m.group('frac')
+ power = len(frac)
+ frac = int(frac) / 10.0 ** power
+
+ if m.group('hour'):
+ h = vals['hour']
+
+ if m.group('minute'):
+ min = vals['minute']
+
+ if m.group('second'):
+ s = vals['second']
+
+ if frac != None:
+ # ok, fractions of hour?
+ if min == None:
+ frac, min = _math.modf(frac * 60.0)
+ min = int(min)
+
+ # fractions of second?
+ if s == None:
+ frac, s = _math.modf(frac * 60.0)
+ s = int(s)
+
+ # and extract microseconds...
+ us = int(frac * 1000000)
+
+ if m.group('tzempty') == 'Z':
+ offsetmins = 0
+ else:
+ # timezone: hour diff with sign
+ offsetmins = vals['tzh'] * 60
+ tzm = m.group('tzm')
+
+ # add optional minutes
+ if tzm != None:
+ tzm = int(tzm)
+ offsetmins += tzm if offsetmins > 0 else -tzm
+
+ tz = _get_fixed_offset_tz(offsetmins)
+ return datetime.datetime(year, month, day, h, min, s, us, tz)
diff --git a/sleekxmpp/thirdparty/suelta/mechanisms/anonymous.py b/sleekxmpp/thirdparty/suelta/mechanisms/anonymous.py
index de89eef2..e44e91a2 100644
--- a/sleekxmpp/thirdparty/suelta/mechanisms/anonymous.py
+++ b/sleekxmpp/thirdparty/suelta/mechanisms/anonymous.py
@@ -10,7 +10,7 @@ class ANONYMOUS(Mechanism):
def __init__(self, sasl, name):
"""
"""
- super(ANONYMOUS, self).__init__(self, sasl, name, 0)
+ super(ANONYMOUS, self).__init__(sasl, name, 0)
def get_values(self):
"""
diff --git a/sleekxmpp/thirdparty/suelta/sasl.py b/sleekxmpp/thirdparty/suelta/sasl.py
index ec7afe9d..2ae9ae61 100644
--- a/sleekxmpp/thirdparty/suelta/sasl.py
+++ b/sleekxmpp/thirdparty/suelta/sasl.py
@@ -225,7 +225,7 @@ class SASL(object):
requested_mech = 'ANONYMOUS'
else:
requested_mech = self.mech
- if requested_mech == '*' and self.user == 'anonymous':
+ if requested_mech == '*' and self.user in ['', 'anonymous', None]:
requested_mech = 'ANONYMOUS'
# If a specific mechanism was requested, try it
@@ -243,7 +243,7 @@ class SASL(object):
if MECH_SEC_SCORES[name] > best_score:
best_score = MECH_SEC_SCORES[name]
best_mech = name
- if best_mech != None:
+ if best_mech is not None:
best_mech = MECHANISMS[best_mech](self, best_mech)
return best_mech
diff --git a/sleekxmpp/xmlstream/stanzabase.py b/sleekxmpp/xmlstream/stanzabase.py
index f1a9e1f5..a2826ead 100644
--- a/sleekxmpp/xmlstream/stanzabase.py
+++ b/sleekxmpp/xmlstream/stanzabase.py
@@ -482,7 +482,8 @@ class ElementBase(object):
if plugin:
if plugin not in self.plugins:
self.init_plugin(plugin)
- handler = getattr(self.plugins[plugin], set_method, None)
+ handler = getattr(self.plugins[plugin],
+ set_method, None)
if handler:
return handler(value)
@@ -1066,7 +1067,7 @@ class ElementBase(object):
stanza_ns = '' if top_level_ns else self.namespace
return tostring(self.xml, xmlns='',
stanza_ns=stanza_ns,
- top_level = not top_level_ns)
+ top_level=not top_level_ns)
def __repr__(self):
"""
@@ -1285,7 +1286,7 @@ class StanzaBase(ElementBase):
return tostring(self.xml, xmlns='',
stanza_ns=stanza_ns,
stream=self.stream,
- top_level = not top_level_ns)
+ top_level=not top_level_ns)
# To comply with PEP8, method names now use underscores.
diff --git a/sleekxmpp/xmlstream/xmlstream.py b/sleekxmpp/xmlstream/xmlstream.py
index 15bbe655..5ba4269f 100644
--- a/sleekxmpp/xmlstream/xmlstream.py
+++ b/sleekxmpp/xmlstream/xmlstream.py
@@ -831,7 +831,7 @@ class XMLStream(object):
self.send_queue.put(data)
return True
- def process(self, threaded=True):
+ def process(self, **kwargs):
"""
Initialize the XML streams and begin processing events.
@@ -839,14 +839,29 @@ class XMLStream(object):
by HANDLER_THREADS.
Arguments:
+ block -- If block=False then event dispatcher will run
+ in a separate thread, allowing for the stream to be
+ used in the background for another application.
+ Otherwise, process(block=True) blocks the current thread.
+ Defaults to False.
+
+ **threaded is deprecated and included for API compatibility**
threaded -- If threaded=True then event dispatcher will run
in a separate thread, allowing for the stream to be
used in the background for another application.
Defaults to True.
- Event handlers and the send queue will be threaded
- regardless of this parameter's value.
+ Event handlers and the send queue will be threaded
+ regardless of these parameters.
"""
+ if 'threaded' in kwargs and 'block' in kwargs:
+ raise ValueError("process() called with both " + \
+ "block and threaded arguments")
+ elif 'block' in kwargs:
+ threaded = not(kwargs.get('block', False))
+ else:
+ threaded = kwargs.get('threaded', True)
+
self.scheduler.process(threaded=True)
def start_thread(name, target):