Add post to timeline for all hashtag followers

pull/539/head
Christof Dorner 2023-03-12 11:40:29 +01:00
rodzic 60148101c7
commit 2ea3aed3fd
3 zmienionych plików z 92 dodań i 2 usunięć

Wyświetl plik

@ -38,6 +38,7 @@ from core.snowflake import Snowflake
from stator.exceptions import TryAgainLater
from stator.models import State, StateField, StateGraph, StatorModel
from users.models.follow import FollowStates
from users.models.hashtag_follow import HashtagFollow
from users.models.identity import Identity, IdentityStates
from users.models.inbox_message import InboxMessage
from users.models.system_actor import SystemActor
@ -726,12 +727,18 @@ class Post(StatorModel):
targets = set()
async for mention in self.mentions.all():
targets.add(mention)
# Then, if it's not mentions only, also deliver to followers
# Then, if it's not mentions only, also deliver to followers and all hashtag followers
if self.visibility != Post.Visibilities.mentioned:
async for follower in self.author.inbound_follows.filter(
state__in=FollowStates.group_active()
).select_related("source"):
targets.add(follower.source)
if self.hashtags:
async for follow in HashtagFollow.objects.by_hashtags(
self.hashtags
).prefetch_related("identity"):
targets.add(follow.identity)
# If it's a reply, always include the original author if we know them
reply_post = await self.ain_reply_to_post()
if reply_post:

Wyświetl plik

@ -1,7 +1,7 @@
import pytest
from django.utils import timezone
from activities.models import Post, TimelineEvent
from activities.models import Hashtag, Post, TimelineEvent
from activities.services import PostService
from core.ld import format_ld_date
from users.models import Block, Follow, Identity, InboxMessage
@ -257,3 +257,70 @@ def test_clear_timeline(
assert TimelineEvent.objects.filter(
type=TimelineEvent.Types.mentioned, identity=identity
).exists() == (not full)
@pytest.mark.django_db
@pytest.mark.parametrize("local", [True, False])
@pytest.mark.parametrize("blocked", ["full", "mute", "no"])
def test_hashtag_followed(
identity: Identity,
other_identity: Identity,
remote_identity: Identity,
stator,
local: bool,
blocked: bool,
):
"""
Ensure that a new or incoming post with a hashtag followed by a local entity
results in a timeline event, unless the author is blocked.
"""
hashtag = Hashtag.objects.get_or_create(hashtag="takahe")[0]
identity.hashtag_follows.get_or_create(hashtag=hashtag)
if local:
Post.create_local(author=other_identity, content="Hello from #Takahe!")
else:
# Create an inbound new post message
message = {
"id": "test",
"type": "Create",
"actor": remote_identity.actor_uri,
"object": {
"id": "https://remote.test/test-post",
"type": "Note",
"published": format_ld_date(timezone.now()),
"attributedTo": remote_identity.actor_uri,
"to": "as:Public",
"content": '<p>Hello from <a href="https://remote.test/tags/takahe/" rel="tag">#Takahe</a>!',
"tag": {
"type": "Hashtag",
"href": "https://remote.test/tags/takahe/",
"name": "#Takahe",
},
},
}
InboxMessage.objects.create(message=message)
# Implement any blocks
author = other_identity if local else remote_identity
if blocked == "full":
Block.create_local_block(identity, author)
elif blocked == "mute":
Block.create_local_mute(identity, author)
# Run stator twice - to make fanouts and then process them
stator.run_single_cycle_sync()
stator.run_single_cycle_sync()
if blocked in ["full", "mute"]:
# Verify post is not in timeline
assert not TimelineEvent.objects.filter(
type=TimelineEvent.Types.post, identity=identity
).exists()
else:
# Verify post is in timeline
event = TimelineEvent.objects.filter(
type=TimelineEvent.Types.post, identity=identity
).first()
assert event
assert "Hello from " in event.subject_post.content

Wyświetl plik

@ -3,6 +3,20 @@ from typing import Optional
from django.db import models
class HashtagFollowQuerySet(models.QuerySet):
def by_hashtags(self, hashtags: list[str]):
query = self.filter(hashtag_id__in=hashtags)
return query
class HashtagFollowManager(models.Manager):
def get_queryset(self):
return HashtagFollowQuerySet(self.model, using=self._db)
def by_hashtags(self, hashtags: list[str]):
return self.get_queryset().by_hashtags(hashtags)
class HashtagFollow(models.Model):
identity = models.ForeignKey(
"users.Identity",
@ -17,6 +31,8 @@ class HashtagFollow(models.Model):
created = models.DateTimeField(auto_now_add=True)
objects = HashtagFollowManager()
class Meta:
unique_together = [("identity", "hashtag")]