Implement progressive thread parent fetching
This commit is contained in:
parent
a93f03d639
commit
011c51b3c4
|
@ -21,10 +21,17 @@ from activities.models.post_types import (
|
||||||
PostTypeDataEncoder,
|
PostTypeDataEncoder,
|
||||||
)
|
)
|
||||||
from core.html import ContentRenderer, strip_html
|
from core.html import ContentRenderer, strip_html
|
||||||
from core.ld import canonicalise, format_ld_date, get_list, parse_ld_date
|
from core.ld import (
|
||||||
|
canonicalise,
|
||||||
|
format_ld_date,
|
||||||
|
get_list,
|
||||||
|
get_value_or_map,
|
||||||
|
parse_ld_date,
|
||||||
|
)
|
||||||
from stator.exceptions import TryAgainLater
|
from stator.exceptions import TryAgainLater
|
||||||
from stator.models import State, StateField, StateGraph, StatorModel
|
from stator.models import State, StateField, StateGraph, StatorModel
|
||||||
from users.models.identity import Identity, IdentityStates
|
from users.models.identity import Identity, IdentityStates
|
||||||
|
from users.models.inbox_message import InboxMessage
|
||||||
from users.models.system_actor import SystemActor
|
from users.models.system_actor import SystemActor
|
||||||
|
|
||||||
|
|
||||||
|
@ -700,16 +707,7 @@ class Post(StatorModel):
|
||||||
if post.type in (cls.Types.article, cls.Types.question):
|
if post.type in (cls.Types.article, cls.Types.question):
|
||||||
type_data = PostTypeData(__root__=data).__root__
|
type_data = PostTypeData(__root__=data).__root__
|
||||||
post.type_data = type_data.dict()
|
post.type_data = type_data.dict()
|
||||||
# Get content in order of: content value, contentmap.und, any contentmap entry
|
post.content = get_value_or_map(data, "content", "contentMap")
|
||||||
if "content" in data:
|
|
||||||
post.content = data["content"]
|
|
||||||
elif "contentMap" in data:
|
|
||||||
if "und" in data["contentMap"]:
|
|
||||||
post.content = data["contentMap"]["und"]
|
|
||||||
else:
|
|
||||||
post.content = list(data["contentMap"].values())[0]
|
|
||||||
else:
|
|
||||||
raise ValueError("Post has no content or content map")
|
|
||||||
post.summary = data.get("summary")
|
post.summary = data.get("summary")
|
||||||
post.sensitive = data.get("sensitive", False)
|
post.sensitive = data.get("sensitive", False)
|
||||||
post.url = data.get("url", data["id"])
|
post.url = data.get("url", data["id"])
|
||||||
|
@ -723,7 +721,9 @@ class Post(StatorModel):
|
||||||
mention_identity = Identity.by_actor_uri(tag["href"], create=True)
|
mention_identity = Identity.by_actor_uri(tag["href"], create=True)
|
||||||
post.mentions.add(mention_identity)
|
post.mentions.add(mention_identity)
|
||||||
elif tag["type"].lower() in ["_:hashtag", "hashtag"]:
|
elif tag["type"].lower() in ["_:hashtag", "hashtag"]:
|
||||||
post.hashtags.append(tag["name"].lower().lstrip("#"))
|
post.hashtags.append(
|
||||||
|
get_value_or_map(tag, "name", "nameMap").lower().lstrip("#")
|
||||||
|
)
|
||||||
elif tag["type"].lower() in ["toot:emoji", "emoji"]:
|
elif tag["type"].lower() in ["toot:emoji", "emoji"]:
|
||||||
emoji = Emoji.by_ap_tag(post.author.domain, tag, create=True)
|
emoji = Emoji.by_ap_tag(post.author.domain, tag, create=True)
|
||||||
post.emojis.add(emoji)
|
post.emojis.add(emoji)
|
||||||
|
@ -758,10 +758,13 @@ class Post(StatorModel):
|
||||||
focal_y=focal_y,
|
focal_y=focal_y,
|
||||||
)
|
)
|
||||||
post.save()
|
post.save()
|
||||||
|
# Potentially schedule a fetch of the reply parent
|
||||||
|
if post.in_reply_to:
|
||||||
|
cls.ensure_object_uri(post.in_reply_to)
|
||||||
return post
|
return post
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def by_object_uri(cls, object_uri, fetch=False):
|
def by_object_uri(cls, object_uri, fetch=False) -> "Post":
|
||||||
"""
|
"""
|
||||||
Gets the post by URI - either looking up locally, or fetching
|
Gets the post by URI - either looking up locally, or fetching
|
||||||
from the other end if it's not here.
|
from the other end if it's not here.
|
||||||
|
@ -798,6 +801,27 @@ class Post(StatorModel):
|
||||||
else:
|
else:
|
||||||
raise cls.DoesNotExist(f"Cannot find Post with URI {object_uri}")
|
raise cls.DoesNotExist(f"Cannot find Post with URI {object_uri}")
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def ensure_object_uri(cls, object_uri: str):
|
||||||
|
"""
|
||||||
|
Sees if the post is in our local set, and if not, schedules a fetch
|
||||||
|
for it (in the background)
|
||||||
|
"""
|
||||||
|
if not object_uri:
|
||||||
|
raise ValueError("No URI provided!")
|
||||||
|
try:
|
||||||
|
cls.by_object_uri(object_uri)
|
||||||
|
except cls.DoesNotExist:
|
||||||
|
InboxMessage.objects.create(
|
||||||
|
message={
|
||||||
|
"type": "__internal__",
|
||||||
|
"object": {
|
||||||
|
"type": "FetchPost",
|
||||||
|
"object": object_uri,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def handle_create_ap(cls, data):
|
def handle_create_ap(cls, data):
|
||||||
"""
|
"""
|
||||||
|
@ -848,6 +872,16 @@ class Post(StatorModel):
|
||||||
raise ValueError("Actor on delete does not match object")
|
raise ValueError("Actor on delete does not match object")
|
||||||
post.delete()
|
post.delete()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def handle_fetch_internal(cls, data):
|
||||||
|
"""
|
||||||
|
Handles an internal fetch-request inbox message
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
cls.by_object_uri(data["object"]["object"], fetch=True)
|
||||||
|
except cls.DoesNotExist:
|
||||||
|
pass
|
||||||
|
|
||||||
### OpenGraph API ###
|
### OpenGraph API ###
|
||||||
|
|
||||||
def to_opengraph_dict(self) -> dict:
|
def to_opengraph_dict(self) -> dict:
|
||||||
|
|
|
@ -103,8 +103,10 @@ class PostService:
|
||||||
ancestors: list[Post] = []
|
ancestors: list[Post] = []
|
||||||
ancestor = self.post
|
ancestor = self.post
|
||||||
while ancestor.in_reply_to and len(ancestors) < num_ancestors:
|
while ancestor.in_reply_to and len(ancestors) < num_ancestors:
|
||||||
ancestor = self.queryset().filter(object_uri=ancestor.in_reply_to).first()
|
object_uri = ancestor.in_reply_to
|
||||||
|
ancestor = self.queryset().filter(object_uri=object_uri).first()
|
||||||
if ancestor is None:
|
if ancestor is None:
|
||||||
|
Post.ensure_object_uri(object_uri)
|
||||||
break
|
break
|
||||||
if ancestor.state in [PostStates.deleted, PostStates.deleted_fanned_out]:
|
if ancestor.state in [PostStates.deleted, PostStates.deleted_fanned_out]:
|
||||||
break
|
break
|
||||||
|
|
14
core/ld.py
14
core/ld.py
|
@ -523,6 +523,20 @@ def get_first_image_url(data) -> str | None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def get_value_or_map(data, key, map_key):
|
||||||
|
"""
|
||||||
|
Retrieves a value that can either be a top level key (like "name") or
|
||||||
|
an entry in a map (like nameMap).
|
||||||
|
"""
|
||||||
|
if key in data:
|
||||||
|
return data[key]
|
||||||
|
if map_key in data:
|
||||||
|
if "und" in map_key:
|
||||||
|
return data[map_key]["und"]
|
||||||
|
return list(data[map_key].values())[0]
|
||||||
|
raise KeyError(f"Cannot find {key} or {map_key}")
|
||||||
|
|
||||||
|
|
||||||
def media_type_from_filename(filename):
|
def media_type_from_filename(filename):
|
||||||
_, extension = os.path.splitext(filename)
|
_, extension = os.path.splitext(filename)
|
||||||
if extension == ".png":
|
if extension == ".png":
|
||||||
|
|
|
@ -137,6 +137,16 @@ class InboxMessageStates(StateGraph):
|
||||||
case "flag":
|
case "flag":
|
||||||
# Received reports
|
# Received reports
|
||||||
await sync_to_async(Report.handle_ap)(instance.message)
|
await sync_to_async(Report.handle_ap)(instance.message)
|
||||||
|
case "__internal__":
|
||||||
|
match instance.message_object_type:
|
||||||
|
case "fetchpost":
|
||||||
|
await sync_to_async(Post.handle_fetch_internal)(
|
||||||
|
instance.message
|
||||||
|
)
|
||||||
|
case unknown:
|
||||||
|
raise ValueError(
|
||||||
|
f"Cannot handle activity of type __internal__.{unknown}"
|
||||||
|
)
|
||||||
case unknown:
|
case unknown:
|
||||||
raise ValueError(f"Cannot handle activity of type {unknown}")
|
raise ValueError(f"Cannot handle activity of type {unknown}")
|
||||||
return cls.processed
|
return cls.processed
|
||||||
|
|
|
@ -187,6 +187,10 @@ class Inbox(View):
|
||||||
exceptions.capture_message("Inbox error: Bad HTTP signature")
|
exceptions.capture_message("Inbox error: Bad HTTP signature")
|
||||||
return HttpResponseUnauthorized("Bad signature")
|
return HttpResponseUnauthorized("Bad signature")
|
||||||
|
|
||||||
|
# Don't allow injection of internal messages
|
||||||
|
if document["type"].startswith("__"):
|
||||||
|
return HttpResponseUnauthorized("Bad type")
|
||||||
|
|
||||||
# Hand off the item to be processed by the queue
|
# Hand off the item to be processed by the queue
|
||||||
InboxMessage.objects.create(message=document)
|
InboxMessage.objects.create(message=document)
|
||||||
return HttpResponse(status=202)
|
return HttpResponse(status=202)
|
||||||
|
|
Loading…
Reference in New Issue