Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for Libro.fm #169

Merged
merged 16 commits into from
May 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .github/workflows/read-advanced.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,11 @@ on:
description: Date to record the status of the book (YYYY-MM-DD). Leave blank for today. Optional.
type: string
identifier:
description: The book's identifier. This is an ISBN or Libby share URL. Required.
description: The book's identifier. This is an ISBN, Libby or Libro.fm share URL. Required.
# Example values:
# 9780062315007
# https://share.libbyapp.com/title/9575390
# https://libro.fm/audiobooks/9781797176888-the-ministry-of-time
required: true
type: string
notes:
Expand Down
3 changes: 2 additions & 1 deletion .github/workflows/read-thumbnail.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,11 @@ on:
workflow_dispatch:
inputs:
identifier:
description: The book's identifier. This is an ISBN or Libby share URL. Required.
description: The book's identifier. This is an ISBN, Libby or Libro.fm share URL. Required.
# Example values:
# 9780062315007
# https://share.libbyapp.com/title/9575390
# https://libro.fm/audiobooks/9781797176888-the-ministry-of-time
required: true
type: string
book-status:
Expand Down
3 changes: 2 additions & 1 deletion .github/workflows/read.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,11 @@ on:
workflow_dispatch:
inputs:
identifier:
description: The book's identifier. This is an ISBN or Libby share URL. Required.
description: The book's identifier. This is an ISBN, Libby or Libro.fm share URL. Required.
# Example values:
# 9780062315007
# https://share.libbyapp.com/title/9575390
# https://libro.fm/audiobooks/9781797176888-the-ministry-of-time
required: true
type: string
book-status:
Expand Down
20 changes: 12 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,11 @@ The action will then fetch the book's metadata using and commit the change in yo

Depending on the type of `identifier` you submit to the action, it will use the follow data provider.

