UNPKG

audible-api

Version:

A Node.js API for searching the audible website

312 lines (240 loc) 11.6 kB
function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) { symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); } keys.push.apply(keys, symbols); } return keys; } function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; } function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { Promise.resolve(value).then(_next, _throw); } } function _asyncToGenerator(fn) { return function () { var self = this, args = arguments; return new Promise(function (resolve, reject) { var gen = fn.apply(self, args); function _next(value) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); } function _throw(err) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err); } _next(undefined); }); }; } import { load as loadPage } from "cheerio"; // @ts-ignore import dJSON from "dirty-json"; import fetch from "node-fetch"; import { URL } from "url"; import { siteCountries } from "./data/audible-search-fields"; import { getLanguageByName } from "./utils/language"; import { cleanDescription, cleanNarratorUrl, cleanTitle, cleanUrl, getCopyrightYear } from "./utils/string"; import { SECONDS_IN_HOUR, SECONDS_IN_MINUTE } from "./utils/time"; /** * Get extended information about the author from their Audible URL * * @param author - An initial object of the creator to extend (reuired url) * @returns An extended object of information about the author */ function parseAuthorInfo(_x) { return _parseAuthorInfo.apply(this, arguments); } function _parseAuthorInfo() { _parseAuthorInfo = _asyncToGenerator(function* (author) { try { if (!author.url) { return author; } var res = yield fetch(author.url); var body = yield res.text(); var page = loadPage(body); var ldJsonList = []; page('script[type="application/ld+json"]').each((i, elSel) => { // Use dirty-json because the description section has line breaks instead of \n characters like it should var jsonObj = dJSON.parse(page(elSel).text()); ldJsonList.push(jsonObj); }); ldJsonList = ldJsonList.flat(); var newAuthor = _objectSpread({}, author); // Get Author's Images var main = ".adbl-main"; newAuthor.thumbnailImageUrl = page("img.author-image-outline", main).attr("src"); // parse useful ld+json from the dom of the authors's page var authorJson = ldJsonList.find(jsonItem => jsonItem["@type"] === "MusicGroup"); if (authorJson && typeof authorJson !== "string") { // Get author's name newAuthor.name = authorJson.name || newAuthor.name; // Get author's bio newAuthor.bio = authorJson.description || newAuthor.bio; // Get author's amazon ID newAuthor.id = authorJson.url.split("/").pop(); // Get clean author url newAuthor.url = authorJson.url || newAuthor.url; } var personJson = ldJsonList.find(jsonItem => jsonItem["@type"] === "Person"); if (personJson && typeof personJson !== "string") { newAuthor.imageUrl = personJson.image; } return newAuthor; } catch (err) { return author; } }); return _parseAuthorInfo.apply(this, arguments); } /** * Get all Audible details about an Audiobook from its ASIN * * @param asin - Amazon Standard Identification Number, Amazon's unique ID that they assign to all of their products * @param opts - The optional arguments * @returns The parsed book data */ export default function getAudibleBook(_x2) { return _getAudibleBook.apply(this, arguments); } function _getAudibleBook() { _getAudibleBook = _asyncToGenerator(function* (asin) { var opts = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; try { var site = opts.site || "us"; var getAuthors = opts.getAuthors || false; var { url: baseUrl // language: siteLanguage, } = siteCountries[site] || siteCountries.us; var bookUrl = "".concat(baseUrl, "/pd/").concat(asin, "?ipRedirectOverride=true"); var res = yield fetch(bookUrl); var body = yield res.text(); var page = loadPage(body); var main = 'div[role="main"]'; // Get URL var book = { url: page('link[rel="canonical"]').attr("href"), authors: [], narrators: [] }; // Get Authors page(".authorLabel a", main).each((i, elSel) => { var el = page(elSel); var newAuthor = { name: el.text().trim() }; var authorUrlPath = el.attr("href"); if (authorUrlPath) { newAuthor.url = cleanUrl(new URL(authorUrlPath, baseUrl).href); } book.authors.push(newAuthor); }); // Get extended author information if (getAuthors) { book.authors = yield Promise.all(book.authors.map(author => parseAuthorInfo(author))); } // Get narrators page(".narratorLabel a", main).each((i, elSel) => { var el = page(elSel); var newNarrator = { name: el.text().trim() }; var narratorUrlPath = el.attr("href"); if (narratorUrlPath) { newNarrator.url = cleanNarratorUrl(new URL(narratorUrlPath, baseUrl).href); } book.narrators.push(newNarrator); }); // Get series and series part try { var series = []; page(".seriesLabel a", main).each((i, elSel) => { var el = page(elSel); var newSeries = { name: el.text().trim() }; var seriesUrlPath = el.attr("href"); if (seriesUrlPath) { newSeries.url = cleanUrl(new URL(seriesUrlPath, baseUrl).href); } series.push(newSeries); }); var seriesArr = page(".seriesLabel").text().replace(/\n/g, "").trim() // remove the beginning text from the series .replace("Series: ", "").split(", ").map(item => item.trim()); seriesArr.forEach((seriesStr, i) => { if (seriesStr.includes("Book")) { var seriesPart = Number(seriesStr.replace("Book ", "")); if (seriesPart) { var matchingBookIndex = series.findIndex(item => item.name === seriesArr[i - 1]); if (matchingBookIndex > -1) { series[matchingBookIndex].part = seriesPart; } } } }); book.series = series; } catch (err) {// console.warn(`ERROR PARSING AUDIBLE SERIES'\n${err.stack}`); } // Get Copyright Info string and year try { var copyright = page(".productPublisherSummary .bc-section > .bc-box:last-child").eq(0).text().trim(); if (copyright && copyright.includes("©")) { book.copyright = copyright.replace(/\s+/g, " "); book.copyrightYear = getCopyrightYear(copyright); } } catch (err) { console.warn("Error parsing copyright", err); } var ldJsonList = []; page('script[type="application/ld+json"]').each((i, elSel) => { var jsonObj; try { jsonObj = JSON.parse(page(elSel).text()); } catch (err) { jsonObj = dJSON.parse(page(elSel).text()); } ldJsonList.push(jsonObj); }); ldJsonList = ldJsonList.flat(); // parse useful ld+json from the dom of the page // "@type": "BreadcrumbList" var breadcrumbJson = ldJsonList.find(jsonItem => jsonItem["@type"] === "BreadcrumbList"); // Get Genres if (breadcrumbJson) { var itemListElement = breadcrumbJson.itemListElement; var newGenres = []; itemListElement.slice(1).forEach(breadcrumb => { if (breadcrumb) { var item = breadcrumb.item; if (typeof item !== "string") { newGenres.push({ name: item.name, url: "".concat(baseUrl).concat(item["@id"]) }); } } }); book.genres = newGenres; } // "@type": "Product" var productJson = ldJsonList.find(jsonItem => jsonItem["@type"] === "Product"); if (productJson) { // Get ASIN book.asin = productJson.productID; // Get SKU book.sku = productJson.sku; } // "@type": "Audiobook" var bookJson = ldJsonList.find(jsonItem => jsonItem["@type"] === "Audiobook"); if (bookJson) { // Get title book.title = bookJson.name; // Get clean title, without series part ("Book N") or "(Unabridged)" book.cleanTitle = cleanTitle(bookJson.name); // Get Publisher book.publisher = bookJson.publisher; // Get language and language codes var language = getLanguageByName(bookJson.inLanguage); if (language) { book.language = language; } // Get full description (without any html) book.description = cleanDescription(bookJson.description); // Get the date the book was published book.datePublished = new Date(bookJson.datePublished); // Get user rating if (bookJson.aggregateRating) { var { ratingValue, ratingCount } = bookJson.aggregateRating; book.rating = { value: parseFloat(ratingValue), count: Number(ratingCount) }; } // Get pricing if (bookJson.offers) { var { lowPrice, highPrice, priceCurrency } = bookJson.offers; book.price = { low: Number(lowPrice), high: Number(highPrice), currency: priceCurrency }; } // Get Abridgement book.isAbridged = bookJson.abridged === "true"; // Get duration in seconds var durationStr = bookJson.duration; if (durationStr) { var _durationStr$match, _durationStr$match2; var hours = Number(((_durationStr$match = durationStr.match(/\d+(?=H)/)) === null || _durationStr$match === void 0 ? void 0 : _durationStr$match[0]) || 0); var minutes = Number(((_durationStr$match2 = durationStr.match(/\d+(?=M)/)) === null || _durationStr$match2 === void 0 ? void 0 : _durationStr$match2[0]) || 0); book.duration = hours * SECONDS_IN_HOUR + minutes * SECONDS_IN_MINUTE; } // Get cover image URL book.coverUrl = bookJson.image; } return book; } catch (err) { console.error("ERROR PARSING AUDIBLE BOOK FROM ASIN: ".concat(asin)); throw err; } }); return _getAudibleBook.apply(this, arguments); } //# sourceMappingURL=get-audible-book.js.map