diff --git a/bookwyrm/outgoing.py b/bookwyrm/outgoing.py index e563a2251..fd4dff8f2 100644 --- a/bookwyrm/outgoing.py +++ b/bookwyrm/outgoing.py @@ -220,8 +220,65 @@ def handle_status(user, form): status.save() # inspect the text for user tags - matches = [] - for match in re.finditer(regex.strict_username, status.content): + content = status.content + for (mention_text, mention_user) in find_mentions(content): + # add them to status mentions fk + status.mention_users.add(mention_user) + + # turn the mention into a link + content = re.sub( + r'%s([^@]|$)' % mention_text, + r'%s\g<1>' % \ + (mention_user.remote_id, mention_text), + content) + + # add reply parent to mentions and notify + if status.reply_parent: + status.mention_users.add(status.reply_parent.user) + for mention_user in status.reply_parent.mention_users.all(): + status.mention_users.add(mention_user) + + if status.reply_parent.user.local: + create_notification( + status.reply_parent.user, + 'REPLY', + related_user=user, + related_status=status + ) + + # deduplicate mentions + status.mention_users.set(set(status.mention_users.all())) + # create mention notifications + for mention_user in status.mention_users.all(): + if status.reply_parent and mention_user == status.reply_parent.user: + continue + if mention_user.local: + create_notification( + mention_user, + 'MENTION', + related_user=user, + related_status=status + ) + + # don't apply formatting to generated notes + if not isinstance(status, models.GeneratedNote): + status.content = to_markdown(content) + # do apply formatting to quotes + if hasattr(status, 'quote'): + status.quote = to_markdown(status.quote) + + status.save() + + broadcast(user, status.to_create_activity(user), software='bookwyrm') + + # re-format the activity for non-bookwyrm servers + remote_activity = status.to_create_activity(user, pure=True) + broadcast(user, remote_activity, software='other') + + +def find_mentions(content): + ''' detect @mentions in raw status content ''' + for match in re.finditer(regex.strict_username, content): username = match.group().strip().split('@')[1:] if len(username) == 1: # this looks like a local user (@user), fill in the domain @@ -232,44 +289,7 @@ def handle_status(user, form): if not mention_user: # we can ignore users we don't know about continue - matches.append((match.group(), mention_user.remote_id)) - # add them to status mentions fk - status.mention_users.add(mention_user) - # create notification if the mentioned user is local - if mention_user.local: - create_notification( - mention_user, - 'MENTION', - related_user=user, - related_status=status - ) - # add mentions - content = status.content - for (username, url) in matches: - content = re.sub( - r'%s([^@])' % username, - r'%s\g<1>' % (url, username), - content) - if not isinstance(status, models.GeneratedNote): - status.content = to_markdown(content) - if hasattr(status, 'quote'): - status.quote = to_markdown(status.quote) - status.save() - - # notify reply parent or tagged users - if status.reply_parent and status.reply_parent.user.local: - create_notification( - status.reply_parent.user, - 'REPLY', - related_user=user, - related_status=status - ) - - broadcast(user, status.to_create_activity(user), software='bookwyrm') - - # re-format the activity for non-bookwyrm servers - remote_activity = status.to_create_activity(user, pure=True) - broadcast(user, remote_activity, software='other') + yield (match.group(), mention_user) def to_markdown(content): diff --git a/bookwyrm/templates/snippets/reply_form.html b/bookwyrm/templates/snippets/reply_form.html index 9834545ef..bf1f0259f 100644 --- a/bookwyrm/templates/snippets/reply_form.html +++ b/bookwyrm/templates/snippets/reply_form.html @@ -10,7 +10,7 @@ {% include 'snippets/content_warning_field.html' with parent_status=status %}
- +
diff --git a/bookwyrm/tests/test_outgoing.py b/bookwyrm/tests/test_outgoing.py index 923600349..14d67aeb3 100644 --- a/bookwyrm/tests/test_outgoing.py +++ b/bookwyrm/tests/test_outgoing.py @@ -8,10 +8,11 @@ from django.test import TestCase from django.test.client import RequestFactory import responses -from bookwyrm import models, outgoing +from bookwyrm import forms, models, outgoing from bookwyrm.settings import DOMAIN +# pylint: disable=too-many-public-methods class Outgoing(TestCase): ''' sends out activities ''' def setUp(self): @@ -255,3 +256,89 @@ class Outgoing(TestCase): with patch('bookwyrm.broadcast.broadcast_task.delay'): outgoing.handle_unshelve(self.local_user, self.book, self.shelf) self.assertEqual(self.shelf.books.count(), 0) + + + def test_handle_status(self): + ''' create a status ''' + form = forms.CommentForm({ + 'content': 'hi', + 'user': self.local_user.id, + 'book': self.book.id, + 'privacy': 'public', + }) + with patch('bookwyrm.broadcast.broadcast_task.delay'): + outgoing.handle_status(self.local_user, form) + status = models.Comment.objects.get() + self.assertEqual(status.content, '

hi

') + self.assertEqual(status.user, self.local_user) + self.assertEqual(status.book, self.book) + + def test_handle_status_reply(self): + ''' create a status in reply to an existing status ''' + user = models.User.objects.create_user( + 'rat', 'rat@rat.com', 'password', local=True) + parent = models.Status.objects.create( + content='parent status', user=self.local_user) + form = forms.ReplyForm({ + 'content': 'hi', + 'user': user.id, + 'reply_parent': parent.id, + 'privacy': 'public', + }) + with patch('bookwyrm.broadcast.broadcast_task.delay'): + outgoing.handle_status(user, form) + status = models.Status.objects.get(user=user) + self.assertEqual(status.content, '

hi

') + self.assertEqual(status.user, user) + self.assertEqual( + models.Notification.objects.get().user, self.local_user) + + def test_handle_status_mentions(self): + ''' @mention a user in a post ''' + user = models.User.objects.create_user( + 'rat', 'rat@rat.com', 'password', local=True) + form = forms.CommentForm({ + 'content': 'hi @rat', + 'user': self.local_user.id, + 'book': self.book.id, + 'privacy': 'public', + }) + + with patch('bookwyrm.broadcast.broadcast_task.delay'): + outgoing.handle_status(self.local_user, form) + status = models.Status.objects.get() + self.assertEqual( + status.content, + '

hi @rat

' % user.remote_id) + self.assertEqual(list(status.mention_users.all()), [user]) + self.assertEqual(models.Notification.objects.get().user, user) + + def test_handle_status_reply_with_mentions(self): + ''' reply to a post with an @mention'ed user ''' + user = models.User.objects.create_user( + 'rat', 'rat@rat.com', 'password', local=True) + form = forms.CommentForm({ + 'content': 'hi @rat@example.com', + 'user': self.local_user.id, + 'book': self.book.id, + 'privacy': 'public', + }) + + with patch('bookwyrm.broadcast.broadcast_task.delay'): + outgoing.handle_status(self.local_user, form) + status = models.Status.objects.get() + + form = forms.ReplyForm({ + 'content': 'right', + 'user': user, + 'privacy': 'public', + 'reply_parent': status.id + }) + with patch('bookwyrm.broadcast.broadcast_task.delay'): + outgoing.handle_status(user, form) + + reply = models.Status.replies(status).first() + self.assertEqual(reply.content, '

right

') + self.assertEqual(reply.user, user) + self.assertTrue(self.remote_user in reply.mention_users.all()) + self.assertTrue(self.local_user in reply.mention_users.all())