plugin XEP-0277: post repeating implementation:
Repeating a post as specified at https://xmpp.org/extensions/xep-0277.html#repeat is now implemented. On sending, one can either use the new `repeat` method (or the bridge `mbRepeat` one from frontends) to repeat any XEP-0277 item, or use the field `repeated` in microblog data's `extra`. This field must mainly contains the `by` field with JID of repeater (the "author*" field being filled with original author metadata), and the `uri` field with link (usually `xmpp:` one) to repeated item. When receiving a repeated item, the same `repeated` field is filled. rel 370
This commit is contained in:
parent
ab221c78b7
commit
2550264eda
1 changed files with 168 additions and 31 deletions
|
|
@ -20,7 +20,7 @@ import time
|
|||
import dateutil
|
||||
import calendar
|
||||
from secrets import token_urlsafe
|
||||
from typing import Optional
|
||||
from typing import Optional, Dict, Union, Any
|
||||
from functools import partial
|
||||
|
||||
import shortuuid
|
||||
|
|
@ -113,6 +113,14 @@ class XEP_0277(object):
|
|||
method=self._mbSend,
|
||||
async_=True,
|
||||
)
|
||||
host.bridge.addMethod(
|
||||
"mbRepeat",
|
||||
".plugin",
|
||||
in_sign="sssss",
|
||||
out_sign="s",
|
||||
method=self._mbRepeat,
|
||||
async_=True,
|
||||
)
|
||||
host.bridge.addMethod(
|
||||
"mbPreview",
|
||||
".plugin",
|
||||
|
|
@ -272,7 +280,11 @@ class XEP_0277(object):
|
|||
if service is None:
|
||||
service = client.jid.userhostJID()
|
||||
|
||||
microblog_data = {"service": service.full()}
|
||||
extra: Dict[str, Any] = {}
|
||||
microblog_data: Dict[str, Any] = {
|
||||
"service": service.full(),
|
||||
"extra": extra
|
||||
}
|
||||
|
||||
def check_conflict(key, increment=False):
|
||||
"""Check if key is already in microblog data
|
||||
|
|
@ -440,10 +452,8 @@ class XEP_0277(object):
|
|||
# links
|
||||
comments = microblog_data['comments'] = []
|
||||
for link_elt in entry_elt.elements(NS_ATOM, "link"):
|
||||
if (
|
||||
link_elt.getAttribute("rel") == "replies"
|
||||
and link_elt.getAttribute("title") == "comments"
|
||||
):
|
||||
rel = link_elt.getAttribute("rel")
|
||||
if (rel == "replies" and link_elt.getAttribute("title") == "comments"):
|
||||
uri = link_elt["href"]
|
||||
comments_data = {
|
||||
"uri": uri,
|
||||
|
|
@ -457,8 +467,36 @@ class XEP_0277(object):
|
|||
comments_data["service"] = comment_service.full()
|
||||
comments_data["node"] = comment_node
|
||||
comments.append(comments_data)
|
||||
elif rel == "via":
|
||||
href = link_elt.getAttribute("href")
|
||||
if not href:
|
||||
log.warning(
|
||||
f'missing href in "via" <link> element: {link_elt.toXml()}'
|
||||
)
|
||||
continue
|
||||
try:
|
||||
repeater_jid = jid.JID(item_elt["publisher"])
|
||||
except (KeyError, RuntimeError):
|
||||
try:
|
||||
# we look for stanza element which is at the root, meaning that it
|
||||
# has not parent
|
||||
top_elt = item_elt.parent
|
||||
while top_elt.parent is not None:
|
||||
top_elt = top_elt.parent
|
||||
repeater_jid = jid.JID(top_elt["from"])
|
||||
except (AttributeError, RuntimeError):
|
||||
# we should always have either the "publisher" attribute or the
|
||||
# stanza available
|
||||
log.error(
|
||||
f"Can't find repeater of the post: {item_elt.toXml()}"
|
||||
)
|
||||
continue
|
||||
|
||||
extra["repeated"] = {
|
||||
"by": repeater_jid.full(),
|
||||
"uri": href
|
||||
}
|
||||
else:
|
||||
rel = link_elt.getAttribute("rel", "")
|
||||
title = link_elt.getAttribute("title", "")
|
||||
href = link_elt.getAttribute("href", "")
|
||||
log.warning(
|
||||
|
|
@ -516,12 +554,13 @@ class XEP_0277(object):
|
|||
elif jid.JID(publisher).userhostJID() == jid.JID(uri).userhostJID():
|
||||
microblog_data["author_jid_verified"] = True
|
||||
else:
|
||||
log.warning(
|
||||
"item atom:uri differ from publisher attribute, spoofing "
|
||||
"attempt ? atom:uri = {} publisher = {}".format(
|
||||
uri, item_elt.getAttribute("publisher")
|
||||
if "repeated" not in extra:
|
||||
log.warning(
|
||||
"item atom:uri differ from publisher attribute, spoofing "
|
||||
"attempt ? atom:uri = {} publisher = {}".format(
|
||||
uri, item_elt.getAttribute("publisher")
|
||||
)
|
||||
)
|
||||
)
|
||||
microblog_data["author_jid_verified"] = False
|
||||
# email
|
||||
try:
|
||||
|
|
@ -555,10 +594,10 @@ class XEP_0277(object):
|
|||
|
||||
defer.returnValue(microblog_data)
|
||||
|
||||
async def data2entry(self, client, data, item_id, service, node):
|
||||
async def data2entry(self, client, mb_data, item_id, service, node):
|
||||
"""Convert a data dict to en entry usable to create an item
|
||||
|
||||
@param data: data dict as given by bridge method.
|
||||
@param mb_data: data dict as given by bridge method.
|
||||
@param item_id(unicode): id of the item to use
|
||||
@param service(jid.JID, None): pubsub service where the item is sent
|
||||
Needed to construct Atom id
|
||||
|
|
@ -569,8 +608,8 @@ class XEP_0277(object):
|
|||
entry_elt = domish.Element((NS_ATOM, "entry"))
|
||||
|
||||
## language ##
|
||||
if "language" in data:
|
||||
entry_elt[(C.NS_XML, "lang")] = data["language"].strip()
|
||||
if "language" in mb_data:
|
||||
entry_elt[(C.NS_XML, "lang")] = mb_data["language"].strip()
|
||||
|
||||
## content and title ##
|
||||
synt = self.host.plugins["TEXT_SYNTAXES"]
|
||||
|
|
@ -578,14 +617,14 @@ class XEP_0277(object):
|
|||
for elem_name in ("title", "content"):
|
||||
for type_ in ["", "_rich", "_xhtml"]:
|
||||
attr = "{}{}".format(elem_name, type_)
|
||||
if attr in data:
|
||||
if attr in mb_data:
|
||||
elem = entry_elt.addElement(elem_name)
|
||||
if type_:
|
||||
if type_ == "_rich": # convert input from current syntax to XHTML
|
||||
xml_content = await synt.convert(
|
||||
data[attr], synt.getCurrentSyntax(client.profile), "XHTML"
|
||||
mb_data[attr], synt.getCurrentSyntax(client.profile), "XHTML"
|
||||
)
|
||||
if "{}_xhtml".format(elem_name) in data:
|
||||
if "{}_xhtml".format(elem_name) in mb_data:
|
||||
raise failure.Failure(
|
||||
exceptions.DataError(
|
||||
_(
|
||||
|
|
@ -594,7 +633,7 @@ class XEP_0277(object):
|
|||
)
|
||||
)
|
||||
else:
|
||||
xml_content = data[attr]
|
||||
xml_content = mb_data[attr]
|
||||
|
||||
div_elt = xml_tools.ElementParser()(
|
||||
xml_content, namespace=C.NS_XHTML
|
||||
|
|
@ -610,7 +649,7 @@ class XEP_0277(object):
|
|||
div_elt = wrap_div_elt
|
||||
elem.addChild(div_elt)
|
||||
elem["type"] = "xhtml"
|
||||
if elem_name not in data:
|
||||
if elem_name not in mb_data:
|
||||
# there is raw text content, which is mandatory
|
||||
# so we create one from xhtml content
|
||||
elem_txt = entry_elt.addElement(elem_name)
|
||||
|
|
@ -626,7 +665,7 @@ class XEP_0277(object):
|
|||
elem_txt["type"] = "text"
|
||||
|
||||
else: # raw text only needs to be escaped to get HTML-safe sequence
|
||||
elem.addContent(data[attr])
|
||||
elem.addContent(mb_data[attr])
|
||||
elem["type"] = "text"
|
||||
|
||||
try:
|
||||
|
|
@ -645,40 +684,40 @@ class XEP_0277(object):
|
|||
## author ##
|
||||
author_elt = entry_elt.addElement("author")
|
||||
try:
|
||||
author_name = data["author"]
|
||||
author_name = mb_data["author"]
|
||||
except KeyError:
|
||||
# FIXME: must use better name
|
||||
author_name = client.jid.user
|
||||
author_elt.addElement("name", content=author_name)
|
||||
|
||||
try:
|
||||
author_jid_s = data["author_jid"]
|
||||
author_jid_s = mb_data["author_jid"]
|
||||
except KeyError:
|
||||
author_jid_s = client.jid.userhost()
|
||||
author_elt.addElement("uri", content="xmpp:{}".format(author_jid_s))
|
||||
|
||||
try:
|
||||
author_jid_s = data["author_email"]
|
||||
author_jid_s = mb_data["author_email"]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
## published/updated time ##
|
||||
current_time = time.time()
|
||||
entry_elt.addElement(
|
||||
"updated", content=utils.xmpp_date(float(data.get("updated", current_time)))
|
||||
"updated", content=utils.xmpp_date(float(mb_data.get("updated", current_time)))
|
||||
)
|
||||
entry_elt.addElement(
|
||||
"published",
|
||||
content=utils.xmpp_date(float(data.get("published", current_time))),
|
||||
content=utils.xmpp_date(float(mb_data.get("published", current_time))),
|
||||
)
|
||||
|
||||
## categories ##
|
||||
for tag in data.get('tags', []):
|
||||
for tag in mb_data.get('tags', []):
|
||||
category_elt = entry_elt.addElement("category")
|
||||
category_elt["term"] = tag
|
||||
|
||||
## id ##
|
||||
entry_id = data.get(
|
||||
entry_id = mb_data.get(
|
||||
"id",
|
||||
xmpp_uri.buildXMPPUri(
|
||||
"pubsub",
|
||||
|
|
@ -690,20 +729,32 @@ class XEP_0277(object):
|
|||
entry_elt.addElement("id", content=entry_id) #
|
||||
|
||||
## comments ##
|
||||
for comments_data in data.get('comments', []):
|
||||
for comments_data in mb_data.get('comments', []):
|
||||
link_elt = entry_elt.addElement("link")
|
||||
# XXX: "uri" is set in self._manageComments if not already existing
|
||||
link_elt["href"] = comments_data["uri"]
|
||||
link_elt["rel"] = "replies"
|
||||
link_elt["title"] = "comments"
|
||||
|
||||
extra = mb_data.get("extra", {})
|
||||
if "repeated" in extra:
|
||||
try:
|
||||
repeated = extra["repeated"]
|
||||
link_elt = entry_elt.addElement("link")
|
||||
link_elt["rel"] = "via"
|
||||
link_elt["href"] = repeated["uri"]
|
||||
except KeyError as e:
|
||||
log.warning(
|
||||
f"invalid repeated element({e}): {extra['repeated']}"
|
||||
)
|
||||
|
||||
## final item building ##
|
||||
item_elt = pubsub.Item(id=item_id, payload=entry_elt)
|
||||
|
||||
## the trigger ##
|
||||
# if other plugins have things to add or change
|
||||
self.host.trigger.point(
|
||||
"XEP-0277_data2entry", client, data, entry_elt, item_elt
|
||||
"XEP-0277_data2entry", client, mb_data, entry_elt, item_elt
|
||||
)
|
||||
|
||||
return item_elt
|
||||
|
|
@ -926,6 +977,92 @@ class XEP_0277(object):
|
|||
item = await self.data2entry(client, data, item_id, service, node)
|
||||
return await self._p.publish(client, service, node, [item])
|
||||
|
||||
def _mbRepeat(
|
||||
self,
|
||||
service_s: str,
|
||||
node: str,
|
||||
item: str,
|
||||
extra_s: str,
|
||||
profile_key: str
|
||||
) -> defer.Deferred:
|
||||
service = jid.JID(service_s) if service_s else None
|
||||
node = node if node else NS_MICROBLOG
|
||||
client = self.host.getClient(profile_key)
|
||||
extra = data_format.deserialise(extra_s)
|
||||
d = defer.ensureDeferred(
|
||||
self.repeat(client, item, service, node, extra)
|
||||
)
|
||||
# [repeat] can return None, and we always need a str
|
||||
d.addCallback(lambda ret: ret or "")
|
||||
return d
|
||||
|
||||
async def repeat(
|
||||
self,
|
||||
client: SatXMPPEntity,
|
||||
item: str,
|
||||
service: Optional[jid.JID] = None,
|
||||
node: str = NS_MICROBLOG,
|
||||
extra: Optional[dict] = None,
|
||||
) -> Optional[str]:
|
||||
"""Re-publish a post from somewhere else
|
||||
|
||||
This is a feature often name "share" or "boost", it is generally used to make a
|
||||
publication more visible by sharing it with our own audience
|
||||
"""
|
||||
if service is None:
|
||||
service = client.jid.userhostJID()
|
||||
|
||||
# we first get the post to repeat
|
||||
items, __ = await self._p.getItems(
|
||||
client,
|
||||
service,
|
||||
node,
|
||||
item_ids = [item]
|
||||
)
|
||||
if not items:
|
||||
raise exceptions.NotFound(
|
||||
f"no item found at node {node!r} on {service} with ID {item!r}"
|
||||
)
|
||||
item_elt = items[0]
|
||||
try:
|
||||
entry_elt = next(item_elt.elements(NS_ATOM, "entry"))
|
||||
except StopIteration:
|
||||
raise exceptions.DataError(
|
||||
"post to repeat is not a XEP-0277 blog item"
|
||||
)
|
||||
|
||||
# we want to be sure that we have an author element
|
||||
try:
|
||||
author_elt = next(entry_elt.elements(NS_ATOM, "author"))
|
||||
except StopIteration:
|
||||
author_elt = entry_elt.addElement("author")
|
||||
|
||||
try:
|
||||
next(author_elt.elements(NS_ATOM, "name"))
|
||||
except StopIteration:
|
||||
author_elt.addElement("name", content=service.user)
|
||||
|
||||
try:
|
||||
next(author_elt.elements(NS_ATOM, "uri"))
|
||||
except StopIteration:
|
||||
entry_elt.addElement(
|
||||
"uri", content=xmpp_uri.buildXMPPUri(None, path=service.full())
|
||||
)
|
||||
|
||||
# we add the link indicating that it's a repeated post
|
||||
link_elt = entry_elt.addElement("link")
|
||||
link_elt["rel"] = "via"
|
||||
link_elt["href"] = xmpp_uri.buildXMPPUri(
|
||||
"pubsub", path=service.full(), node=node, item=item
|
||||
)
|
||||
|
||||
return await self._p.sendItem(
|
||||
client,
|
||||
client.jid.userhostJID(),
|
||||
NS_MICROBLOG,
|
||||
entry_elt
|
||||
)
|
||||
|
||||
def _mbPreview(self, service, node, data, profile_key):
|
||||
service = jid.JID(service) if service else None
|
||||
node = node if node else NS_MICROBLOG
|
||||
|
|
|
|||
Loading…
Reference in a new issue