comparison sat/plugins/plugin_xep_0277.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 0b7ce5daee9b
children 73db9db8b9e1
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 microblogging over XMPP (xep-0277) 4 # SAT plugin for microblogging over XMPP (xep-0277)
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
35 from sat.tools.common import uri as xmpp_uri 35 from sat.tools.common import uri as xmpp_uri
36 36
37 # XXX: sat_tmp.wokkel.pubsub is actually used instead of wokkel version 37 # XXX: sat_tmp.wokkel.pubsub is actually used instead of wokkel version
38 from wokkel import pubsub 38 from wokkel import pubsub
39 from wokkel import disco, iwokkel 39 from wokkel import disco, iwokkel
40 from zope.interface import implements 40 from zope.interface import implementer
41 import shortuuid 41 import shortuuid
42 import time 42 import time
43 import dateutil 43 import dateutil
44 import calendar 44 import calendar
45 import urlparse 45 import urllib.parse
46 46
47 NS_MICROBLOG = "urn:xmpp:microblog:0" 47 NS_MICROBLOG = "urn:xmpp:microblog:0"
48 NS_ATOM = "http://www.w3.org/2005/Atom" 48 NS_ATOM = "http://www.w3.org/2005/Atom"
49 NS_PUBSUB_EVENT = "{}{}".format(pubsub.NS_PUBSUB, "#event") 49 NS_PUBSUB_EVENT = "{}{}".format(pubsub.NS_PUBSUB, "#event")
50 NS_COMMENT_PREFIX = "{}:comments/".format(NS_MICROBLOG) 50 NS_COMMENT_PREFIX = "{}:comments/".format(NS_MICROBLOG)
69 69
70 class XEP_0277(object): 70 class XEP_0277(object):
71 namespace = NS_MICROBLOG 71 namespace = NS_MICROBLOG
72 72
73 def __init__(self, host): 73 def __init__(self, host):
74 log.info(_(u"Microblogging plugin initialization")) 74 log.info(_("Microblogging plugin initialization"))
75 self.host = host 75 self.host = host
76 host.registerNamespace("microblog", NS_MICROBLOG) 76 host.registerNamespace("microblog", NS_MICROBLOG)
77 self._p = self.host.plugins[ 77 self._p = self.host.plugins[
78 "XEP-0060" 78 "XEP-0060"
79 ] # this facilitate the access to pubsub plugin 79 ] # this facilitate the access to pubsub plugin
86 "mbSend", 86 "mbSend",
87 ".plugin", 87 ".plugin",
88 in_sign="ssss", 88 in_sign="ssss",
89 out_sign="", 89 out_sign="",
90 method=self._mbSend, 90 method=self._mbSend,
91 async=True, 91 async_=True,
92 ) 92 )
93 host.bridge.addMethod( 93 host.bridge.addMethod(
94 "mbRetract", 94 "mbRetract",
95 ".plugin", 95 ".plugin",
96 in_sign="ssss", 96 in_sign="ssss",
97 out_sign="", 97 out_sign="",
98 method=self._mbRetract, 98 method=self._mbRetract,
99 async=True, 99 async_=True,
100 ) 100 )
101 host.bridge.addMethod( 101 host.bridge.addMethod(
102 "mbGet", 102 "mbGet",
103 ".plugin", 103 ".plugin",
104 in_sign="ssiasa{ss}s", 104 in_sign="ssiasa{ss}s",
105 out_sign="(asa{ss})", 105 out_sign="(asa{ss})",
106 method=self._mbGet, 106 method=self._mbGet,
107 async=True, 107 async_=True,
108 ) 108 )
109 host.bridge.addMethod( 109 host.bridge.addMethod(
110 "mbSetAccess", 110 "mbSetAccess",
111 ".plugin", 111 ".plugin",
112 in_sign="ss", 112 in_sign="ss",
113 out_sign="", 113 out_sign="",
114 method=self.mbSetAccess, 114 method=self.mbSetAccess,
115 async=True, 115 async_=True,
116 ) 116 )
117 host.bridge.addMethod( 117 host.bridge.addMethod(
118 "mbSubscribeToMany", 118 "mbSubscribeToMany",
119 ".plugin", 119 ".plugin",
120 in_sign="sass", 120 in_sign="sass",
125 "mbGetFromManyRTResult", 125 "mbGetFromManyRTResult",
126 ".plugin", 126 ".plugin",
127 in_sign="ss", 127 in_sign="ss",
128 out_sign="(ua(sssasa{ss}))", 128 out_sign="(ua(sssasa{ss}))",
129 method=self._mbGetFromManyRTResult, 129 method=self._mbGetFromManyRTResult,
130 async=True, 130 async_=True,
131 ) 131 )
132 host.bridge.addMethod( 132 host.bridge.addMethod(
133 "mbGetFromMany", 133 "mbGetFromMany",
134 ".plugin", 134 ".plugin",
135 in_sign="sasia{ss}s", 135 in_sign="sasia{ss}s",
140 "mbGetFromManyWithCommentsRTResult", 140 "mbGetFromManyWithCommentsRTResult",
141 ".plugin", 141 ".plugin",
142 in_sign="ss", 142 in_sign="ss",
143 out_sign="(ua(sssa(sa(sssasa{ss}))a{ss}))", 143 out_sign="(ua(sssa(sa(sssasa{ss}))a{ss}))",
144 method=self._mbGetFromManyWithCommentsRTResult, 144 method=self._mbGetFromManyWithCommentsRTResult,
145 async=True, 145 async_=True,
146 ) 146 )
147 host.bridge.addMethod( 147 host.bridge.addMethod(
148 "mbGetFromManyWithComments", 148 "mbGetFromManyWithComments",
149 ".plugin", 149 ".plugin",
150 in_sign="sasiia{ss}a{ss}s", 150 in_sign="sasiia{ss}a{ss}s",
236 if type_ == "xhtml": 236 if type_ == "xhtml":
237 data_elt = elem.firstChildElement() 237 data_elt = elem.firstChildElement()
238 if data_elt is None: 238 if data_elt is None:
239 raise failure.Failure( 239 raise failure.Failure(
240 exceptions.DataError( 240 exceptions.DataError(
241 u"XHML content not wrapped in a <div/> element, this is not " 241 "XHML content not wrapped in a <div/> element, this is not "
242 u"standard !" 242 "standard !"
243 ) 243 )
244 ) 244 )
245 if data_elt.uri != C.NS_XHTML: 245 if data_elt.uri != C.NS_XHTML:
246 raise failure.Failure( 246 raise failure.Failure(
247 exceptions.DataError( 247 exceptions.DataError(
248 _("Content of type XHTML must declare its namespace!") 248 _("Content of type XHTML must declare its namespace!")
249 ) 249 )
250 ) 250 )
251 key = check_conflict(u"{}_xhtml".format(elem.name)) 251 key = check_conflict("{}_xhtml".format(elem.name))
252 data = data_elt.toXml() 252 data = data_elt.toXml()
253 microblog_data[key] = yield self.host.plugins["TEXT_SYNTAXES"].cleanXHTML( 253 microblog_data[key] = yield self.host.plugins["TEXT_SYNTAXES"].cleanXHTML(
254 data 254 data
255 ) 255 )
256 else: 256 else:
257 key = check_conflict(elem.name) 257 key = check_conflict(elem.name)
258 microblog_data[key] = unicode(elem) 258 microblog_data[key] = str(elem)
259 259
260 id_ = item_elt.getAttribute("id", "") # there can be no id for transient nodes 260 id_ = item_elt.getAttribute("id", "") # there can be no id for transient nodes
261 microblog_data[u"id"] = id_ 261 microblog_data["id"] = id_
262 if item_elt.uri not in (pubsub.NS_PUBSUB, NS_PUBSUB_EVENT): 262 if item_elt.uri not in (pubsub.NS_PUBSUB, NS_PUBSUB_EVENT):
263 msg = u"Unsupported namespace {ns} in pubsub item {id_}".format( 263 msg = "Unsupported namespace {ns} in pubsub item {id_}".format(
264 ns=item_elt.uri, id_=id_ 264 ns=item_elt.uri, id_=id_
265 ) 265 )
266 log.warning(msg) 266 log.warning(msg)
267 raise failure.Failure(exceptions.DataError(msg)) 267 raise failure.Failure(exceptions.DataError(msg))
268 268
269 try: 269 try:
270 entry_elt = item_elt.elements(NS_ATOM, "entry").next() 270 entry_elt = next(item_elt.elements(NS_ATOM, "entry"))
271 except StopIteration: 271 except StopIteration:
272 msg = u"No atom entry found in the pubsub item {}".format(id_) 272 msg = "No atom entry found in the pubsub item {}".format(id_)
273 raise failure.Failure(exceptions.DataError(msg)) 273 raise failure.Failure(exceptions.DataError(msg))
274 274
275 # language 275 # language
276 try: 276 try:
277 microblog_data[u"language"] = entry_elt[(C.NS_XML, u"lang")].strip() 277 microblog_data["language"] = entry_elt[(C.NS_XML, "lang")].strip()
278 except KeyError: 278 except KeyError:
279 pass 279 pass
280 280
281 # atom:id 281 # atom:id
282 try: 282 try:
283 id_elt = entry_elt.elements(NS_ATOM, "id").next() 283 id_elt = next(entry_elt.elements(NS_ATOM, "id"))
284 except StopIteration: 284 except StopIteration:
285 msg = (u"No atom id found in the pubsub item {}, this is not standard !" 285 msg = ("No atom id found in the pubsub item {}, this is not standard !"
286 .format(id_)) 286 .format(id_))
287 log.warning(msg) 287 log.warning(msg)
288 microblog_data[u"atom_id"] = "" 288 microblog_data["atom_id"] = ""
289 else: 289 else:
290 microblog_data[u"atom_id"] = unicode(id_elt) 290 microblog_data["atom_id"] = str(id_elt)
291 291
292 # title/content(s) 292 # title/content(s)
293 293
294 # FIXME: ATOM and XEP-0277 only allow 1 <title/> element 294 # FIXME: ATOM and XEP-0277 only allow 1 <title/> element
295 # but in the wild we have some blogs with several ones 295 # but in the wild we have some blogs with several ones
300 # except StopIteration: 300 # except StopIteration:
301 # msg = u'No atom title found in the pubsub item {}'.format(id_) 301 # msg = u'No atom title found in the pubsub item {}'.format(id_)
302 # raise failure.Failure(exceptions.DataError(msg)) 302 # raise failure.Failure(exceptions.DataError(msg))
303 title_elts = list(entry_elt.elements(NS_ATOM, "title")) 303 title_elts = list(entry_elt.elements(NS_ATOM, "title"))
304 if not title_elts: 304 if not title_elts:
305 msg = u"No atom title found in the pubsub item {}".format(id_) 305 msg = "No atom title found in the pubsub item {}".format(id_)
306 raise failure.Failure(exceptions.DataError(msg)) 306 raise failure.Failure(exceptions.DataError(msg))
307 for title_elt in title_elts: 307 for title_elt in title_elts:
308 yield parseElement(title_elt) 308 yield parseElement(title_elt)
309 309
310 # FIXME: as for <title/>, Atom only authorise at most 1 content 310 # FIXME: as for <title/>, Atom only authorise at most 1 content
315 315
316 # we check that text content is present 316 # we check that text content is present
317 for key in ("title", "content"): 317 for key in ("title", "content"):
318 if key not in microblog_data and ("{}_xhtml".format(key)) in microblog_data: 318 if key not in microblog_data and ("{}_xhtml".format(key)) in microblog_data:
319 log.warning( 319 log.warning(
320 u"item {id_} provide a {key}_xhtml data but not a text one".format( 320 "item {id_} provide a {key}_xhtml data but not a text one".format(
321 id_=id_, key=key 321 id_=id_, key=key
322 ) 322 )
323 ) 323 )
324 # ... and do the conversion if it's not 324 # ... and do the conversion if it's not
325 microblog_data[key] = yield self.host.plugins["TEXT_SYNTAXES"].convert( 325 microblog_data[key] = yield self.host.plugins["TEXT_SYNTAXES"].convert(
326 microblog_data[u"{}_xhtml".format(key)], 326 microblog_data["{}_xhtml".format(key)],
327 self.host.plugins["TEXT_SYNTAXES"].SYNTAX_XHTML, 327 self.host.plugins["TEXT_SYNTAXES"].SYNTAX_XHTML,
328 self.host.plugins["TEXT_SYNTAXES"].SYNTAX_TEXT, 328 self.host.plugins["TEXT_SYNTAXES"].SYNTAX_TEXT,
329 False, 329 False,
330 ) 330 )
331 331
332 if "content" not in microblog_data: 332 if "content" not in microblog_data:
333 # use the atom title data as the microblog body content 333 # use the atom title data as the microblog body content
334 microblog_data[u"content"] = microblog_data[u"title"] 334 microblog_data["content"] = microblog_data["title"]
335 del microblog_data[u"title"] 335 del microblog_data["title"]
336 if "title_xhtml" in microblog_data: 336 if "title_xhtml" in microblog_data:
337 microblog_data[u"content_xhtml"] = microblog_data[u"title_xhtml"] 337 microblog_data["content_xhtml"] = microblog_data["title_xhtml"]
338 del microblog_data[u"title_xhtml"] 338 del microblog_data["title_xhtml"]
339 339
340 # published/updated dates 340 # published/updated dates
341 try: 341 try:
342 updated_elt = entry_elt.elements(NS_ATOM, "updated").next() 342 updated_elt = next(entry_elt.elements(NS_ATOM, "updated"))
343 except StopIteration: 343 except StopIteration:
344 msg = u"No atom updated element found in the pubsub item {}".format(id_) 344 msg = "No atom updated element found in the pubsub item {}".format(id_)
345 raise failure.Failure(exceptions.DataError(msg)) 345 raise failure.Failure(exceptions.DataError(msg))
346 microblog_data[u"updated"] = calendar.timegm( 346 microblog_data["updated"] = calendar.timegm(
347 dateutil.parser.parse(unicode(updated_elt)).utctimetuple() 347 dateutil.parser.parse(str(updated_elt)).utctimetuple()
348 ) 348 )
349 try: 349 try:
350 published_elt = entry_elt.elements(NS_ATOM, "published").next() 350 published_elt = next(entry_elt.elements(NS_ATOM, "published"))
351 except StopIteration: 351 except StopIteration:
352 microblog_data[u"published"] = microblog_data[u"updated"] 352 microblog_data["published"] = microblog_data["updated"]
353 else: 353 else:
354 microblog_data[u"published"] = calendar.timegm( 354 microblog_data["published"] = calendar.timegm(
355 dateutil.parser.parse(unicode(published_elt)).utctimetuple() 355 dateutil.parser.parse(str(published_elt)).utctimetuple()
356 ) 356 )
357 357
358 # links 358 # links
359 for link_elt in entry_elt.elements(NS_ATOM, "link"): 359 for link_elt in entry_elt.elements(NS_ATOM, "link"):
360 if ( 360 if (
364 key = check_conflict("comments", True) 364 key = check_conflict("comments", True)
365 microblog_data[key] = link_elt["href"] 365 microblog_data[key] = link_elt["href"]
366 try: 366 try:
367 service, node = self.parseCommentUrl(microblog_data[key]) 367 service, node = self.parseCommentUrl(microblog_data[key])
368 except: 368 except:
369 log.warning(u"Can't parse url {}".format(microblog_data[key])) 369 log.warning("Can't parse url {}".format(microblog_data[key]))
370 del microblog_data[key] 370 del microblog_data[key]
371 else: 371 else:
372 microblog_data[u"{}_service".format(key)] = service.full() 372 microblog_data["{}_service".format(key)] = service.full()
373 microblog_data[u"{}_node".format(key)] = node 373 microblog_data["{}_node".format(key)] = node
374 else: 374 else:
375 rel = link_elt.getAttribute("rel", "") 375 rel = link_elt.getAttribute("rel", "")
376 title = link_elt.getAttribute("title", "") 376 title = link_elt.getAttribute("title", "")
377 href = link_elt.getAttribute("href", "") 377 href = link_elt.getAttribute("href", "")
378 log.warning( 378 log.warning(
379 u"Unmanaged link element: rel={rel} title={title} href={href}".format( 379 "Unmanaged link element: rel={rel} title={title} href={href}".format(
380 rel=rel, title=title, href=href 380 rel=rel, title=title, href=href
381 ) 381 )
382 ) 382 )
383 383
384 # author 384 # author
385 try: 385 try:
386 author_elt = entry_elt.elements(NS_ATOM, "author").next() 386 author_elt = next(entry_elt.elements(NS_ATOM, "author"))
387 except StopIteration: 387 except StopIteration:
388 log.debug(u"Can't find author element in item {}".format(id_)) 388 log.debug("Can't find author element in item {}".format(id_))
389 else: 389 else:
390 publisher = item_elt.getAttribute("publisher") 390 publisher = item_elt.getAttribute("publisher")
391 # name 391 # name
392 try: 392 try:
393 name_elt = author_elt.elements(NS_ATOM, "name").next() 393 name_elt = next(author_elt.elements(NS_ATOM, "name"))
394 except StopIteration: 394 except StopIteration:
395 log.warning( 395 log.warning(
396 u"No name element found in author element of item {}".format(id_) 396 "No name element found in author element of item {}".format(id_)
397 ) 397 )
398 else: 398 else:
399 microblog_data[u"author"] = unicode(name_elt) 399 microblog_data["author"] = str(name_elt)
400 # uri 400 # uri
401 try: 401 try:
402 uri_elt = author_elt.elements(NS_ATOM, "uri").next() 402 uri_elt = next(author_elt.elements(NS_ATOM, "uri"))
403 except StopIteration: 403 except StopIteration:
404 log.debug( 404 log.debug(
405 u"No uri element found in author element of item {}".format(id_) 405 "No uri element found in author element of item {}".format(id_)
406 ) 406 )
407 if publisher: 407 if publisher:
408 microblog_data[u"author_jid"] = publisher 408 microblog_data["author_jid"] = publisher
409 else: 409 else:
410 uri = unicode(uri_elt) 410 uri = str(uri_elt)
411 if uri.startswith("xmpp:"): 411 if uri.startswith("xmpp:"):
412 uri = uri[5:] 412 uri = uri[5:]
413 microblog_data[u"author_jid"] = uri 413 microblog_data["author_jid"] = uri
414 else: 414 else:
415 microblog_data[u"author_jid"] = ( 415 microblog_data["author_jid"] = (
416 item_elt.getAttribute(u"publisher") or "" 416 item_elt.getAttribute("publisher") or ""
417 ) 417 )
418 418
419 if not publisher: 419 if not publisher:
420 log.debug(u"No publisher attribute, we can't verify author jid") 420 log.debug("No publisher attribute, we can't verify author jid")
421 microblog_data[u"author_jid_verified"] = False 421 microblog_data["author_jid_verified"] = False
422 elif jid.JID(publisher).userhostJID() == jid.JID(uri).userhostJID(): 422 elif jid.JID(publisher).userhostJID() == jid.JID(uri).userhostJID():
423 microblog_data[u"author_jid_verified"] = True 423 microblog_data["author_jid_verified"] = True
424 else: 424 else:
425 log.warning( 425 log.warning(
426 u"item atom:uri differ from publisher attribute, spoofing " 426 "item atom:uri differ from publisher attribute, spoofing "
427 u"attempt ? atom:uri = {} publisher = {}".format( 427 "attempt ? atom:uri = {} publisher = {}".format(
428 uri, item_elt.getAttribute("publisher") 428 uri, item_elt.getAttribute("publisher")
429 ) 429 )
430 ) 430 )
431 microblog_data[u"author_jid_verified"] = False 431 microblog_data["author_jid_verified"] = False
432 # email 432 # email
433 try: 433 try:
434 email_elt = author_elt.elements(NS_ATOM, "email").next() 434 email_elt = next(author_elt.elements(NS_ATOM, "email"))
435 except StopIteration: 435 except StopIteration:
436 pass 436 pass
437 else: 437 else:
438 microblog_data[u"author_email"] = unicode(email_elt) 438 microblog_data["author_email"] = str(email_elt)
439 439
440 # categories 440 # categories
441 categories = [ 441 categories = [
442 category_elt.getAttribute("term", "") 442 category_elt.getAttribute("term", "")
443 for category_elt in entry_elt.elements(NS_ATOM, "category") 443 for category_elt in entry_elt.elements(NS_ATOM, "category")
444 ] 444 ]
445 microblog_data[u"tags"] = categories 445 microblog_data["tags"] = categories
446 446
447 ## the trigger ## 447 ## the trigger ##
448 # if other plugins have things to add or change 448 # if other plugins have things to add or change
449 yield self.host.trigger.point( 449 yield self.host.trigger.point(
450 "XEP-0277_item2data", item_elt, entry_elt, microblog_data 450 "XEP-0277_item2data", item_elt, entry_elt, microblog_data
465 @return: deferred which fire domish.Element 465 @return: deferred which fire domish.Element
466 """ 466 """
467 entry_elt = domish.Element((NS_ATOM, "entry")) 467 entry_elt = domish.Element((NS_ATOM, "entry"))
468 468
469 ## language ## 469 ## language ##
470 if u"language" in data: 470 if "language" in data:
471 entry_elt[(C.NS_XML, u"lang")] = data[u"language"].strip() 471 entry_elt[(C.NS_XML, "lang")] = data["language"].strip()
472 472
473 ## content and title ## 473 ## content and title ##
474 synt = self.host.plugins["TEXT_SYNTAXES"] 474 synt = self.host.plugins["TEXT_SYNTAXES"]
475 475
476 for elem_name in ("title", "content"): 476 for elem_name in ("title", "content"):
526 else: # raw text only needs to be escaped to get HTML-safe sequence 526 else: # raw text only needs to be escaped to get HTML-safe sequence
527 elem.addContent(data[attr]) 527 elem.addContent(data[attr])
528 elem["type"] = "text" 528 elem["type"] = "text"
529 529
530 try: 530 try:
531 entry_elt.elements(NS_ATOM, "title").next() 531 next(entry_elt.elements(NS_ATOM, "title"))
532 except StopIteration: 532 except StopIteration:
533 # we have no title element which is mandatory 533 # we have no title element which is mandatory
534 # so we transform content element to title 534 # so we transform content element to title
535 elems = list(entry_elt.elements(NS_ATOM, "content")) 535 elems = list(entry_elt.elements(NS_ATOM, "content"))
536 if not elems: 536 if not elems:
577 577
578 ## id ## 578 ## id ##
579 entry_id = data.get( 579 entry_id = data.get(
580 "id", 580 "id",
581 xmpp_uri.buildXMPPUri( 581 xmpp_uri.buildXMPPUri(
582 u"pubsub", 582 "pubsub",
583 path=service.full() if service is not None else client.jid.userhost(), 583 path=service.full() if service is not None else client.jid.userhost(),
584 node=node, 584 node=node,
585 item=item_id, 585 item=item_id,
586 ), 586 ),
587 ) 587 )
611 """Generate comment node 611 """Generate comment node
612 612
613 @param item_id(unicode): id of the parent item 613 @param item_id(unicode): id of the parent item
614 @return (unicode): comment node to use 614 @return (unicode): comment node to use
615 """ 615 """
616 return u"{}{}".format(NS_COMMENT_PREFIX, item_id) 616 return "{}{}".format(NS_COMMENT_PREFIX, item_id)
617 617
618 def getCommentsService(self, client, parent_service=None): 618 def getCommentsService(self, client, parent_service=None):
619 """Get prefered PubSub service to create comment node 619 """Get prefered PubSub service to create comment node
620 620
621 @param pubsub_service(jid.JID, None): PubSub service of the parent item 621 @param pubsub_service(jid.JID, None): PubSub service of the parent item
660 if allow_comments is None: 660 if allow_comments is None:
661 return 661 return
662 elif allow_comments == False: 662 elif allow_comments == False:
663 if "comments" in mb_data: 663 if "comments" in mb_data:
664 log.warning( 664 log.warning(
665 u"comments are not allowed but there is already a comments node, " 665 "comments are not allowed but there is already a comments node, "
666 u"it may be lost: {uri}".format( 666 "it may be lost: {uri}".format(
667 uri=mb_data["comments"] 667 uri=mb_data["comments"]
668 ) 668 )
669 ) 669 )
670 del mb_data["comments"] 670 del mb_data["comments"]
671 return 671 return
693 except KeyError: 693 except KeyError:
694 comments_node = self.getCommentsNode(item_id) 694 comments_node = self.getCommentsNode(item_id)
695 else: 695 else:
696 if not comments_node: 696 if not comments_node:
697 raise exceptions.DataError( 697 raise exceptions.DataError(
698 u"if comments_node is present, it must not be empty" 698 "if comments_node is present, it must not be empty"
699 ) 699 )
700 700
701 try: 701 try:
702 comments_service = jid.JID(mb_data["comments_service"]) 702 comments_service = jid.JID(mb_data["comments_service"])
703 except KeyError: 703 except KeyError:
706 try: 706 try:
707 yield self._p.createNode(client, comments_service, comments_node, options) 707 yield self._p.createNode(client, comments_service, comments_node, options)
708 except error.StanzaError as e: 708 except error.StanzaError as e:
709 if e.condition == "conflict": 709 if e.condition == "conflict":
710 log.info( 710 log.info(
711 u"node {} already exists on service {}".format( 711 "node {} already exists on service {}".format(
712 comments_node, comments_service 712 comments_node, comments_service
713 ) 713 )
714 ) 714 )
715 else: 715 else:
716 raise e 716 raise e
720 comments_affiliations = yield self._p.getNodeAffiliations( 720 comments_affiliations = yield self._p.getNodeAffiliations(
721 client, service, node 721 client, service, node
722 ) 722 )
723 # …except for "member", that we transform to publisher 723 # …except for "member", that we transform to publisher
724 # because we wants members to be able to write to comments 724 # because we wants members to be able to write to comments
725 for jid_, affiliation in comments_affiliations.items(): 725 for jid_, affiliation in list(comments_affiliations.items()):
726 if affiliation == "member": 726 if affiliation == "member":
727 comments_affiliations[jid_] == "publisher" 727 comments_affiliations[jid_] == "publisher"
728 728
729 yield self._p.setNodeAffiliations( 729 yield self._p.setNodeAffiliations(
730 client, comments_service, comments_node, comments_affiliations 730 client, comments_service, comments_node, comments_affiliations
734 comments_service = client.jid.userhostJID() 734 comments_service = client.jid.userhostJID()
735 735
736 if "comments" in mb_data: 736 if "comments" in mb_data:
737 if not mb_data["comments"]: 737 if not mb_data["comments"]:
738 raise exceptions.DataError( 738 raise exceptions.DataError(
739 u"if comments is present, it must not be empty" 739 "if comments is present, it must not be empty"
740 ) 740 )
741 if "comments_node" in mb_data or "comments_service" in mb_data: 741 if "comments_node" in mb_data or "comments_service" in mb_data:
742 raise exceptions.DataError( 742 raise exceptions.DataError(
743 u"You can't use comments_service/comments_node and comments at the " 743 "You can't use comments_service/comments_node and comments at the "
744 u"same time" 744 "same time"
745 ) 745 )
746 else: 746 else:
747 mb_data["comments"] = self._p.getNodeURI(comments_service, comments_node) 747 mb_data["comments"] = self._p.getNodeURI(comments_service, comments_node)
748 748
749 def _mbSend(self, service, node, data, profile_key): 749 def _mbSend(self, service, node, data, profile_key):
767 # TODO: check that all data keys are used, this would avoid sending publicly a private message 767 # TODO: check that all data keys are used, this would avoid sending publicly a private message
768 # by accident (e.g. if group plugin is not loaded, and "group*" key are not used) 768 # by accident (e.g. if group plugin is not loaded, and "group*" key are not used)
769 if node is None: 769 if node is None:
770 node = NS_MICROBLOG 770 node = NS_MICROBLOG
771 771
772 item_id = data.get("id") or unicode(shortuuid.uuid()) 772 item_id = data.get("id") or str(shortuuid.uuid())
773 773
774 try: 774 try:
775 yield self._manageComments(client, data, service, node, item_id, access=None) 775 yield self._manageComments(client, data, service, node, item_id, access=None)
776 except error.StanzaError: 776 except error.StanzaError:
777 log.warning(u"Can't create comments node for item {}".format(item_id)) 777 log.warning("Can't create comments node for item {}".format(item_id))
778 item = yield self.data2entry(client, data, item_id, service, node) 778 item = yield self.data2entry(client, data, item_id, service, node)
779 ret = yield self._p.publish(client, service, node, [item]) 779 ret = yield self._p.publish(client, service, node, [item])
780 defer.returnValue(ret) 780 defer.returnValue(ret)
781 781
782 ## retract ## 782 ## retract ##
849 from the href attribute of an entry's link element. For example this input: 849 from the href attribute of an entry's link element. For example this input:
850 xmpp:sat-pubsub.example.net?;node=urn%3Axmpp%3Acomments%3A_af43b363-3259-4b2a-ba4c-1bc33aa87634__urn%3Axmpp%3Agroupblog%3Asomebody%40example.net 850 xmpp:sat-pubsub.example.net?;node=urn%3Axmpp%3Acomments%3A_af43b363-3259-4b2a-ba4c-1bc33aa87634__urn%3Axmpp%3Agroupblog%3Asomebody%40example.net
851 will return(JID(u'sat-pubsub.example.net'), 'urn:xmpp:comments:_af43b363-3259-4b2a-ba4c-1bc33aa87634__urn:xmpp:groupblog:somebody@example.net') 851 will return(JID(u'sat-pubsub.example.net'), 'urn:xmpp:comments:_af43b363-3259-4b2a-ba4c-1bc33aa87634__urn:xmpp:groupblog:somebody@example.net')
852 @return (tuple[jid.JID, unicode]): service and node 852 @return (tuple[jid.JID, unicode]): service and node
853 """ 853 """
854 parsed_url = urlparse.urlparse(node_url, "xmpp") 854 parsed_url = urllib.parse.urlparse(node_url, "xmpp")
855 service = jid.JID(parsed_url.path) 855 service = jid.JID(parsed_url.path)
856 parsed_queries = urlparse.parse_qs(parsed_url.query.encode("utf-8")) 856 parsed_queries = urllib.parse.parse_qs(parsed_url.query.encode("utf-8"))
857 node = parsed_queries.get("node", [""])[0].decode("utf-8") 857 node = parsed_queries.get("node", [""])[0]
858 858
859 if not node: 859 if not node:
860 raise failure.Failure(exceptions.DataError("Invalid comments link")) 860 raise failure.Failure(exceptions.DataError("Invalid comments link"))
861 861
862 return (service, node) 862 return (service, node)
881 self._p.OPT_SEND_ITEM_SUBSCRIBE: 1, 881 self._p.OPT_SEND_ITEM_SUBSCRIBE: 1,
882 } 882 }
883 883
884 def cb(result): 884 def cb(result):
885 # Node is created with right permission 885 # Node is created with right permission
886 log.debug(_(u"Microblog node has now access %s") % access) 886 log.debug(_("Microblog node has now access %s") % access)
887 887
888 def fatal_err(s_error): 888 def fatal_err(s_error):
889 # Something went wrong 889 # Something went wrong
890 log.error(_(u"Can't set microblog access")) 890 log.error(_("Can't set microblog access"))
891 raise NodeAccessChangeException() 891 raise NodeAccessChangeException()
892 892
893 def err_cb(s_error): 893 def err_cb(s_error):
894 # If the node already exists, the condition is "conflict", 894 # If the node already exists, the condition is "conflict",
895 # else we have an unmanaged error 895 # else we have an unmanaged error
946 pass # plugin is not loaded 946 pass # plugin is not loaded
947 else: 947 else:
948 if services: 948 if services:
949 log.debug( 949 log.debug(
950 "Extra PEP followed entities: %s" 950 "Extra PEP followed entities: %s"
951 % ", ".join([unicode(service) for service in services]) 951 % ", ".join([str(service) for service in services])
952 ) 952 )
953 jids_set.update(services) 953 jids_set.update(services)
954 954
955 node_data = [] 955 node_data = []
956 for jid_ in jids_set: 956 for jid_ in jids_set:
1031 1031
1032 profile = self.host.getClient(profile_key).profile 1032 profile = self.host.getClient(profile_key).profile
1033 d = self._p.getRTResults( 1033 d = self._p.getRTResults(
1034 session_id, 1034 session_id,
1035 on_success=onSuccess, 1035 on_success=onSuccess,
1036 on_error=lambda failure: (unicode(failure.value), ([], {})), 1036 on_error=lambda failure: (str(failure.value), ([], {})),
1037 profile=profile, 1037 profile=profile,
1038 ) 1038 )
1039 d.addCallback( 1039 d.addCallback(
1040 lambda ret: ( 1040 lambda ret: (
1041 ret[0], 1041 ret[0],
1042 [ 1042 [
1043 (service.full(), node, failure, items, metadata) 1043 (service.full(), node, failure, items, metadata)
1044 for (service, node), (success, (failure, (items, metadata))) in ret[ 1044 for (service, node), (success, (failure, (items, metadata))) in ret[
1045 1 1045 1
1046 ].iteritems() 1046 ].items()
1047 ], 1047 ],
1048 ) 1048 )
1049 ) 1049 )
1050 return d 1050 return d
1051 1051
1096 This is probably the longest method name of whole SàT ecosystem ^^ 1096 This is probably the longest method name of whole SàT ecosystem ^^
1097 @param data(dict): data as received by rt_sessions 1097 @param data(dict): data as received by rt_sessions
1098 @return (tuple): see [_mbGetFromManyWithCommentsRTResult] 1098 @return (tuple): see [_mbGetFromManyWithCommentsRTResult]
1099 """ 1099 """
1100 ret = [] 1100 ret = []
1101 data_iter = data[1].iteritems() 1101 data_iter = iter(data[1].items())
1102 for (service, node), (success, (failure_, (items_data, metadata))) in data_iter: 1102 for (service, node), (success, (failure_, (items_data, metadata))) in data_iter:
1103 items = [] 1103 items = []
1104 for item, item_metadata in items_data: 1104 for item, item_metadata in items_data:
1105 item = data_format.serialise(item) 1105 item = data_format.serialise(item)
1106 items.append((item, item_metadata)) 1106 items.append((item, item_metadata))
1201 """ 1201 """
1202 items, metadata = items_data 1202 items, metadata = items_data
1203 items_dlist = [] # deferred list for items 1203 items_dlist = [] # deferred list for items
1204 for item in items: 1204 for item in items:
1205 dlist = [] # deferred list for comments 1205 dlist = [] # deferred list for comments
1206 for key, value in item.iteritems(): 1206 for key, value in item.items():
1207 # we look for comments 1207 # we look for comments
1208 if key.startswith("comments") and key.endswith("_service"): 1208 if key.startswith("comments") and key.endswith("_service"):
1209 prefix = key[: key.find("_")] 1209 prefix = key[: key.find("_")]
1210 service_s = value 1210 service_s = value
1211 node = item["{}{}".format(prefix, "_node")] 1211 node = item["{}{}".format(prefix, "_node")]
1226 ) 1226 )
1227 # with failure handling 1227 # with failure handling
1228 d.addCallback( 1228 d.addCallback(
1229 lambda serialised_items_data: ("",) + serialised_items_data 1229 lambda serialised_items_data: ("",) + serialised_items_data
1230 ) 1230 )
1231 d.addErrback(lambda failure: (unicode(failure.value), [], {})) 1231 d.addErrback(lambda failure: (str(failure.value), [], {}))
1232 # and associate with service/node (needed if there are several 1232 # and associate with service/node (needed if there are several
1233 # comments nodes) 1233 # comments nodes)
1234 d.addCallback( 1234 d.addCallback(
1235 lambda serialised, service_s=service_s, node=node: ( 1235 lambda serialised, service_s=service_s, node=node: (
1236 service_s, 1236 service_s,
1260 d.addCallback( 1260 d.addCallback(
1261 lambda items_data: self._p.transItemsDataD(items_data, self.item2mbdata) 1261 lambda items_data: self._p.transItemsDataD(items_data, self.item2mbdata)
1262 ) 1262 )
1263 d.addCallback(getComments) 1263 d.addCallback(getComments)
1264 d.addCallback(lambda items_comments_data: ("", items_comments_data)) 1264 d.addCallback(lambda items_comments_data: ("", items_comments_data))
1265 d.addErrback(lambda failure: (unicode(failure.value), ([], {}))) 1265 d.addErrback(lambda failure: (str(failure.value), ([], {})))
1266 1266
1267 return self.rt_sessions.newSession(deferreds, client.profile) 1267 return self.rt_sessions.newSession(deferreds, client.profile)
1268 1268
1269 1269
1270 @implementer(iwokkel.IDisco)
1270 class XEP_0277_handler(XMPPHandler): 1271 class XEP_0277_handler(XMPPHandler):
1271 implements(iwokkel.IDisco)
1272 1272
1273 def getDiscoInfo(self, requestor, target, nodeIdentifier=""): 1273 def getDiscoInfo(self, requestor, target, nodeIdentifier=""):
1274 return [disco.DiscoFeature(NS_MICROBLOG)] 1274 return [disco.DiscoFeature(NS_MICROBLOG)]
1275 1275
1276 def getDiscoItems(self, requestor, target, nodeIdentifier=""): 1276 def getDiscoItems(self, requestor, target, nodeIdentifier=""):