comparison sat/plugins/plugin_xep_0055.py @ 3028:ab2696e34d29

Python 3 port: /!\ this is a huge commit /!\ starting from this commit, SàT is needs Python 3.6+ /!\ SàT maybe be instable or some feature may not work anymore, this will improve with time This patch port backend, bridge and frontends to Python 3. Roughly this has been done this way: - 2to3 tools has been applied (with python 3.7) - all references to python2 have been replaced with python3 (notably shebangs) - fixed files not handled by 2to3 (notably the shell script) - several manual fixes - fixed issues reported by Python 3 that where not handled in Python 2 - replaced "async" with "async_" when needed (it's a reserved word from Python 3.7) - replaced zope's "implements" with @implementer decorator - temporary hack to handle data pickled in database, as str or bytes may be returned, to be checked later - fixed hash comparison for password - removed some code which is not needed anymore with Python 3 - deactivated some code which needs to be checked (notably certificate validation) - tested with jp, fixed reported issues until some basic commands worked - ported Primitivus (after porting dependencies like urwid satext) - more manual fixes
author Goffi <goffi@goffi.org>
date Tue, 13 Aug 2019 19:08:41 +0200
parents 003b8b4b56a7
children 9d0df638c8b4
comparison
equal deleted inserted replaced
3027:ff5bcb12ae60 3028:ab2696e34d29
1 #!/usr/bin/env python2 1 #!/usr/bin/env python3
2 # -*- coding: utf-8 -*- 2 # -*- coding: utf-8 -*-
3 3
4 # SAT plugin for Jabber Search (xep-0055) 4 # SAT plugin for Jabber Search (xep-0055)
5 # Copyright (C) 2009-2019 Jérôme Poisson (goffi@goffi.org) 5 # Copyright (C) 2009-2019 Jérôme Poisson (goffi@goffi.org)
6 6
34 34
35 try: 35 try:
36 from twisted.words.protocols.xmlstream import XMPPHandler 36 from twisted.words.protocols.xmlstream import XMPPHandler
37 except ImportError: 37 except ImportError:
38 from wokkel.subprotocols import XMPPHandler 38 from wokkel.subprotocols import XMPPHandler
39 from zope.interface import implements 39 from zope.interface import implementer
40 40
41 41
42 NS_SEARCH = "jabber:iq:search" 42 NS_SEARCH = "jabber:iq:search"
43 43
44 PLUGIN_INFO = { 44 PLUGIN_INFO = {
82 "searchGetFieldsUI", 82 "searchGetFieldsUI",
83 ".plugin", 83 ".plugin",
84 in_sign="ss", 84 in_sign="ss",
85 out_sign="s", 85 out_sign="s",
86 method=self._getFieldsUI, 86 method=self._getFieldsUI,
87 async=True, 87 async_=True,
88 ) 88 )
89 host.bridge.addMethod( 89 host.bridge.addMethod(
90 "searchRequest", 90 "searchRequest",
91 ".plugin", 91 ".plugin",
92 in_sign="sa{ss}s", 92 in_sign="sa{ss}s",
93 out_sign="s", 93 out_sign="s",
94 method=self._searchRequest, 94 method=self._searchRequest,
95 async=True, 95 async_=True,
96 ) 96 )
97 97
98 self.__search_menu_id = host.registerCallback(self._getMainUI, with_data=True) 98 self.__search_menu_id = host.registerCallback(self._getMainUI, with_data=True)
99 host.importMenu( 99 host.importMenu(
100 (D_("Contacts"), D_("Search directory")), 100 (D_("Contacts"), D_("Search directory")),
191 main_ui.addButton(self.__search_menu_id, _("Search"), (FIELD_SINGLE,)) 191 main_ui.addButton(self.__search_menu_id, _("Search"), (FIELD_SINGLE,))
192 main_ui.addDivider("blank") 192 main_ui.addDivider("blank")
193 main_ui.addDivider("blank") # a blank line again after the button 193 main_ui.addDivider("blank") # a blank line again after the button
194 194
195 simple_data = { 195 simple_data = {
196 key: value for key, value in data.iteritems() if key in (FIELD_SINGLE,) 196 key: value for key, value in data.items() if key in (FIELD_SINGLE,)
197 } 197 }
198 if simple_data: 198 if simple_data:
199 log.debug("Simple search with %s on %s" % (simple_data, service_jid)) 199 log.debug("Simple search with %s on %s" % (simple_data, service_jid))
200 sub_cont.parent.setSelected(True) 200 sub_cont.parent.setSelected(True)
201 main_ui.changeContainer(sub_cont.append(xml_tools.VerticalContainer(main_ui))) 201 main_ui.changeContainer(sub_cont.append(xml_tools.VerticalContainer(main_ui)))
232 # refresh button has been pushed, select the tab 232 # refresh button has been pushed, select the tab
233 sub_cont.parent.setSelected(True) 233 sub_cont.parent.setSelected(True)
234 # get the selected service 234 # get the selected service
235 service_jid_s = data.get("service_jid_extra", "") 235 service_jid_s = data.get("service_jid_extra", "")
236 if not service_jid_s: 236 if not service_jid_s:
237 service_jid_s = data.get("service_jid", unicode(services[0])) 237 service_jid_s = data.get("service_jid", str(services[0]))
238 log.debug("Refreshing search fields for %s" % service_jid_s) 238 log.debug("Refreshing search fields for %s" % service_jid_s)
239 else: 239 else:
240 service_jid_s = data.get(FIELD_CURRENT_SERVICE, unicode(services[0])) 240 service_jid_s = data.get(FIELD_CURRENT_SERVICE, str(services[0]))
241 services_s = [unicode(service) for service in services] 241 services_s = [str(service) for service in services]
242 if service_jid_s not in services_s: 242 if service_jid_s not in services_s:
243 services_s.append(service_jid_s) 243 services_s.append(service_jid_s)
244 244
245 main_ui.changeContainer(sub_cont.append(xml_tools.PairsContainer(main_ui))) 245 main_ui.changeContainer(sub_cont.append(xml_tools.PairsContainer(main_ui)))
246 main_ui.addLabel(_("Search on")) 246 main_ui.addLabel(_("Search on"))
284 284
285 @return: a __ Deferred 285 @return: a __ Deferred
286 """ 286 """
287 field_list = data_form.Form.fromElement(form_elt).fieldList 287 field_list = data_form.Form.fromElement(form_elt).fieldList
288 adv_fields = [field.var for field in field_list if field.var] 288 adv_fields = [field.var for field in field_list if field.var]
289 adv_data = {key: value for key, value in data.iteritems() if key in adv_fields} 289 adv_data = {key: value for key, value in data.items() if key in adv_fields}
290 290
291 xml_tools.dataForm2Widgets(main_ui, data_form.Form.fromElement(form_elt)) 291 xml_tools.dataForm2Widgets(main_ui, data_form.Form.fromElement(form_elt))
292 292
293 # refill the submitted values 293 # refill the submitted values
294 # FIXME: wokkel's data_form.Form.fromElement doesn't parse the values, so we do it directly in XMLUI for now 294 # FIXME: wokkel's data_form.Form.fromElement doesn't parse the values, so we do it directly in XMLUI for now
330 if [child for child in elt.children if child.name == "item"]: 330 if [child for child in elt.children if child.name == "item"]:
331 headers, xmlui_data = xml_tools.dataFormEltResult2XMLUIData(elt) 331 headers, xmlui_data = xml_tools.dataFormEltResult2XMLUIData(elt)
332 if "jid" in headers: # use XMLUI JidsListWidget to display the results 332 if "jid" in headers: # use XMLUI JidsListWidget to display the results
333 values = {} 333 values = {}
334 for i in range(len(xmlui_data)): 334 for i in range(len(xmlui_data)):
335 header = headers.keys()[i % len(headers)] 335 header = list(headers.keys())[i % len(headers)]
336 widget_type, widget_args, widget_kwargs = xmlui_data[i] 336 widget_type, widget_args, widget_kwargs = xmlui_data[i]
337 value = widget_args[0] 337 value = widget_args[0]
338 values.setdefault(header, []).append( 338 values.setdefault(header, []).append(
339 jid.JID(value) if header == "jid" else value 339 jid.JID(value) if header == "jid" else value
340 ) 340 )
341 main_ui.addJidsList(jids=values["jid"], name=D_(u"Search results")) 341 main_ui.addJidsList(jids=values["jid"], name=D_("Search results"))
342 # TODO: also display the values other than JID 342 # TODO: also display the values other than JID
343 else: 343 else:
344 xml_tools.XMLUIData2AdvancedList(main_ui, headers, xmlui_data) 344 xml_tools.XMLUIData2AdvancedList(main_ui, headers, xmlui_data)
345 else: 345 else:
346 main_ui.addText(D_("The search gave no result")) 346 main_ui.addText(D_("The search gave no result"))
379 379
380 @param answer (domish.Element): search query element 380 @param answer (domish.Element): search query element
381 @return: domish.Element 381 @return: domish.Element
382 """ 382 """
383 try: 383 try:
384 query_elts = answer.elements("jabber:iq:search", "query").next() 384 query_elts = next(answer.elements("jabber:iq:search", "query"))
385 except StopIteration: 385 except StopIteration:
386 log.info(_("No query element found")) 386 log.info(_("No query element found"))
387 raise DataError # FIXME: StanzaError is probably more appropriate, check the RFC 387 raise DataError # FIXME: StanzaError is probably more appropriate, check the RFC
388 try: 388 try:
389 form_elt = query_elts.elements(data_form.NS_X_DATA, "x").next() 389 form_elt = next(query_elts.elements(data_form.NS_X_DATA, "x"))
390 except StopIteration: 390 except StopIteration:
391 log.info(_("No data form found")) 391 log.info(_("No data form found"))
392 raise NotImplementedError( 392 raise NotImplementedError(
393 "Only search through data form is implemented so far" 393 "Only search through data form is implemented so far"
394 ) 394 )
398 """Errback to self.getFieldsUI. 398 """Errback to self.getFieldsUI.
399 399
400 @param failure (defer.failure.Failure): twisted failure 400 @param failure (defer.failure.Failure): twisted failure
401 @raise: the unchanged defer.failure.Failure 401 @raise: the unchanged defer.failure.Failure
402 """ 402 """
403 log.info(_("Fields request failure: %s") % unicode(failure.getErrorMessage())) 403 log.info(_("Fields request failure: %s") % str(failure.getErrorMessage()))
404 raise failure 404 raise failure
405 405
406 ## Do the search ## 406 ## Do the search ##
407 407
408 def _searchRequest(self, to_jid_s, search_data, profile_key): 408 def _searchRequest(self, to_jid_s, search_data, profile_key):
486 486
487 @param answer (domish.Element): search query element 487 @param answer (domish.Element): search query element
488 @return: domish.Element 488 @return: domish.Element
489 """ 489 """
490 try: 490 try:
491 query_elts = answer.elements("jabber:iq:search", "query").next() 491 query_elts = next(answer.elements("jabber:iq:search", "query"))
492 except StopIteration: 492 except StopIteration:
493 log.info(_("No query element found")) 493 log.info(_("No query element found"))
494 raise DataError # FIXME: StanzaError is probably more appropriate, check the RFC 494 raise DataError # FIXME: StanzaError is probably more appropriate, check the RFC
495 try: 495 try:
496 form_elt = query_elts.elements(data_form.NS_X_DATA, "x").next() 496 form_elt = next(query_elts.elements(data_form.NS_X_DATA, "x"))
497 except StopIteration: 497 except StopIteration:
498 log.info(_("No data form found")) 498 log.info(_("No data form found"))
499 raise NotImplementedError( 499 raise NotImplementedError(
500 "Only search through data form is implemented so far" 500 "Only search through data form is implemented so far"
501 ) 501 )
505 """Errback to self.searchRequest. 505 """Errback to self.searchRequest.
506 506
507 @param failure (defer.failure.Failure): twisted failure 507 @param failure (defer.failure.Failure): twisted failure
508 @raise: the unchanged defer.failure.Failure 508 @raise: the unchanged defer.failure.Failure
509 """ 509 """
510 log.info(_("Search request failure: %s") % unicode(failure.getErrorMessage())) 510 log.info(_("Search request failure: %s") % str(failure.getErrorMessage()))
511 raise failure 511 raise failure
512 512
513 513
514 @implementer(iwokkel.IDisco)
514 class XEP_0055_handler(XMPPHandler): 515 class XEP_0055_handler(XMPPHandler):
515 implements(iwokkel.IDisco)
516 516
517 def __init__(self, plugin_parent, profile): 517 def __init__(self, plugin_parent, profile):
518 self.plugin_parent = plugin_parent 518 self.plugin_parent = plugin_parent
519 self.host = plugin_parent.host 519 self.host = plugin_parent.host
520 self.profile = profile 520 self.profile = profile