Mercurial > libervia-backend
comparison sat/plugins/plugin_xep_0048.py @ 2562:26edcf3a30eb
core, setup: huge cleaning:
- moved directories from src and frontends/src to sat and sat_frontends, which is the recommanded naming convention
- move twisted directory to root
- removed all hacks from setup.py, and added missing dependencies, it is now clean
- use https URL for website in setup.py
- removed "Environment :: X11 Applications :: GTK", as wix is deprecated and removed
- renamed sat.sh to sat and fixed its installation
- added python_requires to specify Python version needed
- replaced glib2reactor which use deprecated code by gtk3reactor
sat can now be installed directly from virtualenv without using --system-site-packages anymore \o/
author | Goffi <goffi@goffi.org> |
---|---|
date | Mon, 02 Apr 2018 19:44:50 +0200 |
parents | src/plugins/plugin_xep_0048.py@0046283a285d |
children | 56f94936df1e |
comparison
equal
deleted
inserted
replaced
2561:bd30dc3ffe5a | 2562:26edcf3a30eb |
---|---|
1 #!/usr/bin/env python2 | |
2 # -*- coding: utf-8 -*- | |
3 | |
4 # SAT plugin for Bookmarks (xep-0048) | |
5 # Copyright (C) 2009-2018 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 sat.core.i18n import _, D_ | |
21 from sat.core import exceptions | |
22 from sat.core.constants import Const as C | |
23 from sat.memory.persistent import PersistentBinaryDict | |
24 from sat.tools import xml_tools | |
25 from sat.core.log import getLogger | |
26 log = getLogger(__name__) | |
27 from twisted.words.xish import domish | |
28 from twisted.words.protocols.jabber import jid | |
29 from twisted.words.protocols.jabber.error import StanzaError | |
30 | |
31 from twisted.internet import defer | |
32 | |
33 NS_BOOKMARKS = 'storage:bookmarks' | |
34 | |
35 PLUGIN_INFO = { | |
36 C.PI_NAME: "Bookmarks", | |
37 C.PI_IMPORT_NAME: "XEP-0048", | |
38 C.PI_TYPE: "XEP", | |
39 C.PI_PROTOCOLS: ["XEP-0048"], | |
40 C.PI_DEPENDENCIES: ["XEP-0045"], | |
41 C.PI_RECOMMENDATIONS: ["XEP-0049"], | |
42 C.PI_MAIN: "XEP_0048", | |
43 C.PI_HANDLER: "no", | |
44 C.PI_DESCRIPTION: _("""Implementation of bookmarks""") | |
45 } | |
46 | |
47 | |
48 class XEP_0048(object): | |
49 MUC_TYPE = 'muc' | |
50 URL_TYPE = 'url' | |
51 MUC_KEY = 'jid' | |
52 URL_KEY = 'url' | |
53 MUC_ATTRS = ('autojoin', 'name') | |
54 URL_ATTRS = ('name',) | |
55 | |
56 def __init__(self, host): | |
57 log.info(_("Bookmarks plugin initialization")) | |
58 self.host = host | |
59 # self.__menu_id = host.registerCallback(self._bookmarksMenu, with_data=True) | |
60 self.__bm_save_id = host.registerCallback(self._bookmarksSaveCb, with_data=True) | |
61 host.importMenu((D_("Groups"), D_("Bookmarks")), self._bookmarksMenu, security_limit=0, help_string=D_("Use and manage bookmarks")) | |
62 self.__selected_id = host.registerCallback(self._bookmarkSelectedCb, with_data=True) | |
63 host.bridge.addMethod("bookmarksList", ".plugin", in_sign='sss', out_sign='a{sa{sa{ss}}}', method=self._bookmarksList, async=True) | |
64 host.bridge.addMethod("bookmarksRemove", ".plugin", in_sign='ssss', out_sign='', method=self._bookmarksRemove, async=True) | |
65 host.bridge.addMethod("bookmarksAdd", ".plugin", in_sign='ssa{ss}ss', out_sign='', method=self._bookmarksAdd, async=True) | |
66 try: | |
67 self.private_plg = self.host.plugins["XEP-0049"] | |
68 except KeyError: | |
69 self.private_plg = None | |
70 try: | |
71 self.host.plugins[C.TEXT_CMDS].registerTextCommands(self) | |
72 except KeyError: | |
73 log.info(_("Text commands not available")) | |
74 | |
75 @defer.inlineCallbacks | |
76 def profileConnected(self, client): | |
77 local = client.bookmarks_local = PersistentBinaryDict(NS_BOOKMARKS, client.profile) | |
78 yield local.load() | |
79 if not local: | |
80 local[XEP_0048.MUC_TYPE] = dict() | |
81 local[XEP_0048.URL_TYPE] = dict() | |
82 private = yield self._getServerBookmarks('private', client.profile) | |
83 pubsub = client.bookmarks_pubsub = None | |
84 | |
85 for bookmarks in (local, private, pubsub): | |
86 if bookmarks is not None: | |
87 for (room_jid, data) in bookmarks[XEP_0048.MUC_TYPE].items(): | |
88 if data.get('autojoin', 'false') == 'true': | |
89 nick = data.get('nick', client.jid.user) | |
90 self.host.plugins['XEP-0045'].join(client, room_jid, nick, {}) | |
91 | |
92 @defer.inlineCallbacks | |
93 def _getServerBookmarks(self, storage_type, profile): | |
94 """Get distants bookmarks | |
95 | |
96 update also the client.bookmarks_[type] key, with None if service is not available | |
97 @param storage_type: storage type, can be: | |
98 - 'private': XEP-0049 storage | |
99 - 'pubsub': XEP-0223 storage | |
100 @param profile: %(doc_profile)s | |
101 @return: data dictionary, or None if feature is not available | |
102 """ | |
103 client = self.host.getClient(profile) | |
104 if storage_type == 'private': | |
105 try: | |
106 bookmarks_private_xml = yield self.private_plg.privateXMLGet('storage', NS_BOOKMARKS, profile) | |
107 data = client.bookmarks_private = self._bookmarkElt2Dict(bookmarks_private_xml) | |
108 except (StanzaError, AttributeError): | |
109 log.info(_("Private XML storage not available")) | |
110 data = client.bookmarks_private = None | |
111 elif storage_type == 'pubsub': | |
112 raise NotImplementedError | |
113 else: | |
114 raise ValueError("storage_type must be 'private' or 'pubsub'") | |
115 defer.returnValue(data) | |
116 | |
117 @defer.inlineCallbacks | |
118 def _setServerBookmarks(self, storage_type, bookmarks_elt, profile): | |
119 """Save bookmarks on server | |
120 | |
121 @param storage_type: storage type, can be: | |
122 - 'private': XEP-0049 storage | |
123 - 'pubsub': XEP-0223 storage | |
124 @param bookmarks_elt (domish.Element): bookmarks XML | |
125 @param profile: %(doc_profile)s | |
126 """ | |
127 if storage_type == 'private': | |
128 yield self.private_plg.privateXMLStore(bookmarks_elt, profile) | |
129 elif storage_type == 'pubsub': | |
130 raise NotImplementedError | |
131 else: | |
132 raise ValueError("storage_type must be 'private' or 'pubsub'") | |
133 | |
134 def _bookmarkElt2Dict(self, storage_elt): | |
135 """Parse bookmarks to get dictionary | |
136 @param storage_elt (domish.Element): bookmarks storage | |
137 @return (dict): bookmark data (key: bookmark type, value: list) where key can be: | |
138 - XEP_0048.MUC_TYPE | |
139 - XEP_0048.URL_TYPE | |
140 - value (dict): data as for addBookmark | |
141 """ | |
142 conf_data = {} | |
143 url_data = {} | |
144 | |
145 conference_elts = storage_elt.elements(NS_BOOKMARKS, 'conference') | |
146 for conference_elt in conference_elts: | |
147 try: | |
148 room_jid = jid.JID(conference_elt[XEP_0048.MUC_KEY]) | |
149 except KeyError: | |
150 log.warning ("invalid bookmark found, igoring it:\n%s" % conference_elt.toXml()) | |
151 continue | |
152 | |
153 data = conf_data[room_jid] = {} | |
154 | |
155 for attr in XEP_0048.MUC_ATTRS: | |
156 if conference_elt.hasAttribute(attr): | |
157 data[attr] = conference_elt[attr] | |
158 try: | |
159 data['nick'] = unicode(conference_elt.elements(NS_BOOKMARKS, 'nick').next()) | |
160 except StopIteration: | |
161 pass | |
162 # TODO: manage password (need to be secured, see XEP-0049 §4) | |
163 | |
164 url_elts = storage_elt.elements(NS_BOOKMARKS, 'url') | |
165 for url_elt in url_elts: | |
166 try: | |
167 url = url_elt[XEP_0048.URL_KEY] | |
168 except KeyError: | |
169 log.warning ("invalid bookmark found, igoring it:\n%s" % url_elt.toXml()) | |
170 continue | |
171 data = url_data[url] = {} | |
172 for attr in XEP_0048.URL_ATTRS: | |
173 if url_elt.hasAttribute(attr): | |
174 data[attr] = url_elt[attr] | |
175 | |
176 return {XEP_0048.MUC_TYPE: conf_data, XEP_0048.URL_TYPE: url_data} | |
177 | |
178 def _dict2BookmarkElt(self, type_, data): | |
179 """Construct a bookmark element from a data dict | |
180 @param data (dict): bookmark data (key: bookmark type, value: list) where key can be: | |
181 - XEP_0048.MUC_TYPE | |
182 - XEP_0048.URL_TYPE | |
183 - value (dict): data as for addBookmark | |
184 @return (domish.Element): bookmark element | |
185 """ | |
186 rooms_data = data.get(XEP_0048.MUC_TYPE, {}) | |
187 urls_data = data.get(XEP_0048.URL_TYPE, {}) | |
188 storage_elt = domish.Element((NS_BOOKMARKS, 'storage')) | |
189 for room_jid in rooms_data: | |
190 conference_elt = storage_elt.addElement('conference') | |
191 conference_elt[XEP_0048.MUC_KEY] = room_jid.full() | |
192 for attr in XEP_0048.MUC_ATTRS: | |
193 try: | |
194 conference_elt[attr] = rooms_data[room_jid][attr] | |
195 except KeyError: | |
196 pass | |
197 try: | |
198 conference_elt.addElement('nick', content=rooms_data[room_jid]['nick']) | |
199 except KeyError: | |
200 pass | |
201 | |
202 for url in urls_data: | |
203 url_elt = storage_elt.addElement('url') | |
204 url_elt[XEP_0048.URL_KEY] = url | |
205 for attr in XEP_0048.URL_ATTRS: | |
206 try: | |
207 url_elt[attr] = url[attr] | |
208 except KeyError: | |
209 pass | |
210 | |
211 return storage_elt | |
212 | |
213 def _bookmarkSelectedCb(self, data, profile): | |
214 try: | |
215 room_jid_s, nick = data['index'].split(' ', 1) | |
216 room_jid = jid.JID(room_jid_s) | |
217 except (KeyError, RuntimeError): | |
218 log.warning(_("No room jid selected")) | |
219 return {} | |
220 | |
221 client = self.host.getClient(profile) | |
222 d = self.host.plugins['XEP-0045'].join(client, room_jid, nick, {}) | |
223 def join_eb(failure): | |
224 log.warning(u"Error while trying to join room: {}".format(failure)) | |
225 # FIXME: failure are badly managed in plugin XEP-0045. Plugin XEP-0045 need to be fixed before managing errors correctly here | |
226 return {} | |
227 d.addCallbacks(lambda dummy: {}, join_eb) | |
228 return d | |
229 | |
230 def _bookmarksMenu(self, data, profile): | |
231 """ XMLUI activated by menu: return Gateways UI | |
232 @param profile: %(doc_profile)s | |
233 | |
234 """ | |
235 client = self.host.getClient(profile) | |
236 xmlui = xml_tools.XMLUI(title=_('Bookmarks manager')) | |
237 adv_list = xmlui.changeContainer('advanced_list', columns=3, selectable='single', callback_id=self.__selected_id) | |
238 for bookmarks in (client.bookmarks_local, client.bookmarks_private, client.bookmarks_pubsub): | |
239 if bookmarks is None: | |
240 continue | |
241 for (room_jid, data) in sorted(bookmarks[XEP_0048.MUC_TYPE].items(), key=lambda item: item[1].get('name',item[0].user)): | |
242 room_jid_s = room_jid.full() | |
243 adv_list.setRowIndex(u'%s %s' % (room_jid_s, data.get('nick') or client.jid.user)) | |
244 xmlui.addText(data.get('name','')) | |
245 xmlui.addJid(room_jid) | |
246 if data.get('autojoin', 'false') == 'true': | |
247 xmlui.addText('autojoin') | |
248 else: | |
249 xmlui.addEmpty() | |
250 adv_list.end() | |
251 xmlui.addDivider('dash') | |
252 xmlui.addText(_("add a bookmark")) | |
253 xmlui.changeContainer("pairs") | |
254 xmlui.addLabel(_('Name')) | |
255 xmlui.addString('name') | |
256 xmlui.addLabel(_('jid')) | |
257 xmlui.addString('jid') | |
258 xmlui.addLabel(_('Nickname')) | |
259 xmlui.addString('nick', client.jid.user) | |
260 xmlui.addLabel(_('Autojoin')) | |
261 xmlui.addBool('autojoin') | |
262 xmlui.changeContainer("vertical") | |
263 xmlui.addButton(self.__bm_save_id, _("Save"), ('name', 'jid', 'nick', 'autojoin')) | |
264 return {'xmlui': xmlui.toXml()} | |
265 | |
266 def _bookmarksSaveCb(self, data, profile): | |
267 bm_data = xml_tools.XMLUIResult2DataFormResult(data) | |
268 try: | |
269 location = jid.JID(bm_data.pop('jid')) | |
270 except KeyError: | |
271 raise exceptions.InternalError("Can't find mandatory key") | |
272 d = self.addBookmark(XEP_0048.MUC_TYPE, location, bm_data, profile_key=profile) | |
273 d.addCallback(lambda dummy: {}) | |
274 return d | |
275 | |
276 @defer.inlineCallbacks | |
277 def addBookmark(self, type_, location, data, storage_type="auto", profile_key=C.PROF_KEY_NONE): | |
278 """Store a new bookmark | |
279 | |
280 @param type_: bookmark type, one of: | |
281 - XEP_0048.MUC_TYPE: Multi-User chat room | |
282 - XEP_0048.URL_TYPE: web page URL | |
283 @param location: dependeding on type_, can be a MUC room jid or an url | |
284 @param data (dict): depending on type_, can contains the following keys: | |
285 - name: human readable name of the bookmark | |
286 - nick: user preferred room nick (default to user part of profile's jid) | |
287 - autojoin: "true" if room must be automatically joined on connection | |
288 - password: unused yet TODO | |
289 @param storage_type: where the bookmark will be stored, can be: | |
290 - "auto": find best available option: pubsub, private, local in that order | |
291 - "pubsub": PubSub private storage (XEP-0223) | |
292 - "private": Private XML storage (XEP-0049) | |
293 - "local": Store in SàT database | |
294 @param profile_key: %(doc_profile_key)s | |
295 """ | |
296 assert storage_type in ('auto', 'pubsub', 'private', 'local') | |
297 if type_ == XEP_0048.URL_TYPE and {'autojoin', 'nick'}.intersection(data.keys()): | |
298 raise ValueError("autojoin or nick can't be used with URLs") | |
299 client = self.host.getClient(profile_key) | |
300 if storage_type == 'auto': | |
301 if client.bookmarks_pubsub is not None: | |
302 storage_type = 'pubsub' | |
303 elif client.bookmarks_private is not None: | |
304 storage_type = 'private' | |
305 else: | |
306 storage_type = 'local' | |
307 log.warning(_("Bookmarks will be local only")) | |
308 log.info(_('Type selected for "auto" storage: %s') % storage_type) | |
309 | |
310 if storage_type == 'local': | |
311 client.bookmarks_local[type_][location] = data | |
312 yield client.bookmarks_local.force(type_) | |
313 else: | |
314 bookmarks = yield self._getServerBookmarks(storage_type, client.profile) | |
315 bookmarks[type_][location] = data | |
316 bookmark_elt = self._dict2BookmarkElt(type_, bookmarks) | |
317 yield self._setServerBookmarks(storage_type, bookmark_elt, client.profile) | |
318 | |
319 @defer.inlineCallbacks | |
320 def removeBookmark(self, type_, location, storage_type="all", profile_key=C.PROF_KEY_NONE): | |
321 """Remove a stored bookmark | |
322 | |
323 @param type_: bookmark type, one of: | |
324 - XEP_0048.MUC_TYPE: Multi-User chat room | |
325 - XEP_0048.URL_TYPE: web page URL | |
326 @param location: dependeding on type_, can be a MUC room jid or an url | |
327 @param storage_type: where the bookmark is stored, can be: | |
328 - "all": remove from everywhere | |
329 - "pubsub": PubSub private storage (XEP-0223) | |
330 - "private": Private XML storage (XEP-0049) | |
331 - "local": Store in SàT database | |
332 @param profile_key: %(doc_profile_key)s | |
333 """ | |
334 assert storage_type in ('all', 'pubsub', 'private', 'local') | |
335 client = self.host.getClient(profile_key) | |
336 | |
337 if storage_type in ('all', 'local'): | |
338 try: | |
339 del client.bookmarks_local[type_][location] | |
340 yield client.bookmarks_local.force(type_) | |
341 except KeyError: | |
342 log.debug("Bookmark is not present in local storage") | |
343 | |
344 if storage_type in ('all', 'private'): | |
345 bookmarks = yield self._getServerBookmarks('private', client.profile) | |
346 try: | |
347 del bookmarks[type_][location] | |
348 bookmark_elt = self._dict2BookmarkElt(type_, bookmarks) | |
349 yield self._setServerBookmarks('private', bookmark_elt, client.profile) | |
350 except KeyError: | |
351 log.debug("Bookmark is not present in private storage") | |
352 | |
353 if storage_type == 'pubsub': | |
354 raise NotImplementedError | |
355 | |
356 def _bookmarksList(self, type_, storage_location, profile_key=C.PROF_KEY_NONE): | |
357 """Return stored bookmarks | |
358 | |
359 @param type_: bookmark type, one of: | |
360 - XEP_0048.MUC_TYPE: Multi-User chat room | |
361 - XEP_0048.URL_TYPE: web page URL | |
362 @param storage_location: can be: | |
363 - 'all' | |
364 - 'local' | |
365 - 'private' | |
366 - 'pubsub' | |
367 @param profile_key: %(doc_profile_key)s | |
368 @param return (dict): (key: storage_location, value dict) with: | |
369 - value (dict): (key: bookmark_location, value: bookmark data) | |
370 """ | |
371 client = self.host.getClient(profile_key) | |
372 ret = {} | |
373 ret_d = defer.succeed(ret) | |
374 | |
375 def fillBookmarks(dummy, _storage_location): | |
376 bookmarks_ori = getattr(client, "bookmarks_" + _storage_location) | |
377 if bookmarks_ori is None: | |
378 return ret | |
379 data = bookmarks_ori[type_] | |
380 for bookmark in data: | |
381 ret[_storage_location][bookmark.full()] = data[bookmark].copy() | |
382 return ret | |
383 | |
384 for _storage_location in ('local', 'private', 'pubsub'): | |
385 if storage_location in ('all', _storage_location): | |
386 ret[_storage_location] = {} | |
387 if _storage_location in ('private',): | |
388 # we update distant bookmarks, just in case an other client added something | |
389 d = self._getServerBookmarks(_storage_location, client.profile) | |
390 else: | |
391 d = defer.succeed(None) | |
392 d.addCallback(fillBookmarks, _storage_location) | |
393 ret_d.addCallback(lambda dummy: d) | |
394 | |
395 return ret_d | |
396 | |
397 def _bookmarksRemove(self, type_, location, storage_location, profile_key=C.PROF_KEY_NONE): | |
398 """Return stored bookmarks | |
399 | |
400 @param type_: bookmark type, one of: | |
401 - XEP_0048.MUC_TYPE: Multi-User chat room | |
402 - XEP_0048.URL_TYPE: web page URL | |
403 @param location: dependeding on type_, can be a MUC room jid or an url | |
404 @param storage_location: can be: | |
405 - "all": remove from everywhere | |
406 - "pubsub": PubSub private storage (XEP-0223) | |
407 - "private": Private XML storage (XEP-0049) | |
408 - "local": Store in SàT database | |
409 @param profile_key: %(doc_profile_key)s | |
410 """ | |
411 if type_ == XEP_0048.MUC_TYPE: | |
412 location = jid.JID(location) | |
413 return self.removeBookmark(type_, location, storage_location, profile_key) | |
414 | |
415 def _bookmarksAdd(self, type_, location, data, storage_type="auto", profile_key=C.PROF_KEY_NONE): | |
416 if type_ == XEP_0048.MUC_TYPE: | |
417 location = jid.JID(location) | |
418 return self.addBookmark(type_, location, data, storage_type, profile_key) | |
419 | |
420 def cmd_bookmark(self, client, mess_data): | |
421 """(Un)bookmark a MUC room | |
422 | |
423 @command (group): [autojoin | remove] | |
424 - autojoin: join room automatically on connection | |
425 - remove: remove bookmark(s) for this room | |
426 """ | |
427 txt_cmd = self.host.plugins[C.TEXT_CMDS] | |
428 | |
429 options = mess_data["unparsed"].strip().split() | |
430 if options and options[0] not in ('autojoin', 'remove'): | |
431 txt_cmd.feedBack(client, _("Bad arguments"), mess_data) | |
432 return False | |
433 | |
434 room_jid = mess_data["to"].userhostJID() | |
435 | |
436 if "remove" in options: | |
437 self.removeBookmark(XEP_0048.MUC_TYPE, room_jid, profile_key = client.profile) | |
438 txt_cmd.feedBack(client, _("All [%s] bookmarks are being removed") % room_jid.full(), mess_data) | |
439 return False | |
440 | |
441 data = { "name": room_jid.user, | |
442 "nick": client.jid.user, | |
443 "autojoin": "true" if "autojoin" in options else "false", | |
444 } | |
445 self.addBookmark(XEP_0048.MUC_TYPE, room_jid, data, profile_key=client.profile) | |
446 txt_cmd.feedBack(client, _("Bookmark added"), mess_data) | |
447 | |
448 return False |