| Identifier | Data provider |
| ---------- | ---------------------------------------------------------------------- |
| ISBN | [@library-pals/isbn](https://www.npmjs.com/package/@library-pals/isbn) |
| Libby URL | [Libby](https://libbyapp.com/shelf) via metatag and HTML scraping |
| Identifier | Data provider |
| ------------ | ---------------------------------------------------------------------- |
| ISBN | [@library-pals/isbn](https://www.npmjs.com/package/@library-pals/isbn) |
| Libby URL | [Libby](https://libbyapp.com) via metatag and HTML scraping |
| Libro.fm URL | [Libro.fm](https://libro.fm) via metatag scraping |

## Book lifecycle

Expand All @@ -42,10 +43,11 @@ on:
workflow_dispatch:
inputs:
identifier:
description: The book's identifier. This is an ISBN or Libby share URL. Required.
description: The book's identifier. This is an ISBN, Libby or Libro.fm share URL. Required.
# Example values:
# 9780062315007
# https://share.libbyapp.com/title/9575390
# https://libro.fm/audiobooks/9781797176888-the-ministry-of-time
required: true
type: string
book-status:
Expand Down Expand Up @@ -136,10 +138,11 @@ on:
description: Date to record the status of the book (YYYY-MM-DD). Leave blank for today. Optional.
type: string
identifier:
description: The book's identifier. This is an ISBN or Libby share URL. Required.
description: The book's identifier. This is an ISBN, Libby or Libro.fm share URL. Required.
# Example values:
# 9780062315007
# https://share.libbyapp.com/title/9575390
# https://libro.fm/audiobooks/9781797176888-the-ministry-of-time
required: true
type: string
notes:
Expand Down Expand Up @@ -235,10 +238,11 @@ on:
workflow_dispatch:
inputs:
identifier:
description: The book's identifier. This is an ISBN or Libby share URL. Required.
description: The book's identifier. This is an ISBN, Libby or Libro.fm share URL. Required.
# Example values:
# 9780062315007
# https://share.libbyapp.com/title/9575390
# https://libro.fm/audiobooks/9781797176888-the-ministry-of-time
required: true
type: string
book-status:
Expand Down Expand Up @@ -328,7 +332,7 @@ To trigger the action, [create a workflow dispatch event](https://docs.github.co
{
"ref": "main", // Required. The git reference for the workflow, a branch or tag name.
"inputs": {
"identifier": "", // Required. The book's identifier. This is an ISBN or Libby share URL. Required.
"identifier": "", // Required. The book's identifier. This is an ISBN, Libby or Libro.fm share URL. Required.
"book-status": "", // Required. What is the status of the book? Required. Default: `want to read`.
"date": "", // Date to record the status of the book (YYYY-MM-DD). Leave blank for today. Optional.
"notes": "", // Notes about the book. Optional.
Expand Down
42 changes: 42 additions & 0 deletions _data/read.json
Original file line number Diff line number Diff line change
Expand Up @@ -394,5 +394,47 @@
],
"format": "audiobook",
"dateFinished": "2024-01-05"
},
{
"identifier": "9781797176888",
"identifiers": {
"librofm": "9781797176888",
"isbn": "9781797176888"
},
"dateStarted": "2024-05-25",
"status": "finished",
"rating": "unrated",
"image": "book-9781797176888.png",
"link": "https://libro.fm/audiobooks/9781797176888",
"format": "audiobook",
"title": "The Ministry of Time",
"description": "“This summer’s hottest debut.” —Cosmopolitan • “Witty, sexy escapist fiction [that] packs a substantial punch...It’s a smart, gripping work that’s also a feast for the senses...Fresh and thrilling.” —Los Angeles Times • “Electric...I loved every second.” —Emily Henry “Utterly winning...Imagine if The Time Traveler’s Wife had an affair with A Gentleman in Moscow...Readers, I envy you: There’s a smart, witty novel in your future.” —Ron Charles, The Washington Post A time travel romance, a spy thriller, a workplace comedy, and an ingenious exploration of the nature of power and the potential for love to change it all: Welcome to The Ministry of Time, the exhilarating debut novel by Kaliane Bradley.In the near future, a civil servant is offered the salary of her dreams and is, shortly afterward, told what project she’ll be working on. A recently established government ministry is gathering “expats” from across history to establish whether time travel is feasible—for the body, but also for the fabric of space-time. She is tasked with working as a “bridge”: living with, assisting, and monitoring the expat known as “1847” or Commander Graham Gore. As far as history is concerned, Commander Gore died on Sir John Franklin’s doomed 1845 expedition to the Arctic, so he’s a little disoriented to be living with an unmarried woman who regularly shows her calves, surrounded by outlandish concepts such as “washing machines,” “Spotify,” and “the collapse of the British Empire.” But with an appetite for discovery, a seven-a-day cigarette habit, and the support of a charming and chaotic cast of fellow expats, he soon adjusts. Over the next year, what the bridge initially thought would be, at best, a horrifically uncomfortable roommate dynamic, evolves into something much deeper. By the time the true shape of the Ministry’s project comes to light, the bridge has fallen haphazardly, fervently in love, with consequences she never could have imagined. Forced to confront the choices that brought them together, the bridge must finally reckon with how—and whether she believes—what she does next can change the future. An exquisitely original and feverishly fun fusion of genres and ideas, The Ministry of Time asks: What does it mean to defy history, when history is living in your house? Kaliane Bradley’s answer is a blazing, unforgettable testament to what we owe each other in a changing world.",
"thumbnail": "https://covers.libro.fm/9781797176888_1120.jpg",
"authors": [
"Kaliane Bradley"
],
"publisher": "Simon & Schuster Audio",
"publishedDate": "2024-05-07",
"dateFinished": "2024-05-26"
},
{
"identifier": "9781478975311",
"identifiers": {
"librofm": "9781478975311",
"isbn": "9781478975311"
},
"dateAdded": "2024-05-25",
"status": "want to read",
"rating": "unrated",
"link": "https://libro.fm/audiobooks/9781478975311-circe",
"format": "audiobook",
"title": "Circe",
"description": "This #1 is a \"bold and subversive retelling of the goddess's story\" that brilliantly reimagines the life of Circe, formidable sorceress of The Odyssey (Alexandra Alter, TheNew York Times). In the house of Helios, god of the sun and mightiest of the Titans, a daughter is born. But Circe is a strange child -- not powerful, like her father, nor viciously alluring like her mother. Turning to the world of mortals for companionship, she discovers that she does possess power -- the power of witchcraft, which can transform rivals into monsters and menace the gods themselves. Threatened, Zeus banishes her to a deserted island, where she hones her occult craft, tames wild beasts and crosses paths with many of the most famous figures in all of mythology, including the Minotaur, Daedalus and his doomed son Icarus, the murderous Medea, and, of course, wily Odysseus. But there is danger, too, for a woman who stands alone, and Circe unwittingly draws the wrath of both men and gods, ultimately finding herself pitted against one of the most terrifying and vengeful of the Olympians. To protect what she loves most, Circe must summon all her strength and choose, once and for all, whether she belongs with the gods she is born from, or the mortals she has come to love. With unforgettably vivid characters, mesmerizing language, and page-turning suspense, Circe is a triumph of storytelling, an intoxicating epic of family rivalry, palace intrigue, love and loss, as well as a celebration of indomitable female strength in a man's world.#1 -- named one of the Best Books of the Year by NPR, the Washington Post, People, Time, Amazon, Entertainment Weekly, Bustle, Newsweek, the A.V. Club, Christian Science Monitor, Refinery 29, Buzzfeed, Paste, Audible, Kirkus, Publishers Weekly, Thrillist, NYPL, Self, Real Simple, Goodreads, Boston Globe, Electric Literature, BookPage, the Guardian, Book Riot, Seattle Times, and Business Insider.",
"thumbnail": "https://covers.libro.fm/9781478975311_1120.jpg",
"authors": [
"Madeline Miller"
],
"publisher": "Hachette Audio",
"publishedDate": "2018-04-10"
}
]
95 changes: 89 additions & 6 deletions dist/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -99228,12 +99228,23 @@ function utils_toArray(tags) {
return tags.split(",").map((f) => f.trim());
}
function lookUp(book, inputIdentifier) {
const isLibby = inputIdentifier.startsWith("https://share.libbyapp.com/");
if (isLibby) {
return book.identifier === inputIdentifier.split("/").pop();
if (inputIdentifier.startsWith("https://share.libbyapp.com/")) {
return book.identifier === getLibbyId(inputIdentifier);
}
if (inputIdentifier.startsWith("https://libro.fm/")) {
return book.identifier === getLibrofmId(inputIdentifier);
}
return book.identifier === inputIdentifier;
}
function getLibbyId(inputIdentifier) {
return inputIdentifier.split("/").pop();
}
function getLibrofmId(inputIdentifier) {
const isbn = inputIdentifier.split("/").pop();
if (!isbn)
return inputIdentifier;
return isbn?.split("-")[0];
}

;// CONCATENATED MODULE: ./src/checkout-book.ts

Expand Down Expand Up @@ -99298,6 +99309,7 @@ function validatePayload(payload) {
(0,core.setFailed)("Missing `identifier` in payload");
}
if (!(payload["identifier"].startsWith("https://share.libbyapp.com/") ||
payload["identifier"].startsWith("https://libro.fm/") ||
isIsbn(payload["identifier"]))) {
(0,core.setFailed)(`Invalid \`identifier\` in payload: ${payload["identifier"]}. Must be an ISBN or start with "https://share.libbyapp.com/"`);
}
Expand Down Expand Up @@ -116695,7 +116707,7 @@ const { root: esm_root } = static_namespaceObject;



async function getMetadata(options) {
async function getLibby(options) {
const { notes, inputIdentifier, dateType, bookStatus, rating, tags, setImage, } = options;
try {
const ogsOptions = {
Expand All @@ -116709,7 +116721,7 @@ async function getMetadata(options) {
],
};
const { result, html } = await dist_default()(ogsOptions);
const libbyIdentifier = inputIdentifier.split("/").pop();
const libbyIdentifier = getLibbyId(inputIdentifier);
const parsedHtmlMetadata = parseLibbyPage(html);
const parsedResultMetadata = parseResult(result);
return {
Expand Down Expand Up @@ -116788,14 +116800,85 @@ function parseLibbyPage(html) {
};
}

;// CONCATENATED MODULE: ./src/librofm.ts


async function getLibrofm(options) {
const { notes, inputIdentifier, dateType, bookStatus, rating, tags, setImage, } = options;
try {
const ogsOptions = {
url: inputIdentifier,
};
const { result } = await dist_default()(ogsOptions);
const librofmIdentifier = getLibrofmId(inputIdentifier);
const parsedResultMetadata = librofm_parseResult(result);
const { isbn, ...remainingMetadata } = parsedResultMetadata;
return {
identifier: librofmIdentifier,
identifiers: {
librofm: librofmIdentifier,
...(isbn && { isbn: isbn }),
},
...dateType,
status: bookStatus,
...(rating && { rating }),
...(notes && { notes }),
...(tags && { tags }),
...(setImage && {
image: `book-${librofmIdentifier}.png`,
}),
link: inputIdentifier,
...remainingMetadata,
};
}
catch (error) {
throw new Error(`Failed to get book from Libby: ${error.result.error}`);
}
}
function librofm_parseResult(result) {
if (result.jsonLD || result.jsonLD?.length) {
const book = result.jsonLD[0];
return {
format: formatFormat(book.bookFormat),
title: book.name,
description: formatDescription(book.description),
isbn: book.isbn,
thumbnail: book.image,
authors: book.author?.map((author) => author.name),
publisher: book.publisher,
publishedDate: book.datePublished,
};
}
return {
title: result.ogTitle,
description: formatDescription(result.ogDescription),
thumbnail: result?.ogImage?.[0]?.url ?? "",
authors: undefined,
publisher: undefined,
publishedDate: undefined,
isbn: undefined,
format: undefined,
};
}
function formatFormat(format) {
if (format.includes("Audiobook")) {
return "audiobook";
}
return format;
}

;// CONCATENATED MODULE: ./src/new-book.ts




async function handleNewBook({ bookParams, library, bookStatus, setImage, }) {
let newBook;
if (bookParams.inputIdentifier.startsWith("https://share.libbyapp.com/")) {
newBook = await getMetadata(bookParams);
newBook = await getLibby(bookParams);
}
else if (bookParams.inputIdentifier.startsWith("https://libro.fm/audiobooks/")) {
newBook = await getLibrofm(bookParams);
}
else {
newBook = await getBook(bookParams);
Expand Down
Binary file added img/book-9781797176888.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
16 changes: 8 additions & 8 deletions src/__tests__/libby.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { getMetadata, parseLibbyPage } from "../libby";
import { getLibby, parseLibbyPage } from "../libby";
import ogs from "open-graph-scraper";
import { readFileSync } from "fs";

Expand All @@ -16,7 +16,7 @@ const resultHtmlEbook = readFileSync(

jest.mock("open-graph-scraper");

describe("getMetadata", () => {
describe("getLibby", () => {
afterEach(() => {
jest.clearAllMocks();
});
Expand All @@ -36,7 +36,7 @@ describe("getMetadata", () => {
html: "",
});

const result = await getMetadata({
const result = await getLibby({
inputIdentifier: "test",
dateType: {},
bookStatus: "started",
Expand Down Expand Up @@ -79,7 +79,7 @@ describe("getMetadata", () => {
html: resultHtmlAudiobook,
});

const result = await getMetadata({
const result = await getLibby({
inputIdentifier: "https://share.libbyapp.com/title/9575390",
dateType: {},
bookStatus: "started",
Expand Down Expand Up @@ -127,7 +127,7 @@ describe("getMetadata", () => {
html: resultHtmlEbook,
});

const result = await getMetadata({
const result = await getLibby({
inputIdentifier: "https://share.libbyapp.com/title/9575390",
dateType: {},
bookStatus: "started",
Expand Down Expand Up @@ -172,7 +172,7 @@ describe("getMetadata", () => {
});

await expect(
getMetadata({ inputIdentifier: "test" })
getLibby({ inputIdentifier: "test" })
).rejects.toThrowErrorMatchingInlineSnapshot(
`"Cannot read properties of undefined (reading 'error')"`
);
Expand All @@ -189,7 +189,7 @@ describe("getMetadata", () => {
html: `<h2 class="share-category">Library</h2><table class='share-table-1d'><tr><th></th><td></td></tr></table>`,
});

const result = await getMetadata({
const result = await getLibby({
inputIdentifier: "test",
dateType: {},
bookStatus: "started",
Expand Down Expand Up @@ -231,7 +231,7 @@ describe("getMetadata", () => {
html: `<table class='share-table-1d'><tr><th>Subjects</th><td></td></tr></table>`,
});

const result = await getMetadata({
const result = await getLibby({
inputIdentifier: "test",
dateType: {},
bookStatus: "started",
Expand Down
Loading