diff options
Diffstat (limited to 'sleekxmpp/plugins')
-rw-r--r-- | sleekxmpp/plugins/__init__.py | 20 | ||||
-rw-r--r-- | sleekxmpp/plugins/base.py | 22 | ||||
-rw-r--r-- | sleekxmpp/plugins/gmail_notify.py | 193 | ||||
-rw-r--r-- | sleekxmpp/plugins/old_0004.py | 417 | ||||
-rw-r--r-- | sleekxmpp/plugins/stanza_pubsub.py | 123 | ||||
-rw-r--r-- | sleekxmpp/plugins/xep_0004.py | 732 | ||||
-rw-r--r-- | sleekxmpp/plugins/xep_0009.py | 9 | ||||
-rw-r--r-- | sleekxmpp/plugins/xep_0030.py | 23 | ||||
-rw-r--r-- | sleekxmpp/plugins/xep_0033.py | 161 | ||||
-rw-r--r-- | sleekxmpp/plugins/xep_0045.py | 34 | ||||
-rw-r--r-- | sleekxmpp/plugins/xep_0050.py | 37 | ||||
-rw-r--r-- | sleekxmpp/plugins/xep_0060.py | 2 | ||||
-rw-r--r-- | sleekxmpp/plugins/xep_0078.py | 22 | ||||
-rw-r--r-- | sleekxmpp/plugins/xep_0085.py | 101 | ||||
-rw-r--r-- | sleekxmpp/plugins/xep_0092.py | 24 | ||||
-rw-r--r-- | sleekxmpp/plugins/xep_0128.py | 51 | ||||
-rw-r--r-- | sleekxmpp/plugins/xep_0199.py | 25 |
17 files changed, 1306 insertions, 690 deletions
diff --git a/sleekxmpp/plugins/__init__.py b/sleekxmpp/plugins/__init__.py index 1868365e..b51977b8 100644 --- a/sleekxmpp/plugins/__init__.py +++ b/sleekxmpp/plugins/__init__.py @@ -1,20 +1,8 @@ """ SleekXMPP: The Sleek XMPP Library - Copyright (C) 2007 Nathanael C. Fritz + Copyright (C) 2010 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 + + See the file LICENSE for copying permission. """ -__all__ = ['xep_0004', 'xep_0030', 'xep_0045', 'xep_0050', 'xep_0078', 'xep_0092', 'xep_0199', 'gmail_notify', 'xep_0060'] +__all__ = ['xep_0004', 'xep_0030', 'xep_0033', 'xep_0045', 'xep_0050', 'xep_0078', 'xep_0085', 'xep_0092', 'xep_0199', 'gmail_notify', 'xep_0060'] diff --git a/sleekxmpp/plugins/base.py b/sleekxmpp/plugins/base.py index 4223646a..a5260b0c 100644 --- a/sleekxmpp/plugins/base.py +++ b/sleekxmpp/plugins/base.py @@ -1,22 +1,12 @@ """ - 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: The Sleek XMPP Library + Copyright (C) 2010 Nathanael C. Fritz + This file is part of SleekXMPP. + + See the file LICENSE for copying permission. +""" - 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): diff --git a/sleekxmpp/plugins/gmail_notify.py b/sleekxmpp/plugins/gmail_notify.py index b709ef69..7e442346 100644 --- a/sleekxmpp/plugins/gmail_notify.py +++ b/sleekxmpp/plugins/gmail_notify.py @@ -1,57 +1,146 @@ """ - 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 + SleekXMPP: The Sleek XMPP Library + Copyright (C) 2010 Nathanael C. Fritz, Lance J.T. Stout + This file is part of SleekXMPP. + + See the file LICENSE for copying permission. """ -from __future__ import with_statement -from . import base + import logging -from xml.etree import cElementTree as ET -import traceback -import time +from . import base +from .. xmlstream.handler.callback import Callback +from .. xmlstream.matcher.xpath import MatchXPath +from .. xmlstream.stanzabase import registerStanzaPlugin, ElementBase, ET, JID +from .. stanza.iq import Iq + + +class GmailQuery(ElementBase): + namespace = 'google:mail:notify' + name = 'query' + plugin_attrib = 'gmail' + interfaces = set(('newer-than-time', 'newer-than-tid', 'q', 'search')) + + def getSearch(self): + return self['q'] + + def setSearch(self, search): + self['q'] = search + + def delSearch(self): + del self['q'] + + +class MailBox(ElementBase): + namespace = 'google:mail:notify' + name = 'mailbox' + plugin_attrib = 'mailbox' + interfaces = set(('result-time', 'total-matched', 'total-estimate', + 'url', 'threads', 'matched', 'estimate')) + + def getThreads(self): + threads = [] + for threadXML in self.xml.findall('{%s}%s' % (MailThread.namespace, + MailThread.name)): + threads.append(MailThread(xml=threadXML, parent=None)) + return threads + + def getMatched(self): + return self['total-matched'] + + def getEstimate(self): + return self['total-estimate'] == '1' + + +class MailThread(ElementBase): + namespace = 'google:mail:notify' + name = 'mail-thread-info' + plugin_attrib = 'thread' + interfaces = set(('tid', 'participation', 'messages', 'date', + 'senders', 'url', 'labels', 'subject', 'snippet')) + sub_interfaces = set(('labels', 'subject', 'snippet')) + + def getSenders(self): + senders = [] + sendersXML = self.xml.find('{%s}senders' % self.namespace) + if sendersXML is not None: + for senderXML in sendersXML.findall('{%s}sender' % self.namespace): + senders.append(MailSender(xml=senderXML, parent=None)) + return senders + + +class MailSender(ElementBase): + namespace = 'google:mail:notify' + name = 'sender' + plugin_attrib = 'sender' + interfaces = set(('address', 'name', 'originator', 'unread')) + + def getOriginator(self): + return self.xml.attrib.get('originator', '0') == '1' + + def getUnread(self): + return self.xml.attrib.get('unread', '0') == '1' + + +class NewMail(ElementBase): + namespace = 'google:mail:notify' + name = 'new-mail' + plugin_attrib = 'new-mail' + 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 = iq.send() - mailbox = emails.find('{google:mail:notify}mailbox') - total = int(mailbox.get('total-matched', 0)) - logging.info("%s New Gmail Messages" % total) + """ + Google Talk: Gmail Notifications + """ + + def plugin_init(self): + self.description = 'Google Talk: Gmail Notifications' + + self.xmpp.registerHandler( + Callback('Gmail Result', + MatchXPath('{%s}iq/{%s}%s' % (self.xmpp.default_ns, + MailBox.namespace, + MailBox.name)), + self.handle_gmail)) + + self.xmpp.registerHandler( + Callback('Gmail New Mail', + MatchXPath('{%s}iq/{%s}%s' % (self.xmpp.default_ns, + NewMail.namespace, + NewMail.name)), + self.handle_new_mail)) + + registerStanzaPlugin(Iq, GmailQuery) + registerStanzaPlugin(Iq, MailBox) + registerStanzaPlugin(Iq, NewMail) + + self.last_result_time = None + + def handle_gmail(self, iq): + mailbox = iq['mailbox'] + approx = ' approximately' if mailbox['estimated'] else '' + logging.info('Gmail: Received%s %s emails' % (approx, mailbox['total-matched'])) + self.last_result_time = mailbox['result-time'] + self.xmpp.event('gmail_messages', iq) + + def handle_new_mail(self, iq): + logging.info("Gmail: New emails received!") + self.xmpp.event('gmail_notify') + self.checkEmail() + + def getEmail(self, query=None): + return self.search(query) + + def checkEmail(self): + return self.search(newer=self.last_result_time) + + def search(self, query=None, newer=None): + if query is None: + logging.info("Gmail: Checking for new emails") + else: + logging.info('Gmail: Searching for emails matching: "%s"' % query) + iq = self.xmpp.Iq() + iq['type'] = 'get' + iq['to'] = self.xmpp.jid + iq['gmail']['q'] = query + iq['gmail']['newer-than-time'] = newer + return iq.send() diff --git a/sleekxmpp/plugins/old_0004.py b/sleekxmpp/plugins/old_0004.py new file mode 100644 index 00000000..651408ae --- /dev/null +++ b/sleekxmpp/plugins/old_0004.py @@ -0,0 +1,417 @@ +""" + SleekXMPP: The Sleek XMPP Library + Copyright (C) 2010 Nathanael C. Fritz + This file is part of SleekXMPP. + + See the file LICENSE for copying permission. +""" +from . import base +import logging +from xml.etree import cElementTree as ET +import copy +import logging +#TODO support item groups and results + +class old_0004(base.base_plugin): + + def plugin_init(self): + self.xep = '0004' + self.description = '*Deprecated Data Forms' + self.xmpp.add_handler("<message><x xmlns='jabber:x:data' /></message>", self.handler_message_xform, name='Old Message Form') + + def post_init(self): + base.base_plugin.post_init(self) + self.xmpp.plugin['xep_0030'].add_feature('jabber:x:data') + logging.warning("This implementation of XEP-0004 is deprecated.") + + 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(ftype=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 merge(self, form2): + form1 = Form(ftype=self.type) + form1.fromXML(self.getXML(self.type)) + for field in form2.fields: + if not field.var in form1.field: + form1.addField(field.var, field.type, field.label, field.desc, field.required, field.value) + else: + form1.field[field.var].value = field.value + for option, label in field.options: + if (option, label) not in form1.field[field.var].options: + form1.fields[field.var].addOption(option, label) + return form1 + + def copy(self): + newform = Form(ftype=self.type) + newform.fromXML(self.getXML(self.type)) + return newform + + def update(self, form): + values = form.getValues() + for var in values: + if var in self.fields: + self.fields[var].setValue(self.fields[var]) + + 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 setValues(self, values={}): + for field in values: + if field in self.field: + if isinstance(values[field], list) or isinstance(values[field], tuple): + for value in values[field]: + self.field[field].setValue(value) + else: + self.field[field].setValue(values[field]) + + 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): + 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('{jabber:x:data}title') + title.text = self.title + form.append(title) + if self.instructions and self.type == 'form': + instructions = ET.Element('{jabber:x:data}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('{jabber:x:data}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.type == 'boolean': + if value in ('1', 1, True, 'true', 'True', 'yes'): + value = True + else: + value = False + 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('{jabber:x:data}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('{jabber:x:data}option') + optionxml.attrib['label'] = option[1] + optionval = ET.Element('{jabber:x:data}value') + optionval.text = option[0] + optionxml.append(optionval) + field.append(optionxml) + if self.required: + required = ET.Element('{jabber:x:data}required') + field.append(required) + if self.desc: + desc = ET.Element('{jabber:x:data}desc') + desc.text = self.desc + field.append(desc) + for value in self.value: + valuexml = ET.Element('{jabber:x:data}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/stanza_pubsub.py b/sleekxmpp/plugins/stanza_pubsub.py index 1a1526f0..96d02f93 100644 --- a/sleekxmpp/plugins/stanza_pubsub.py +++ b/sleekxmpp/plugins/stanza_pubsub.py @@ -1,4 +1,4 @@ -from .. xmlstream.stanzabase import ElementBase, ET, JID +from .. xmlstream.stanzabase import registerStanzaPlugin, ElementBase, ET, JID from .. stanza.iq import Iq from .. stanza.message import Message from .. basexmpp import basexmpp @@ -6,9 +6,6 @@ from .. xmlstream.xmlstream import XMLStream import logging from . import xep_0004 -def stanzaPlugin(stanza, plugin): - stanza.plugin_attrib_map[plugin.plugin_attrib] = plugin - stanza.plugin_tag_map["{%s}%s" % (plugin.namespace, plugin.name)] = plugin class PubsubState(ElementBase): namespace = 'http://jabber.org/protocol/psstate' @@ -30,7 +27,7 @@ class PubsubState(ElementBase): for child in self.xml.getchildren(): self.xml.remove(child) -stanzaPlugin(Iq, PubsubState) +registerStanzaPlugin(Iq, PubsubState) class PubsubStateEvent(ElementBase): namespace = 'http://jabber.org/protocol/psstate#event' @@ -40,8 +37,8 @@ class PubsubStateEvent(ElementBase): plugin_attrib_map = {} plugin_tag_map = {} -stanzaPlugin(Message, PubsubStateEvent) -stanzaPlugin(PubsubStateEvent, PubsubState) +registerStanzaPlugin(Message, PubsubStateEvent) +registerStanzaPlugin(PubsubStateEvent, PubsubState) class Pubsub(ElementBase): namespace = 'http://jabber.org/protocol/pubsub' @@ -51,7 +48,7 @@ class Pubsub(ElementBase): plugin_attrib_map = {} plugin_tag_map = {} -stanzaPlugin(Iq, Pubsub) +registerStanzaPlugin(Iq, Pubsub) class PubsubOwner(ElementBase): namespace = 'http://jabber.org/protocol/pubsub#owner' @@ -61,7 +58,7 @@ class PubsubOwner(ElementBase): plugin_attrib_map = {} plugin_tag_map = {} -stanzaPlugin(Iq, PubsubOwner) +registerStanzaPlugin(Iq, PubsubOwner) class Affiliation(ElementBase): namespace = 'http://jabber.org/protocol/pubsub' @@ -86,7 +83,7 @@ class Affiliations(ElementBase): self.xml.append(affiliation.xml) return self.iterables.append(affiliation) -stanzaPlugin(Pubsub, Affiliations) +registerStanzaPlugin(Pubsub, Affiliations) class Subscription(ElementBase): @@ -103,7 +100,7 @@ class Subscription(ElementBase): def getjid(self): return jid(self._getattr('jid')) -stanzaPlugin(Pubsub, Subscription) +registerStanzaPlugin(Pubsub, Subscription) class Subscriptions(ElementBase): namespace = 'http://jabber.org/protocol/pubsub' @@ -114,7 +111,7 @@ class Subscriptions(ElementBase): plugin_tag_map = {} subitem = (Subscription,) -stanzaPlugin(Pubsub, Subscriptions) +registerStanzaPlugin(Pubsub, Subscriptions) class OptionalSetting(object): interfaces = set(('required',)) @@ -147,7 +144,7 @@ class SubscribeOptions(ElementBase, OptionalSetting): plugin_tag_map = {} interfaces = set(('required',)) -stanzaPlugin(Subscription, SubscribeOptions) +registerStanzaPlugin(Subscription, SubscribeOptions) class Item(ElementBase): namespace = 'http://jabber.org/protocol/pubsub' @@ -178,7 +175,7 @@ class Items(ElementBase): plugin_tag_map = {} subitem = (Item,) -stanzaPlugin(Pubsub, Items) +registerStanzaPlugin(Pubsub, Items) class Create(ElementBase): namespace = 'http://jabber.org/protocol/pubsub' @@ -188,7 +185,7 @@ class Create(ElementBase): plugin_attrib_map = {} plugin_tag_map = {} -stanzaPlugin(Pubsub, Create) +registerStanzaPlugin(Pubsub, Create) #class Default(ElementBase): # namespace = 'http://jabber.org/protocol/pubsub' @@ -203,7 +200,7 @@ stanzaPlugin(Pubsub, Create) # if not t: t == 'leaf' # return t # -#stanzaPlugin(Pubsub, Default) +#registerStanzaPlugin(Pubsub, Default) class Publish(Items): namespace = 'http://jabber.org/protocol/pubsub' @@ -214,7 +211,7 @@ class Publish(Items): plugin_tag_map = {} subitem = (Item,) -stanzaPlugin(Pubsub, Publish) +registerStanzaPlugin(Pubsub, Publish) class Retract(Items): namespace = 'http://jabber.org/protocol/pubsub' @@ -224,7 +221,7 @@ class Retract(Items): plugin_attrib_map = {} plugin_tag_map = {} -stanzaPlugin(Pubsub, Retract) +registerStanzaPlugin(Pubsub, Retract) class Unsubscribe(ElementBase): namespace = 'http://jabber.org/protocol/pubsub' @@ -254,13 +251,13 @@ class Subscribe(ElementBase): def getJid(self): return JID(self._getAttr('jid')) -stanzaPlugin(Pubsub, Subscribe) +registerStanzaPlugin(Pubsub, Subscribe) class Configure(ElementBase): namespace = 'http://jabber.org/protocol/pubsub' name = 'configure' plugin_attrib = name - interfaces = set(('node', 'type', 'config')) + interfaces = set(('node', 'type')) plugin_attrib_map = {} plugin_tag_map = {} @@ -269,22 +266,8 @@ class Configure(ElementBase): if not t: t == 'leaf' return t - def getConfig(self): - config = self.xml.find('{jabber:x:data}x') - form = xep_0004.Form() - if config is not None: - form.fromXML(config) - return form - - def setConfig(self, value): - self.xml.append(value.getXML()) - return self - - def delConfig(self): - config = self.xml.find('{jabber:x:data}x') - self.xml.remove(config) - -stanzaPlugin(Pubsub, Configure) +registerStanzaPlugin(Pubsub, Configure) +registerStanzaPlugin(Configure, xep_0004.Form) class DefaultConfig(ElementBase): namespace = 'http://jabber.org/protocol/pubsub#owner' @@ -296,28 +279,14 @@ class DefaultConfig(ElementBase): def __init__(self, *args, **kwargs): ElementBase.__init__(self, *args, **kwargs) - - def getConfig(self): - config = self.xml.find('{jabber:x:data}x') - form = xep_0004.Form() - if config is not None: - form.fromXML(config) - return form - - def setConfig(self, value): - self.xml.append(value.getXML()) - return self - - def delConfig(self): - config = self.xml.find('{jabber:x:data}x') - self.xml.remove(config) def getType(self): t = self._getAttr('type') if not t: t = 'leaf' return t -stanzaPlugin(PubsubOwner, DefaultConfig) +registerStanzaPlugin(PubsubOwner, DefaultConfig) +registerStanzaPlugin(DefaultConfig, xep_0004.Form) class Options(ElementBase): namespace = 'http://jabber.org/protocol/pubsub' @@ -351,8 +320,8 @@ class Options(ElementBase): def getJid(self): return JID(self._getAttr('jid')) -stanzaPlugin(Pubsub, Options) -stanzaPlugin(Subscribe, Options) +registerStanzaPlugin(Pubsub, Options) +registerStanzaPlugin(Subscribe, Options) class OwnerAffiliations(Affiliations): namespace = 'http://jabber.org/protocol/pubsub#owner' @@ -366,7 +335,7 @@ class OwnerAffiliations(Affiliations): self.xml.append(affiliation.xml) return self.affiliations.append(affiliation) -stanzaPlugin(PubsubOwner, OwnerAffiliations) +registerStanzaPlugin(PubsubOwner, OwnerAffiliations) class OwnerAffiliation(Affiliation): namespace = 'http://jabber.org/protocol/pubsub#owner' @@ -380,7 +349,7 @@ class OwnerConfigure(Configure): plugin_attrib_map = {} plugin_tag_map = {} -stanzaPlugin(PubsubOwner, OwnerConfigure) +registerStanzaPlugin(PubsubOwner, OwnerConfigure) class OwnerDefault(OwnerConfigure): namespace = 'http://jabber.org/protocol/pubsub#owner' @@ -388,7 +357,7 @@ class OwnerDefault(OwnerConfigure): plugin_attrib_map = {} plugin_tag_map = {} -stanzaPlugin(PubsubOwner, OwnerDefault) +registerStanzaPlugin(PubsubOwner, OwnerDefault) class OwnerDelete(ElementBase, OptionalSetting): namespace = 'http://jabber.org/protocol/pubsub#owner' @@ -398,7 +367,7 @@ class OwnerDelete(ElementBase, OptionalSetting): plugin_tag_map = {} interfaces = set(('node',)) -stanzaPlugin(PubsubOwner, OwnerDelete) +registerStanzaPlugin(PubsubOwner, OwnerDelete) class OwnerPurge(ElementBase, OptionalSetting): namespace = 'http://jabber.org/protocol/pubsub#owner' @@ -407,7 +376,7 @@ class OwnerPurge(ElementBase, OptionalSetting): plugin_attrib_map = {} plugin_tag_map = {} -stanzaPlugin(PubsubOwner, OwnerPurge) +registerStanzaPlugin(PubsubOwner, OwnerPurge) class OwnerRedirect(ElementBase): namespace = 'http://jabber.org/protocol/pubsub#owner' @@ -423,7 +392,7 @@ class OwnerRedirect(ElementBase): def getJid(self): return JID(self._getAttr('jid')) -stanzaPlugin(OwnerDelete, OwnerRedirect) +registerStanzaPlugin(OwnerDelete, OwnerRedirect) class OwnerSubscriptions(Subscriptions): namespace = 'http://jabber.org/protocol/pubsub#owner' @@ -437,7 +406,7 @@ class OwnerSubscriptions(Subscriptions): self.xml.append(subscription.xml) return self.subscriptions.append(subscription) -stanzaPlugin(PubsubOwner, OwnerSubscriptions) +registerStanzaPlugin(PubsubOwner, OwnerSubscriptions) class OwnerSubscription(ElementBase): namespace = 'http://jabber.org/protocol/pubsub#owner' @@ -461,7 +430,7 @@ class Event(ElementBase): plugin_attrib_map = {} plugin_tag_map = {} -stanzaPlugin(Message, Event) +registerStanzaPlugin(Message, Event) class EventItem(ElementBase): namespace = 'http://jabber.org/protocol/pubsub#event' @@ -501,7 +470,7 @@ class EventItems(ElementBase): plugin_tag_map = {} subitem = (EventItem, EventRetract) -stanzaPlugin(Event, EventItems) +registerStanzaPlugin(Event, EventItems) class EventCollection(ElementBase): namespace = 'http://jabber.org/protocol/pubsub#event' @@ -511,7 +480,7 @@ class EventCollection(ElementBase): plugin_attrib_map = {} plugin_tag_map = {} -stanzaPlugin(Event, EventCollection) +registerStanzaPlugin(Event, EventCollection) class EventAssociate(ElementBase): namespace = 'http://jabber.org/protocol/pubsub#event' @@ -521,7 +490,7 @@ class EventAssociate(ElementBase): plugin_attrib_map = {} plugin_tag_map = {} -stanzaPlugin(EventCollection, EventAssociate) +registerStanzaPlugin(EventCollection, EventAssociate) class EventDisassociate(ElementBase): namespace = 'http://jabber.org/protocol/pubsub#event' @@ -531,7 +500,7 @@ class EventDisassociate(ElementBase): plugin_attrib_map = {} plugin_tag_map = {} -stanzaPlugin(EventCollection, EventDisassociate) +registerStanzaPlugin(EventCollection, EventDisassociate) class EventConfiguration(ElementBase): namespace = 'http://jabber.org/protocol/pubsub#event' @@ -541,22 +510,8 @@ class EventConfiguration(ElementBase): plugin_attrib_map = {} plugin_tag_map = {} - def getConfig(self): - config = self.xml.find('{jabber:x:data}x') - form = xep_0004.Form() - if config is not None: - form.fromXML(config) - return form - - def setConfig(self, value): - self.xml.append(value.getXML()) - return self - - def delConfig(self): - config = self.xml.find('{jabber:x:data}x') - self.xml.remove(config) - -stanzaPlugin(Event, EventConfiguration) +registerStanzaPlugin(Event, EventConfiguration) +registerStanzaPlugin(EventConfiguration, xep_0004.Form) class EventPurge(ElementBase): namespace = 'http://jabber.org/protocol/pubsub#event' @@ -566,7 +521,7 @@ class EventPurge(ElementBase): plugin_attrib_map = {} plugin_tag_map = {} -stanzaPlugin(Event, EventPurge) +registerStanzaPlugin(Event, EventPurge) class EventSubscription(ElementBase): namespace = 'http://jabber.org/protocol/pubsub#event' @@ -582,4 +537,4 @@ class EventSubscription(ElementBase): def getJid(self): return JID(self._getAttr('jid')) -stanzaPlugin(Event, EventSubscription) +registerStanzaPlugin(Event, EventSubscription) diff --git a/sleekxmpp/plugins/xep_0004.py b/sleekxmpp/plugins/xep_0004.py index 015bd8bc..037fc090 100644 --- a/sleekxmpp/plugins/xep_0004.py +++ b/sleekxmpp/plugins/xep_0004.py @@ -1,427 +1,347 @@ """ SleekXMPP: The Sleek XMPP Library - Copyright (C) 2007 Nathanael C. Fritz + Copyright (C) 2010 Nathanael C. Fritz, Lance J.T. Stout 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 + See the file LICENSE for copying permission. """ -from . import base + import logging -from xml.etree import cElementTree as ET import copy -#TODO support item groups and results +from . import base +from .. xmlstream.handler.callback import Callback +from .. xmlstream.matcher.xpath import MatchXPath +from .. xmlstream.stanzabase import registerStanzaPlugin, ElementBase, ET, JID +from .. stanza.message import Message -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): - base.base_plugin.post_init(self) - self.xmpp.plugin['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(ftype=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 = [] + +class Form(ElementBase): + namespace = 'jabber:x:data' + name = 'x' + plugin_attrib = 'form' + interfaces = set(('fields', 'instructions', 'items', 'reported', 'title', 'type', 'values')) + sub_interfaces = set(('title',)) + form_types = set(('cancel', 'form', 'result', 'submit')) + + def setup(self, xml=None): + if ElementBase.setup(self, xml): #if we had to generate xml + self['type'] = 'form' + + def addField(self, var='', ftype=None, label='', desc='', required=False, value=None, options=None, **kwargs): + kwtype = kwargs.get('type', None) + if kwtype is None: + kwtype = ftype + + field = FormField(parent=self) + field['var'] = var + field['type'] = kwtype + field['label'] = label + field['desc'] = desc + field['required'] = required + field['value'] = value + if options is not None: + field['options'] = options + return field + + def getXML(self): + logging.warning("Form.getXML() is deprecated API compatibility with plugins/old_0004.py") + return self.xml - def merge(self, form2): - form1 = Form(ftype=self.type) - form1.fromXML(self.getXML(self.type)) - for field in form2.fields: - if not field.var in form1.field: - form1.addField(field.var, field.type, field.label, field.desc, field.required, field.value) + def fromXML(self, xml): + logging.warning("Form.fromXML() is deprecated API compatibility with plugins/old_0004.py") + n = Form(xml=xml) + return n + + def addItem(self, values): + itemXML = ET.Element('{%s}item' % self.namespace) + self.xml.append(itemXML) + reported_vars = self['reported'].keys() + for var in reported_vars: + fieldXML = ET.Element('{%s}field' % FormField.namespace) + itemXML.append(fieldXML) + field = FormField(xml=fieldXML) + field['var'] = var + field['value'] = values.get(var, None) + + def addReported(self, var, ftype=None, label='', desc='', **kwargs): + kwtype = kwargs.get('type', None) + if kwtype is None: + kwtype = ftype + reported = self.xml.find('{%s}reported' % self.namespace) + if reported is None: + reported = ET.Element('{%s}reported' % self.namespace) + self.xml.append(reported) + fieldXML = ET.Element('{%s}field' % FormField.namespace) + reported.append(fieldXML) + field = FormField(xml=fieldXML) + field['var'] = var + field['type'] = kwtype + field['label'] = label + field['desc'] = desc + return field + + def cancel(self): + self['type'] = 'cancel' + + def delFields(self): + fieldsXML = self.xml.findall('{%s}field' % FormField.namespace) + for fieldXML in fieldsXML: + self.xml.remove(fieldXML) + + def delInstructions(self): + instsXML = self.xml.findall('{%s}instructions') + for instXML in instsXML: + self.xml.remove(instXML) + + def delItems(self): + itemsXML = self.xml.find('{%s}item' % self.namespace) + for itemXML in itemsXML: + self.xml.remove(itemXML) + + def delReported(self): + reportedXML = self.xml.find('{%s}reported' % self.namespace) + if reportedXML is not None: + self.xml.remove(reportedXML) + + def getFields(self, use_dict=False): + fields = {} if use_dict else [] + fieldsXML = self.xml.findall('{%s}field' % FormField.namespace) + for fieldXML in fieldsXML: + field = FormField(xml=fieldXML) + if use_dict: + fields[field['var']] = field else: - form1.field[field.var].value = field.value - for option, label in field.options: - if (option, label) not in form1.field[field.var].options: - form1.fields[field.var].addOption(option, label) - return form1 - - def copy(self): - newform = Form(ftype=self.type) - newform.fromXML(self.getXML(self.type)) - return newform - - def update(self, form): - values = form.getValues() - for var in values: - if var in self.fields: - self.fields[var].setValue(self.fields[var]) - + fields.append((field['var'], field)) + return fields + + def getInstructions(self): + instructions = '' + instsXML = self.xml.findall('{%s}instructions' % self.namespace) + return "\n".join([instXML.text for instXML in instsXML]) + + def getItems(self): + items = [] + itemsXML = self.xml.findall('{%s}item' % self.namespace) + for itemXML in itemsXML: + item = {} + fieldsXML = itemXML.findall('{%s}field' % FormField.namespace) + for fieldXML in fieldsXML: + field = FormField(xml=fieldXML) + item[field['var']] = field['value'] + items.append(item) + return items + + def getReported(self): + fields = {} + fieldsXML = self.xml.findall('{%s}reported/{%s}field' % (self.namespace, + FormField.namespace)) + for fieldXML in fieldsXML: + field = FormField(xml=fieldXML) + fields[field['var']] = field + return fields + def getValues(self): - result = {} - for field in self.fields: - value = field.value - if len(value) == 1: - value = value[0] - result[field.var] = value - return result - - def setValues(self, values={}): - for field in values: - if field in self.field: - if isinstance(values[field], list) or isinstance(values[field], tuple): - for value in values[field]: - self.field[field].setValue(value) - else: - self.field[field].setValue(values[field]) - - 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 - + values = {} + fields = self.getFields(use_dict=True) + for var in fields: + values[var] = fields[var]['value'] + return values + + def reply(self): + if self['type'] == 'form': + self['type'] = 'submit' + elif self['type'] == 'submit': + self['type'] = 'result' + + def setFields(self, fields, default=None): + del self['fields'] + for field_data in fields: + var = field_data[0] + field = field_data[1] + field['var'] = var + + self.addField(**field) + def setInstructions(self, instructions): - 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): - 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('{jabber:x:data}title') - title.text = self.title - form.append(title) - if self.instructions and self.type == 'form': - instructions = ET.Element('{jabber:x:data}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('{jabber:x:data}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 + del self['instructions'] + if instructions in [None, '']: + return + instructions = instructions.split('\n') + for instruction in instructions: + inst = ET.Element('{%s}instructions' % self.namespace) + inst.text = instruction + self.xml.append(inst) + + def setItems(self, items): + for item in items: + self.addItem(item) + + def setReported(self, reported, default=None): + for var in reported: + field = reported[var] + field['var'] = var + self.addReported(var, **field) + + def setValues(self, values): + fields = self.getFields(use_dict=True) + for field in values: + fields[field]['value'] = values[field] + + +class FormField(ElementBase): + namespace = 'jabber:x:data' + name = 'field' + plugin_attrib = 'field' + interfaces = set(('answer', 'desc', 'required', 'value', 'options', 'label', 'type', 'var')) + sub_interfaces = set(('desc',)) + field_types = set(('boolean', 'fixed', 'hidden', 'jid-multi', 'jid-single', 'list-multi', + 'list-single', 'text-multi', 'text-private', 'text-single')) + multi_value_types = set(('hidden', 'jid-multi', 'list-multi', 'text-multi')) + multi_line_types = set(('hidden', 'text-multi')) + option_types = set(('list-multi', 'list-single')) + true_values = set((True, '1', 'true')) + + def addOption(self, label='', value=''): + if self['type'] in self.option_types: + opt = FieldOption(parent=self) + opt['label'] = label + opt['value'] = value else: - self.islinebreak = False - if value: - self.setValue(value) - - def addOption(self, value, label): - if self.islist: - self.options.append((value, label)) + raise ValueError("Cannot add options to a %s field." % self['type']) + + def delOptions(self): + optsXML = self.xml.findall('{%s}option' % self.namespace) + for optXML in optsXML: + self.xml.remove(optXML) + + def delRequired(self): + reqXML = self.xml.find('{%s}required' % self.namespace) + if reqXML is not None: + self.xml.remove(reqXML) + + def delValue(self): + valsXML = self.xml.findall('{%s}value' % self.namespace) + for valXML in valsXML: + self.xml.remove(valXML) + + def getAnswer(self): + return self.getValue() + + def getOptions(self): + options = [] + optsXML = self.xml.findall('{%s}option' % self.namespace) + for optXML in optsXML: + opt = FieldOption(xml=optXML) + options.append({'label': opt['label'], 'value':opt['value']}) + return options + + def getRequired(self): + reqXML = self.xml.find('{%s}required' % self.namespace) + return reqXML is not None + + def getValue(self): + valsXML = self.xml.findall('{%s}value' % self.namespace) + if len(valsXML) == 0: + return None + elif self['type'] == 'boolean': + return valsXML[0].text in self.true_values + elif self['type'] in self.multi_value_types: + values = [] + for valXML in valsXML: + if valXML.text is None: + valXML.text = '' + values.append(valXML.text) + if self['type'] == 'text-multi': + values = "\n".join(values) + return values else: - raise ValueError("Cannot add options to non-list type field.") - - def setTrue(self): - if self.type == 'boolean': - self.value = [True] + return valsXML[0].text + + def setAnswer(self, answer): + self.setValue(answer) def setFalse(self): - if self.type == 'boolean': - self.value = [False] + self.setValue(False) - def require(self): - self.required = True - - def setDescription(self, desc): - self.desc = desc - - def setValue(self, value): - if self.type == 'boolean': - if value in ('1', 1, True, 'true', 'True', 'yes'): - value = True + def setOptions(self, options): + for value in options: + if isinstance(value, dict): + self.addOption(**value) else: - value = False - 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] + self.addOption(value=value) + + def setRequired(self, required): + exists = self.getRequired() + if not exists and required: + self.xml.append(ET.Element('{%s}required' % self.namespace)) + elif exists and not required: + self.delRequired() + + def setTrue(self): + self.setValue(True) + + def setValue(self, value): + self.delValue() + valXMLName = '{%s}value' % self.namespace + + if self['type'] == 'boolean': + if value in self.true_values: + valXML = ET.Element(valXMLName) + valXML.text = '1' + self.xml.append(valXML) else: - 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 + valXML = ET.Element(valXMLName) + valXML.text = '0' + self.xml.append(valXML) + elif self['type'] in self.multi_value_types or self['type'] in ['', None]: + if self['type'] in self.multi_line_types and isinstance(value, str): + value = value.split('\n') + if not isinstance(value, list): + value = [value] + for val in value: + if self['type'] in ['', None] and val in self.true_values: + val = '1' + valXML = ET.Element(valXMLName) + valXML.text = val + self.xml.append(valXML) else: - 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('{jabber:x:data}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('{jabber:x:data}option') - optionxml.attrib['label'] = option[1] - optionval = ET.Element('{jabber:x:data}value') - optionval.text = option[0] - optionxml.append(optionval) - field.append(optionxml) - if self.required: - required = ET.Element('{jabber:x:data}required') - field.append(required) - if self.desc: - desc = ET.Element('{jabber:x:data}desc') - desc.text = self.desc - field.append(desc) - for value in self.value: - valuexml = ET.Element('{jabber:x:data}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 + if isinstance(value, list): + raise ValueError("Cannot add multiple values to a %s field." % self['type']) + valXML = ET.Element(valXMLName) + valXML.text = value + self.xml.append(valXML) + + +class FieldOption(ElementBase): + namespace = 'jabber:x:data' + name = 'option' + plugin_attrib = 'option' + interfaces = set(('label', 'value')) + sub_interfaces = set(('value',)) + + +class xep_0004(base.base_plugin): + """ + XEP-0004: Data Forms + """ + + def plugin_init(self): + self.xep = '0004' + self.description = 'Data Forms' + + self.xmpp.registerHandler( + Callback('Data Form', + MatchXPath('{%s}message/{%s}x' % (self.xmpp.default_ns, + Form.namespace)), + self.handle_form)) + + registerStanzaPlugin(FormField, FieldOption) + registerStanzaPlugin(Form, FormField) + registerStanzaPlugin(Message, Form) - def 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 - + def post_init(self): + base.base_plugin.post_init(self) + self.xmpp.plugin['xep_0030'].add_feature('jabber:x:data') + + def handle_form(self, message): + self.xmpp.event("message_xform", message) diff --git a/sleekxmpp/plugins/xep_0009.py b/sleekxmpp/plugins/xep_0009.py index 49ffac41..625b03fb 100644 --- a/sleekxmpp/plugins/xep_0009.py +++ b/sleekxmpp/plugins/xep_0009.py @@ -178,9 +178,12 @@ 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.xmpp.add_handler("<iq type='set'><query xmlns='jabber:iq:rpc' /></iq>",
+ self._callMethod, name='Jabber RPC Call')
+ self.xmpp.add_handler("<iq type='result'><query xmlns='jabber:iq:rpc' /></iq>",
+ self._callResult, name='Jabber RPC Result')
+ self.xmpp.add_handler("<iq type='error'><query xmlns='jabber:iq:rpc' /></iq>",
+ self._callError, name='Jabber RPC Error')
self.entries = {}
self.activeCalls = []
diff --git a/sleekxmpp/plugins/xep_0030.py b/sleekxmpp/plugins/xep_0030.py index 6a31d243..a9d8d6a7 100644 --- a/sleekxmpp/plugins/xep_0030.py +++ b/sleekxmpp/plugins/xep_0030.py @@ -3,14 +3,14 @@ Copyright (C) 2010 Nathanael C. Fritz, Lance J.T. Stout This file is part of SleekXMPP. - See the file license.txt for copying permissio + See the file LICENSE for copying permission. """ import logging from . import base from .. xmlstream.handler.callback import Callback from .. xmlstream.matcher.xpath import MatchXPath -from .. xmlstream.stanzabase import ElementBase, ET, JID +from .. xmlstream.stanzabase import registerStanzaPlugin, ElementBase, ET, JID from .. stanza.iq import Iq class DiscoInfo(ElementBase): @@ -138,6 +138,9 @@ class DiscoNode(object): self.info = DiscoInfo() self.items = DiscoItems() + self.info['node'] = name + self.items['node'] = name + # This is a bit like poor man's inheritance, but # to simplify adding information to the node we # map node functions to either the info or items @@ -201,8 +204,8 @@ class xep_0030(base.base_plugin): DiscoInfo.namespace)), self.handle_info_query)) - self.xmpp.stanzaPlugin(Iq, DiscoInfo) - self.xmpp.stanzaPlugin(Iq, DiscoItems) + registerStanzaPlugin(Iq, DiscoInfo) + registerStanzaPlugin(Iq, DiscoItems) self.xmpp.add_event_handler('disco_items_request', self.handle_disco_items) self.xmpp.add_event_handler('disco_info_request', self.handle_disco_info) @@ -290,21 +293,21 @@ class xep_0030(base.base_plugin): # Older interface methods for backwards compatibility - def getInfo(self, jid, node=''): + def getInfo(self, jid, node='', dfrom=None): iq = self.xmpp.Iq() iq['type'] = 'get' iq['to'] = jid - iq['from'] = self.xmpp.fulljid + iq['from'] = dfrom iq['disco_info']['node'] = node - iq.send() + return iq.send() - def getItems(self, jid, node=''): + def getItems(self, jid, node='', dfrom=None): iq = self.xmpp.Iq() iq['type'] = 'get' iq['to'] = jid - iq['from'] = self.xmpp.fulljid + iq['from'] = dfrom iq['disco_items']['node'] = node - iq.send() + return iq.send() def add_feature(self, feature, node='main'): self.add_node(node) diff --git a/sleekxmpp/plugins/xep_0033.py b/sleekxmpp/plugins/xep_0033.py new file mode 100644 index 00000000..ea0b10b7 --- /dev/null +++ b/sleekxmpp/plugins/xep_0033.py @@ -0,0 +1,161 @@ +""" + SleekXMPP: The Sleek XMPP Library + Copyright (C) 2010 Nathanael C. Fritz, Lance J.T. Stout + This file is part of SleekXMPP. + + See the file LICENSE for copying permission. +""" + +import logging +from . import base +from .. xmlstream.handler.callback import Callback +from .. xmlstream.matcher.xpath import MatchXPath +from .. xmlstream.stanzabase import registerStanzaPlugin, ElementBase, ET, JID +from .. stanza.message import Message + + +class Addresses(ElementBase): + namespace = 'http://jabber.org/protocol/address' + name = 'addresses' + plugin_attrib = 'addresses' + interfaces = set(('addresses', 'bcc', 'cc', 'noreply', 'replyroom', 'replyto', 'to')) + + def addAddress(self, atype='to', jid='', node='', uri='', desc='', delivered=False): + address = Address(parent=self) + address['type'] = atype + address['jid'] = jid + address['node'] = node + address['uri'] = uri + address['desc'] = desc + address['delivered'] = delivered + return address + + def getAddresses(self, atype=None): + addresses = [] + for addrXML in self.xml.findall('{%s}address' % Address.namespace): + # ElementTree 1.2.6 does not support [@attr='value'] in findall + if atype is None or addrXML.attrib.get('type') == atype: + addresses.append(Address(xml=addrXML, parent=None)) + return addresses + + def setAddresses(self, addresses, set_type=None): + self.delAddresses(set_type) + for addr in addresses: + addr = dict(addr) + # Remap 'type' to 'atype' to match the add method + if set_type is not None: + addr['type'] = set_type + curr_type = addr.get('type', None) + if curr_type is not None: + del addr['type'] + addr['atype'] = curr_type + self.addAddress(**addr) + + def delAddresses(self, atype=None): + if atype is None: + return + for addrXML in self.xml.findall('{%s}address' % Address.namespace): + # ElementTree 1.2.6 does not support [@attr='value'] in findall + if addrXML.attrib.get('type') == atype: + self.xml.remove(addrXML) + + # -------------------------------------------------------------- + + def delBcc(self): + self.delAddresses('bcc') + + def delCc(self): + self.delAddresses('cc') + + def delNoreply(self): + self.delAddresses('noreply') + + def delReplyroom(self): + self.delAddresses('replyroom') + + def delReplyto(self): + self.delAddresses('replyto') + + def delTo(self): + self.delAddresses('to') + + # -------------------------------------------------------------- + + def getBcc(self): + return self.getAddresses('bcc') + + def getCc(self): + return self.getAddresses('cc') + + def getNoreply(self): + return self.getAddresses('noreply') + + def getReplyroom(self): + return self.getAddresses('replyroom') + + def getReplyto(self): + return self.getAddresses('replyto') + + def getTo(self): + return self.getAddresses('to') + + # -------------------------------------------------------------- + + def setBcc(self, addresses): + self.setAddresses(addresses, 'bcc') + + def setCc(self, addresses): + self.setAddresses(addresses, 'cc') + + def setNoreply(self, addresses): + self.setAddresses(addresses, 'noreply') + + def setReplyroom(self, addresses): + self.setAddresses(addresses, 'replyroom') + + def setReplyto(self, addresses): + self.setAddresses(addresses, 'replyto') + + def setTo(self, addresses): + self.setAddresses(addresses, 'to') + + +class Address(ElementBase): + namespace = 'http://jabber.org/protocol/address' + name = 'address' + plugin_attrib = 'address' + interfaces = set(('delivered', 'desc', 'jid', 'node', 'type', 'uri')) + address_types = set(('bcc', 'cc', 'noreply', 'replyroom', 'replyto', 'to')) + + def getDelivered(self): + return self.xml.attrib.get('delivered', False) + + def setDelivered(self, delivered): + if delivered: + self.xml.attrib['delivered'] = "true" + else: + del self['delivered'] + + def setUri(self, uri): + if uri: + del self['jid'] + del self['node'] + self.xml.attrib['uri'] = uri + elif 'uri' in self.xml.attrib: + del self.xml.attrib['uri'] + + +class xep_0030(base.base_plugin): + """ + XEP-0033: Extended Stanza Addressing + """ + + def plugin_init(self): + self.xep = '0033' + self.description = 'Extended Stanza Addressing' + + registerStanzaPlugin(Message, Addresses) + + def post_init(self): + base.base_plugin.post_init(self) + self.xmpp.plugin['xep_0030'].add_feature(Addresses.namespace) diff --git a/sleekxmpp/plugins/xep_0045.py b/sleekxmpp/plugins/xep_0045.py index 937c6f96..1892eeab 100644 --- a/sleekxmpp/plugins/xep_0045.py +++ b/sleekxmpp/plugins/xep_0045.py @@ -1,27 +1,15 @@ """ - 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 + SleekXMPP: The Sleek XMPP Library + Copyright (C) 2010 Nathanael C. Fritz + This file is part of SleekXMPP. + + See the file LICENSE for copying permission. """ from __future__ import with_statement from . import base import logging from xml.etree import cElementTree as ET -from .. xmlstream.stanzabase import ElementBase, JID +from .. xmlstream.stanzabase import registerStanzaPlugin, ElementBase, JID from .. stanza.presence import Presence from .. xmlstream.handler.callback import Callback from .. xmlstream.matcher.xpath import MatchXPath @@ -125,7 +113,7 @@ class xep_0045(base.base_plugin): self.xep = '0045' self.description = 'Multi User Chat' # load MUC support in presence stanzas - self.xmpp.stanzaPlugin(Presence, MUCPresence) + registerStanzaPlugin(Presence, MUCPresence) self.xmpp.registerHandler(Callback('MUCPresence', MatchXMLMask("<presence xmlns='%s' />" % self.xmpp.default_ns), self.handle_groupchat_presence)) self.xmpp.registerHandler(Callback('MUCMessage', MatchXMLMask("<message xmlns='%s' type='groupchat'><body/></message>" % self.xmpp.default_ns), self.handle_groupchat_message)) @@ -134,7 +122,7 @@ class xep_0045(base.base_plugin): """ if pr['muc']['room'] not in self.rooms.keys(): return - entry = pr['muc'].getValues() + entry = pr['muc'].getStanzaValues() if pr['type'] == 'unavailable': del self.rooms[entry['room']][entry['nick']] else: @@ -166,13 +154,13 @@ class xep_0045(base.base_plugin): return False xform = result.xml.find('{http://jabber.org/protocol/muc#owner}query/{jabber:x:data}x') if xform is None: return False - form = self.xmpp.plugin['xep_0004'].buildForm(xform) + form = self.xmpp.plugin['old_0004'].buildForm(xform) return form def configureRoom(self, room, form=None, ifrom=None): if form is None: form = self.getRoomForm(room, ifrom=ifrom) - #form = self.xmpp.plugin['xep_0004'].makeForm(ftype='submit') + #form = self.xmpp.plugin['old_0004'].makeForm(ftype='submit') #form.addField('FORM_TYPE', value='http://jabber.org/protocol/muc#roomconfig') iq = self.xmpp.makeIqSet() iq['to'] = room @@ -274,7 +262,7 @@ class xep_0045(base.base_plugin): form = result.xml.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) + return self.xmpp.plugin['old_0004'].buildForm(form) def cancelConfig(self, room): query = ET.Element('{http://jabber.org/protocol/muc#owner}query') diff --git a/sleekxmpp/plugins/xep_0050.py b/sleekxmpp/plugins/xep_0050.py index 2f356e17..5efb9116 100644 --- a/sleekxmpp/plugins/xep_0050.py +++ b/sleekxmpp/plugins/xep_0050.py @@ -1,27 +1,14 @@ """ - 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 + SleekXMPP: The Sleek XMPP Library + Copyright (C) 2010 Nathanael C. Fritz + This file is part of SleekXMPP. + + See the file LICENSE for copying permission. """ from __future__ import with_statement from . import base import logging from xml.etree import cElementTree as ET -import traceback import time class xep_0050(base.base_plugin): @@ -32,11 +19,11 @@ class xep_0050(base.base_plugin): 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.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, name='Ad-Hoc None') + 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, name='Ad-Hoc Execute') + 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, name='Ad-Hoc 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, name='Ad-Hoc 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, name='Ad-Hoc Complete') self.commands = {} self.sessions = {} self.sd = self.xmpp.plugin['xep_0030'] @@ -83,7 +70,7 @@ class xep_0050(base.base_plugin): in_command = xml.find('{http://jabber.org/protocol/commands}command') sessionid = in_command.get('sessionid', None) pointer = self.sessions[sessionid]['next'] - results = self.xmpp.plugin['xep_0004'].makeForm('result') + results = self.xmpp.plugin['old_0004'].makeForm('result') results.fromXML(in_command.find('{jabber:x:data}x')) 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=[])) @@ -94,7 +81,7 @@ class xep_0050(base.base_plugin): in_command = xml.find('{http://jabber.org/protocol/commands}command') sessionid = in_command.get('sessionid', None) pointer = self.sessions[sessionid]['next'] - results = self.xmpp.plugin['xep_0004'].makeForm('result') + results = self.xmpp.plugin['old_0004'].makeForm('result') results.fromXML(in_command.find('{jabber:x:data}x')) form, npointer, next = pointer(results,sessionid) self.sessions[sessionid]['next'] = npointer diff --git a/sleekxmpp/plugins/xep_0060.py b/sleekxmpp/plugins/xep_0060.py index bff158a0..a92a3844 100644 --- a/sleekxmpp/plugins/xep_0060.py +++ b/sleekxmpp/plugins/xep_0060.py @@ -2,7 +2,7 @@ from __future__ import with_statement from . import base import logging #from xml.etree import cElementTree as ET -from .. xmlstream.stanzabase import ElementBase, ET +from .. xmlstream.stanzabase import registerStanzaPlugin, ElementBase, ET from . import stanza_pubsub class xep_0060(base.base_plugin): diff --git a/sleekxmpp/plugins/xep_0078.py b/sleekxmpp/plugins/xep_0078.py index f8732905..4b3ab829 100644 --- a/sleekxmpp/plugins/xep_0078.py +++ b/sleekxmpp/plugins/xep_0078.py @@ -1,21 +1,9 @@ """ - 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 + SleekXMPP: The Sleek XMPP Library + Copyright (C) 2010 Nathanael C. Fritz + This file is part of SleekXMPP. + + See the file LICENSE for copying permission. """ from __future__ import with_statement from xml.etree import cElementTree as ET diff --git a/sleekxmpp/plugins/xep_0085.py b/sleekxmpp/plugins/xep_0085.py new file mode 100644 index 00000000..b7b5d6dd --- /dev/null +++ b/sleekxmpp/plugins/xep_0085.py @@ -0,0 +1,101 @@ +""" + SleekXMPP: The Sleek XMPP Library + Copyright (C) 2010 Nathanael C. Fritz, Lance J.T. Stout + This file is part of SleekXMPP. + + See the file LICENSE for copying permissio +""" + +import logging +from . import base +from .. xmlstream.handler.callback import Callback +from .. xmlstream.matcher.xpath import MatchXPath +from .. xmlstream.stanzabase import registerStanzaPlugin, ElementBase, ET, JID +from .. stanza.message import Message + + +class ChatState(ElementBase): + namespace = 'http://jabber.org/protocol/chatstates' + plugin_attrib = 'chat_state' + interface = set(('state',)) + states = set(('active', 'composing', 'gone', 'inactive', 'paused')) + + def active(self): + self.setState('active') + + def composing(self): + self.setState('composing') + + def gone(self): + self.setState('gone') + + def inactive(self): + self.setState('inactive') + + def paused(self): + self.setState('paused') + + def setState(self, state): + if state in self.states: + self.name = state + self.xml.tag = '{%s}%s' % (self.namespace, state) + else: + raise ValueError('Invalid chat state') + + def getState(self): + return self.name + +# In order to match the various chat state elements, +# we need one stanza object per state, even though +# they are all the same except for the initial name +# value. Do not depend on the type of the chat state +# stanza object for the actual state. + +class Active(ChatState): + name = 'active' +class Composing(ChatState): + name = 'composing' +class Gone(ChatState): + name = 'gone' +class Inactive(ChatState): + name = 'inactive' +class Paused(ChatState): + name = 'paused' + + +class xep_0085(base.base_plugin): + """ + XEP-0085 Chat State Notifications + """ + + def plugin_init(self): + self.xep = '0085' + self.description = 'Chat State Notifications' + + handlers = [('Active Chat State', 'active'), + ('Composing Chat State', 'composing'), + ('Gone Chat State', 'gone'), + ('Inactive Chat State', 'inactive'), + ('Paused Chat State', 'paused')] + for handler in handlers: + self.xmpp.registerHandler( + Callback(handler[0], + MatchXPath("{%s}message/{%s}%s" % (self.xmpp.default_ns, + ChatState.namespace, + handler[1])), + self._handleChatState)) + + registerStanzaPlugin(Message, Active) + registerStanzaPlugin(Message, Composing) + registerStanzaPlugin(Message, Gone) + registerStanzaPlugin(Message, Inactive) + registerStanzaPlugin(Message, Paused) + + def post_init(self): + base.base_plugin.post_init(self) + self.xmpp.plugin['xep_0030'].add_feature('http://jabber.org/protocol/chatstates') + + def _handleChatState(self, msg): + state = msg['chat_state'].name + logging.debug("Chat State: %s, %s" % (state, msg['from'].jid)) + self.xmpp.event('chatstate_%s' % state, msg) diff --git a/sleekxmpp/plugins/xep_0092.py b/sleekxmpp/plugins/xep_0092.py index aeebbe0c..ca02c4a8 100644 --- a/sleekxmpp/plugins/xep_0092.py +++ b/sleekxmpp/plugins/xep_0092.py @@ -1,21 +1,9 @@ """ - 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 + SleekXMPP: The Sleek XMPP Library + Copyright (C) 2010 Nathanael C. Fritz + This file is part of SleekXMPP. + + See the file LICENSE for copying permission. """ from xml.etree import cElementTree as ET from . import base @@ -30,7 +18,7 @@ class xep_0092(base.base_plugin): 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) + self.xmpp.add_handler("<iq type='get' xmlns='%s'><query xmlns='jabber:iq:version' /></iq>" % self.xmpp.default_ns, self.report_version, name='Sofware Version') def post_init(self): base.base_plugin.post_init(self) diff --git a/sleekxmpp/plugins/xep_0128.py b/sleekxmpp/plugins/xep_0128.py new file mode 100644 index 00000000..824977b6 --- /dev/null +++ b/sleekxmpp/plugins/xep_0128.py @@ -0,0 +1,51 @@ +""" + SleekXMPP: The Sleek XMPP Library + Copyright (C) 2010 Nathanael C. Fritz, Lance J.T. Stout + This file is part of SleekXMPP. + + See the file LICENSE for copying permission. +""" + +import logging +from . import base +from .. xmlstream.handler.callback import Callback +from .. xmlstream.matcher.xpath import MatchXPath +from .. xmlstream.stanzabase import registerStanzaPlugin, ElementBase, ET, JID +from .. stanza.iq import Iq +from . xep_0030 import DiscoInfo, DiscoItems +from . xep_0004 import Form + + +class xep_0128(base.base_plugin): + """ + XEP-0128 Service Discovery Extensions + """ + + def plugin_init(self): + self.xep = '0128' + self.description = 'Service Discovery Extensions' + + registerStanzaPlugin(DiscoInfo, Form) + registerStanzaPlugin(DiscoItems, Form) + + def extend_info(self, node, data=None): + if data is None: + data = {} + node = self.xmpp['xep_0030'].nodes.get(node, None) + if node is None: + self.xmpp['xep_0030'].add_node(node) + + info = node.info + info['form']['type'] = 'result' + info['form'].setFields(data, default=None) + + def extend_items(self, node, data=None): + if data is None: + data = {} + node = self.xmpp['xep_0030'].nodes.get(node, None) + if node is None: + self.xmpp['xep_0030'].add_node(node) + + items = node.items + items['form']['type'] = 'result' + items['form'].setFields(data, default=None) diff --git a/sleekxmpp/plugins/xep_0199.py b/sleekxmpp/plugins/xep_0199.py index ccaf0b3a..3fc62f55 100644 --- a/sleekxmpp/plugins/xep_0199.py +++ b/sleekxmpp/plugins/xep_0199.py @@ -1,22 +1,9 @@ """ - 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 + SleekXMPP: The Sleek XMPP Library + Copyright (C) 2010 Nathanael C. Fritz + This file is part of SleekXMPP. + + See the file LICENSE for copying permission. """ from xml.etree import cElementTree as ET from . import base @@ -29,7 +16,7 @@ class xep_0199(base.base_plugin): 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.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, name='XMPP Ping') self.running = False #if self.config.get('keepalive', True): #self.xmpp.add_event_handler('session_start', self.handler_pingserver, threaded=True) |