kopia lustrzana https://github.com/jointakahe/takahe
Add post to timeline for all hashtag followers
rodzic
60148101c7
commit
2ea3aed3fd
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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")]
|
||||
|
||||
|
|
Ładowanie…
Reference in New Issue