summaryrefslogtreecommitdiff
path: root/sleekxmpp/plugins
diff options
context:
space:
mode:
authorNathan Fritz <fritzy@netflint.net>2009-06-03 22:56:51 +0000
committerNathan Fritz <fritzy@netflint.net>2009-06-03 22:56:51 +0000
commit96b103b27599e5af247c1e3b95d62c80c1e32a63 (patch)
tree0527b1607b16adb020759ee9a944e1b22e3e0e6b /sleekxmpp/plugins
downloadslixmpp-96b103b27599e5af247c1e3b95d62c80c1e32a63.tar.gz
slixmpp-96b103b27599e5af247c1e3b95d62c80c1e32a63.tar.bz2
slixmpp-96b103b27599e5af247c1e3b95d62c80c1e32a63.tar.xz
slixmpp-96b103b27599e5af247c1e3b95d62c80c1e32a63.zip
moved seesmic branch to trunk
Diffstat (limited to 'sleekxmpp/plugins')
-rw-r--r--sleekxmpp/plugins/__init__.py20
-rw-r--r--sleekxmpp/plugins/base.py35
-rw-r--r--sleekxmpp/plugins/gmail_notify.py57
-rw-r--r--sleekxmpp/plugins/xep_0004.py389
-rw-r--r--sleekxmpp/plugins/xep_0009.py273
-rw-r--r--sleekxmpp/plugins/xep_0030.py117
-rw-r--r--sleekxmpp/plugins/xep_0045.py193
-rw-r--r--sleekxmpp/plugins/xep_0050.py142
-rw-r--r--sleekxmpp/plugins/xep_0060.py306
-rw-r--r--sleekxmpp/plugins/xep_0078.py81
-rw-r--r--sleekxmpp/plugins/xep_0086.py49
-rw-r--r--sleekxmpp/plugins/xep_0092.py67
-rw-r--r--sleekxmpp/plugins/xep_0199.py70
13 files changed, 1799 insertions, 0 deletions
diff --git a/sleekxmpp/plugins/__init__.py b/sleekxmpp/plugins/__init__.py
new file mode 100644
index 00000000..1868365e
--- /dev/null
+++ b/sleekxmpp/plugins/__init__.py
@@ -0,0 +1,20 @@
+"""
+ SleekXMPP: The Sleek XMPP Library
+ Copyright (C) 2007 Nathanael C. Fritz
+ This file is part of SleekXMPP.
+
+ SleekXMPP is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ SleekXMPP is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with SleekXMPP; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+"""
+__all__ = ['xep_0004', 'xep_0030', 'xep_0045', 'xep_0050', 'xep_0078', 'xep_0092', 'xep_0199', 'gmail_notify', 'xep_0060']
diff --git a/sleekxmpp/plugins/base.py b/sleekxmpp/plugins/base.py
new file mode 100644
index 00000000..685833f4
--- /dev/null
+++ b/sleekxmpp/plugins/base.py
@@ -0,0 +1,35 @@
+"""
+ SleekXMPP: The Sleek XMPP Library
+ Copyright (C) 2007 Nathanael C. Fritz
+ This file is part of SleekXMPP.
+
+ SleekXMPP is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ SleekXMPP is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with SleekXMPP; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+"""
+class base_plugin(object):
+
+ def __init__(self, xmpp, config):
+ self.xep = 'base'
+ self.description = 'Base Plugin'
+ self.xmpp = xmpp
+ self.config = config
+ self.enable = config.get('enable', True)
+ if self.enable:
+ self.plugin_init()
+
+ def plugin_init(self):
+ pass
+
+ def post_init(self):
+ pass
diff --git a/sleekxmpp/plugins/gmail_notify.py b/sleekxmpp/plugins/gmail_notify.py
new file mode 100644
index 00000000..8bc4423a
--- /dev/null
+++ b/sleekxmpp/plugins/gmail_notify.py
@@ -0,0 +1,57 @@
+"""
+ SleekXMPP: The Sleek XMPP Library
+ Copyright (C) 2007 Nathanael C. Fritz
+ This file is part of SleekXMPP.
+
+ SleekXMPP is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ SleekXMPP is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with SleekXMPP; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+"""
+from __future__ import with_statement
+import base
+import logging
+from xml.etree import cElementTree as ET
+import traceback
+import time
+
+class gmail_notify(base.base_plugin):
+
+ def plugin_init(self):
+ self.description = 'Google Talk Gmail Notification'
+ self.xmpp.add_event_handler('sent_presence', self.handler_gmailcheck, threaded=True)
+ self.emails = []
+
+ def handler_gmailcheck(self, payload):
+ #TODO XEP 30 should cache results and have getFeature
+ result = self.xmpp['xep_0030'].getInfo(self.xmpp.server)
+ features = []
+ for feature in result.findall('{http://jabber.org/protocol/disco#info}query/{http://jabber.org/protocol/disco#info}feature'):
+ features.append(feature.get('var'))
+ if 'google:mail:notify' in features:
+ logging.debug("Server supports Gmail Notify")
+ self.xmpp.add_handler("<iq type='set' xmlns='%s'><new-mail xmlns='google:mail:notify' /></iq>" % self.xmpp.default_ns, self.handler_notify)
+ self.getEmail()
+
+ def handler_notify(self, xml):
+ logging.info("New Gmail recieved!")
+ self.xmpp.event('gmail_notify')
+
+ def getEmail(self):
+ iq = self.xmpp.makeIqGet()
+ iq.attrib['from'] = self.xmpp.fulljid
+ iq.attrib['to'] = self.xmpp.jid
+ self.xmpp.makeIqQuery(iq, 'google:mail:notify')
+ emails = self.xmpp.send(iq, self.xmpp.makeIq(self.xmpp.id))
+ mailbox = emails.find('{google:mail:notify}mailbox')
+ total = int(mailbox.get('total-matched', 0))
+ logging.info("%s New Gmail Messages" % total)
diff --git a/sleekxmpp/plugins/xep_0004.py b/sleekxmpp/plugins/xep_0004.py
new file mode 100644
index 00000000..c85b09a5
--- /dev/null
+++ b/sleekxmpp/plugins/xep_0004.py
@@ -0,0 +1,389 @@
+"""
+ SleekXMPP: The Sleek XMPP Library
+ Copyright (C) 2007 Nathanael C. Fritz
+ This file is part of SleekXMPP.
+
+ SleekXMPP is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ SleekXMPP is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with SleekXMPP; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+"""
+from . import base
+import logging
+from xml.etree import cElementTree as ET
+import copy
+#TODO support item groups and results
+
+class xep_0004(base.base_plugin):
+
+ def plugin_init(self):
+ self.xep = '0004'
+ self.description = 'Data Forms'
+ self.xmpp.add_handler("<message><x xmlns='jabber:x:data' /></message>", self.handler_message_xform)
+
+ def post_init(self):
+ self.xmpp['xep_0030'].add_feature('jabber:x:data')
+
+ def handler_message_xform(self, xml):
+ object = self.handle_form(xml)
+ self.xmpp.event("message_form", object)
+
+ def handler_presence_xform(self, xml):
+ object = self.handle_form(xml)
+ self.xmpp.event("presence_form", object)
+
+ def handle_form(self, xml):
+ xmlform = xml.find('{jabber:x:data}x')
+ object = self.buildForm(xmlform)
+ self.xmpp.event("message_xform", object)
+ return object
+
+ def buildForm(self, xml):
+ form = Form(xml.attrib['type'])
+ form.fromXML(xml)
+ return form
+
+ def makeForm(self, ftype='form', title='', instructions=''):
+ return Form(self.xmpp, ftype, title, instructions)
+
+class FieldContainer(object):
+ def __init__(self, stanza = 'form'):
+ self.fields = []
+ self.field = {}
+ self.stanza = stanza
+
+ def addField(self, var, ftype='text-single', label='', desc='', required=False, value=None):
+ self.field[var] = FormField(var, ftype, label, desc, required, value)
+ self.fields.append(self.field[var])
+ return self.field[var]
+
+ def buildField(self, xml):
+ self.field[xml.get('var', '__unnamed__')] = FormField(xml.get('var', '__unnamed__'), xml.get('type', 'text-single'))
+ self.fields.append(self.field[xml.get('var', '__unnamed__')])
+ self.field[xml.get('var', '__unnamed__')].buildField(xml)
+
+ def buildContainer(self, xml):
+ self.stanza = xml.tag
+ for field in xml.findall('{jabber:x:data}field'):
+ self.buildField(field)
+
+ def getXML(self, ftype):
+ container = ET.Element(self.stanza)
+ for field in self.fields:
+ container.append(field.getXML(ftype))
+ return container
+
+class Form(FieldContainer):
+ types = ('form', 'submit', 'cancel', 'result')
+ def __init__(self, xmpp=None, ftype='form', title='', instructions=''):
+ if not ftype in self.types:
+ raise ValueError("Invalid Form Type")
+ FieldContainer.__init__(self)
+ self.xmpp = xmpp
+ self.type = ftype
+ self.title = title
+ self.instructions = instructions
+ self.reported = []
+ self.items = []
+
+ def getValues(self):
+ result = {}
+ for field in self.fields:
+ value = field.value
+ if len(value) == 1:
+ value = value[0]
+ result[field.var] = value
+ return result
+
+ def fromXML(self, xml):
+ self.buildForm(xml)
+
+ def addItem(self):
+ newitem = FieldContainer('item')
+ self.items.append(newitem)
+ return newitem
+
+ def buildItem(self, xml):
+ newitem = self.addItem()
+ newitem.buildContainer(xml)
+
+ def addReported(self):
+ reported = FieldContainer('reported')
+ self.reported.append(reported)
+ return reported
+
+ def buildReported(self, xml):
+ reported = self.addReported()
+ reported.buildContainer(xml)
+
+ def setTitle(self, title):
+ self.title = title
+
+ def setInstructions(self, instructions):
+ self.instructions = instructions
+
+ def setType(self, ftype):
+ self.type = ftype
+
+ def getXMLMessage(self, to):
+ msg = self.xmpp.makeMessage(to)
+ msg.append(self.getXML())
+ return msg
+
+ def buildForm(self, xml):
+ self.type = xml.get('type', 'form')
+ if xml.find('{jabber:x:data}title') is not None:
+ self.setTitle(xml.find('{jabber:x:data}title').text)
+ if xml.find('{jabber:x:data}instructions') is not None:
+ self.setInstructions(xml.find('{jabber:x:data}instructions').text)
+ for field in xml.findall('{jabber:x:data}field'):
+ self.buildField(field)
+ for reported in xml.findall('{jabber:x:data}reported'):
+ self.buildReported(reported)
+ for item in xml.findall('{jabber:x:data}item'):
+ self.buildItem(item)
+
+ #def getXML(self, tostring = False):
+ def getXML(self, ftype=None):
+ logging.debug("creating form as %s" % ftype)
+ if ftype:
+ self.type = ftype
+ form = ET.Element('{jabber:x:data}x')
+ form.attrib['type'] = self.type
+ if self.title and self.type in ('form', 'result'):
+ title = ET.Element('title')
+ title.text = self.title
+ form.append(title)
+ if self.instructions and self.type == 'form':
+ instructions = ET.Element('instructions')
+ instructions.text = self.instructions
+ form.append(instructions)
+ for field in self.fields:
+ form.append(field.getXML(self.type))
+ for reported in self.reported:
+ form.append(reported.getXML('reported'))
+ for item in self.items:
+ form.append(item.getXML(self.type))
+ #if tostring:
+ # form = self.xmpp.tostring(form)
+ return form
+
+ def getXHTML(self):
+ form = ET.Element('{http://www.w3.org/1999/xhtml}form')
+ if self.title:
+ title = ET.Element('h2')
+ title.text = self.title
+ form.append(title)
+ if self.instructions:
+ instructions = ET.Element('p')
+ instructions.text = self.instructions
+ form.append(instructions)
+ for field in self.fields:
+ form.append(field.getXHTML())
+ for field in self.reported:
+ form.append(field.getXHTML())
+ for field in self.items:
+ form.append(field.getXHTML())
+ return form
+
+
+ def makeSubmit(self):
+ self.setType('submit')
+
+class FormField(object):
+ types = ('boolean', 'fixed', 'hidden', 'jid-multi', 'jid-single', 'list-multi', 'list-single', 'text-multi', 'text-private', 'text-single')
+ listtypes = ('jid-multi', 'jid-single', 'list-multi', 'list-single')
+ lbtypes = ('fixed', 'text-multi')
+ def __init__(self, var, ftype='text-single', label='', desc='', required=False, value=None):
+ if not ftype in self.types:
+ raise ValueError("Invalid Field Type")
+ self.type = ftype
+ self.var = var
+ self.label = label
+ self.desc = desc
+ self.options = []
+ self.required = False
+ self.value = []
+ if self.type in self.listtypes:
+ self.islist = True
+ else:
+ self.islist = False
+ if self.type in self.lbtypes:
+ self.islinebreak = True
+ else:
+ self.islinebreak = False
+ if value:
+ self.setValue(value)
+
+ def addOption(self, value, label):
+ if self.islist:
+ self.options.append((value, label))
+ else:
+ raise ValueError("Cannot add options to non-list type field.")
+
+ def setTrue(self):
+ if self.type == 'boolean':
+ self.value = [True]
+
+ def setFalse(self):
+ if self.type == 'boolean':
+ self.value = [False]
+
+ def require(self):
+ self.required = True
+
+ def setDescription(self, desc):
+ self.desc = desc
+
+ def setValue(self, value):
+ if self.islinebreak and value is not None:
+ self.value += value.split('\n')
+ else:
+ if len(self.value) and (not self.islist or self.type == 'list-single'):
+ self.value = [value]
+ else:
+ self.value.append(value)
+
+ def delValue(self, value):
+ if type(self.value) == type([]):
+ try:
+ idx = self.value.index(value)
+ if idx != -1:
+ self.value.pop(idx)
+ except ValueError:
+ pass
+ else:
+ self.value = ''
+
+ def setAnswer(self, value):
+ self.setValue(value)
+
+ def buildField(self, xml):
+ self.type = xml.get('type', 'text-single')
+ self.label = xml.get('label', '')
+ for option in xml.findall('{jabber:x:data}option'):
+ self.addOption(option.find('{jabber:x:data}value').text, option.get('label', ''))
+ for value in xml.findall('{jabber:x:data}value'):
+ self.setValue(value.text)
+ if xml.find('{jabber:x:data}required') is not None:
+ self.require()
+ if xml.find('{jabber:x:data}desc') is not None:
+ self.setDescription(xml.find('{jabber:x:data}desc').text)
+
+ def getXML(self, ftype):
+ field = ET.Element('field')
+ if ftype != 'result':
+ field.attrib['type'] = self.type
+ if self.type != 'fixed':
+ if self.var:
+ field.attrib['var'] = self.var
+ if self.label:
+ field.attrib['label'] = self.label
+ if ftype == 'form':
+ for option in self.options:
+ optionxml = ET.Element('option')
+ optionxml.attrib['label'] = option[1]
+ optionval = ET.Element('value')
+ optionval.text = option[0]
+ optionxml.append(optionval)
+ field.append(optionxml)
+ if self.required:
+ required = ET.Element('required')
+ field.append(required)
+ if self.desc:
+ desc = ET.Element('desc')
+ desc.text = self.desc
+ field.append(desc)
+ for value in self.value:
+ valuexml = ET.Element('value')
+ if value is True or value is False:
+ if value:
+ valuexml.text = '1'
+ else:
+ valuexml.text = '0'
+ else:
+ valuexml.text = value
+ field.append(valuexml)
+ return field
+
+ def getXHTML(self):
+ field = ET.Element('div', {'class': 'xmpp-xforms-%s' % self.type})
+ if self.label:
+ label = ET.Element('p')
+ label.text = "%s: " % self.label
+ else:
+ label = ET.Element('p')
+ label.text = "%s: " % self.var
+ field.append(label)
+ if self.type == 'boolean':
+ formf = ET.Element('input', {'type': 'checkbox', 'name': self.var})
+ if len(self.value) and self.value[0] in (True, 'true', '1'):
+ formf.attrib['checked'] = 'checked'
+ elif self.type == 'fixed':
+ formf = ET.Element('p')
+ try:
+ formf.text = ', '.join(self.value)
+ except:
+ pass
+ field.append(formf)
+ formf = ET.Element('input', {'type': 'hidden', 'name': self.var})
+ try:
+ formf.text = ', '.join(self.value)
+ except:
+ pass
+ elif self.type == 'hidden':
+ formf = ET.Element('input', {'type': 'hidden', 'name': self.var})
+ try:
+ formf.text = ', '.join(self.value)
+ except:
+ pass
+ elif self.type in ('jid-multi', 'list-multi'):
+ formf = ET.Element('select', {'name': self.var})
+ for option in self.options:
+ optf = ET.Element('option', {'value': option[0], 'multiple': 'multiple'})
+ optf.text = option[1]
+ if option[1] in self.value:
+ optf.attrib['selected'] = 'selected'
+ formf.append(option)
+ elif self.type in ('jid-single', 'text-single'):
+ formf = ET.Element('input', {'type': 'text', 'name': self.var})
+ try:
+ formf.attrib['value'] = ', '.join(self.value)
+ except:
+ pass
+ elif self.type == 'list-single':
+ formf = ET.Element('select', {'name': self.var})
+ for option in self.options:
+ optf = ET.Element('option', {'value': option[0]})
+ optf.text = option[1]
+ if not optf.text:
+ optf.text = option[0]
+ if option[1] in self.value:
+ optf.attrib['selected'] = 'selected'
+ formf.append(optf)
+ elif self.type == 'text-multi':
+ formf = ET.Element('textarea', {'name': self.var})
+ try:
+ formf.text = ', '.join(self.value)
+ except:
+ pass
+ if not formf.text:
+ formf.text = ' '
+ elif self.type == 'text-private':
+ formf = ET.Element('input', {'type': 'password', 'name': self.var})
+ try:
+ formf.attrib['value'] = ', '.join(self.value)
+ except:
+ pass
+ label.append(formf)
+ return field
+
diff --git a/sleekxmpp/plugins/xep_0009.py b/sleekxmpp/plugins/xep_0009.py
new file mode 100644
index 00000000..c6b7b5df
--- /dev/null
+++ b/sleekxmpp/plugins/xep_0009.py
@@ -0,0 +1,273 @@
+"""
+XEP-0009 XMPP Remote Procedure Calls
+"""
+from __future__ import with_statement
+import base
+import logging
+from xml.etree import cElementTree as ET
+import copy
+import time
+import base64
+
+def py2xml(*args):
+ params = ET.Element("params")
+ for x in args:
+ param = ET.Element("param")
+ param.append(_py2xml(x))
+ params.append(param) #<params><param>...
+ return params
+
+def _py2xml(*args):
+ for x in args:
+ val = ET.Element("value")
+ if type(x) is int:
+ i4 = ET.Element("i4")
+ i4.text = str(x)
+ val.append(i4)
+ if type(x) is bool:
+ boolean = ET.Element("boolean")
+ boolean.text = str(int(x))
+ val.append(boolean)
+ elif type(x) is str:
+ string = ET.Element("string")
+ string.text = x
+ val.append(string)
+ elif type(x) is float:
+ double = ET.Element("double")
+ double.text = str(x)
+ val.append(double)
+ elif type(x) is rpcbase64:
+ b64 = ET.Element("Base64")
+ b64.text = x.encoded()
+ val.append(b64)
+ elif type(x) is rpctime:
+ iso = ET.Element("dateTime.iso8601")
+ iso.text = str(x)
+ val.append(iso)
+ elif type(x) is list:
+ array = ET.Element("array")
+ data = ET.Element("data")
+ for y in x:
+ data.append(_py2xml(y))
+ array.append(data)
+ val.append(array)
+ elif type(x) is dict:
+ struct = ET.Element("struct")
+ for y in x.keys():
+ member = ET.Element("member")
+ name = ET.Element("name")
+ name.text = y
+ member.append(name)
+ member.append(_py2xml(x[y]))
+ struct.append(member)
+ val.append(struct)
+ return val
+
+def xml2py(params):
+ vals = []
+ for param in params.findall('param'):
+ vals.append(_xml2py(param.find('value')))
+ return vals
+
+def _xml2py(value):
+ if value.find('i4') is not None:
+ return int(value.find('i4').text)
+ if value.find('int') is not None:
+ return int(value.find('int').text)
+ if value.find('boolean') is not None:
+ return bool(value.find('boolean').text)
+ if value.find('string') is not None:
+ return value.find('string').text
+ if value.find('double') is not None:
+ return float(value.find('double').text)
+ if value.find('Base64') is not None:
+ return rpcbase64(value.find('Base64').text)
+ if value.find('dateTime.iso8601') is not None:
+ return rpctime(value.find('dateTime.iso8601'))
+ if value.find('struct') is not None:
+ struct = {}
+ for member in value.find('struct').findall('member'):
+ struct[member.find('name').text] = _xml2py(member.find('value'))
+ return struct
+ if value.find('array') is not None:
+ array = []
+ for val in value.find('array').find('data').findall('value'):
+ array.append(_xml2py(val))
+ return array
+ raise ValueError()
+
+class rpcbase64(object):
+ def __init__(self, data):
+ #base 64 encoded string
+ self.data = data
+
+ def decode(self):
+ return base64.decodestring(data)
+
+ def __str__(self):
+ return self.decode()
+
+ def encoded(self):
+ return self.data
+
+class rpctime(object):
+ def __init__(self,data=None):
+ #assume string data is in iso format YYYYMMDDTHH:MM:SS
+ if type(data) is str:
+ self.timestamp = time.strptime(data,"%Y%m%dT%H:%M:%S")
+ elif type(data) is time.struct_time:
+ self.timestamp = data
+ elif data is None:
+ self.timestamp = time.gmtime()
+ else:
+ raise ValueError()
+
+ def iso8601(self):
+ #return a iso8601 string
+ return time.strftime("%Y%m%dT%H:%M:%S",self.timestamp)
+
+ def __str__(self):
+ return self.iso8601()
+
+class JabberRPCEntry(object):
+ def __init__(self,call):
+ self.call = call
+ self.result = None
+ self.error = None
+ self.allow = {} #{'<jid>':['<resource1>',...],...}
+ self.deny = {}
+
+ def check_acl(self, jid, resource):
+ #Check for deny
+ if jid in self.deny.keys():
+ if self.deny[jid] == None or resource in self.deny[jid]:
+ return False
+ #Check for allow
+ if allow == None:
+ return True
+ if jid in self.allow.keys():
+ if self.allow[jid] == None or resource in self.allow[jid]:
+ return True
+ return False
+
+ def acl_allow(self, jid, resource):
+ if jid == None:
+ self.allow = None
+ elif resource == None:
+ self.allow[jid] = None
+ elif jid in self.allow.keys():
+ self.allow[jid].append(resource)
+ else:
+ self.allow[jid] = [resource]
+
+ def acl_deny(self, jid, resource):
+ if jid == None:
+ self.deny = None
+ elif resource == None:
+ self.deny[jid] = None
+ elif jid in self.deny.keys():
+ self.deny[jid].append(resource)
+ else:
+ self.deny[jid] = [resource]
+
+ def call_method(self, args):
+ ret = self.call(*args)
+
+class xep_0009(base.base_plugin):
+
+ def plugin_init(self):
+ self.xep = '0009'
+ self.description = 'Jabber-RPC'
+ self.xmpp.add_handler("<iq type='set'><query xmlns='jabber:iq:rpc' /></iq>", self._callMethod)
+ self.xmpp.add_handler("<iq type='result'><query xmlns='jabber:iq:rpc' /></iq>", self._callResult)
+ self.xmpp.add_handler("<iq type='error'><query xmlns='jabber:iq:rpc' /></iq>", self._callError)
+ self.entries = {}
+ self.activeCalls = []
+
+ def post_init(self):
+ self.xmpp['xep_0030'].add_feature('jabber:iq:rpc')
+ self.xmpp['xep_0030'].add_identity('automatition','rpc')
+
+ def register_call(self, method, name=None):
+ #@returns an string that can be used in acl commands.
+ with self.lock:
+ if name is None:
+ self.entries[method.__name__] = JabberRPCEntry(method)
+ return method.__name__
+ else:
+ self.entries[name] = JabberRPCEntry(method)
+ return name
+
+ def acl_allow(self, entry, jid=None, resource=None):
+ #allow the method entry to be called by the given jid and resource.
+ #if jid is None it will allow any jid/resource.
+ #if resource is None it will allow any resource belonging to the jid.
+ with self.lock:
+ if self.entries[entry]:
+ self.entries[entry].acl_allow(jid,resource)
+ else:
+ raise ValueError()
+
+ def acl_deny(self, entry, jid=None, resource=None):
+ #Note: by default all requests are denied unless allowed with acl_allow.
+ #If you deny an entry it will not be allowed regardless of acl_allow
+ with self.lock:
+ if self.entries[entry]:
+ self.entries[entry].acl_deny(jid,resource)
+ else:
+ raise ValueError()
+
+ def unregister_call(self, entry):
+ #removes the registered call
+ with self.lock:
+ if self.entries[entry]:
+ del self.entries[entry]
+ else:
+ raise ValueError()
+
+ def makeMethodCallQuery(self,pmethod,params):
+ query = self.xmpp.makeIqQuery(iq,"jabber:iq:rpc")
+ methodCall = ET.Element('methodCall')
+ methodName = ET.Element('methodName')
+ methodName.text = pmethod
+ methodCall.append(methodName)
+ methodCall.append(params)
+ query.append(methodCall)
+ return query
+
+ def makeIqMethodCall(self,pto,pmethod,params):
+ iq = self.xmpp.makeIqSet()
+ iq.set('to',pto)
+ iq.append(self.makeMethodCallQuery(pmethod,params))
+ return iq
+
+ def makeIqMethodResponse(self,pto,pid,params):
+ iq = self.xmpp.makeIqResult(pid)
+ iq.set('to',pto)
+ query = self.xmpp.makeIqQuery(iq,"jabber:iq:rpc")
+ methodResponse = ET.Element('methodResponse')
+ methodResponse.append(params)
+ query.append(methodResponse)
+ return iq
+
+ def makeIqMethodError(self,pto,id,pmethod,params,condition):
+ iq = self.xmpp.makeIqError(id)
+ iq.set('to',pto)
+ iq.append(self.makeMethodCallQuery(pmethod,params))
+ iq.append(self.xmpp['xep_0086'].makeError(condition))
+ return iq
+
+
+
+ def call_remote(self, pto, pmethod, *args):
+ #calls a remote method. Returns the id of the Iq.
+ pass
+
+ def _callMethod(self,xml):
+ pass
+
+ def _callResult(self,xml):
+ pass
+
+ def _callError(self,xml):
+ pass
diff --git a/sleekxmpp/plugins/xep_0030.py b/sleekxmpp/plugins/xep_0030.py
new file mode 100644
index 00000000..d3795308
--- /dev/null
+++ b/sleekxmpp/plugins/xep_0030.py
@@ -0,0 +1,117 @@
+"""
+ SleekXMPP: The Sleek XMPP Library
+ Copyright (C) 2007 Nathanael C. Fritz
+ This file is part of SleekXMPP.
+
+ SleekXMPP is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ SleekXMPP is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with SleekXMPP; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+"""
+from __future__ import absolute_import, with_statement
+from . import base
+import logging
+from xml.etree import cElementTree as ET
+import thread
+
+class xep_0030(base.base_plugin):
+ """
+ XEP-0030 Service Discovery
+ """
+
+ def plugin_init(self):
+ self.xep = '0030'
+ self.description = 'Service Discovery'
+ self.features = {'main': ['http://jabber.org/protocol/disco#info', 'http://jabber.org/protocol/disco#items']}
+ self.identities = {'main': [{'category': 'client', 'type': 'pc', 'name': 'SleekXMPP'}]}
+ self.items = {'main': []}
+ self.xmpp.add_handler("<iq type='get' xmlns='%s'><query xmlns='http://jabber.org/protocol/disco#info' /></iq>" % self.xmpp.default_ns, self.info_handler)
+ self.xmpp.add_handler("<iq type='get' xmlns='%s'><query xmlns='http://jabber.org/protocol/disco#items' /></iq>" % self.xmpp.default_ns, self.item_handler)
+ self.lock = thread.allocate_lock()
+
+ def add_feature(self, feature, node='main'):
+ with self.lock:
+ if not self.features.has_key(node):
+ self.features[node] = []
+ self.features[node].append(feature)
+
+ def add_identity(self, category=None, itype=None, name=None, node='main'):
+ if not self.identities.has_key(node):
+ self.identities[node] = []
+ self.identities[node].append({'category': category, 'type': itype, 'name': name})
+
+ def add_item(self, jid=None, name=None, node='main', subnode=''):
+ if not self.items.has_key(node):
+ self.items[node] = []
+ self.items[node].append({'jid': jid, 'name': name, 'node': subnode})
+
+ def info_handler(self, xml):
+ logging.debug("Info request from %s" % xml.get('from', ''))
+ iq = self.xmpp.makeIqResult(xml.get('id', self.xmpp.getNewId()))
+ iq.attrib['from'] = self.xmpp.fulljid
+ iq.attrib['to'] = xml.get('from', self.xmpp.server)
+ query = xml.find('{http://jabber.org/protocol/disco#info}query')
+ node = query.get('node', 'main')
+ for identity in self.identities.get(node, []):
+ idxml = ET.Element('identity')
+ for attrib in identity:
+ if identity[attrib]:
+ idxml.attrib[attrib] = identity[attrib]
+ query.append(idxml)
+ for feature in self.features.get(node, []):
+ featxml = ET.Element('feature')
+ featxml.attrib['var'] = feature
+ query.append(featxml)
+ iq.append(query)
+ #print ET.tostring(iq)
+ self.xmpp.send(iq)
+
+ def item_handler(self, xml):
+ logging.debug("Item request from %s" % xml.get('from', ''))
+ iq = self.xmpp.makeIqResult(xml.get('id', self.xmpp.getNewId()))
+ iq.attrib['from'] = self.xmpp.fulljid
+ iq.attrib['to'] = xml.get('from', self.xmpp.server)
+ query = self.xmpp.makeIqQuery(iq, 'http://jabber.org/protocol/disco#items').find('{http://jabber.org/protocol/disco#items}query')
+ node = xml.find('{http://jabber.org/protocol/disco#items}query').get('node', 'main')
+ for item in self.items.get(node, []):
+ itemxml = ET.Element('item')
+ itemxml.attrib = item
+ if itemxml.attrib['jid'] is None:
+ itemxml.attrib['jid'] = self.xmpp.fulljid
+ query.append(itemxml)
+ self.xmpp.send(iq)
+
+ def getItems(self, jid, node=None):
+ iq = self.xmpp.makeIqGet()
+ iq.attrib['from'] = self.xmpp.fulljid
+ iq.attrib['to'] = jid
+ self.xmpp.makeIqQuery(iq, 'http://jabber.org/protocol/disco#items')
+ if node:
+ iq.find('{http://jabber.org/protocol/disco#items}query').attrib['node'] = node
+ return self.xmpp.send(iq, "<iq id='%s' />" % iq.get('id'))
+
+ def getInfo(self, jid, node=None):
+ iq = self.xmpp.makeIqGet()
+ iq.attrib['from'] = self.xmpp.fulljid
+ iq.attrib['to'] = jid
+ self.xmpp.makeIqQuery(iq, 'http://jabber.org/protocol/disco#info')
+ if node:
+ iq.find('{http://jabber.org/protocol/disco#info}query').attrib['node'] = node
+ return self.xmpp.send(iq, self.xmpp.makeIq(iq.get('id')))
+
+ def parseInfo(self, xml):
+ result = {'identity': {}, 'feature': []}
+ for identity in xml.findall('{http://jabber.org/protocol/disco#info}query/{{http://jabber.org/protocol/disco#info}identity'):
+ result['identity'][identity['name']] = identity.attrib
+ for feature in xml.findall('{http://jabber.org/protocol/disco#info}query/{{http://jabber.org/protocol/disco#info}feature'):
+ result['feature'].append(feature.get('var', '__unknown__'))
+ return result
diff --git a/sleekxmpp/plugins/xep_0045.py b/sleekxmpp/plugins/xep_0045.py
new file mode 100644
index 00000000..a85bfec8
--- /dev/null
+++ b/sleekxmpp/plugins/xep_0045.py
@@ -0,0 +1,193 @@
+"""
+ SleekXMPP: The Sleek XMPP Library
+ Copyright (C) 2007 Nathanael C. Fritz
+ This file is part of SleekXMPP.
+
+ SleekXMPP is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ SleekXMPP is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with SleekXMPP; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+"""
+from __future__ import with_statement
+import base
+import logging
+from xml.etree import cElementTree as ET
+
+class xep_0045(base.base_plugin):
+ """
+ Impliments XEP-0045 Multi User Chat
+ """
+
+ def plugin_init(self):
+ self.rooms = {}
+ self.ourNicks = {}
+ self.xep = '0045'
+ self.description = 'Multi User Chat (Very Basic Still)'
+ self.xmpp.add_handler("<message xmlns='%s' type='groupchat'><body/></message>" % self.xmpp.default_ns, self.handle_groupchat_message)
+ self.xmpp.add_handler("<presence />", self.handle_groupchat_presence)
+
+ def handle_groupchat_presence(self, xml):
+ """ Handle a presence in a muc.
+ """
+ source = xml.attrib['from']
+ room = self.xmpp.getjidbare(source)
+ if room not in self.rooms.keys():
+ return
+ nick = self.xmpp.getjidresource(source)
+ entry = {
+ 'nick': nick,
+ 'room': room,
+ }
+ if 'type' in xml.attrib.keys():
+ entry['type'] = xml.attrib['type']
+ for tag in ['status','show','priority']:
+ if xml.find(('{%s}' % self.xmpp.default_ns) + tag) != None:
+ entry[tag] = xml.find(('{%s}' % self.xmpp.default_ns) + tag).text
+ else:
+ entry[tag] = None
+
+ for tag in ['affiliation','role','jid']:
+ item = xml.find('{http://jabber.org/protocol/muc#user}x/{http://jabber.org/protocol/muc#user}item')
+ if item != None:
+ if tag in item.attrib:
+ entry[tag] = item.attrib[tag]
+ else:
+ entry[tag] = None
+ else:
+ entry[tag] = None
+
+ if entry['status'] == 'unavailable':
+ self.rooms[room][nick] = None
+ else:
+ self.rooms[room][nick] = entry
+ logging.debug("MUC presence from %s/%s : %s" % (entry['room'],entry['nick'], entry))
+ self.xmpp.event("groupchat_presence", entry)
+
+ def handle_groupchat_message(self, xml):
+ """ Handle a message event in a muc.
+ """
+ mfrom = xml.attrib['from']
+ message = xml.find('{%s}body' % self.xmpp.default_ns).text
+ subject = xml.find('{%s}subject' % self.xmpp.default_ns)
+ if subject:
+ subject = subject.text
+ else:
+ subject = ''
+ resource = self.xmpp.getjidresource(mfrom)
+ mfrom = self.xmpp.getjidbare(mfrom)
+ mtype = xml.attrib.get('type', 'normal')
+ self.xmpp.event("groupchat_message", {'room': mfrom, 'name': resource, 'type': mtype, 'subject': subject, 'message': message})
+
+ def joinMUC(self, room, nick, maxhistory="0", password='', wait=False):
+ """ Join the specified room, requesting 'maxhistory' lines of history.
+ """
+ stanza = self.xmpp.makePresence(pto="%s/%s" % (room, nick))
+ x = ET.Element('{http://jabber.org/protocol/muc}x')
+ if password:
+ passelement = ET.Element('password')
+ passelement.text = password
+ x.append(passelement)
+ history = ET.Element('history')
+ history.attrib['maxstanzas'] = maxhistory
+ x.append(history)
+ stanza.append(x)
+ if not wait:
+ self.xmpp.send(stanza)
+ else:
+ #wait for our own room presence back
+ expect = ET.Element('{jabber:client}presence', {'from':"%s/%s" % (room, nick)})
+ self.xmpp.send(stanza, expect)
+ self.rooms[room] = {}
+ self.ourNicks[room] = nick
+
+ def setAffiliation(self, room, jid, affiliation='member'):
+ """ Change room affiliation."""
+ if affiliation not in ('outcast', 'member', 'admin', 'owner', 'none'):
+ raise TypeError
+ query = ET.Element('{http://jabber.org/protocol/muc#admin}query')
+ item = ET.Element('item', {'affiliation':affiliation, 'jid':jid})
+ query.append(item)
+ iq = self.xmpp.makeIqSet(query)
+ iq.attrib['to'] = room
+ result = self.xmpp.send(iq, "<iq id='%s' />" % iq.get('id'))
+ if result is None or result.get('type') != 'result':
+ raise ValueError
+ return True
+
+ def invite(self, room, jid, reason=''):
+ """ Invite a jid to a room."""
+ msg = self.xmpp.makeMessage(room, mtype='none')
+ x = ET.Element('{http://jabber.org/protocol/muc#user}x')
+ invite = ET.Element('invite', {'to': jid})
+ if reason:
+ rxml = ET.Element('reason')
+ rxml.text = reason
+ invite.append(rxml)
+ x.append(invite)
+ msg.append(x)
+ self.xmpp.send(msg)
+
+ def leaveMUC(self, room, nick):
+ """ Leave the specified room.
+ """
+ self.xmpp.sendPresence(pshow='unavailable', pto="%s/%s" % (room, nick))
+ del self.rooms[room]
+
+ def getRoomConfig(self, room):
+ iq = self.xmpp.makeIqGet('http://jabber.org/protocol/muc#owner')
+ iq.attrib['to'] = room
+ result = self.xmpp.send(iq, "<iq id='%s' />" % iq.get('id'))
+ if result is None or result.get('type') != 'result':
+ raise ValueError
+ form = result.find('{http://jabber.org/protocol/muc#owner}query/{jabber:x:data}x')
+ if form is None:
+ raise ValueError
+ return self.xmpp.plugin['xep_0004'].buildForm(form)
+
+ def cancelConfig(self, room):
+ query = ET.Element('{http://jabber.org/protocol/muc#owner}query')
+ x = ET.Element('{jabber:x:data}x', type='cancel')
+ query.append(x)
+ iq = self.xmpp.makeIqSet(query)
+ self.xmpp.send(iq, "<iq id='%s' />" % iq.get('id'))
+
+ def setRoomConfig(self, room, config):
+ query = ET.Element('{http://jabber.org/protocol/muc#owner}query')
+ x = config.getXML('submit')
+ query.append(x)
+ iq = self.xmpp.makeIqSet(query)
+ iq.attrib['to'] = room
+ self.xmpp.send(iq, "<iq id='%s' />" % iq.get('id'))
+
+ def getJoinedRooms(self):
+ return self.rooms.keys()
+
+ def getOurJidInRoom(self, roomJid):
+ """ Return the jid we're using in a room.
+ """
+ return "%s/%s" % (roomJid, self.ourNicks[roomJid])
+
+ def getJidProperty(self, room, nick, jidProperty):
+ """ Get the property of a nick in a room, such as its 'jid' or 'affiliation'
+ If not found, return None.
+ """
+ if self.rooms.has_key(room) and self.rooms[room].has_key(nick) and self.rooms[room][nick].has_key(jidProperty):
+ return self.rooms[room][nick][jidProperty]
+ else:
+ return None
+
+ def getRoster(self, room):
+ """ Get the list of nicks in a room.
+ """
+ if room not in self.rooms.keys():
+ return None
+ return self.rooms[room].keys()
diff --git a/sleekxmpp/plugins/xep_0050.py b/sleekxmpp/plugins/xep_0050.py
new file mode 100644
index 00000000..bbfd1c41
--- /dev/null
+++ b/sleekxmpp/plugins/xep_0050.py
@@ -0,0 +1,142 @@
+"""
+ SleekXMPP: The Sleek XMPP Library
+ Copyright (C) 2007 Nathanael C. Fritz
+ This file is part of SleekXMPP.
+
+ SleekXMPP is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ SleekXMPP is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with SleekXMPP; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+"""
+from __future__ import with_statement
+import base
+import logging
+from xml.etree import cElementTree as ET
+import traceback
+import time
+import thread
+
+class xep_0050(base.base_plugin):
+ """
+ XEP-0050 Ad-Hoc Commands
+ """
+
+ def plugin_init(self):
+ self.xep = '0050'
+ self.description = 'Ad-Hoc Commands'
+ self.xmpp.add_handler("<iq type='set' xmlns='%s'><command xmlns='http://jabber.org/protocol/commands' action='__None__'/></iq>" % self.xmpp.default_ns, self.handler_command)
+ self.xmpp.add_handler("<iq type='set' xmlns='%s'><command xmlns='http://jabber.org/protocol/commands' action='execute'/></iq>" % self.xmpp.default_ns, self.handler_command)
+ self.xmpp.add_handler("<iq type='set' xmlns='%s'><command xmlns='http://jabber.org/protocol/commands' action='next'/></iq>" % self.xmpp.default_ns, self.handler_command_next, threaded=True)
+ self.xmpp.add_handler("<iq type='set' xmlns='%s'><command xmlns='http://jabber.org/protocol/commands' action='cancel'/></iq>" % self.xmpp.default_ns, self.handler_command_cancel)
+ self.xmpp.add_handler("<iq type='set' xmlns='%s'><command xmlns='http://jabber.org/protocol/commands' action='complete'/></iq>" % self.xmpp.default_ns, self.handler_command_complete)
+ self.commands = {}
+ self.sessions = {}
+
+ def post_init(self):
+ self.xmpp['xep_0030'].add_feature('http://jabber.org/protocol/commands')
+
+ def addCommand(self, node, name, form, pointer=None, multi=False):
+ self.xmpp['xep_0030'].add_item(None, name, 'http://jabber.org/protocol/commands', node)
+ self.xmpp['xep_0030'].add_identity('automation', 'command-node', name, node)
+ self.xmpp['xep_0030'].add_feature('http://jabber.org/protocol/commands', node)
+ self.xmpp['xep_0030'].add_feature('jabber:x:data', node)
+ self.commands[node] = (name, form, pointer, multi)
+
+ def getNewSession(self):
+ return str(time.time()) + '-' + self.xmpp.getNewId()
+
+ def handler_command(self, xml):
+ in_command = xml.find('{http://jabber.org/protocol/commands}command')
+ sessionid = in_command.get('sessionid', None)
+ node = in_command.get('node')
+ sessionid = self.getNewSession()
+ name, form, pointer, multi = self.commands[node]
+ self.sessions[sessionid] = {}
+ self.sessions[sessionid]['jid'] = xml.get('from')
+ self.sessions[sessionid]['past'] = [(form, None)]
+ self.sessions[sessionid]['next'] = pointer
+ npointer = pointer
+ if multi:
+ actions = ['next']
+ status = 'executing'
+ else:
+ if pointer is None:
+ status = 'completed'
+ actions = []
+ else:
+ status = 'executing'
+ actions = ['complete']
+ self.xmpp.send(self.makeCommand(xml.attrib['from'], in_command.attrib['node'], form=form, id=xml.attrib['id'], sessionid=sessionid, status=status, actions=actions))
+
+ def handler_command_complete(self, xml):
+ in_command = xml.find('{http://jabber.org/protocol/commands}command')
+ sessionid = in_command.get('sessionid', None)
+ pointer = self.sessions[sessionid]['next']
+ results = self.xmpp['xep_0004'].makeForm('result')
+ results.fromXML(in_command.find('{jabber:x:data}x'))
+ apply(pointer, (results,sessionid))
+ self.xmpp.send(self.makeCommand(xml.attrib['from'], in_command.attrib['node'], form=None, id=xml.attrib['id'], sessionid=sessionid, status='completed', actions=[]))
+ del self.sessions[command.get('sessionid')]
+
+
+ def handler_command_next(self, xml):
+ in_command = xml.find('{http://jabber.org/protocol/commands}command')
+ sessionid = in_command.get('sessionid', None)
+ pointer = self.sessions[sessionid]['next']
+ results = self.xmpp['xep_0004'].makeForm('result')
+ results.fromXML(in_command.find('{jabber:x:data}x'))
+ form, npointer, next = apply(pointer, (results,sessionid))
+ self.sessions[sessionid]['next'] = npointer
+ self.sessions[sessionid]['past'].append((form, pointer))
+ actions = []
+ actions.append('prev')
+ if npointer is None:
+ status = 'completed'
+ else:
+ status = 'executing'
+ if next:
+ actions.append('next')
+ else:
+ actions.append('complete')
+ self.xmpp.send(self.makeCommand(xml.attrib['from'], in_command.attrib['node'], form=form, id=xml.attrib['id'], sessionid=sessionid, status=status, actions=actions))
+
+ def handler_command_cancel(self, xml):
+ command = xml.find('{http://jabber.org/protocol/commands}command')
+ try:
+ del self.sessions[command.get('sessionid')]
+ except:
+ pass
+ self.xmpp.send(self.makeCommand(xml.attrib['from'], command.attrib['node'], id=xml.attrib['id'], sessionid=command.attrib['sessionid'], status='canceled'))
+
+ def makeCommand(self, to, node, id=None, form=None, sessionid=None, status='executing', actions=[]):
+ if not id:
+ id = self.xmpp.getNewId()
+ iq = self.xmpp.makeIqResult(id)
+ iq.attrib['from'] = self.xmpp.fulljid
+ iq.attrib['to'] = to
+ command = ET.Element('{http://jabber.org/protocol/commands}command')
+ command.attrib['node'] = node
+ command.attrib['status'] = status
+ xmlactions = ET.Element('actions')
+ for action in actions:
+ xmlactions.append(ET.Element(action))
+ if xmlactions:
+ command.append(xmlactions)
+ if not sessionid:
+ sessionid = self.getNewSession()
+ command.attrib['sessionid'] = sessionid
+ if form is not None:
+ if hasattr(form,'getXML'):
+ form = form.getXML()
+ command.append(form)
+ iq.append(command)
+ return iq
diff --git a/sleekxmpp/plugins/xep_0060.py b/sleekxmpp/plugins/xep_0060.py
new file mode 100644
index 00000000..015c2210
--- /dev/null
+++ b/sleekxmpp/plugins/xep_0060.py
@@ -0,0 +1,306 @@
+from __future__ import with_statement
+from . import base
+import logging
+from xml.etree import cElementTree as ET
+
+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):
+ 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 config is None:
+ submitform = self.xmpp.plugin['xep_0004'].makeForm('submit')
+ else:
+ 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 collection:
+ if 'pubsub#node_type' in submitform.field:
+ submitform.field['pubsub#node_type'].setValue('collection')
+ else:
+ submitform.addField('pubsub#node_type', value='collection')
+ else:
+ if 'pubsub#node_type' in submitform.field:
+ submitform.field['pubsub#node_type'].setValue('leaf')
+ else:
+ submitform.addField('pubsub#node_type', value='leaf')
+ configure.append(submitform.getXML('submit'))
+ pubsub.append(configure)
+ iq = self.xmpp.makeIqSet(pubsub)
+ iq.attrib['to'] = jid
+ iq.attrib['from'] = self.xmpp.fulljid
+ id = iq.get('id')
+ result = self.xmpp.send(iq, "<iq id='%s'/>" % id)
+ if result is False or result is None or result.get('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.jid
+ else:
+ subscribe.attrib['jid'] = self.xmpp.fulljid
+ else:
+ subscribe.attrib['jid'] = subscribee
+ pubsub.append(subscribe)
+ iq = self.xmpp.makeIqSet(pubsub)
+ iq.attrib['to'] = jid
+ iq.attrib['from'] = self.xmpp.fulljid
+ id = iq.get('id')
+ result = self.xmpp.send(iq, "<iq id='%s'/>" % id)
+ if result is False or result is None or result.get('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.jid
+ else:
+ unsubscribe.attrib['jid'] = self.xmpp.fulljid
+ else:
+ unsubscribe.attrib['jid'] = subscribee
+ pubsub.append(unsubscribe)
+ iq = self.xmpp.makeIqSet(pubsub)
+ iq.attrib['to'] = jid
+ iq.attrib['from'] = self.xmpp.fulljid
+ id = iq.get('id')
+ result = self.xmpp.send(iq, "<iq id='%s'/>" % id)
+ if result is False or result is None or result.get('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.fulljid
+ id = iq.get('id')
+ #self.xmpp.add_handler("<iq id='%s'/>" % id, self.handlerCreateNodeResponse)
+ result = self.xmpp.send(iq, "<iq id='%s'/>" % id)
+ if result is None or result == False or result.get('type') == 'error':
+ logging.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:
+ logging.error("No form found.")
+ return False
+ return self.xmpp.plugin['xep_0004'].buildForm(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.fulljid
+ id = iq.get('id')
+ result = self.xmpp.send(iq, "<iq id='%s'/>" % id)
+ if result is None or result == False or result.get('type') == 'error':
+ logging.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.fulljid
+ id = iq.get('id')
+ result = self.xmpp.send(iq, "<iq id='%s'/>" % id)
+ if result is None or result == False or result.get('type') == 'error':
+ logging.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.fulljid
+ id = iq.get('id')
+ result = self.xmpp.send(iq, "<iq id='%s'/>" % id)
+ if result is not None and result is not False and result.attrib.get('type', 'error') != '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.fulljid
+ id = iq.get('id')
+ result = self.xmpp.send(iq, "<iq id='%s'/>" % id)
+ if result is None or result.get('type') == 'error':
+ print "---------- returning false, apparently"
+ 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.fulljid
+ id = iq.get('id')
+ result = self.xmpp.send(iq, "<iq id='%s'/>" % id)
+ if result is None or result is False or result.get('type') == 'error': return False
+ return True
+
+ 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.fulljid
+ id = iq.get('id')
+ result = self.xmpp.send(iq, "<iq id='%s'/>" % id)
+ if result is None or result is False or result.get('type') == 'error': return False
+ return True
+
+ def addItem(self, jid, node, items=[]):
+ return setItem(jid, node, items)
+
+ 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:
+ logging.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.fulljid
+ id = iq.get('id')
+ result = self.xmpp.send(iq, "<iq id='%s'/>" % id)
+ if result is None or result is False or result.get('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:
+ logging.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, '')
+
diff --git a/sleekxmpp/plugins/xep_0078.py b/sleekxmpp/plugins/xep_0078.py
new file mode 100644
index 00000000..28aaeb20
--- /dev/null
+++ b/sleekxmpp/plugins/xep_0078.py
@@ -0,0 +1,81 @@
+"""
+ SleekXMPP: The Sleek XMPP Library
+ Copyright (C) 2007 Nathanael C. Fritz
+ This file is part of SleekXMPP.
+
+ SleekXMPP is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ SleekXMPP is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with SleekXMPP; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+"""
+from __future__ import with_statement
+from xml.etree import cElementTree as ET
+import logging
+import sha
+import base
+
+
+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_start_handler(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):
+ logging.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.server
+ username = ET.Element('username')
+ username.text = self.xmpp.username
+ auth_request_query.append(username)
+ auth_request.append(auth_request_query)
+ result = self.xmpp.send(auth_request, self.xmpp.makeIqResult(self.xmpp.id))
+ 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:
+ logging.warning("Authenticating via jabber:iq:auth Plain.")
+ password = ET.Element('password')
+ password.text = self.xmpp.password
+ query.append(password)
+ else:
+ logging.debug("Authenticating via jabber:iq:auth Digest")
+ digest = ET.Element('digest')
+ digest.text = sha.sha("%s%s" % (self.streamid, self.xmpp.password)).hexdigest()
+ query.append(digest)
+ attempt.append(query)
+ result = self.xmpp.send(attempt, self.xmpp.makeIq(self.xmpp.id))
+ if result.attrib['type'] == 'result':
+ with self.xmpp.lock:
+ self.xmpp.authenticated = True
+ self.xmpp.sessionstarted = True
+ self.xmpp.event("session_start")
+ else:
+ logging.info("Authentication failed")
+ self.xmpp.disconnect()
+ self.xmpp.event("failed_auth")
diff --git a/sleekxmpp/plugins/xep_0086.py b/sleekxmpp/plugins/xep_0086.py
new file mode 100644
index 00000000..6871ef3f
--- /dev/null
+++ b/sleekxmpp/plugins/xep_0086.py
@@ -0,0 +1,49 @@
+
+from __future__ import with_statement
+import base
+import logging
+from xml.etree import cElementTree as ET
+import copy
+
+class xep_0086(base.base_plugin):
+ """
+ XEP-0086 Error Condition Mappings
+ """
+
+ def plugin_init(self):
+ self.xep = '0086'
+ self.description = 'Error Condition Mappings'
+ self.error_map = {
+ 'bad-request':('modify','400'),
+ 'conflict':('cancel','409'),
+ 'feature-not-implemented':('cancel','501'),
+ 'forbidden':('auth','403'),
+ 'gone':('modify','302'),
+ 'internal-server-error':('wait','500'),
+ 'item-not-found':('cancel','404'),
+ 'jid-malformed':('modify','400'),
+ 'not-acceptable':('modify','406'),
+ 'not-allowed':('cancel','405'),
+ 'not-authorized':('auth','401'),
+ 'payment-required':('auth','402'),
+ 'recipient-unavailable':('wait','404'),
+ 'redirect':('modify','302'),
+ 'registration-required':('auth','407'),
+ 'remote-server-not-found':('cancel','404'),
+ 'remote-server-timeout':('wait','504'),
+ 'resource-constraint':('wait','500'),
+ 'service-unavailable':('cancel','503'),
+ 'subscription-required':('auth','407'),
+ 'undefined-condition':(None,'500'),
+ 'unexpected-request':('wait','400')
+ }
+
+
+ def makeError(self, condition, cdata=None, errorType=None, text=None, customElem=None):
+ conditionElem = self.xmpp.makeStanzaErrorCondition(condition, cdata)
+ if errorType is None:
+ error = self.xmpp.makeStanzaError(conditionElem, self.error_map[condition][0], self.error_map[condition][1], text, customElem)
+ else:
+ error = self.xmpp.makeStanzaError(conditionElem, errorType, self.error_map[condition][1], text, customElem)
+ error.append(conditionElem)
+ return error
diff --git a/sleekxmpp/plugins/xep_0092.py b/sleekxmpp/plugins/xep_0092.py
new file mode 100644
index 00000000..97346d91
--- /dev/null
+++ b/sleekxmpp/plugins/xep_0092.py
@@ -0,0 +1,67 @@
+"""
+ SleekXMPP: The Sleek XMPP Library
+ Copyright (C) 2007 Nathanael C. Fritz
+ This file is part of SleekXMPP.
+
+ SleekXMPP is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ SleekXMPP is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with SleekXMPP; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+"""
+from xml.etree import cElementTree as ET
+from . import base
+from .. xmlstream.handler.xmlwaiter import XMLWaiter
+
+class xep_0092(base.base_plugin):
+ """
+ XEP-0092 Software Version
+ """
+ def plugin_init(self):
+ self.description = "Software Version"
+ self.xep = "0092"
+ self.name = self.config.get('name', 'SleekXMPP')
+ self.version = self.config.get('version', '0.1-dev')
+ self.xmpp.add_handler("<iq type='get' xmlns='%s'><query xmlns='jabber:iq:version' /></iq>" % self.xmpp.default_ns, self.report_version)
+
+ def post_init(self):
+ self.xmpp['xep_0030'].add_feature('jabber:iq:version')
+
+ def report_version(self, xml):
+ iq = self.xmpp.makeIqResult(xml.get('id', 'unknown'))
+ iq.attrib['to'] = xml.get('from', self.xmpp.server)
+ query = ET.Element('{jabber:iq:version}query')
+ name = ET.Element('name')
+ name.text = self.name
+ version = ET.Element('version')
+ version.text = self.version
+ query.append(name)
+ query.append(version)
+ iq.append(query)
+ self.xmpp.send(iq)
+
+ def getVersion(self, jid):
+ iq = self.xmpp.makeIqGet()
+ query = ET.Element('{jabber:iq:version}query')
+ iq.append(query)
+ iq.attrib['to'] = jid
+ iq.attrib['from'] = self.xmpp.fulljid
+ id = iq.get('id')
+ result = self.xmpp.send(iq, "<iq xmlns='%s' id='%s'/>" % (self.xmpp.default_ns, id))
+ if result and result is not None and result.get('type', 'error') != 'error':
+ qry = result.find('{jabber:iq:version}query')
+ version = {}
+ for child in qry.getchildren():
+ version[child.tag.split('}')[-1]] = child.text
+ return version
+ else:
+ return False
+
diff --git a/sleekxmpp/plugins/xep_0199.py b/sleekxmpp/plugins/xep_0199.py
new file mode 100644
index 00000000..cab84ac9
--- /dev/null
+++ b/sleekxmpp/plugins/xep_0199.py
@@ -0,0 +1,70 @@
+"""
+ SleekXMPP: The Sleek XMPP Library
+ XEP-0199 (Ping) support
+ Copyright (C) 2007 Kevin Smith
+ This file is part of SleekXMPP.
+
+ SleekXMPP is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ SleekXMPP is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with SleekXMPP; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+"""
+from xml.etree import cElementTree as ET
+from . import base
+import time
+import logging
+
+class xep_0199(base.base_plugin):
+ """XEP-0199 XMPP Ping"""
+
+ def plugin_init(self):
+ self.description = "XMPP Ping"
+ self.xep = "0199"
+ self.xmpp.add_handler("<iq type='get' xmlns='%s'><ping xmlns='http://www.xmpp.org/extensions/xep-0199.html#ns'/></iq>" % self.xmpp.default_ns, self.handler_ping)
+ self.running = False
+ if self.config.get('keepalive', True):
+ self.xmpp.add_event_handler('session_start', self.handler_pingserver, threaded=True)
+
+ def post_init(self):
+ self.xmpp['xep_0030'].add_feature('http://www.xmpp.org/extensions/xep-0199.html#ns')
+
+ def handler_pingserver(self, xml):
+ if not self.running:
+ time.sleep(self.config.get('frequency', 300))
+ while self.sendPing(self.xmpp.server, self.config.get('timeout', 30)) is not False:
+ time.sleep(self.config.get('frequency', 300))
+ logging.debug("Did not recieve ping back in time. Requesting Reconnect.")
+ self.xmpp.disconnect(reconnect=True)
+
+ def handler_ping(self, xml):
+ iq = self.xmpp.makeIqResult(xml.get('id', 'unknown'))
+ iq.attrib['to'] = xml.get('from', self.xmpp.server)
+ self.xmpp.send(iq)
+
+ def sendPing(self, jid, timeout = 30):
+ """ sendPing(jid, timeout)
+ Sends a ping to the specified jid, returning the time (in seconds)
+ to receive a reply, or None if no reply is received in timeout seconds.
+ """
+ id = self.xmpp.getNewId()
+ iq = self.xmpp.makeIq(id)
+ iq.attrib['type'] = 'get'
+ iq.attrib['to'] = jid
+ ping = ET.Element('{http://www.xmpp.org/extensions/xep-0199.html#ns}ping')
+ iq.append(ping)
+ startTime = time.clock()
+ pingresult = self.xmpp.send(iq, self.xmpp.makeIq(id), timeout)
+ endTime = time.clock()
+ if pingresult == False:
+ #self.xmpp.disconnect(reconnect=True)
+ return False
+ return endTime - startTime