diff --git a/bookwyrm/activitypub/book.py b/bookwyrm/activitypub/book.py index 49f2deb81..60d36bd02 100644 --- a/bookwyrm/activitypub/book.py +++ b/bookwyrm/activitypub/book.py @@ -55,7 +55,6 @@ class Work(Book): @dataclass(init=False) class Author(ActivityObject): ''' author of a book ''' - url: str name: str born: str died: str diff --git a/bookwyrm/broadcast.py b/bookwyrm/broadcast.py index 9b071b393..a1eebaee3 100644 --- a/bookwyrm/broadcast.py +++ b/bookwyrm/broadcast.py @@ -61,7 +61,7 @@ def broadcast_task(sender_id, activity, recipients): return errors -def sign_and_send(sender, activity, destination): +def sign_and_send(sender, data, destination): ''' crpyto whatever and http junk ''' now = http_date() @@ -69,7 +69,6 @@ def sign_and_send(sender, activity, destination): # this shouldn't happen. it would be bad if it happened. raise ValueError('No private key found for sender') - data = json.dumps(activity).encode('utf-8') digest = make_digest(data) response = requests.post( diff --git a/bookwyrm/incoming.py b/bookwyrm/incoming.py index 5f4cc15b1..c6a22caa1 100644 --- a/bookwyrm/incoming.py +++ b/bookwyrm/incoming.py @@ -62,6 +62,8 @@ def shared_inbox(request): 'Announce': handle_boost, 'Add': { 'Tag': handle_tag, + 'Edition': handle_shelve, + 'Work': handle_shelve, }, 'Undo': { 'Follow': handle_unfollow, @@ -318,6 +320,22 @@ def handle_tag(activity): status_builder.create_tag(user, book, activity['object']['name']) +@app.task +def handle_shelve(activity): + ''' putting a book on a shelf ''' + user = get_or_create_remote_user(activity['actor']) + book = books_manager.get_or_create_book(activity['object']) + try: + shelf = models.Shelf.objects.get(remote_id=activity['target']) + except models.Shelf.DoesNotExist: + return + if shelf.user != user: + # this doesn't add up. + return + shelf.books.add(book) + shelf.save() + + @app.task def handle_update_user(activity): ''' receive an updated user Person activity object ''' diff --git a/bookwyrm/models/base_model.py b/bookwyrm/models/base_model.py index 8150d650c..2215a28a9 100644 --- a/bookwyrm/models/base_model.py +++ b/bookwyrm/models/base_model.py @@ -68,7 +68,10 @@ class ActivitypubMixin: if not hasattr(self, mapping.model_key) or not mapping.activity_key: continue value = getattr(self, mapping.model_key) - if hasattr(value, 'remote_id'): + print(value) + if hasattr(value, 'local_id'): + value = value.local_id + elif hasattr(value, 'remote_id'): value = value.remote_id if isinstance(value, datetime): value = value.isoformat() diff --git a/bookwyrm/models/book.py b/bookwyrm/models/book.py index b6261b8b7..93d9393de 100644 --- a/bookwyrm/models/book.py +++ b/bookwyrm/models/book.py @@ -56,7 +56,7 @@ class Book(ActivitypubMixin, BookWyrmModel): @property def ap_authors(self): ''' the activitypub serialization should be a list of author ids ''' - return [a.remote_id for a in self.authors.all()] + return [a.local_id for a in self.authors.all()] @property def ap_cover(self): @@ -73,7 +73,7 @@ class Book(ActivitypubMixin, BookWyrmModel): return self.parent_work.local_id activity_mappings = [ - ActivityMapping('id', 'remote_id'), + ActivityMapping('id', 'local_id'), ActivityMapping('authors', 'ap_authors'), ActivityMapping('first_published_date', 'first_published_date'), @@ -258,7 +258,7 @@ class Author(ActivitypubMixin, BookWyrmModel): an instance, so it needs a local url for federation. but it still needs the remote_id for easier deduplication and, if appropriate, to sync with the remote canonical copy (ditto here for author)''' - return 'https://%s/book/%d' % (DOMAIN, self.id) + return 'https://%s/author/%d' % (DOMAIN, self.id) @property def display_name(self): @@ -271,8 +271,7 @@ class Author(ActivitypubMixin, BookWyrmModel): return self.last_name or self.first_name activity_mappings = [ - ActivityMapping('id', 'remote_id'), - ActivityMapping('url', 'remote_id'), + ActivityMapping('id', 'local_id'), ActivityMapping('name', 'display_name'), ActivityMapping('born', 'born'), ActivityMapping('died', 'died'), diff --git a/bookwyrm/models/shelf.py b/bookwyrm/models/shelf.py index 53e557eea..24f1fdced 100644 --- a/bookwyrm/models/shelf.py +++ b/bookwyrm/models/shelf.py @@ -49,8 +49,8 @@ class ShelfBook(BookWyrmModel): return activitypub.Add( id='%s#add' % self.remote_id, actor=user.remote_id, - object=self.book.to_activity(), - target=self.shelf.to_activity() + object=self.book.local_id, + target=self.shelf.remote_id, ).serialize() def to_remove_activity(self, user): diff --git a/bookwyrm/signatures.py b/bookwyrm/signatures.py index 7e3c637ff..57c181dfb 100644 --- a/bookwyrm/signatures.py +++ b/bookwyrm/signatures.py @@ -44,7 +44,8 @@ def make_signature(sender, destination, date, digest): def make_digest(data): ''' creates a message digest for signing ''' - return 'SHA-256=' + b64encode(hashlib.sha256(data).digest()).decode('utf-8') + return 'SHA-256=' + b64encode(hashlib.sha256(data.encode('utf-8'))\ + .digest()).decode('utf-8') def verify_digest(request): diff --git a/bookwyrm/templates/layout.html b/bookwyrm/templates/layout.html index dfe483279..e4b7ce5e3 100644 --- a/bookwyrm/templates/layout.html +++ b/bookwyrm/templates/layout.html @@ -2,7 +2,7 @@ - BookWyrm + {% if title %}{{ title }} | {% endif %}BookWyrm diff --git a/bookwyrm/views.py b/bookwyrm/views.py index a4d997be4..fc066f59e 100644 --- a/bookwyrm/views.py +++ b/bookwyrm/views.py @@ -36,12 +36,12 @@ def is_api_request(request): def server_error_page(request): ''' 500 errors ''' - return TemplateResponse(request, 'error.html') + return TemplateResponse(request, 'error.html', {'title': 'Oops!'}) def not_found_page(request, _): ''' 404s ''' - return TemplateResponse(request, 'notfound.html') + return TemplateResponse(request, 'notfound.html', {'title': 'Not found'}) @login_required @@ -97,6 +97,7 @@ def home_tab(request, tab): next_page = '/?page=%d#feed' % (page + 1) prev_page = '/?page=%d#feed' % (page - 1) data = { + 'title': 'Updates Feed', 'user': request.user, 'suggested_books': set(suggested_books), 'activities': activities, @@ -181,6 +182,7 @@ def search(request): book_results = books_manager.search(query) data = { + 'title': 'Search Results', 'book_results': book_results, 'user_results': user_results, 'query': query, @@ -192,6 +194,7 @@ def search(request): def import_page(request): ''' import history from goodreads ''' return TemplateResponse(request, 'import.html', { + 'title': 'Import Books', 'import_form': forms.ImportForm(), 'jobs': models.ImportJob. objects.filter(user=request.user).order_by('-created_date'), @@ -207,6 +210,7 @@ def import_status(request, job_id): raise PermissionDenied task = app.AsyncResult(job.task_id) return TemplateResponse(request, 'import_status.html', { + 'title': 'Import Status', 'job': job, 'items': job.items.order_by('index').all(), 'task': task @@ -219,6 +223,7 @@ def login_page(request): return redirect('/') # send user to the login page data = { + 'title': 'Login', 'site_settings': models.SiteSettings.get(), 'login_form': forms.LoginForm(), 'register_form': forms.RegisterForm(), @@ -229,6 +234,7 @@ def login_page(request): def about_page(request): ''' more information about the instance ''' data = { + 'title': 'About', 'site_settings': models.SiteSettings.get(), } return TemplateResponse(request, 'about.html', data) @@ -236,7 +242,7 @@ def about_page(request): def password_reset_request(request): ''' invite management page ''' - return TemplateResponse(request, 'password_reset_request.html') + return TemplateResponse(request, 'password_reset_request.html', {'title': 'Reset Password'}) def password_reset(request, code): @@ -253,7 +259,7 @@ def password_reset(request, code): return TemplateResponse( request, 'password_reset.html', - {'code': reset_code.code} + {'title': 'Reset Password', 'code': reset_code.code} ) @@ -269,6 +275,7 @@ def invite_page(request, code): raise PermissionDenied data = { + 'title': 'Join', 'site_settings': models.SiteSettings.get(), 'register_form': forms.RegisterForm(), 'invite': invite, @@ -280,6 +287,7 @@ def invite_page(request, code): def manage_invites(request): ''' invite management page ''' data = { + 'title': 'Invitations', 'invites': models.SiteInvite.objects.filter(user=request.user), 'form': forms.CreateInviteForm(), } @@ -293,6 +301,7 @@ def notifications_page(request): .order_by('-created_date') unread = [n.id for n in notifications.filter(read=False)] data = { + 'title': 'Notifications', 'notifications': notifications, 'unread': unread, } @@ -313,6 +322,7 @@ def user_page(request, username, subpage=None, shelf=None): # otherwise we're at a UI view data = { + 'title': user.name, 'user': user, 'is_self': request.user.id == user.id, } @@ -416,6 +426,7 @@ def status_page(request, username, status_id): return JsonResponse(status.to_activity(), encoder=ActivityEncoder) data = { + 'title': status.type, 'status': status, } return TemplateResponse(request, 'status.html', data) @@ -460,6 +471,7 @@ def edit_profile_page(request): form = forms.EditUserForm(instance=request.user) data = { + 'title': 'Edit profile', 'form': form, 'user': user, } @@ -506,6 +518,7 @@ def book_page(request, book_id): ).distinct().all() data = { + 'title': book.title, 'book': book, 'reviews': reviews.filter(content__isnull=False), 'ratings': reviews.filter(content__isnull=True), @@ -539,6 +552,7 @@ def edit_book_page(request, book_id): if not book.description: book.description = book.parent_work.description data = { + 'title': 'Edit Book', 'book': book, 'form': forms.EditionForm(instance=book) } @@ -550,6 +564,7 @@ def editions_page(request, work_id): work = models.Work.objects.get(id=work_id) editions = models.Edition.objects.filter(parent_work=work).all() data = { + 'title': 'Editions of %s' % work.title, 'editions': editions, 'work': work, } @@ -568,6 +583,7 @@ def author_page(request, author_id): books = models.Work.objects.filter(authors=author) data = { + 'title': author.name, 'author': author, 'books': [b.default_edition for b in books], } @@ -586,6 +602,7 @@ def tag_page(request, tag_id): books = models.Edition.objects.filter(tag__identifier=tag_id).distinct() data = { + 'title': tag_obj.name, 'books': books, 'tag': tag_obj, }