Remove explicit broadcast calls

This commit is contained in:
Mouse Reeve 2021-02-04 13:21:55 -08:00
parent 44996917c7
commit 42d80ce238
18 changed files with 25 additions and 685 deletions

View file

@ -3,7 +3,6 @@ import csv
import logging import logging
from bookwyrm import models from bookwyrm import models
from bookwyrm.broadcast import broadcast
from bookwyrm.models import ImportJob, ImportItem from bookwyrm.models import ImportJob, ImportItem
from bookwyrm.status import create_notification from bookwyrm.status import create_notification
from bookwyrm.tasks import app from bookwyrm.tasks import app
@ -90,9 +89,8 @@ def handle_imported_book(user, item, include_reviews, privacy):
identifier=item.shelf, identifier=item.shelf,
user=user user=user
) )
shelf_book = models.ShelfBook.objects.create( models.ShelfBook.objects.create(
book=item.book, shelf=desired_shelf, added_by=user) book=item.book, shelf=desired_shelf, added_by=user)
broadcast(user, shelf_book.to_add_activity(user), privacy=privacy)
for read in item.reads: for read in item.reads:
# check for an existing readthrough with the same dates # check for an existing readthrough with the same dates
@ -114,7 +112,7 @@ def handle_imported_book(user, item, include_reviews, privacy):
# we don't know the publication date of the review, # we don't know the publication date of the review,
# but "now" is a bad guess # but "now" is a bad guess
published_date_guess = item.date_read or item.date_added published_date_guess = item.date_read or item.date_added
review = models.Review.objects.create( models.Review.objects.create(
user=user, user=user,
book=item.book, book=item.book,
name=review_title, name=review_title,
@ -123,6 +121,3 @@ def handle_imported_book(user, item, include_reviews, privacy):
published_date=published_date_guess, published_date=published_date_guess,
privacy=privacy, privacy=privacy,
) )
# we don't need to send out pure activities because non-bookwyrm
# instances don't need this data
broadcast(user, review.to_create_activity(user), privacy=privacy)

View file

