Mercurial > salut
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 |