summaryrefslogtreecommitdiffstats
path: root/activities/models
diff options
context:
space:
mode:
authorAndrew Godwin2022-11-16 06:53:39 -0700
committerAndrew Godwin2022-11-16 13:53:40 -0700
commit495e955378d62dc439c4c210785e5d401bc77f64 (patch)
tree859813b06314f387470295e752d1f1b3828830a7 /activities/models
parent906ed2f27c9105dbd78f416930f1aa2b49497567 (diff)
downloadtakahe-495e955378d62dc439c4c210785e5d401bc77f64.tar.gz
takahe-495e955378d62dc439c4c210785e5d401bc77f64.tar.bz2
takahe-495e955378d62dc439c4c210785e5d401bc77f64.zip
Tag and visibility handling
Diffstat (limited to 'activities/models')
-rw-r--r--activities/models/post.py90
-rw-r--r--activities/models/timeline_event.py11
2 files changed, 71 insertions, 30 deletions
diff --git a/activities/models/post.py b/activities/models/post.py
index 22e6412..4896e58 100644
--- a/activities/models/post.py
+++ b/activities/models/post.py
@@ -8,7 +8,7 @@ from django.utils import timezone
from activities.models.fan_out import FanOut
from activities.models.timeline_event import TimelineEvent
from core.html import sanitize_post
-from core.ld import canonicalise, format_ld_date, parse_ld_date
+from core.ld import canonicalise, format_ld_date, get_list, parse_ld_date
from stator.models import State, StateField, StateGraph, StatorModel
from users.models.follow import Follow
from users.models.identity import Identity
@@ -25,7 +25,32 @@ class PostStates(StateGraph):
"""
Creates all needed fan-out objects for a new Post.
"""
- await instance.afan_out()
+ post = await instance.afetch_full()
+ # Non-local posts should not be here
+ if not post.local:
+ raise ValueError("Trying to run handle_new on a non-local post!")
+ # Build list of targets - mentions always included
+ targets = set()
+ async for mention in post.mentions.all():
+ targets.add(mention)
+ # Then, if it's not mentions only, also deliver to followers
+ if post.visibility != Post.Visibilities.mentioned:
+ async for follower in post.author.inbound_follows.select_related("source"):
+ targets.add(follower.source)
+ # Fan out to each one
+ for follow in targets:
+ await FanOut.objects.acreate(
+ identity=follow,
+ type=FanOut.Types.post,
+ subject_post=post,
+ )
+ # And one for themselves if they're local
+ if post.author.local:
+ await FanOut.objects.acreate(
+ identity_id=post.author_id,
+ type=FanOut.Types.post,
+ subject_post=post,
+ )
return cls.fanned_out
@@ -91,6 +116,9 @@ class Post(StatorModel):
blank=True,
)
+ # Hashtags in the post
+ hashtags = models.JSONField(default=[])
+
# When the post was originally created (as opposed to when we received it)
published = models.DateTimeField(default=timezone.now)
@@ -133,7 +161,11 @@ class Post(StatorModel):
@classmethod
def create_local(
- cls, author: Identity, content: str, summary: Optional[str] = None
+ cls,
+ author: Identity,
+ content: str,
+ summary: Optional[str] = None,
+ visibility: int = Visibilities.public,
) -> "Post":
with transaction.atomic():
post = cls.objects.create(
@@ -142,6 +174,7 @@ class Post(StatorModel):
summary=summary or None,
sensitive=bool(summary),
local=True,
+ visibility=visibility,
)
post.object_uri = post.urls.object_uri
post.url = post.urls.view_nice
@@ -150,29 +183,6 @@ class Post(StatorModel):
### ActivityPub (outbound) ###
- async def afan_out(self):
- """
- Creates FanOuts for a new post
- """
- # Send a copy to all people who follow this user
- post = await self.afetch_full()
- async for follow in post.author.inbound_follows.select_related(
- "source", "target"
- ):
- if follow.source.local or follow.target.local:
- await FanOut.objects.acreate(
- identity_id=follow.source_id,
- type=FanOut.Types.post,
- subject_post=post,
- )
- # And one for themselves if they're local
- if post.author.local:
- await FanOut.objects.acreate(
- identity_id=post.author_id,
- type=FanOut.Types.post,
- subject_post=post,
- )
-
def to_ap(self) -> Dict:
"""
Returns the AP JSON for this object
@@ -185,7 +195,7 @@ class Post(StatorModel):
"content": self.safe_content,
"to": "as:Public",
"as:sensitive": self.sensitive,
- "url": self.urls.view_nice if self.local else self.url,
+ "url": str(self.urls.view_nice if self.local else self.url),
}
if self.summary:
value["summary"] = self.summary
@@ -236,8 +246,24 @@ class Post(StatorModel):
post.url = data.get("url", None)
post.published = parse_ld_date(data.get("published", None))
# TODO: to
- # TODO: mentions
- # TODO: visibility
+ # Mentions and hashtags
+ post.hashtags = []
+ for tag in get_list(data, "tag"):
+ if tag["type"].lower() == "mention":
+ mention_identity = Identity.by_actor_uri(tag["href"], create=True)
+ post.mentions.add(mention_identity)
+ elif tag["type"].lower() == "as:hashtag":
+ post.hashtags.append(tag["name"].lstrip("#"))
+ else:
+ raise ValueError(f"Unknown tag type {tag['type']}")
+ # Visibility and to
+ # (a post is public if it's ever to/cc as:Public, otherwise we
+ # regard it as unlisted for now)
+ targets = get_list(data, "to") + get_list(data, "cc")
+ post.visibility = Post.Visibilities.unlisted
+ for target in targets:
+ if target.lower() == "as:public":
+ post.visibility = Post.Visibilities.public
post.save()
return post
@@ -275,9 +301,13 @@ class Post(StatorModel):
raise ValueError("Create actor does not match its Post object", data)
# Create it
post = cls.by_ap(data["object"], create=True, update=True)
- # Make timeline events as appropriate
+ # Make timeline events for followers
for follow in Follow.objects.filter(target=post.author, source__local=True):
TimelineEvent.add_post(follow.source, post)
+ # Make timeline events for mentions if they're local
+ for mention in post.mentions.all():
+ if mention.local:
+ TimelineEvent.add_mentioned(mention, post)
# Force it into fanned_out as it's not ours
post.transition_perform(PostStates.fanned_out)
diff --git a/activities/models/timeline_event.py b/activities/models/timeline_event.py
index 29dec19..368fdad 100644
--- a/activities/models/timeline_event.py
+++ b/activities/models/timeline_event.py
@@ -82,6 +82,17 @@ class TimelineEvent(models.Model):
)[0]
@classmethod
+ def add_mentioned(cls, identity, post):
+ """
+ Adds a mention of identity by post
+ """
+ return cls.objects.get_or_create(
+ identity=identity,
+ type=cls.Types.mentioned,
+ subject_post=post,
+ )[0]
+
+ @classmethod
def add_post_interaction(cls, identity, interaction):
"""
Adds a boost/like to the timeline if it's not there already.