Mercurial > libervia-backend
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=""): |