@ -1,438 +0,0 @@
''' base model with default fields '''
from base64 import b64encode
from functools import reduce
import json
import operator
from uuid import uuid4
import requests
from Crypto.PublicKey import RSA
from Crypto.Signature import pkcs1_15
from Crypto.Hash import SHA256
from django.apps import apps
from django.core.paginator import Paginator
from django.db import models
from django.db.models import Q
from django.dispatch import receiver
from django.utils.http import http_date
from bookwyrm import activitypub
from bookwyrm.settings import PAGE_LENGTH, USER_AGENT
from bookwyrm.signatures import make_signature, make_digest
from bookwyrm.tasks import app
from .fields import ImageField, ManyToManyField
class ActivitypubMixin:
''' add this mixin for models that are AP serializable '''
activity_serializer = lambda: {}
reverse_unfurl = False
def __init__(self, *args, **kwargs):
''' collect some info on model fields '''
self.image_fields = []
self.many_to_many_fields = []
self.simple_fields = [] # "simple"
for field in self._meta.get_fields():
if not hasattr(field, 'field_to_activity'):
continue
if isinstance(field, ImageField):
self.image_fields.append(field)
elif isinstance(field, ManyToManyField):
self.many_to_many_fields.append(field)
else:
self.simple_fields.append(field)
self.activity_fields = self.image_fields + \
self.many_to_many_fields + self.simple_fields
self.deserialize_reverse_fields = self.deserialize_reverse_fields \
if hasattr(self, 'deserialize_reverse_fields') else []
self.serialize_reverse_fields = self.serialize_reverse_fields \
if hasattr(self, 'serialize_reverse_fields') else []
super().__init__(*args, **kwargs)
def delete(self, *args, **kwargs):
''' broadcast suitable delete activities '''
@classmethod
def find_existing_by_remote_id(cls, remote_id):
''' look up a remote id in the db '''
return cls.find_existing({'id': remote_id})
@classmethod
def find_existing(cls, data):
''' compare data to fields that can be used for deduplation.
This always includes remote_id, but can also be unique identifiers
like an isbn for an edition '''
filters = []
for field in cls._meta.get_fields():
if not hasattr(field, 'deduplication_field') or \
not field.deduplication_field:
continue
value = data.get(field.get_activitypub_field())
if not value:
continue
filters.append({field.name: value})
if hasattr(cls, 'origin_id') and 'id' in data:
# kinda janky, but this handles special case for books
filters.append({'origin_id': data['id']})
if not filters:
# if there are no deduplication fields, it will match the first
# item no matter what. this shouldn't happen but just in case.
return None
objects = cls.objects
if hasattr(objects, 'select_subclasses'):
objects = objects.select_subclasses()
# an OR operation on all the match fields
match = objects.filter(
reduce(
operator.or_, (Q(**f) for f in filters)
)
)
# there OUGHT to be only one match
return match.first()
def broadcast(self, activity, sender, software=None):
''' send out an activity '''
broadcast_task.delay(
sender.id,
json.dumps(activity, cls=activitypub.ActivityEncoder),
self.get_recipients(software=software)
)
def get_recipients(self, software=None):
''' figure out which inbox urls to post to '''
# first we have to figure out who should receive this activity
privacy = self.privacy if hasattr(self, 'privacy') else 'public'
# is this activity owned by a user (statuses, lists, shelves), or is it
# general to the instance (like books)
user = self.user if hasattr(self, 'user') else None
if not user and self.__model__ == 'user':
# or maybe the thing itself is a user
user = self
# find anyone who's tagged in a status, for example
mentions = self.mention_users if hasattr(self, 'mention_users') else []
# we always send activities to explicitly mentioned users' inboxes
recipients = [u.inbox for u in mentions or []]
# unless it's a dm, all the followers should receive the activity
if privacy != 'direct':
user_model = apps.get_model('bookwyrm.User', require_ready=True)
# filter users first by whether they're using the desired software
# this lets us send book updates only to other bw servers
queryset = user_model.objects.filter(
bookwyrm_user=(software == 'bookwyrm')
)
# if there's a user, we only want to send to the user's followers
if user:
queryset = queryset.filter(following=user)
# ideally, we will send to shared inboxes for efficiency
shared_inboxes = queryset.filter(
shared_inbox__isnull=False
).values_list('shared_inbox', flat=True).distinct()
# but not everyone has a shared inbox
inboxes = queryset.filter(
shared_inboxes__isnull=True
).values_list('inbox', flat=True)
recipients += list(shared_inboxes) + list(inboxes)
return recipients
def to_activity(self):
''' convert from a model to an activity '''
activity = generate_activity(self)
return self.activity_serializer(**activity).serialize()
def to_create_activity(self, user, **kwargs):
''' returns the object wrapped in a Create activity '''
activity_object = self.to_activity(**kwargs)
signature = None
create_id = self.remote_id + '/activity'
if 'content' in activity_object:
signer = pkcs1_15.new(RSA.import_key(user.key_pair.private_key))
content = activity_object['content']
signed_message = signer.sign(SHA256.new(content.encode('utf8')))
signature = activitypub.Signature(
creator='%s#main-key' % user.remote_id,
created=activity_object['published'],
signatureValue=b64encode(signed_message).decode('utf8')
)
return activitypub.Create(
id=create_id,
actor=user.remote_id,
to=activity_object['to'],
cc=activity_object['cc'],
object=activity_object,
signature=signature,
).serialize()
def to_delete_activity(self, user):
''' notice of deletion '''
return activitypub.Delete(
id=self.remote_id + '/activity',
actor=user.remote_id,
to=['%s/followers' % user.remote_id],
cc=['https://www.w3.org/ns/activitystreams#Public'],
object=self.to_activity(),
).serialize()
def to_update_activity(self, user):
''' wrapper for Updates to an activity '''
activity_id = '%s#update/%s' % (self.remote_id, uuid4())
return activitypub.Update(
id=activity_id,
actor=user.remote_id,
to=['https://www.w3.org/ns/activitystreams#Public'],
object=self.to_activity()
).serialize()
def to_undo_activity(self, user):
''' undo an action '''
return activitypub.Undo(
id='%s#undo' % self.remote_id,
actor=user.remote_id,
object=self.to_activity()
).serialize()
class OrderedCollectionPageMixin(ActivitypubMixin):
''' just the paginator utilities, so you don't HAVE to
override ActivitypubMixin's to_activity (ie, for outbox '''
@property
def collection_remote_id(self):
''' this can be overriden if there's a special remote id, ie outbox '''
return self.remote_id
def to_ordered_collection(self, queryset, \
remote_id=None, page=False, collection_only=False, **kwargs):
''' an ordered collection of whatevers '''
if not queryset.ordered:
raise RuntimeError('queryset must be ordered')
remote_id = remote_id or self.remote_id
if page:
return to_ordered_collection_page(
queryset, remote_id, **kwargs)
if collection_only or not hasattr(self, 'activity_serializer'):
serializer = activitypub.OrderedCollection
activity = {}
else:
serializer = self.activity_serializer
# a dict from the model fields
activity = generate_activity(self)
if remote_id:
activity['id'] = remote_id
paginated = Paginator(queryset, PAGE_LENGTH)
# add computed fields specific to orderd collections
activity['totalItems'] = paginated.count
activity['first'] = '%s?page=1' % remote_id
activity['last'] = '%s?page=%d' % (remote_id, paginated.num_pages)
return serializer(**activity).serialize()
# pylint: disable=unused-argument
def to_ordered_collection_page(
queryset, remote_id, id_only=False, page=1, **kwargs):
''' serialize and pagiante a queryset '''
paginated = Paginator(queryset, PAGE_LENGTH)
activity_page = paginated.page(page)
if id_only:
items = [s.remote_id for s in activity_page.object_list]
else:
items = [s.to_activity() for s in activity_page.object_list]
prev_page = next_page = None
if activity_page.has_next():
next_page = '%s?page=%d' % (remote_id, activity_page.next_page_number())
if activity_page.has_previous():
prev_page = '%s?page=%d' % \
(remote_id, activity_page.previous_page_number())
return activitypub.OrderedCollectionPage(
id='%s?page=%s' % (remote_id, page),
partOf=remote_id,
orderedItems=items,
next=next_page,
prev=prev_page
).serialize()
class OrderedCollectionMixin(OrderedCollectionPageMixin):
''' extends activitypub models to work as ordered collections '''
@property
def collection_queryset(self):
''' usually an ordered collection model aggregates a different model '''
raise NotImplementedError('Model must define collection_queryset')
activity_serializer = activitypub.OrderedCollection
def to_activity(self, **kwargs):
''' an ordered collection of the specified model queryset '''
return self.to_ordered_collection(self.collection_queryset, **kwargs)
class CollectionItemMixin(ActivitypubMixin):
''' for items that are part of an (Ordered)Collection '''
activity_serializer = activitypub.Add
object_field = collection_field = None
def to_add_activity(self):
''' AP for shelving a book'''
object_field = getattr(self, self.object_field)
collection_field = getattr(self, self.collection_field)
return activitypub.Add(
id='%s#add' % self.remote_id,
actor=self.user.remote_id,
object=object_field.to_activity(),
target=collection_field.remote_id
).serialize()
def to_remove_activity(self):
''' AP for un-shelving a book'''
object_field = getattr(self, self.object_field)
collection_field = getattr(self, self.collection_field)
return activitypub.Remove(
id='%s#remove' % self.remote_id,
actor=self.user.remote_id,
object=object_field.to_activity(),
target=collection_field.remote_id
).serialize()
def generate_activity(obj):
''' go through the fields on an object '''
activity = {}
for field in obj.activity_fields:
field.set_activity_from_field(activity, obj)
if hasattr(obj, 'serialize_reverse_fields'):
# for example, editions of a work
for model_field_name, activity_field_name, sort_field in \
obj.serialize_reverse_fields:
related_field = getattr(obj, model_field_name)
activity[activity_field_name] = \
unfurl_related_field(related_field, sort_field)
if not activity.get('id'):
activity['id'] = obj.get_remote_id()
return activity
def unfurl_related_field(related_field, sort_field=None):
''' load reverse lookups (like public key owner or Status attachment '''
if hasattr(related_field, 'all'):
return [unfurl_related_field(i) for i in related_field.order_by(
sort_field).all()]
if related_field.reverse_unfurl:
return related_field.field_to_activity()
return related_field.remote_id
@app.task
def broadcast_task(sender_id, activity, recipients):
''' the celery task for broadcast '''
user_model = apps.get_model('bookwyrm.User', require_ready=True)
sender = user_model.objects.get(id=sender_id)
errors = []
for recipient in recipients:
try:
sign_and_send(sender, activity, recipient)
except requests.exceptions.HTTPError as e:
errors.append({
'error': str(e),
'recipient': recipient,
'activity': activity,
})
return errors
def sign_and_send(sender, data, destination):
''' crpyto whatever and http junk '''
now = http_date()
if not sender.key_pair.private_key:
# this shouldn't happen. it would be bad if it happened.
raise ValueError('No private key found for sender')
digest = make_digest(data)
response = requests.post(
destination,
data=data,
headers={
'Date': now,
'Digest': digest,
'Signature': make_signature(sender, destination, now, digest),
'Content-Type': 'application/activity+json; charset=utf-8',
'User-Agent': USER_AGENT,
},
)
if not response.ok:
response.raise_for_status()
return response
@receiver(models.signals.post_save)
#pylint: disable=unused-argument
def execute_after_save(sender, instance, created, *args, **kwargs):
''' broadcast when a model instance is created or updated '''
# user content like statuses, lists, and shelves, have a "user" field
user = instance.user if hasattr(instance, 'user') else None
if user and not user.local:
# we don't want to broadcast when we save remote activities
return
if created:
if not user:
# book data and users don't need to broadcast on creation
return
# ordered collection items get "Add"ed
if hasattr(instance, 'to_add_activity'):
activity = instance.to_add_activity()
else:
# everything else gets "Create"d
activity = instance.to_create_activity(user)
else:
# now, handle updates
if not user:
# users don't have associated users, they ARE users
if sender.__class__ == 'User':
user = instance
# book data trakcs last editor
elif hasattr(instance, 'last_edited_by'):
user = instance.last_edited_by
# again, if we don't know the user or they're remote, don't bother
if not user or not user.local:
return
activity = instance.to_update_activity(user)
if activity and user and user.local:
instance.broadcast(activity, user)

