def handle_incoming_follow(activity): ''' { "@context": "https://www.w3.org/ns/activitystreams", "id": "https://friend.camp/768222ce-a1c7-479c-a544-c93b8b67fb54", "type": "Follow", "actor": "https://friend.camp/users/tripofmice", "object": "https://ff2cb3e9.ngrok.io/api/u/mouse" } ''' # figure out who they want to follow to_follow = models.User.objects.get(actor=activity['object']) # figure out who they are user = get_or_create_remote_user(activity['actor']) to_follow.followers.add(user) # verify uuid and accept the request models.FollowActivity( uuid=activity['id'], user=user, followed=to_follow, content=activity, activity_type='Follow', ) uuid = uuid4() # TODO does this need to be signed? return JsonResponse({ '@context': 'https://www.w3.org/ns/activitystreams', 'id': 'https://%s/%s' % (DOMAIN, uuid), 'type': 'Accept', 'actor': user.actor, 'object': activity, })
def handle_incoming_create(activity): ''' someone did something, good on them ''' user = get_or_create_remote_user(activity['actor']) if not 'object' in activity: return HttpResponseBadRequest() # TODO: should only create notes if they are relevent to a book, # so, not every single thing someone posts on mastodon response = HttpResponse() content = activity['object'].get('content') if activity['object'].get('fedireadsType') == 'Review' and \ 'inReplyToBook' in activity['object']: book = activity['object']['inReplyToBook'] book = book.split('/')[-1] name = activity['object'].get('name') rating = activity['object'].get('rating') if user.local: review_id = activity['object']['id'].split('/')[-1] models.Review.objects.get(id=review_id) else: try: create_review(user, book, name, content, rating) except ValueError: return HttpResponseBadRequest() elif not user.local: try: create_status(user, content) except ValueError: return HttpResponseBadRequest() return response
def handle_follow(activity): ''' someone wants to follow a local user ''' # figure out who they want to follow to_follow = models.User.objects.get(actor=activity['object']) # figure out who they are user = get_or_create_remote_user(activity['actor']) try: request = models.UserFollowRequest.objects.create( user_subject=user, user_object=to_follow, relationship_id=activity['id']) except django.db.utils.IntegrityError as err: if err.__cause__.diag.constraint_name != 'userfollowrequest_unique': raise # Duplicate follow request. Not sure what the correct behaviour is, but # just dropping it works for now. We should perhaps generate the # Accept, but then do we need to match the activity id? return if not to_follow.manually_approves_followers: status_builder.create_notification(to_follow, 'FOLLOW', related_user=user) outgoing.handle_accept(user, to_follow, request) else: status_builder.create_notification(to_follow, 'FOLLOW_REQUEST', related_user=user)
def handle_incoming_create(activity): ''' someone did something, good on them ''' user = get_or_create_remote_user(activity['actor']) uuid = activity['id'] # if it's an article and in reply to a book, we have a review if activity['object']['type'] == 'Article' and \ 'inReplyTo' in activity['object']: possible_book = activity['object']['inReplyTo'] try: # TODO idk about this error handling, should probs be more granular book = get_or_create_book(possible_book) models.Review( uuid=uuid, user=user, content=activity, activity_type='Article', book=book, name=activity['object']['name'], rating=activity['object']['rating'], review_content=activity['objet']['content'], ).save() return HttpResponse() except KeyError: pass models.Activity( uuid=uuid, user=user, content=activity, activity_type=activity['object']['type'] ) return HttpResponse()
def handle_incoming_follow_accept(activity): ''' hurray, someone remote accepted a follow request ''' # figure out who they want to follow requester = models.User.objects.get(actor=activity['object']['actor']) # figure out who they are accepter = get_or_create_remote_user(activity['actor']) accepter.followers.add(requester) 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']) return HttpResponse() return HttpResponse() return HttpResponseNotFound()
def handle_unfollow(activity): ''' unfollow a local user ''' obj = activity['object'] try: requester = get_or_create_remote_user(obj['actor']) to_unfollow = models.User.objects.get(remote_id=obj['object']) except models.User.DoesNotExist: return False to_unfollow.followers.remove(requester)
def handle_follow_reject(activity): ''' someone is rejecting a follow request ''' requester = models.User.objects.get(actor=activity['object']['actor']) rejecter = get_or_create_remote_user(activity['actor']) try: request = models.UserFollowRequest.objects.get(user_subject=requester, user_object=rejecter) request.delete() except models.UserFollowRequest.DoesNotExist: pass
def handle_incoming_accept(activity): ''' someone is accepting a follow request ''' # our local user user = models.User.objects.get(actor=activity['actor']) # the person our local user wants to follow, who said yes followed = get_or_create_remote_user(activity['object']['actor']) # save this relationship in the db followed.followers.add(user) return HttpResponse()
def handle_incoming_favorite(activity): ''' approval of your good good post ''' try: status_id = activity['object'].split('/')[-1] status = models.Status.objects.get(id=status_id) liker = get_or_create_remote_user(activity['actor']) except (models.Status.DoesNotExist, models.User.DoesNotExist): return HttpResponseNotFound() if not liker.local: status.favorites.add(liker) return HttpResponse()
def handle_unfollow(activity): ''' unfollow a local user ''' obj = activity['object'] if not obj['type'] == 'Follow': #idk how to undo other things return HttpResponseNotFound() try: requester = get_or_create_remote_user(obj['actor']) to_unfollow = models.User.objects.get(actor=obj['object']) except models.User.DoesNotExist: return HttpResponseNotFound() to_unfollow.followers.remove(requester)
def handle_incoming_follow(activity): ''' someone wants to follow a local user ''' # figure out who they want to follow to_follow = models.User.objects.get(actor=activity['object']) # figure out who they are user = get_or_create_remote_user(activity['actor']) # TODO: allow users to manually approve requests models.UserRelationship.objects.create(user_subject=to_follow, user_object=user, status='follow_request', relationship_id=activity['id']) outgoing.handle_outgoing_accept(user, to_follow, activity) return HttpResponse()
def handle_follow_accept(activity): ''' hurray, someone remote accepted a follow request ''' # figure out who they want to follow requester = models.User.objects.get(actor=activity['object']['actor']) # figure out who they are accepter = get_or_create_remote_user(activity['actor']) try: request = models.UserFollowRequest.objects.get(user_subject=requester, user_object=accepter) request.delete() except models.UserFollowRequest.DoesNotExist: pass accepter.followers.add(requester)
def handle_account_search(query): ''' webfingerin' other servers ''' user = None domain = query.split('@')[1] try: user = models.User.objects.get(username=query) except models.User.DoesNotExist: url = 'https://%s/.well-known/webfinger?resource=acct:%s' % \ (domain, query) response = requests.get(url) if not response.ok: response.raise_for_status() data = response.json() for link in data['links']: if link['rel'] == 'self': user = get_or_create_remote_user(link['href']) return user
def handle_boost(activity): ''' someone gave us a boost! ''' try: status_id = activity['object'].split('/')[-1] status = models.Status.objects.get(id=status_id) booster = get_or_create_remote_user(activity['actor']) except (models.Status.DoesNotExist, models.User.DoesNotExist): return False if not booster.local: status_builder.create_boost_from_activity(booster, activity) status_builder.create_notification( status.user, 'BOOST', related_user=booster, related_status=status, )
def handle_favorite(activity): ''' approval of your good good post ''' try: status_id = activity['object'].split('/')[-1] status = models.Status.objects.get(id=status_id) liker = get_or_create_remote_user(activity['actor']) except (models.Status.DoesNotExist, models.User.DoesNotExist): return False if not liker.local: status_builder.create_favorite_from_activity(liker, activity) status_builder.create_notification( status.user, 'FAVORITE', related_user=liker, related_status=status, )
def handle_incoming_follow(activity): ''' someone wants to follow a local user ''' # figure out who they want to follow to_follow = models.User.objects.get(actor=activity['object']) # figure out who they are user = get_or_create_remote_user(activity['actor']) # TODO: allow users to manually approve requests try: models.UserRelationship.objects.create(user_subject=to_follow, user_object=user, status='follow_request', relationship_id=activity['id']) except django.db.utils.IntegrityError: # Duplicate follow request. Not sure what the correct behaviour is, but # just dropping it works for now. We should perhaps generate the # Accept, but then do we need to match the activity id? return HttpResponse() outgoing.handle_outgoing_accept(user, to_follow, activity) return HttpResponse()
def has_valid_signature(request, activity): try: signature = Signature.parse(request) key_actor = urldefrag(signature.key_id).url if key_actor != activity.get('actor'): raise ValueError("Wrong actor created signature.") remote_user = get_or_create_remote_user(key_actor) try: signature.verify(remote_user.public_key, request) except ValueError: old_key = remote_user.public_key refresh_remote_user(remote_user) if remote_user.public_key == old_key: raise # Key unchanged. signature.verify(remote_user.public_key, request) except (ValueError, requests.exceptions.HTTPError): return False return True
def handle_create(activity): ''' someone did something, good on them ''' user = get_or_create_remote_user(activity['actor']) if not 'object' in activity: return HttpResponseBadRequest() if user.local: # we really oughtn't even be sending in this case return if activity['object'].get('fedireadsType') and \ 'inReplyToBook' in activity['object']: try: if activity['object']['fedireadsType'] == 'Review': builder = status_builder.create_review_from_activity elif activity['object']['fedireadsType'] == 'Quotation': builder = status_builder.create_quotation_from_activity else: builder = status_builder.create_comment_from_activity # create the status, it'll throw a valueerror if anything is missing builder(user, activity['object']) except ValueError: return HttpResponseBadRequest() else: # TODO: should only create notes if they are relevent to a book, # so, not every single thing someone posts on mastodon try: status = status_builder.create_status_from_activity( user, activity['object']) if status and status.reply_parent: status_builder.create_notification( status.reply_parent.user, 'REPLY', related_user=status.user, related_status=status, ) except ValueError: return HttpResponseBadRequest()
def handle_create(activity): ''' someone did something, good on them ''' user = get_or_create_remote_user(activity['actor']) if user.local: # we really oughtn't even be sending in this case return True if activity['object'].get('fedireadsType') and \ 'inReplyToBook' in activity['object']: if activity['object']['fedireadsType'] == 'Review': builder = status_builder.create_review_from_activity elif activity['object']['fedireadsType'] == 'Quotation': builder = status_builder.create_quotation_from_activity else: builder = status_builder.create_comment_from_activity # create the status, it'll throw a ValueError if anything is missing builder(user, activity['object']) elif activity['object'].get('inReplyTo'): # only create the status if it's in reply to a status we already know if not status_builder.get_status(activity['object']['inReplyTo']): return True status = status_builder.create_status_from_activity( user, activity['object'] ) if status and status.reply_parent: status_builder.create_notification( status.reply_parent.user, 'REPLY', related_user=status.user, related_status=status, ) return True
def test_get_remote_user(self): actor = 'https://example.com/users/mouse' user = remote_user.get_or_create_remote_user(actor) self.assertEqual(user, self.remote_user)
def handle_tag(activity): ''' someone is tagging a book ''' user = get_or_create_remote_user(activity['actor']) if not user.local: book = activity['target']['id'] status_builder.create_tag(user, book, activity['object']['name'])