Mercurial > salut
diff salut.py @ 0:d1bc50b64974
initial commit
author | Goffi <goffi@goffi.org> |
---|---|
date | Tue, 25 Feb 2014 22:22:18 +0100 |
parents | |
children | 92549e4336a6 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/salut.py Tue Feb 25 22:22:18 2014 +0100 @@ -0,0 +1,207 @@ +#!/usr/bin/env python2 +# -*- coding: utf-8 -*- + +# SAT plugin for account creation (experimental) +# Copyright (C) 2009, 2010, 2011, 2012, 2013, 2014 Jérôme Poisson (goffi@goffi.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']" +SUBSCRIBE_CMD = "/iq[@type='set']/command[@xmlns='"+NS_COMMANDS+"' and @node='subscribe']" +UNSUBSCRIBE_CMD = "/iq[@type='set']/command[@xmlns='"+NS_COMMANDS+"' and @node='unsubscribe']" +INSTRUCTIONS = _(u'This is a minimal directory for the Libervia demo (Salut à Toi project). For testing purpose only.') +FORM_INSTRUCTIONS = [INSTRUCTIONS] + +SUBSCRIBE_TXT = _(u"Please give some words about you, then submit to be registered on the directory") +SUBSCRIBE_DONE = _("You are now registered on the directory") +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(SUBSCRIBE_CMD, self.onSubscribe) + self.xmlstream.addObserver(UNSUBSCRIBE_CMD, self.onUnsubscribe) + self.discoHandler.setHandlerParent(self.parent) + + def connectionLost(self, reason): + print "Disconnected!" + + def handleFieldsRequest(self, request): + 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=_('jid'))) + form.addField(data_form.Field('text-single', 'description', label=_('Description'))) + query_elt.addChild(form.toElement()) + self.xmlstream.send(result) + + def handleSearchRequest(self, request): + query = ["SELECT jid, description FROM directory"] + args = OrderedDict() + try: + query_elt = request.elements(NS_SEARCH, 'query').next() + form_elt = query_elt.elements(data_form.NS_X_DATA, 'x').next() + parsed_form = data_form.Form.fromElement(form_elt) + for col in ('jid', 'description'): + value = parsed_form[col].strip() + if value: + args[col] = value + except (StopIteration, KeyError): + raise ValueError # TODO: proper error handling + + if args: + query.append("WHERE") + query.append(" AND ".join(("%s LIKE ?" % col for col in args))) + + row_iter = self.db.execute(' '.join(query), tuple(['%'+arg+'%' for arg in args.values()])) + + 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 row_iter: + 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 onSubscribe(self, request): + result = domish.Element((None, 'iq')) + result['type'] = 'result' + result['id'] = request['id'] + result['to'] = request['from'] + from_ = jid.JID(request['from']) + request_cmd = request.elements(NS_COMMANDS, 'command').next() + command_elt = result.addElement('command', NS_COMMANDS) + 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'] = 'next' + actions_elt.addElement('next') + form = data_form.Form('form', instructions=FORM_INSTRUCTIONS, title=_('Directory subscription')) + infos = data_form.Field('fixed', value=SUBSCRIBE_TXT) + desc = data_form.Field('text-single', 'description', label=_(u"Some words about you")) + form.addField(infos) + form.addField(desc) + command_elt.addChild(form.toElement()) + else: + req_forms = request_cmd.elements(data_form.NS_X_DATA, 'x') + try: + req_form = req_forms.next() + parsed_form = data_form.Form.fromElement(req_form) + description = parsed_form['description'] + except (StopIteration, KeyError): + raise ValueError # TODO: properly cancel the command + self.db.execute('REPLACE INTO directory(jid,description) VALUES (?,?)', (from_.userhost(), description)) + self.db.connection.commit() + command_elt['status'] = 'completed' + note_elt = command_elt.addElement('note') + note_elt['type'] = 'info' + note_elt.addContent(SUBSCRIBE_DONE) + command_elt['sessionid'] = session_id + command_elt['node'] = request_cmd['node'] + self.xmlstream.send(result) + + def onUnsubscribe(self, request): + result = domish.Element((None, 'iq')) + result['type'] = 'result' + result['id'] = request['id'] + result['to'] = request['from'] + from_ = jid.JID(request['from']) + request_cmd = request.elements(NS_COMMANDS, 'command').next() + command_elt = result.addElement('command', NS_COMMANDS) + self.db.execute('DELETE FROM directory WHERE jid=?', (from_.userhost(),)) + self.db.connection.commit() + command_elt['status'] = 'completed' + note_elt = command_elt.addElement('note') + note_elt['type'] = 'info' + note_elt.addContent(UNSUBSCRIBE_DONE) + command_elt['node'] = request_cmd['node'] + 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, "subscribe", "Subscribe to the directory")) + ret.append(disco.DiscoItem(target, "unsubscribe", "Unsubscribe from the directory")) + return ret