View file

@ -1,14 +1,9 @@
''' base model with default fields ''' ''' activitypub model functionality '''
from base64 import b64encode
from functools import reduce from functools import reduce
import json import json
import operator import operator
from uuid import uuid4
import requests import requests
from Crypto.PublicKey import RSA
from Crypto.Signature import pkcs1_15
from Crypto.Hash import SHA256
from django.apps import apps from django.apps import apps
from django.db import models from django.db import models
from django.db.models import Q from django.db.models import Q
@ -55,10 +50,6 @@ class ActivitypubMixin:
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
def delete(self, *args, **kwargs):
''' broadcast suitable delete activities '''
@classmethod @classmethod
def find_existing_by_remote_id(cls, remote_id): def find_existing_by_remote_id(cls, remote_id):
''' look up a remote id in the db ''' ''' look up a remote id in the db '''
@ -158,66 +149,6 @@ class ActivitypubMixin:
return self.activity_serializer(**activity).serialize() return self.activity_serializer(**activity).serialize()
def to_create_activity(self, user, **kwargs):
''' returns the object wrapped in a Create activity '''
activity_object = self.to_activity(**kwargs)
signature = None
create_id = self.remote_id + '/activity'
if 'content' in activity_object:
signer = pkcs1_15.new(RSA.import_key(user.key_pair.private_key))
content = activity_object['content']
signed_message = signer.sign(SHA256.new(content.encode('utf8')))
signature = activitypub.Signature(
creator='%s#main-key' % user.remote_id,
created=activity_object['published'],
signatureValue=b64encode(signed_message).decode('utf8')
)
return activitypub.Create(
id=create_id,
actor=user.remote_id,
to=activity_object['to'],
cc=activity_object['cc'],
object=activity_object,
signature=signature,
).serialize()
def to_delete_activity(self, user):
''' notice of deletion '''
return activitypub.Delete(
id=self.remote_id + '/activity',
actor=user.remote_id,
to=['%s/followers' % user.remote_id],
cc=['https://www.w3.org/ns/activitystreams#Public'],
object=self.to_activity(),
).serialize()
def to_update_activity(self, user):
''' wrapper for Updates to an activity '''
activity_id = '%s#update/%s' % (self.remote_id, uuid4())
return activitypub.Update(
id=activity_id,
actor=user.remote_id,
to=['https://www.w3.org/ns/activitystreams#Public'],
object=self.to_activity()
).serialize()
def to_undo_activity(self, user):
''' undo an action '''
return activitypub.Undo(
id='%s#undo' % self.remote_id,
actor=user.remote_id,
object=self.to_activity()
).serialize()
def generate_activity(obj): def generate_activity(obj):
''' go through the fields on an object ''' ''' go through the fields on an object '''
activity = {} activity = {}
@ -297,13 +228,14 @@ def execute_after_save(sender, instance, created, *args, **kwargs):
''' broadcast when a model instance is created or updated ''' ''' broadcast when a model instance is created or updated '''
# user content like statuses, lists, and shelves, have a "user" field # user content like statuses, lists, and shelves, have a "user" field
user = instance.user if hasattr(instance, 'user') else None user = instance.user if hasattr(instance, 'user') else None
# we don't want to broadcast when we save remote activities
if user and not user.local: if user and not user.local:
# we don't want to broadcast when we save remote activities
return return
if created: if created:
# book data and users don't need to broadcast on creation
if not user: if not user:
# book data and users don't need to broadcast on creation
return return
# ordered collection items get "Add"ed # ordered collection items get "Add"ed
@ -312,19 +244,6 @@ def execute_after_save(sender, instance, created, *args, **kwargs):
else: else:
# everything else gets "Create"d # everything else gets "Create"d
activity = instance.to_create_activity(user) activity = instance.to_create_activity(user)
else:
# now, handle updates
if not user:
# users don't have associated users, they ARE users
if sender.__class__ == 'User':
user = instance
# book data trakcs last editor
elif hasattr(instance, 'last_edited_by'):
user = instance.last_edited_by
# again, if we don't know the user or they're remote, don't bother
if not user or not user.local:
return
activity = instance.to_update_activity(user)
if activity and user and user.local: if activity and user and user.local:
instance.broadcast(activity, user) instance.broadcast(activity, user)

