From 7455467c408cffb95695c1ed55157f396ea2be03 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Sat, 21 Mar 2020 15:21:27 -0700 Subject: [PATCH] Unfavorite statuses --- fedireads/activitypub/__init__.py | 2 +- fedireads/activitypub/status.py | 17 ++++++ fedireads/incoming.py | 55 +++++++++++++++---- .../migrations/0018_favorite_remote_id.py | 18 ++++++ fedireads/models/status.py | 8 +++ fedireads/outgoing.py | 16 ++++++ fedireads/status.py | 35 ++++++++++-- fedireads/templates/snippets/interaction.html | 2 +- fedireads/urls.py | 1 + fedireads/view_actions.py | 8 +++ 10 files changed, 143 insertions(+), 19 deletions(-) create mode 100644 fedireads/migrations/0018_favorite_remote_id.py diff --git a/fedireads/activitypub/__init__.py b/fedireads/activitypub/__init__.py index 05ac4aedc..a7ed5b946 100644 --- a/fedireads/activitypub/__init__.py +++ b/fedireads/activitypub/__init__.py @@ -5,4 +5,4 @@ from .collection import get_outbox, get_outbox_page, get_add, get_remove, \ from .create import get_create from .follow import get_follow_request, get_unfollow, get_accept, get_reject from .status import get_review, get_review_article, get_status, get_replies, \ - get_favorite, get_add_tag, get_remove_tag, get_replies_page + get_favorite, get_unfavorite, get_add_tag, get_remove_tag, get_replies_page diff --git a/fedireads/activitypub/status.py b/fedireads/activitypub/status.py index d56edc0e1..3d2c30720 100644 --- a/fedireads/activitypub/status.py +++ b/fedireads/activitypub/status.py @@ -77,6 +77,7 @@ def get_replies(status, replies): def get_replies_page(status, replies): + ''' actual reply list content ''' id_slug = status.absolute_id + '/replies?page=true&only_other_accounts=true' items = [] for reply in replies: @@ -105,6 +106,22 @@ def get_favorite(favorite): } +def get_unfavorite(favorite): + ''' like a post ''' + return { + '@context': 'https://www.w3.org/ns/activitystreams', + 'id': '%s/undo' % favorite.absolute_id, + 'type': 'Undo', + 'actor': favorite.user.actor, + 'object': { + 'id': favorite.absolute_id, + 'type': 'Like', + 'actor': favorite.user.actor, + 'object': favorite.status.absolute_id, + } + } + + def get_add_tag(tag): ''' add activity for tagging a book ''' uuid = uuid4() diff --git a/fedireads/incoming.py b/fedireads/incoming.py index 7c2808d7f..6fbe5da8b 100644 --- a/fedireads/incoming.py +++ b/fedireads/incoming.py @@ -12,8 +12,7 @@ import requests from fedireads import models from fedireads import outgoing -from fedireads.status import create_review_from_activity, \ - create_status_from_activity, create_tag, create_notification +from fedireads import status as status_builder from fedireads.remote_user import get_or_create_remote_user @@ -39,7 +38,14 @@ def shared_inbox(request): response = handle_incoming_follow(activity) elif activity['type'] == 'Undo': - response = handle_incoming_undo(activity) + if not 'object' in activity: + return HttpResponseNotFound() + if activity['object']['type'] == 'Follow': + response = handle_incoming_undo(activity) + elif activity['object']['type'] == 'Like': + response = handle_incoming_unfavorite(activity) + else: + return HttpResponseNotFound() elif activity['type'] == 'Create': response = handle_incoming_create(activity) @@ -141,10 +147,18 @@ def handle_incoming_follow(activity): return HttpResponse() if not to_follow.manually_approves_followers: - create_notification(to_follow, 'FOLLOW', related_user=user) + status_builder.create_notification( + to_follow, + 'FOLLOW', + related_user=user + ) outgoing.handle_outgoing_accept(user, to_follow, request) else: - create_notification(to_follow, 'FOLLOW_REQUEST', related_user=user) + status_builder.create_notification( + to_follow, + 'FOLLOW_REQUEST', + related_user=user + ) return HttpResponse() @@ -216,14 +230,20 @@ def handle_incoming_create(activity): models.Review.objects.get(id=review_id) else: try: - create_review_from_activity(user, activity['object']) + status_builder.create_review_from_activity( + user, + activity['object'] + ) except ValueError: return HttpResponseBadRequest() elif not user.local: try: - status = create_status_from_activity(user, activity['object']) + status = status_builder.create_status_from_activity( + user, + activity['object'] + ) if status and status.reply_parent: - create_notification( + status_builder.create_notification( status.reply_parent.user, 'REPLY', related_user=status.user, @@ -245,9 +265,9 @@ def handle_incoming_favorite(activity): return HttpResponseNotFound() if not liker.local: - status.favorites.add(liker) + status_builder.create_favorite_from_activity(liker, activity) - create_notification( + status_builder.create_notification( status.user, 'FAVORITE', related_user=liker, @@ -256,13 +276,26 @@ def handle_incoming_favorite(activity): return HttpResponse() +def handle_incoming_unfavorite(activity): + ''' approval of your good good post ''' + try: + favorite_id = activity['object']['id'] + fav = status_builder.get_favorite(favorite_id) + except models.Favorite.DoesNotExist: + return HttpResponseNotFound() + + fav.delete() + return HttpResponse() + + def handle_incoming_add(activity): ''' someone is tagging or shelving a book ''' if activity['object']['type'] == 'Tag': user = get_or_create_remote_user(activity['actor']) if not user.local: book = activity['target']['id'].split('/')[-1] - create_tag(user, book, activity['object']['name']) + status_builder.create_tag(user, book, activity['object']['name']) return HttpResponse() return HttpResponse() return HttpResponseNotFound() + diff --git a/fedireads/migrations/0018_favorite_remote_id.py b/fedireads/migrations/0018_favorite_remote_id.py new file mode 100644 index 000000000..89519a9b7 --- /dev/null +++ b/fedireads/migrations/0018_favorite_remote_id.py @@ -0,0 +1,18 @@ +# Generated by Django 3.0.3 on 2020-03-21 21:44 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('fedireads', '0017_auto_20200314_2152'), + ] + + operations = [ + migrations.AddField( + model_name='favorite', + name='remote_id', + field=models.CharField(max_length=255, null=True, unique=True), + ), + ] diff --git a/fedireads/models/status.py b/fedireads/models/status.py index 31d90d69c..1229db3b4 100644 --- a/fedireads/models/status.py +++ b/fedireads/models/status.py @@ -65,6 +65,14 @@ class Favorite(FedireadsModel): ''' fav'ing a post ''' user = models.ForeignKey('User', on_delete=models.PROTECT) status = models.ForeignKey('Status', on_delete=models.PROTECT) + remote_id = models.CharField(max_length=255, unique=True, null=True) + + @property + def absolute_id(self): + ''' constructs the absolute reference to any db object ''' + if self.remote_id: + return self.remote_id + return super().absolute_id class Meta: unique_together = ('user', 'status') diff --git a/fedireads/outgoing.py b/fedireads/outgoing.py index b5c81aa56..250779aaf 100644 --- a/fedireads/outgoing.py +++ b/fedireads/outgoing.py @@ -228,3 +228,19 @@ def handle_outgoing_favorite(user, status): recipients = get_recipients(user, 'direct', [status.user]) broadcast(user, fav_activity, recipients) + +def handle_outgoing_unfavorite(user, status): + ''' a user likes a status ''' + try: + favorite = models.Favorite.objects.get( + status=status, + user=user + ) + except models.Favorite.DoesNotExist: + # can't find that status, idk + return + + fav_activity = activitypub.get_unfavorite(favorite) + recipients = get_recipients(user, 'direct', [status.user]) + broadcast(user, fav_activity, recipients) + diff --git a/fedireads/status.py b/fedireads/status.py index 34bd80504..c00fcd821 100644 --- a/fedireads/status.py +++ b/fedireads/status.py @@ -57,20 +57,44 @@ def create_status_from_activity(author, activity): return status +def create_favorite_from_activity(user, activity): + status = get_status(activity['object']) + remote_id = activity['id'] + try: + return models.Favorite.objects.create( + status=status, + user=user, + remote_id=remote_id, + ) + except IntegrityError: + return models.Favorite.objects.get(status=status, user=user) + + def get_status(absolute_id): ''' find a status in the database ''' + return get_by_absolute_id(absolute_id, models.Status) + + +def get_favorite(absolute_id): + ''' find a status in the database ''' + return get_by_absolute_id(absolute_id, models.Favorite) + + +def get_by_absolute_id(absolute_id, model): # check if it's a remote status try: - return models.Status.objects.get(remote_id=absolute_id) - except models.Status.DoesNotExist: + return model.objects.get(remote_id=absolute_id) + except model.DoesNotExist: pass # try finding a local status with that id local_id = absolute_id.split('/')[-1] try: - possible_match = models.Status.objects.select_subclasses() \ - .get(id=local_id) - except models.Status.DoesNotExist: + if hasattr(model.objects, 'select_subclasses'): + possible_match = model.objects.select_subclasses().get(id=local_id) + else: + possible_match = model.objects.get(id=local_id) + except model.DoesNotExist: return None # make sure it's not actually a remote status with an id that @@ -80,7 +104,6 @@ def get_status(absolute_id): return None - def create_status(user, content, reply_parent=None, mention_books=None, remote_id=None): ''' a status update ''' diff --git a/fedireads/templates/snippets/interaction.html b/fedireads/templates/snippets/interaction.html index a755311aa..d56afe0ad 100644 --- a/fedireads/templates/snippets/interaction.html +++ b/fedireads/templates/snippets/interaction.html @@ -37,7 +37,7 @@ -
+ {% csrf_token %}