diff options
Diffstat (limited to 'sleekxmpp/plugins')
-rw-r--r-- | sleekxmpp/plugins/__init__.py | 20 | ||||
-rw-r--r-- | sleekxmpp/plugins/base.py | 35 | ||||
-rw-r--r-- | sleekxmpp/plugins/gmail_notify.py | 57 | ||||
-rw-r--r-- | sleekxmpp/plugins/xep_0004.py | 389 | ||||
-rw-r--r-- | sleekxmpp/plugins/xep_0009.py | 273 | ||||
-rw-r--r-- | sleekxmpp/plugins/xep_0030.py | 117 | ||||
-rw-r--r-- | sleekxmpp/plugins/xep_0045.py | 193 | ||||
-rw-r--r-- | sleekxmpp/plugins/xep_0050.py | 142 | ||||
-rw-r--r-- | sleekxmpp/plugins/xep_0060.py | 306 | ||||
-rw-r--r-- | sleekxmpp/plugins/xep_0078.py | 81 | ||||
-rw-r--r-- | sleekxmpp/plugins/xep_0086.py | 49 | ||||
-rw-r--r-- | sleekxmpp/plugins/xep_0092.py | 67 | ||||
-rw-r--r-- | sleekxmpp/plugins/xep_0199.py | 70 |
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 |