View file

@ -3,12 +3,12 @@ from django.core.paginator import Paginator
from bookwyrm import activitypub from bookwyrm import activitypub
from bookwyrm.settings import PAGE_LENGTH from bookwyrm.settings import PAGE_LENGTH
from . import ActivitypubMixin, generate_activity from . import ActivitypubMixin, ObjectMixin, generate_activity
class OrderedCollectionPageMixin(ActivitypubMixin): class OrderedCollectionPageMixin(ObjectMixin):
''' just the paginator utilities, so you don't HAVE to ''' just the paginator utilities, so you don't HAVE to
override ActivitypubMixin's to_activity (ie, for outbox ''' override ActivitypubMixin's to_activity (ie, for outbox) '''
@property @property
def collection_remote_id(self): def collection_remote_id(self):
''' this can be overriden if there's a special remote id, ie outbox ''' ''' this can be overriden if there's a special remote id, ie outbox '''

View file

@ -7,11 +7,11 @@ from model_utils.managers import InheritanceManager
from bookwyrm import activitypub from bookwyrm import activitypub
from bookwyrm.settings import DOMAIN from bookwyrm.settings import DOMAIN
from .activitypub_mixin import ActivitypubMixin, OrderedCollectionPageMixin from .activitypub_mixin import ObjectMixin, OrderedCollectionPageMixin
from .base_model import BookWyrmModel from .base_model import BookWyrmModel
from . import fields from . import fields
class BookDataModel(ActivitypubMixin, BookWyrmModel): class BookDataModel(ObjectMixin, BookWyrmModel):
''' fields shared between editable book data (books, works, authors) ''' ''' fields shared between editable book data (books, works, authors) '''
origin_id = models.CharField(max_length=255, null=True, blank=True) origin_id = models.CharField(max_length=255, null=True, blank=True)
openlibrary_key = fields.CharField( openlibrary_key = fields.CharField(

View file

@ -8,7 +8,6 @@ from django.views import View
from bookwyrm import forms, models from bookwyrm import forms, models
from bookwyrm.activitypub import ActivitypubResponse from bookwyrm.activitypub import ActivitypubResponse
from bookwyrm.broadcast import broadcast
from .helpers import is_api_request from .helpers import is_api_request
@ -62,5 +61,4 @@ class EditAuthor(View):
return TemplateResponse(request, 'edit_author.html', data) return TemplateResponse(request, 'edit_author.html', data)
author = form.save() author = form.save()
broadcast(request.user, author.to_update_activity(request.user))
return redirect('/author/%s' % author.id) return redirect('/author/%s' % author.id)

View file

@ -8,7 +8,6 @@ from django.views import View
from django.views.decorators.http import require_POST from django.views.decorators.http import require_POST
from bookwyrm import models from bookwyrm import models
from bookwyrm.broadcast import broadcast
# pylint: disable= no-self-use # pylint: disable= no-self-use
@method_decorator(login_required, name='dispatch') @method_decorator(login_required, name='dispatch')
@ -22,15 +21,8 @@ class Block(View):
def post(self, request, user_id): def post(self, request, user_id):
''' block a user ''' ''' block a user '''
to_block = get_object_or_404(models.User, id=user_id) to_block = get_object_or_404(models.User, id=user_id)
block = models.UserBlocks.objects.create( models.UserBlocks.objects.create(
user_subject=request.user, user_object=to_block) user_subject=request.user, user_object=to_block)
if not to_block.local:
broadcast(
request.user,
block.to_activity(),
privacy='direct',
direct_recipients=[to_block]
)
return redirect('/preferences/block') return redirect('/preferences/block')
@ -46,13 +38,5 @@ def unblock(request, user_id):
) )
except models.UserBlocks.DoesNotExist: except models.UserBlocks.DoesNotExist:
return HttpResponseNotFound() return HttpResponseNotFound()
if not to_unblock.local:
broadcast(
request.user,
block.to_undo_activity(request.user),
privacy='direct',
direct_recipients=[to_unblock]
)
block.delete() block.delete()
return redirect('/preferences/block') return redirect('/preferences/block')

