Mercurial > salut
view salut.py @ 3:593345584d21 default tip
replace ad-hoc commands "subscribe" and "unsubscribe" with a single "update" command
author | souliane <souliane@mailoo.org> |
---|---|
date | Wed, 02 Sep 2015 14:02:50 +0200 |
parents | 77b77f48c975 |
children |
line wrap: on
line source
#!/usr/bin/env python2 # -*- coding: utf-8 -*- # SAT plugin for account creation (experimental) # Copyright (C) 2009, 2010, 2011, 2012, 2013, 2014, 2015 Jérôme Poisson (goffi@goffi.org) # Copyright (C) 2015 Adrien Cossa (souliane@mailoo.org) # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program 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 Affero General Public License for more details. # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. from twisted.words.xish import domish from wokkel.subprotocols import XMPPHandler, IQHandlerMixin from wokkel import disco, iwokkel, data_form from twisted.words.protocols.jabber import jid from zope.interface import implements import uuid import sqlite3 from os import path from collections import OrderedDict import gettext gettext.install('sat', "i18n", unicode=True) DATABASE = "salut.db" ID_CMD_LIST = disco.DiscoIdentity("automation", "command-list") NS_COMMANDS = "http://jabber.org/protocol/commands" NS_SEARCH = 'jabber:iq:search' QUERY_SEARCH = "/query[@xmlns='jabber:iq:search']" UPDATE_CMD = "/iq[@type='set']/command[@xmlns='"+NS_COMMANDS+"' and @node='update']" INSTRUCTIONS = _(u'Update your subscription to the Jabber search directory.') FORM_INSTRUCTIONS = [INSTRUCTIONS] SUBSCRIBE_DONE = _("You registration to the directory has been updated.") UNSUBSCRIBE_DONE = _("You have been removed from the directory.") DB_CREATE = ['PRAGMA user_version=0', 'CREATE TABLE directory (jid TEXT PRIMARY KEY, description TEXT)'] class SalutGateway(XMPPHandler, IQHandlerMixin): implements(iwokkel.IDisco) def __init__(self, component=False): XMPPHandler.__init__(self) IQHandlerMixin.__init__(self) self.component = component self.discoHandler = disco.DiscoHandler() new_db = not path.exists(DATABASE) conn = sqlite3.connect(DATABASE) self.db = conn.cursor() # we use SQLite in a blocking way, performance is not such a big deal here if new_db: for statement in DB_CREATE: self.db.execute(statement) def connectionMade(self): print "Connected!" self.xmlstream.addObserver("/iq[@type='get']" + QUERY_SEARCH, self.handleFieldsRequest) self.xmlstream.addObserver("/iq[@type='set']" + QUERY_SEARCH, self.handleSearchRequest) self.xmlstream.addObserver(UPDATE_CMD, self.onUpdate) self.discoHandler.setHandlerParent(self.parent) def connectionLost(self, reason): print "Disconnected!" def handleFieldsRequest(self, request): """Respond to a fields request with the fields' list. @param request (domish.Element): the request to respond to """ result = domish.Element((None, 'iq')) result['type'] = 'result' result['id'] = request['id'] result['to'] = request['from'] query_elt = result.addElement('query', NS_SEARCH) instructions_elt = query_elt.addElement('instructions', content=INSTRUCTIONS) form = data_form.Form('form', title=_('Directory search'), instructions=FORM_INSTRUCTIONS, formNamespace=NS_SEARCH) form.addField(data_form.Field('fixed', label=_('Enter part of description or jid to find somebody,'))) form.addField(data_form.Field('fixed', label=('let empty to have a full list of people'))) form.addField(data_form.Field('text-single', 'jid', label=_('Jabber ID'))) form.addField(data_form.Field('text-single', 'description', label=_('Description'))) query_elt.addChild(form.toElement()) self.xmlstream.send(result) def handleSearchRequest(self, request): """Respond to a search request with the search results. @param request (domish.Element): the request to respond to """ args = OrderedDict() try: query_elt = request.elements(NS_SEARCH, 'query').next() form_elt = query_elt.elements(data_form.NS_X_DATA, 'x').next() except StopIteration: raise ValueError # TODO: proper error handling else: parsed_form = data_form.Form.fromElement(form_elt) for col in ('jid', 'description'): try: value = parsed_form[col].strip() except KeyError: pass else: if value: args[col] = value result = domish.Element((None, 'iq')) result['type'] = 'result' result['id'] = request['id'] result['to'] = request['from'] query_elt = result.addElement('query', NS_SEARCH) x_form = data_form.Form('result', formNamespace = NS_SEARCH) x_form_elt = x_form.toElement() reported_elt = x_form_elt.addElement('reported') jid_field_elt = reported_elt.addElement('field') jid_field_elt['label'] = 'Jabber ID' jid_field_elt['var'] = 'jid' description_field_elt = reported_elt.addElement('field') description_field_elt['label'] = 'Description' description_field_elt['var'] = 'description' for row in self._getSubscriptions(args): for col, value in zip(('jid', 'description'), row): item_elt = x_form_elt.addElement('item') field_elt = item_elt.addElement('field') field_elt['var'] = col value_elt = field_elt.addElement('value', content=value) query_elt.addChild(x_form_elt) self.xmlstream.send(result) def _getSubscriptions(self, args=None): """Retrieve the subscriptions to the directory. @param args (OrderedDict): arguments to filter the request @return: list[(boolean, unicode)] """ query = ["SELECT jid, description FROM directory"] if args: query.append("WHERE") query.append(" AND ".join(("%s LIKE ?" % col for col in args))) return self.db.execute(' '.join(query), tuple(['%'+arg+'%' for arg in args.values()])) def _getSubscription(self, jid): """Retrieve the subscription of the given JID. @param jid (jid.JID): JID to search for @return: (boolean, unicode) """ return self._getSubscriptions(OrderedDict({'jid': jid.userhost()})).fetchone() def _setSubscription(self, from_jid, subscribed, description): """Set the subscription of the given JID. @param from_jid (jid.JID): JID to subscribe (or unsubscribe) @param subscribed (boolean): set to True to subscribe and False to unsubscribe @param description (unicode): description """ if subscribed: self.db.execute('REPLACE INTO directory(jid,description) VALUES (?,?)', (from_jid.userhost(), description)) else: self.db.execute('DELETE FROM directory WHERE jid=?', (from_jid.userhost(),)) self.db.connection.commit() def onUpdate(self, request): """Process the request to update a subscription. @param request (domish.Element): the update request """ result = domish.Element((None, 'iq')) result['type'] = 'result' result['id'] = request['id'] result['to'] = request['from'] from_jid = jid.JID(request['from']) request_cmd = request.elements(NS_COMMANDS, 'command').next() command_elt = result.addElement('command', NS_COMMANDS) command_elt['node'] = request_cmd['node'] try: session_id = request_cmd['sessionid'] except KeyError: session_id = None if session_id is None: # first request, we send the form command_elt['status'] = 'executing' session_id = str(uuid.uuid4()) actions_elt = command_elt.addElement("actions") actions_elt['execute'] = 'complete' actions_elt.addElement('complete') subscription = self._getSubscription(from_jid) subscribed, desc = ('true', subscription[1]) if subscription else ('false', '') form = data_form.Form('form', title=_('Directory subscription'), instructions=FORM_INSTRUCTIONS, formNamespace=NS_SEARCH) form.addField(data_form.Field('boolean', 'subscribed', label=_('Subscribed'), values=[subscribed])) form.addField(data_form.Field('text-single', 'description', label=_(u"Some words about you"), values=[desc])) command_elt.addChild(form.toElement()) else: try: request_form = request_cmd.elements(data_form.NS_X_DATA).next() except (StopIteration, KeyError): raise ValueError # TODO: properly cancel the command parsed_form = data_form.Form.fromElement(request_form) subscribed = parsed_form['subscribed'] in ("true", "1") description = parsed_form['description'] self._setSubscription(from_jid, subscribed, description) command_elt['status'] = 'completed' note_elt = command_elt.addElement('note') note_elt['type'] = 'info' if subscribed: note_elt.addContent(SUBSCRIBE_DONE) else: note_elt.addContent(UNSUBSCRIBE_DONE) command_elt['sessionid'] = session_id self.xmlstream.send(result) def getDiscoInfo(self, requestor, target, nodeIdentifier=''): return [disco.DiscoFeature(NS_SEARCH), disco.DiscoIdentity(u"directory", u"user", u"salut"), disco.DiscoFeature(NS_COMMANDS), ID_CMD_LIST] def getDiscoItems(self, requestor, target, nodeIdentifier=''): ret = [] if nodeIdentifier == NS_COMMANDS: ret.append(disco.DiscoItem(target, "update", "Update your subscription to the directory")) return ret