comparison salut.py @ 0:d1bc50b64974

initial commit
author Goffi <goffi@goffi.org>
date Tue, 25 Feb 2014 22:22:18 +0100 (2014-02-25)
parents
children 92549e4336a6
comparison
equal deleted inserted replaced
-1:000000000000 0:d1bc50b64974
1 #!/usr/bin/env python2
2 # -*- coding: utf-8 -*-
3
4 # SAT plugin for account creation (experimental)
5 # Copyright (C) 2009, 2010, 2011, 2012, 2013, 2014 Jérôme Poisson (goffi@goffi.org)
6
7 # This program is free software: you can redistribute it and/or modify
8 # it under the terms of the GNU Affero General Public License as published by
9 # the Free Software Foundation, either version 3 of the License, or
10 # (at your option) any later version.
11
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU Affero General Public License for more details.
16
17 # You should have received a copy of the GNU Affero General Public License
18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
19
20 from twisted.words.xish import domish
21 from wokkel.subprotocols import XMPPHandler, IQHandlerMixin
22 from wokkel import disco, iwokkel, data_form
23 from twisted.words.protocols.jabber import jid
24 from zope.interface import implements
25 import uuid
26 import sqlite3
27 from os import path
28 from collections import OrderedDict
29 import gettext
30 gettext.install('sat', "i18n", unicode=True)
31
32 DATABASE = "salut.db"
33 ID_CMD_LIST = disco.DiscoIdentity("automation", "command-list")
34 NS_COMMANDS = "http://jabber.org/protocol/commands"
35 NS_SEARCH = 'jabber:iq:search'
36 QUERY_SEARCH = "/query[@xmlns='jabber:iq:search']"
37 SUBSCRIBE_CMD = "/iq[@type='set']/command[@xmlns='"+NS_COMMANDS+"' and @node='subscribe']"
38 UNSUBSCRIBE_CMD = "/iq[@type='set']/command[@xmlns='"+NS_COMMANDS+"' and @node='unsubscribe']"
39 INSTRUCTIONS = _(u'This is a minimal directory for the Libervia demo (Salut à Toi project). For testing purpose only.')
40 FORM_INSTRUCTIONS = [INSTRUCTIONS]
41
42 SUBSCRIBE_TXT = _(u"Please give some words about you, then submit to be registered on the directory")
43 SUBSCRIBE_DONE = _("You are now registered on the directory")
44 UNSUBSCRIBE_DONE = _("You have been removed from the directory")
45 DB_CREATE = ['PRAGMA user_version=0',
46 'CREATE TABLE directory (jid TEXT PRIMARY KEY, description TEXT)']
47
48 class SalutGateway(XMPPHandler, IQHandlerMixin):
49 implements(iwokkel.IDisco)
50
51 def __init__(self, component=False):
52 XMPPHandler.__init__(self)
53 IQHandlerMixin.__init__(self)
54 self.component = component
55 self.discoHandler = disco.DiscoHandler()
56 new_db = not path.exists(DATABASE)
57 conn = sqlite3.connect(DATABASE)
58 self.db = conn.cursor() # we use SQLite in a blocking way, performance is not such a big deal here
59 if new_db:
60 for statement in DB_CREATE:
61 self.db.execute(statement)
62
63 def connectionMade(self):
64 print "Connected!"
65 self.xmlstream.addObserver("/iq[@type='get']" + QUERY_SEARCH, self.handleFieldsRequest)
66 self.xmlstream.addObserver("/iq[@type='set']" + QUERY_SEARCH, self.handleSearchRequest)
67 self.xmlstream.addObserver(SUBSCRIBE_CMD, self.onSubscribe)
68 self.xmlstream.addObserver(UNSUBSCRIBE_CMD, self.onUnsubscribe)
69 self.discoHandler.setHandlerParent(self.parent)
70
71 def connectionLost(self, reason):
72 print "Disconnected!"
73
74 def handleFieldsRequest(self, request):
75 result = domish.Element((None, 'iq'))
76 result['type'] = 'result'
77 result['id'] = request['id']
78 result['to'] = request['from']
79 query_elt = result.addElement('query', NS_SEARCH)
80 instructions_elt = query_elt.addElement('instructions', content=INSTRUCTIONS)
81 form = data_form.Form('form', title=_('Directory search'),
82 instructions=FORM_INSTRUCTIONS,
83 formNamespace=NS_SEARCH)
84 form.addField(data_form.Field('fixed', label=_('Enter part of description or jid to find somebody,')))
85 form.addField(data_form.Field('fixed', label=('let empty to have a full list of people')))
86 form.addField(data_form.Field('text-single', 'jid', label=_('jid')))
87 form.addField(data_form.Field('text-single', 'description', label=_('Description')))
88 query_elt.addChild(form.toElement())
89 self.xmlstream.send(result)
90
91 def handleSearchRequest(self, request):
92 query = ["SELECT jid, description FROM directory"]
93 args = OrderedDict()
94 try:
95 query_elt = request.elements(NS_SEARCH, 'query').next()
96 form_elt = query_elt.elements(data_form.NS_X_DATA, 'x').next()
97 parsed_form = data_form.Form.fromElement(form_elt)
98 for col in ('jid', 'description'):
99 value = parsed_form[col].strip()
100 if value:
101 args[col] = value
102 except (StopIteration, KeyError):
103 raise ValueError # TODO: proper error handling
104
105 if args:
106 query.append("WHERE")
107 query.append(" AND ".join(("%s LIKE ?" % col for col in args)))
108
109 row_iter = self.db.execute(' '.join(query), tuple(['%'+arg+'%' for arg in args.values()]))
110
111 result = domish.Element((None, 'iq'))
112 result['type'] = 'result'
113 result['id'] = request['id']
114 result['to'] = request['from']
115 query_elt = result.addElement('query', NS_SEARCH)
116 x_form = data_form.Form('result', formNamespace = NS_SEARCH)
117 x_form_elt = x_form.toElement()
118 reported_elt = x_form_elt.addElement('reported')
119 jid_field_elt = reported_elt.addElement('field')
120 jid_field_elt['label'] = 'Jabber ID'
121 jid_field_elt['var'] = 'jid'
122 description_field_elt = reported_elt.addElement('field')
123 description_field_elt['label'] = 'Description'
124 description_field_elt['var'] = 'description'
125 for row in row_iter:
126 for col, value in zip(('jid', 'description'), row):
127 item_elt = x_form_elt.addElement('item')
128 field_elt = item_elt.addElement('field')
129 field_elt['var'] = col
130 value_elt = field_elt.addElement('value', content=value)
131
132 query_elt.addChild(x_form_elt)
133 self.xmlstream.send(result)
134
135 def onSubscribe(self, request):
136 result = domish.Element((None, 'iq'))
137 result['type'] = 'result'
138 result['id'] = request['id']
139 result['to'] = request['from']
140 from_ = jid.JID(request['from'])
141 request_cmd = request.elements(NS_COMMANDS, 'command').next()
142 command_elt = result.addElement('command', NS_COMMANDS)
143 try:
144 session_id = request_cmd['sessionid']
145 except KeyError:
146 session_id = None
147
148 if session_id is None:
149 # first request, we send the form
150 command_elt['status'] = 'executing'
151 session_id = str(uuid.uuid4())
152 actions_elt = command_elt.addElement('actions')
153 actions_elt['execute'] = 'next'
154 actions_elt.addElement('next')
155 form = data_form.Form('form', instructions=FORM_INSTRUCTIONS, title=_('Directory subscription'))
156 infos = data_form.Field('fixed', value=SUBSCRIBE_TXT)
157 desc = data_form.Field('text-single', 'description', label=_(u"Some words about you"))
158 form.addField(infos)
159 form.addField(desc)
160 command_elt.addChild(form.toElement())
161 else:
162 req_forms = request_cmd.elements(data_form.NS_X_DATA, 'x')
163 try:
164 req_form = req_forms.next()
165 parsed_form = data_form.Form.fromElement(req_form)
166 description = parsed_form['description']
167 except (StopIteration, KeyError):
168 raise ValueError # TODO: properly cancel the command
169 self.db.execute('REPLACE INTO directory(jid,description) VALUES (?,?)', (from_.userhost(), description))
170 self.db.connection.commit()
171 command_elt['status'] = 'completed'
172 note_elt = command_elt.addElement('note')
173 note_elt['type'] = 'info'
174 note_elt.addContent(SUBSCRIBE_DONE)
175 command_elt['sessionid'] = session_id
176 command_elt['node'] = request_cmd['node']
177 self.xmlstream.send(result)
178
179 def onUnsubscribe(self, request):
180 result = domish.Element((None, 'iq'))
181 result['type'] = 'result'
182 result['id'] = request['id']
183 result['to'] = request['from']
184 from_ = jid.JID(request['from'])
185 request_cmd = request.elements(NS_COMMANDS, 'command').next()
186 command_elt = result.addElement('command', NS_COMMANDS)
187 self.db.execute('DELETE FROM directory WHERE jid=?', (from_.userhost(),))
188 self.db.connection.commit()
189 command_elt['status'] = 'completed'
190 note_elt = command_elt.addElement('note')
191 note_elt['type'] = 'info'
192 note_elt.addContent(UNSUBSCRIBE_DONE)
193 command_elt['node'] = request_cmd['node']
194 self.xmlstream.send(result)
195
196 def getDiscoInfo(self, requestor, target, nodeIdentifier=''):
197 return [disco.DiscoFeature(NS_SEARCH),
198 disco.DiscoIdentity(u"directory", u"user", u"salut"),
199 disco.DiscoFeature(NS_COMMANDS),
200 ID_CMD_LIST]
201
202 def getDiscoItems(self, requestor, target, nodeIdentifier=''):
203 ret = []
204 if nodeIdentifier == NS_COMMANDS:
205 ret.append(disco.DiscoItem(target, "subscribe", "Subscribe to the directory"))
206 ret.append(disco.DiscoItem(target, "unsubscribe", "Unsubscribe from the directory"))
207 return ret