View file

@ -12,7 +12,6 @@ from django.views.decorators.http import require_POST
from bookwyrm import forms, models from bookwyrm import forms, models
from bookwyrm.activitypub import ActivitypubResponse from bookwyrm.activitypub import ActivitypubResponse
from bookwyrm.broadcast import broadcast
from bookwyrm.connectors import connector_manager from bookwyrm.connectors import connector_manager
from bookwyrm.settings import PAGE_LENGTH from bookwyrm.settings import PAGE_LENGTH
from .helpers import is_api_request, get_activity_feed, get_edition from .helpers import is_api_request, get_activity_feed, get_edition
@ -136,7 +135,6 @@ class EditBook(View):
return TemplateResponse(request, 'edit_book.html', data) return TemplateResponse(request, 'edit_book.html', data)
book = form.save() book = form.save()
broadcast(request.user, book.to_update_activity(request.user))
return redirect('/book/%s' % book.id) return redirect('/book/%s' % book.id)
@ -170,7 +168,6 @@ def upload_cover(request, book_id):
book.cover = form.files['cover'] book.cover = form.files['cover']
book.save() book.save()
broadcast(request.user, book.to_update_activity(request.user))
return redirect('/book/%s' % book.id) return redirect('/book/%s' % book.id)
@ -189,7 +186,6 @@ def add_description(request, book_id):
book.description = description book.description = description
book.save() book.save()
broadcast(request.user, book.to_update_activity(request.user))
return redirect('/book/%s' % book.id) return redirect('/book/%s' % book.id)
@ -215,13 +211,10 @@ def switch_edition(request):
shelf__user=request.user shelf__user=request.user
) )
for shelfbook in shelfbooks.all(): for shelfbook in shelfbooks.all():
broadcast(request.user, shelfbook.to_remove_activity(request.user)) # TODO: this needs to be a delete and re-create
shelfbook.book = new_edition shelfbook.book = new_edition
shelfbook.save() shelfbook.save()
broadcast(request.user, shelfbook.to_add_activity(request.user))
readthroughs = models.ReadThrough.objects.filter( readthroughs = models.ReadThrough.objects.filter(
book__parent_work=new_edition.parent_work, book__parent_work=new_edition.parent_work,
user=request.user user=request.user

View file

@ -6,7 +6,6 @@ from django.shortcuts import redirect
from django.views.decorators.http import require_POST from django.views.decorators.http import require_POST
from bookwyrm import models from bookwyrm import models
from bookwyrm.broadcast import broadcast
from .helpers import get_user_from_username from .helpers import get_user_from_username
@login_required @login_required
@ -19,13 +18,10 @@ def follow(request):
except models.User.DoesNotExist: except models.User.DoesNotExist:
return HttpResponseBadRequest() return HttpResponseBadRequest()
relationship, _ = models.UserFollowRequest.objects.get_or_create( models.UserFollowRequest.objects.get_or_create(
user_subject=request.user, user_subject=request.user,
user_object=to_follow, user_object=to_follow,
) )
activity = relationship.to_activity()
broadcast(
request.user, activity, privacy='direct', direct_recipients=[to_follow])
return redirect(to_follow.local_path) return redirect(to_follow.local_path)
@ -39,14 +35,10 @@ def unfollow(request):
except models.User.DoesNotExist: except models.User.DoesNotExist:
return HttpResponseBadRequest() return HttpResponseBadRequest()
relationship = models.UserFollows.objects.get( models.UserFollows.objects.get(
user_subject=request.user, user_subject=request.user,
user_object=to_unfollow user_object=to_unfollow
) )
activity = relationship.to_undo_activity(request.user)
broadcast(
request.user, activity,
privacy='direct', direct_recipients=[to_unfollow])
to_unfollow.followers.remove(request.user) to_unfollow.followers.remove(request.user)
return redirect(to_unfollow.local_path) return redirect(to_unfollow.local_path)
@ -77,16 +69,11 @@ def accept_follow_request(request):
def handle_accept(follow_request): def handle_accept(follow_request):
''' send an acceptance message to a follow request ''' ''' send an acceptance message to a follow request '''
user = follow_request.user_subject
to_follow = follow_request.user_object
with transaction.atomic(): with transaction.atomic():
relationship = models.UserFollows.from_request(follow_request) relationship = models.UserFollows.from_request(follow_request)
follow_request.delete() follow_request.delete()
relationship.save() relationship.save()
activity = relationship.to_accept_activity()
broadcast(to_follow, activity, privacy='direct', direct_recipients=[user])
@login_required @login_required
@require_POST @require_POST
@ -106,8 +93,5 @@ def delete_follow_request(request):
except models.UserFollowRequest.DoesNotExist: except models.UserFollowRequest.DoesNotExist:
return HttpResponseBadRequest() return HttpResponseBadRequest()
activity = follow_request.to_reject_activity()
follow_request.delete() follow_request.delete()
broadcast(
request.user, activity, privacy='direct', direct_recipients=[requester])
return redirect('/user/%s' % request.user.localname) return redirect('/user/%s' % request.user.localname)

View file

@ -7,7 +7,6 @@ from django.utils.decorators import method_decorator
from django.views import View from django.views import View
from bookwyrm import forms, models from bookwyrm import forms, models
from bookwyrm.broadcast import broadcast
from bookwyrm.status import create_generated_note from bookwyrm.status import create_generated_note
from .helpers import get_user_from_username, object_visible_to_user from .helpers import get_user_from_username, object_visible_to_user
@ -63,23 +62,10 @@ class Goal(View):
if request.POST.get('post-status'): if request.POST.get('post-status'):
# create status, if appropraite # create status, if appropraite
status = create_generated_note( create_generated_note(
request.user, request.user,
'set a goal to read %d books in %d' % (goal.goal, goal.year), 'set a goal to read %d books in %d' % (goal.goal, goal.year),
privacy=goal.privacy privacy=goal.privacy
) )
broadcast(
request.user,
status.to_create_activity(request.user),
privacy=status.privacy,
software='bookwyrm')
# re-format the activity for non-bookwyrm servers
remote_activity = status.to_create_activity(request.user, pure=True)
broadcast(
request.user,
remote_activity,
privacy=status.privacy,
software='other')
return redirect(request.headers.get('Referer', '/')) return redirect(request.headers.get('Referer', '/'))

View file

@ -4,7 +4,6 @@ from requests import HTTPError
from django.db.models import Q from django.db.models import Q
from bookwyrm import activitypub, models from bookwyrm import activitypub, models
from bookwyrm.broadcast import broadcast
from bookwyrm.connectors import ConnectorException, get_data from bookwyrm.connectors import ConnectorException, get_data
from bookwyrm.status import create_generated_note from bookwyrm.status import create_generated_note
from bookwyrm.utils import regex from bookwyrm.utils import regex
@ -199,7 +198,6 @@ def handle_reading_status(user, shelf, book, privacy):
) )
status.save() status.save()
broadcast(user, status.to_create_activity(user))
def is_blocked(viewer, user): def is_blocked(viewer, user):
''' is this viewer blocked by the user? ''' ''' is this viewer blocked by the user? '''

