mirror of
https://github.com/bookwyrm-social/bookwyrm.git
synced 2024-11-20 08:31:07 +00:00
Merge pull request #261 from mouse-reeve/federated-book-lookup
Federated book lookup
This commit is contained in:
commit
0b2889d747
11 changed files with 74 additions and 10 deletions
|
@ -4,7 +4,7 @@ import sys
|
||||||
|
|
||||||
from .base_activity import ActivityEncoder, Image, PublicKey, Signature
|
from .base_activity import ActivityEncoder, Image, PublicKey, Signature
|
||||||
from .note import Note, GeneratedNote, Article, Comment, Review, Quotation
|
from .note import Note, GeneratedNote, Article, Comment, Review, Quotation
|
||||||
from .note import Tombstone
|
from .note import Tombstone, Link
|
||||||
from .interaction import Boost, Like
|
from .interaction import Boost, Like
|
||||||
from .ordered_collection import OrderedCollection, OrderedCollectionPage
|
from .ordered_collection import OrderedCollection, OrderedCollectionPage
|
||||||
from .person import Person
|
from .person import Person
|
||||||
|
|
|
@ -15,7 +15,6 @@ class ActivityEncoder(JSONEncoder):
|
||||||
@dataclass
|
@dataclass
|
||||||
class Image:
|
class Image:
|
||||||
''' image block '''
|
''' image block '''
|
||||||
mediaType: str
|
|
||||||
url: str
|
url: str
|
||||||
type: str = 'Image'
|
type: str = 'Image'
|
||||||
|
|
||||||
|
|
|
@ -36,9 +36,17 @@ class Article(Note):
|
||||||
type: str = 'Article'
|
type: str = 'Article'
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Link():
|
||||||
|
''' for tagging a book in a status '''
|
||||||
|
href: str
|
||||||
|
name: str
|
||||||
|
type: str = 'Link'
|
||||||
|
|
||||||
@dataclass(init=False)
|
@dataclass(init=False)
|
||||||
class GeneratedNote(Note):
|
class GeneratedNote(Note):
|
||||||
''' just a re-typed note '''
|
''' just a re-typed note '''
|
||||||
|
tag: List[Link]
|
||||||
type: str = 'GeneratedNote'
|
type: str = 'GeneratedNote'
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -122,11 +122,11 @@ class AbstractConnector(ABC):
|
||||||
# atomic so that we don't save a work with no edition for vice versa
|
# atomic so that we don't save a work with no edition for vice versa
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
if not work:
|
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)
|
work = self.create_book(work_key, work_data, models.Work)
|
||||||
|
|
||||||
if not edition:
|
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 = self.create_book(ed_key, edition_data, models.Edition)
|
||||||
edition.default = True
|
edition.default = True
|
||||||
edition.parent_work = work
|
edition.parent_work = work
|
||||||
|
@ -161,6 +161,7 @@ class AbstractConnector(ABC):
|
||||||
author_text = []
|
author_text = []
|
||||||
for author in self.get_authors_from_data(data):
|
for author in self.get_authors_from_data(data):
|
||||||
book.authors.add(author)
|
book.authors.add(author)
|
||||||
|
if author.display_name:
|
||||||
author_text.append(author.display_name)
|
author_text.append(author.display_name)
|
||||||
book.author_text = ', '.join(author_text)
|
book.author_text = ', '.join(author_text)
|
||||||
book.save()
|
book.save()
|
||||||
|
@ -215,6 +216,11 @@ class AbstractConnector(ABC):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def get_remote_id_from_data(self, data):
|
||||||
|
''' otherwise we won't properly set the remote_id in the db '''
|
||||||
|
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def is_work_data(self, data):
|
def is_work_data(self, data):
|
||||||
''' differentiate works and editions '''
|
''' differentiate works and editions '''
|
||||||
|
|
|
@ -41,14 +41,22 @@ class Connector(AbstractConnector):
|
||||||
]
|
]
|
||||||
|
|
||||||
self.author_mappings = [
|
self.author_mappings = [
|
||||||
Mapping('born', remote_field='birth_date', formatter=get_date),
|
Mapping('name'),
|
||||||
Mapping('died', remote_field='death_date', formatter=get_date),
|
|
||||||
Mapping('bio'),
|
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):
|
def is_work_data(self, data):
|
||||||
return data['book_type'] == 'Work'
|
return data['type'] == 'Work'
|
||||||
|
|
||||||
|
|
||||||
def get_edition_from_work_data(self, data):
|
def get_edition_from_work_data(self, data):
|
||||||
|
|
|
@ -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):
|
def is_work_data(self, data):
|
||||||
return bool(re.match(r'^[\/\w]+OL\d+W$', data['key']))
|
return bool(re.match(r'^[\/\w]+OL\d+W$', data['key']))
|
||||||
|
|
||||||
|
|
|
@ -51,6 +51,9 @@ class Connector(AbstractConnector):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_remote_id_from_data(self, data):
|
||||||
|
pass
|
||||||
|
|
||||||
def is_work_data(self, data):
|
def is_work_data(self, data):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
|
@ -225,6 +225,15 @@ def handle_create(activity):
|
||||||
if not reply:
|
if not reply:
|
||||||
return
|
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]
|
model = models.activity_models[activity.type]
|
||||||
status = activity.to_model(model)
|
status = activity.to_model(model)
|
||||||
|
|
||||||
|
|
|
@ -58,6 +58,16 @@ class Book(ActivitypubMixin, BookWyrmModel):
|
||||||
''' the activitypub serialization should be a list of author ids '''
|
''' the activitypub serialization should be a list of author ids '''
|
||||||
return [a.remote_id for a in self.authors.all()]
|
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 = [
|
activity_mappings = [
|
||||||
ActivityMapping('id', 'remote_id'),
|
ActivityMapping('id', 'remote_id'),
|
||||||
|
|
||||||
|
@ -90,6 +100,7 @@ class Book(ActivitypubMixin, BookWyrmModel):
|
||||||
|
|
||||||
ActivityMapping('lccn', 'lccn'),
|
ActivityMapping('lccn', 'lccn'),
|
||||||
ActivityMapping('editions', 'editions_path'),
|
ActivityMapping('editions', 'editions_path'),
|
||||||
|
ActivityMapping('attachment', 'ap_cover'),
|
||||||
]
|
]
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
|
|
|
@ -72,7 +72,7 @@ class ImportItem(models.Model):
|
||||||
def get_book_from_isbn(self):
|
def get_book_from_isbn(self):
|
||||||
''' search by isbn '''
|
''' search by isbn '''
|
||||||
search_result = books_manager.first_search_result(
|
search_result = books_manager.first_search_result(
|
||||||
self.isbn, min_confidence=0.992
|
self.isbn, min_confidence=0.995
|
||||||
)
|
)
|
||||||
if search_result:
|
if search_result:
|
||||||
try:
|
try:
|
||||||
|
@ -90,7 +90,7 @@ class ImportItem(models.Model):
|
||||||
self.data['Author']
|
self.data['Author']
|
||||||
)
|
)
|
||||||
search_result = books_manager.first_search_result(
|
search_result = books_manager.first_search_result(
|
||||||
search_term, min_confidence=0.992
|
search_term, min_confidence=0.995
|
||||||
)
|
)
|
||||||
if search_result:
|
if search_result:
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -57,6 +57,17 @@ class Status(OrderedCollectionPageMixin, BookWyrmModel):
|
||||||
''' structured replies block '''
|
''' structured replies block '''
|
||||||
return self.to_replies()
|
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 = [
|
shared_mappings = [
|
||||||
ActivityMapping('id', 'remote_id'),
|
ActivityMapping('id', 'remote_id'),
|
||||||
ActivityMapping('url', 'remote_id'),
|
ActivityMapping('url', 'remote_id'),
|
||||||
|
@ -66,6 +77,7 @@ class Status(OrderedCollectionPageMixin, BookWyrmModel):
|
||||||
ActivityMapping('to', 'ap_to'),
|
ActivityMapping('to', 'ap_to'),
|
||||||
ActivityMapping('cc', 'ap_cc'),
|
ActivityMapping('cc', 'ap_cc'),
|
||||||
ActivityMapping('replies', 'ap_replies'),
|
ActivityMapping('replies', 'ap_replies'),
|
||||||
|
ActivityMapping('tag', 'ap_tag'),
|
||||||
]
|
]
|
||||||
|
|
||||||
# serializing to bookwyrm expanded activitypub
|
# serializing to bookwyrm expanded activitypub
|
||||||
|
|
Loading…
Reference in a new issue