diff --git a/bookwyrm/activitypub/__init__.py b/bookwyrm/activitypub/__init__.py index 446455fa3..c10d1ca1c 100644 --- a/bookwyrm/activitypub/__init__.py +++ b/bookwyrm/activitypub/__init__.py @@ -4,7 +4,7 @@ import sys from .base_activity import ActivityEncoder, Image, PublicKey, Signature from .note import Note, GeneratedNote, Article, Comment, Review, Quotation -from .note import Tombstone +from .note import Tombstone, Link from .interaction import Boost, Like from .ordered_collection import OrderedCollection, OrderedCollectionPage from .person import Person diff --git a/bookwyrm/activitypub/base_activity.py b/bookwyrm/activitypub/base_activity.py index 1cffd0edc..fc5b11288 100644 --- a/bookwyrm/activitypub/base_activity.py +++ b/bookwyrm/activitypub/base_activity.py @@ -15,7 +15,6 @@ class ActivityEncoder(JSONEncoder): @dataclass class Image: ''' image block ''' - mediaType: str url: str type: str = 'Image' diff --git a/bookwyrm/activitypub/note.py b/bookwyrm/activitypub/note.py index 4e36b01f8..d187acc6e 100644 --- a/bookwyrm/activitypub/note.py +++ b/bookwyrm/activitypub/note.py @@ -36,9 +36,17 @@ class Article(Note): type: str = 'Article' +@dataclass +class Link(): + ''' for tagging a book in a status ''' + href: str + name: str + type: str = 'Link' + @dataclass(init=False) class GeneratedNote(Note): ''' just a re-typed note ''' + tag: List[Link] type: str = 'GeneratedNote' diff --git a/bookwyrm/activitypub/verbs.py b/bookwyrm/activitypub/verbs.py index bd6d882d3..eb166260f 100644 --- a/bookwyrm/activitypub/verbs.py +++ b/bookwyrm/activitypub/verbs.py @@ -26,7 +26,6 @@ class Delete(Verb): ''' Create activity ''' to: List cc: List - signature: Signature type: str = 'Delete' diff --git a/bookwyrm/connectors/abstract_connector.py b/bookwyrm/connectors/abstract_connector.py index 3172ea797..9f9aed43a 100644 --- a/bookwyrm/connectors/abstract_connector.py +++ b/bookwyrm/connectors/abstract_connector.py @@ -122,11 +122,11 @@ class AbstractConnector(ABC): # atomic so that we don't save a work with no edition for vice versa with transaction.atomic(): if not work: - work_key = work_data.get('url') + work_key = self.get_remote_id_from_data(work_data) work = self.create_book(work_key, work_data, models.Work) if not edition: - ed_key = edition_data.get('url') + ed_key = self.get_remote_id_from_data(edition_data) edition = self.create_book(ed_key, edition_data, models.Edition) edition.default = True edition.parent_work = work @@ -161,7 +161,8 @@ class AbstractConnector(ABC): author_text = [] for author in self.get_authors_from_data(data): book.authors.add(author) - author_text.append(author.display_name) + if author.display_name: + author_text.append(author.display_name) book.author_text = ', '.join(author_text) book.save() @@ -215,6 +216,11 @@ class AbstractConnector(ABC): return None + @abstractmethod + def get_remote_id_from_data(self, data): + ''' otherwise we won't properly set the remote_id in the db ''' + + @abstractmethod def is_work_data(self, data): ''' differentiate works and editions ''' diff --git a/bookwyrm/connectors/bookwyrm_connector.py b/bookwyrm/connectors/bookwyrm_connector.py index a9517ca0f..c7a0f2ece 100644 --- a/bookwyrm/connectors/bookwyrm_connector.py +++ b/bookwyrm/connectors/bookwyrm_connector.py @@ -41,14 +41,22 @@ class Connector(AbstractConnector): ] self.author_mappings = [ - Mapping('born', remote_field='birth_date', formatter=get_date), - Mapping('died', remote_field='death_date', formatter=get_date), + Mapping('name'), Mapping('bio'), + Mapping('openlibrary_key'), + Mapping('wikipedia_link'), + Mapping('aliases'), + Mapping('born', formatter=get_date), + Mapping('died', formatter=get_date), ] + def get_remote_id_from_data(self, data): + return data.get('id') + + def is_work_data(self, data): - return data['book_type'] == 'Work' + return data['type'] == 'Work' def get_edition_from_work_data(self, data): diff --git a/bookwyrm/connectors/openlibrary.py b/bookwyrm/connectors/openlibrary.py index 0ae3ce35b..00b76f41d 100644 --- a/bookwyrm/connectors/openlibrary.py +++ b/bookwyrm/connectors/openlibrary.py @@ -73,6 +73,14 @@ class Connector(AbstractConnector): + def get_remote_id_from_data(self, data): + try: + key = data['key'] + except KeyError: + raise ConnectorException('Invalid book data') + return '%s/%s' % (self.books_url, key) + + def is_work_data(self, data): return bool(re.match(r'^[\/\w]+OL\d+W$', data['key'])) diff --git a/bookwyrm/connectors/self_connector.py b/bookwyrm/connectors/self_connector.py index 0e77ecf67..fb70978d8 100644 --- a/bookwyrm/connectors/self_connector.py +++ b/bookwyrm/connectors/self_connector.py @@ -51,6 +51,9 @@ class Connector(AbstractConnector): ) + def get_remote_id_from_data(self, data): + pass + def is_work_data(self, data): pass diff --git a/bookwyrm/incoming.py b/bookwyrm/incoming.py index 7507ee5b5..56bea4567 100644 --- a/bookwyrm/incoming.py +++ b/bookwyrm/incoming.py @@ -225,6 +225,15 @@ def handle_create(activity): if not reply: return + # look up books + book_urls = [] + if hasattr(activity, 'inReplyToBook'): + book_urls.append(activity.inReplyToBook) + if hasattr(activity, 'tag'): + book_urls += [t.href for t in activity.tag if t.type == 'Book'] + for remote_id in book_urls: + books_manager.get_or_create_book(remote_id) + model = models.activity_models[activity.type] status = activity.to_model(model) diff --git a/bookwyrm/models/base_model.py b/bookwyrm/models/base_model.py index 7d3cb344b..8150d650c 100644 --- a/bookwyrm/models/base_model.py +++ b/bookwyrm/models/base_model.py @@ -108,6 +108,20 @@ class ActivitypubMixin: ).serialize() + def to_delete_activity(self, user): + ''' notice of deletion ''' + # this should be a tombstone + activity_object = self.to_activity() + + 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=activity_object, + ).serialize() + + def to_update_activity(self, user): ''' wrapper for Updates to an activity ''' activity_id = '%s#update/%s' % (user.remote_id, uuid4()) diff --git a/bookwyrm/models/book.py b/bookwyrm/models/book.py index 03b2c1f8b..b993ef5eb 100644 --- a/bookwyrm/models/book.py +++ b/bookwyrm/models/book.py @@ -58,6 +58,16 @@ class Book(ActivitypubMixin, BookWyrmModel): ''' the activitypub serialization should be a list of author ids ''' return [a.remote_id for a in self.authors.all()] + @property + def ap_cover(self): + ''' an image attachment ''' + if not self.cover or not hasattr(self.cover, 'url'): + return [] + return [activitypub.Image( + url='https://%s%s' % (DOMAIN, self.cover.url), + )] + + activity_mappings = [ ActivityMapping('id', 'remote_id'), @@ -90,6 +100,7 @@ class Book(ActivitypubMixin, BookWyrmModel): ActivityMapping('lccn', 'lccn'), ActivityMapping('editions', 'editions_path'), + ActivityMapping('attachment', 'ap_cover'), ] def save(self, *args, **kwargs): diff --git a/bookwyrm/models/import_job.py b/bookwyrm/models/import_job.py index f7b5e8a29..891bfd1b7 100644 --- a/bookwyrm/models/import_job.py +++ b/bookwyrm/models/import_job.py @@ -72,7 +72,7 @@ class ImportItem(models.Model): def get_book_from_isbn(self): ''' search by isbn ''' search_result = books_manager.first_search_result( - self.isbn, min_confidence=0.992 + self.isbn, min_confidence=0.995 ) if search_result: try: @@ -90,7 +90,7 @@ class ImportItem(models.Model): self.data['Author'] ) search_result = books_manager.first_search_result( - search_term, min_confidence=0.992 + search_term, min_confidence=0.995 ) if search_result: try: diff --git a/bookwyrm/models/status.py b/bookwyrm/models/status.py index 8798589f3..36dbb06d8 100644 --- a/bookwyrm/models/status.py +++ b/bookwyrm/models/status.py @@ -57,6 +57,17 @@ class Status(OrderedCollectionPageMixin, BookWyrmModel): ''' structured replies block ''' return self.to_replies() + @property + def ap_tag(self): + tags = [] + for book in self.mention_books.all(): + tags.append(activitypub.Link( + href=book.local_id, + name=book.title, + type='Book' + )) + return tags + shared_mappings = [ ActivityMapping('id', 'remote_id'), ActivityMapping('url', 'remote_id'), @@ -66,6 +77,7 @@ class Status(OrderedCollectionPageMixin, BookWyrmModel): ActivityMapping('to', 'ap_to'), ActivityMapping('cc', 'ap_cc'), ActivityMapping('replies', 'ap_replies'), + ActivityMapping('tag', 'ap_tag'), ] # serializing to bookwyrm expanded activitypub diff --git a/bookwyrm/outgoing.py b/bookwyrm/outgoing.py index 908f3b5bf..d236819e2 100644 --- a/bookwyrm/outgoing.py +++ b/bookwyrm/outgoing.py @@ -204,7 +204,7 @@ def handle_imported_book(user, item, include_reviews, privacy): def handle_delete_status(user, status): ''' delete a status and broadcast deletion to other servers ''' delete_status(status) - broadcast(user, status.to_activity()) + broadcast(user, status.to_delete_activity(user)) def handle_status(user, form): diff --git a/bookwyrm/static/css/format.css b/bookwyrm/static/css/format.css index c37131f46..b1bf20cde 100644 --- a/bookwyrm/static/css/format.css +++ b/bookwyrm/static/css/format.css @@ -116,3 +116,8 @@ input.toggle-control:checked ~ .toggle-content { content: "\e903"; right: 0; } + +/* --- BLOCKQUOTE --- */ +blockquote { + white-space: pre-line; +} diff --git a/bookwyrm/templates/snippets/book_description.html b/bookwyrm/templates/snippets/book_description.html index f5fb3f439..12c9ccfb0 100644 --- a/bookwyrm/templates/snippets/book_description.html +++ b/bookwyrm/templates/snippets/book_description.html @@ -1,6 +1,2 @@ -{% if book.description %} -
{{ book.description }}
-{% elif book.parent_work.description %} -
{{ book.parent_work.description }}
-{% endif %} +
{% if book.description %}{{ book.description }}{% elif book.parent_work.description %}{{ book.parent_work.description }}{% endif %}