View file

@ -7,7 +7,6 @@ from django.utils.decorators import method_decorator
from django.views import View from django.views import View
from bookwyrm import models from bookwyrm import models
from bookwyrm.broadcast import broadcast
from bookwyrm.status import create_notification from bookwyrm.status import create_notification
@ -19,7 +18,7 @@ class Favorite(View):
''' create a like ''' ''' create a like '''
status = models.Status.objects.get(id=status_id) status = models.Status.objects.get(id=status_id)
try: try:
favorite = models.Favorite.objects.create( models.Favorite.objects.create(
status=status, status=status,
user=request.user user=request.user
) )
@ -27,10 +26,6 @@ class Favorite(View):
# you already fav'ed that # you already fav'ed that
return HttpResponseBadRequest() return HttpResponseBadRequest()
fav_activity = favorite.to_activity()
broadcast(
request.user, fav_activity, privacy='direct',
direct_recipients=[status.user])
if status.user.local: if status.user.local:
create_notification( create_notification(
status.user, status.user,
@ -56,9 +51,7 @@ class Unfavorite(View):
# can't find that status, idk # can't find that status, idk
return HttpResponseNotFound() return HttpResponseNotFound()
fav_activity = favorite.to_undo_activity(request.user)
favorite.delete() favorite.delete()
broadcast(request.user, fav_activity, direct_recipients=[status.user])
# check for notification # check for notification
if status.user.local: if status.user.local:
@ -86,15 +79,12 @@ class Boost(View):
# you already boosted that. # you already boosted that.
return redirect(request.headers.get('Referer', '/')) return redirect(request.headers.get('Referer', '/'))
boost = models.Boost.objects.create( models.Boost.objects.create(
boosted_status=status, boosted_status=status,
privacy=status.privacy, privacy=status.privacy,
user=request.user, user=request.user,
) )
boost_activity = boost.to_activity()
broadcast(request.user, boost_activity)
if status.user.local: if status.user.local:
create_notification( create_notification(
status.user, status.user,
@ -114,10 +104,8 @@ class Unboost(View):
boost = models.Boost.objects.filter( boost = models.Boost.objects.filter(
boosted_status=status, user=request.user boosted_status=status, user=request.user
).first() ).first()
activity = boost.to_undo_activity(request.user)
boost.delete() boost.delete()
broadcast(request.user, activity)
# delete related notification # delete related notification
if status.user.local: if status.user.local:

View file

@ -11,7 +11,6 @@ from django.views.decorators.http import require_POST
from bookwyrm import forms, models from bookwyrm import forms, models
from bookwyrm.activitypub import ActivitypubResponse from bookwyrm.activitypub import ActivitypubResponse
from bookwyrm.broadcast import broadcast
from bookwyrm.connectors import connector_manager from bookwyrm.connectors import connector_manager
from .helpers import is_api_request, object_visible_to_user, privacy_filter from .helpers import is_api_request, object_visible_to_user, privacy_filter
from .helpers import get_user_from_username from .helpers import get_user_from_username
@ -51,13 +50,6 @@ class Lists(View):
return redirect('lists') return redirect('lists')
book_list = form.save() book_list = form.save()
# let the world know
broadcast(
request.user,
book_list.to_create_activity(request.user),
privacy=book_list.privacy,
software='bookwyrm'
)
return redirect(book_list.local_path) return redirect(book_list.local_path)
class UserLists(View): class UserLists(View):
@ -138,13 +130,6 @@ class List(View):
if not form.is_valid(): if not form.is_valid():
return redirect('list', book_list.id) return redirect('list', book_list.id)
book_list = form.save() book_list = form.save()
# let the world know
broadcast(
request.user,
book_list.to_update_activity(request.user),
privacy=book_list.privacy,
software='bookwyrm'
)
return redirect(book_list.local_path) return redirect(book_list.local_path)
@ -178,13 +163,6 @@ class Curate(View):
if approved: if approved:
suggestion.approved = True suggestion.approved = True
suggestion.save() suggestion.save()
# let the world know
broadcast(
request.user,
suggestion.to_add_activity(request.user),
privacy=book_list.privacy,
software='bookwyrm'
)
else: else:
suggestion.delete() suggestion.delete()
return redirect('list-curate', book_list.id) return redirect('list-curate', book_list.id)
@ -201,18 +179,11 @@ def add_book(request, list_id):
# do you have permission to add to the list? # do you have permission to add to the list?
if request.user == book_list.user or book_list.curation == 'open': if request.user == book_list.user or book_list.curation == 'open':
# go ahead and add it # go ahead and add it
item = models.ListItem.objects.create( models.ListItem.objects.create(
book=book, book=book,
book_list=book_list, book_list=book_list,
added_by=request.user, added_by=request.user,
) )
# let the world know
broadcast(
request.user,
item.to_add_activity(request.user),
privacy=book_list.privacy,
software='bookwyrm'
)
elif book_list.curation == 'curated': elif book_list.curation == 'curated':
# make a pending entry # make a pending entry
models.ListItem.objects.create( models.ListItem.objects.create(
@ -237,13 +208,5 @@ def remove_book(request, list_id):
if not book_list.user == request.user and not item.added_by == request.user: if not book_list.user == request.user and not item.added_by == request.user:
return HttpResponseNotFound() return HttpResponseNotFound()
activity = item.to_remove_activity(request.user)
item.delete() item.delete()
# let the world know
broadcast(
request.user,
activity,
privacy=book_list.privacy,
software='bookwyrm'
)
return redirect('list', list_id) return redirect('list', list_id)

View file

@ -9,7 +9,6 @@ from django.utils import timezone
from django.views.decorators.http import require_POST from django.views.decorators.http import require_POST
from bookwyrm import models from bookwyrm import models
from bookwyrm.broadcast import broadcast
from .helpers import get_edition, handle_reading_status from .helpers import get_edition, handle_reading_status
from .shelf import handle_unshelve from .shelf import handle_unshelve
@ -44,9 +43,8 @@ def start_reading(request, book_id):
except models.Shelf.DoesNotExist: except models.Shelf.DoesNotExist:
# this just means it isn't currently on the user's shelves # this just means it isn't currently on the user's shelves
pass pass
shelfbook = models.ShelfBook.objects.create( models.ShelfBook.objects.create(
book=book, shelf=shelf, added_by=request.user) book=book, shelf=shelf, added_by=request.user)
broadcast(request.user, shelfbook.to_add_activity(request.user))
# post about it (if you want) # post about it (if you want)
if request.POST.get('post-status'): if request.POST.get('post-status'):
@ -82,9 +80,8 @@ def finish_reading(request, book_id):
except models.Shelf.DoesNotExist: except models.Shelf.DoesNotExist:
# this just means it isn't currently on the user's shelves # this just means it isn't currently on the user's shelves
pass pass
shelfbook = models.ShelfBook.objects.create( models.ShelfBook.objects.create(
book=book, shelf=shelf, added_by=request.user) book=book, shelf=shelf, added_by=request.user)
broadcast(request.user, shelfbook.to_add_activity(request.user))
# post about it (if you want) # post about it (if you want)
if request.POST.get('post-status'): if request.POST.get('post-status'):

View file

@ -9,7 +9,6 @@ from django.views.decorators.http import require_POST
from bookwyrm import forms, models from bookwyrm import forms, models
from bookwyrm.activitypub import ActivitypubResponse from bookwyrm.activitypub import ActivitypubResponse
from bookwyrm.broadcast import broadcast
from .helpers import is_api_request, get_edition, get_user_from_username from .helpers import is_api_request, get_edition, get_user_from_username
from .helpers import handle_reading_status from .helpers import handle_reading_status
@ -136,14 +135,8 @@ def shelve(request):
except models.Shelf.DoesNotExist: except models.Shelf.DoesNotExist:
# this just means it isn't currently on the user's shelves # this just means it isn't currently on the user's shelves
pass pass
shelfbook = models.ShelfBook.objects.create( models.ShelfBook.objects.create(
book=book, shelf=desired_shelf, added_by=request.user) book=book, shelf=desired_shelf, added_by=request.user)
broadcast(
request.user,
shelfbook.to_add_activity(request.user),
privacy=shelfbook.shelf.privacy,
software='bookwyrm'
)
# post about "want to read" shelves # post about "want to read" shelves
if desired_shelf.identifier == 'to-read': if desired_shelf.identifier == 'to-read':
@ -168,10 +161,8 @@ def unshelve(request):
return redirect(request.headers.get('Referer', '/')) return redirect(request.headers.get('Referer', '/'))
#pylint: disable=unused-argument
def handle_unshelve(user, book, shelf): def handle_unshelve(user, book, shelf):
''' unshelve a book ''' ''' unshelve a book '''
row = models.ShelfBook.objects.get(book=book, shelf=shelf) row = models.ShelfBook.objects.get(book=book, shelf=shelf)
activity = row.to_remove_activity(user)
row.delete() row.delete()
broadcast(user, activity, privacy=shelf.privacy, software='bookwyrm')

View file

@ -8,7 +8,6 @@ from django.views import View
from markdown import markdown from markdown import markdown
from bookwyrm import forms, models from bookwyrm import forms, models
from bookwyrm.broadcast import broadcast
from bookwyrm.sanitize_html import InputHtmlParser from bookwyrm.sanitize_html import InputHtmlParser
from bookwyrm.settings import DOMAIN from bookwyrm.settings import DOMAIN
from bookwyrm.status import create_notification, delete_status from bookwyrm.status import create_notification, delete_status
@ -84,15 +83,6 @@ class CreateStatus(View):
status.quote = to_markdown(status.quote) status.quote = to_markdown(status.quote)
status.save() status.save()
broadcast(
request.user,
status.to_create_activity(request.user),
software='bookwyrm')
# re-format the activity for non-bookwyrm servers
remote_activity = status.to_create_activity(request.user, pure=True)
broadcast(request.user, remote_activity, software='other')
return redirect(request.headers.get('Referer', '/')) return redirect(request.headers.get('Referer', '/'))
@ -108,7 +98,6 @@ class DeleteStatus(View):
# perform deletion # perform deletion
delete_status(status) delete_status(status)
broadcast(request.user, status.to_delete_activity(request.user))
return redirect(request.headers.get('Referer', '/')) return redirect(request.headers.get('Referer', '/'))
def find_mentions(content): def find_mentions(content):

View file

@ -8,7 +8,6 @@ from django.views import View
from bookwyrm import models from bookwyrm import models
from bookwyrm.activitypub import ActivitypubResponse from bookwyrm.activitypub import ActivitypubResponse
from bookwyrm.broadcast import broadcast
from .helpers import is_api_request from .helpers import is_api_request
@ -45,17 +44,15 @@ class AddTag(View):
name = request.POST.get('name') name = request.POST.get('name')
book_id = request.POST.get('book') book_id = request.POST.get('book')
book = get_object_or_404(models.Edition, id=book_id) book = get_object_or_404(models.Edition, id=book_id)
tag_obj, created = models.Tag.objects.get_or_create( tag_obj, _ = models.Tag.objects.get_or_create(
name=name, name=name,
) )
user_tag, _ = models.UserTag.objects.get_or_create( models.UserTag.objects.get_or_create(
user=request.user, user=request.user,
book=book, book=book,
tag=tag_obj, tag=tag_obj,
) )
if created:
broadcast(request.user, user_tag.to_add_activity(request.user))
return redirect('/book/%s' % book_id) return redirect('/book/%s' % book_id)
@ -71,8 +68,6 @@ class RemoveTag(View):
user_tag = get_object_or_404( user_tag = get_object_or_404(
models.UserTag, tag=tag_obj, book=book, user=request.user) models.UserTag, tag=tag_obj, book=book, user=request.user)
tag_activity = user_tag.to_remove_activity(request.user)
user_tag.delete() user_tag.delete()
broadcast(request.user, tag_activity)
return redirect('/book/%s' % book_id) return redirect('/book/%s' % book_id)

View file

@ -15,7 +15,6 @@ from django.views import View
from bookwyrm import forms, models from bookwyrm import forms, models
from bookwyrm.activitypub import ActivitypubResponse from bookwyrm.activitypub import ActivitypubResponse
from bookwyrm.broadcast import broadcast
from bookwyrm.settings import PAGE_LENGTH from bookwyrm.settings import PAGE_LENGTH
from .helpers import get_activity_feed, get_user_from_username, is_api_request from .helpers import get_activity_feed, get_user_from_username, is_api_request
from .helpers import is_blocked, object_visible_to_user from .helpers import is_blocked, object_visible_to_user
@ -176,7 +175,6 @@ class EditUser(View):
user.avatar.save(filename, image) user.avatar.save(filename, image)
user.save() user.save()
broadcast(user, user.to_update_activity(user))
return redirect(user.local_path) return redirect(user.local_path)