Files

131 lines
4.0 KiB
Python

import book_cache
import goodreads
import library_api
SHELVES = ["read", "reading", "want-to-read"]
DESCRIPTION_MAX_LENGTH = 600
GENRE_LIMIT = 5
def check_rating(rating: int) -> int:
if not 0 <= rating <= 10:
raise ValueError("Rating must be between 0 and 10")
return rating
def check_shelf(shelf: str) -> str:
if shelf not in SHELVES:
raise ValueError(f"Shelf must be one of {', '.join(SHELVES)}")
return shelf
def load_books() -> list[dict]:
return library_api.fetch_library()["books"]
def find_book(books: list[dict], text: str) -> dict | None:
identifier = goodreads.book_id(text) or (text if text.isdigit() else None)
for book in books:
if identifier and book.get("goodreadsId") == identifier:
return book
if book.get("title", "").lower() == text.lower():
return book
return None
def listed_identifier(text: str) -> str:
book = find_book(load_books(), text)
if not book:
raise ValueError(f"{text} is not listed")
return book["goodreadsId"]
def short_description(text: str | None) -> str | None:
if not text or len(text) <= DESCRIPTION_MAX_LENGTH:
return text
return text[:DESCRIPTION_MAX_LENGTH].rsplit(" ", 1)[0] + "..."
def scraped_fields(scraped: dict) -> dict:
return {
"title": scraped["title"],
"authors": scraped["authors"],
"coverSource": scraped["cover"],
"description": short_description(scraped["description"]),
"pages": scraped["pages"],
"published": scraped["published"],
"genres": scraped["genres"][:GENRE_LIMIT],
"averageRating": scraped["averageRating"],
"ratingCount": scraped["ratingCount"],
}
def update_book(text: str, fields: dict) -> dict:
return library_api.save_book(listed_identifier(text), fields)
def series_tag(series: dict, entry: dict) -> dict:
tag = {"name": series["name"], "url": series["url"]}
if entry.get("position"):
tag["position"] = entry["position"]
return tag
def add_series_book(series: dict, entry: dict, shelf: str) -> dict:
check_shelf(shelf)
tag = series_tag(series, entry)
if find_book(load_books(), entry["goodreadsId"]):
return library_api.save_book(entry["goodreadsId"], {"shelf": shelf, "series": tag})
return add_book(entry["url"], shelf, series=tag)
def add_book(url: str, shelf: str = "read", rating: int | None = None, series: dict | None = None) -> dict:
check_shelf(shelf)
if rating is not None:
check_rating(rating)
identifier = goodreads.book_id(url)
if not identifier:
raise ValueError(f"{url} is not a Goodreads book link")
if find_book(load_books(), identifier):
raise ValueError(f"{identifier} is already listed")
scraped = book_cache.scrape_book(url)
if not scraped["title"]:
raise ValueError("Could not read the book from Goodreads")
fields = {**scraped_fields(scraped), "shelf": shelf, "rating": rating}
if series:
fields["series"] = series
return library_api.save_book(identifier, fields)
def remove_book(text: str) -> None:
library_api.delete_book(listed_identifier(text))
def rate_book(text: str, rating: int | None) -> dict:
if rating is not None:
check_rating(rating)
return update_book(text, {"rating": rating})
def move_book(text: str, shelf: str) -> dict:
check_shelf(shelf)
return update_book(text, {"shelf": shelf})
def set_dates(text: str, started: str | None, finished: str | None) -> dict:
return update_book(text, {"started": started, "finished": finished})
def refresh_book(text: str) -> dict:
book = find_book(load_books(), text)
if not book:
raise ValueError(f"{text} is not listed")
scraped = book_cache.scrape_book(book["url"], fresh=True)
return library_api.save_book(book["goodreadsId"], scraped_fields(scraped))
def store_book_cover(book: dict) -> dict:
return library_api.save_book(book["goodreadsId"], {"coverSource": book["coverSource"]})