UNPKG

audible-api

Version:

A Node.js API for searching the audible website

420 lines (331 loc) 15.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports["default"] = getAudibleBook; var _cheerio = require("cheerio"); var _dirtyJson = _interopRequireDefault(require("dirty-json")); var _nodeFetch = _interopRequireDefault(require("node-fetch")); var _url = require("url"); var _audibleSearchFields = require("./data/audible-search-fields"); var _language = require("./utils/language"); var _string = require("./utils/string"); var _time = require("./utils/time"); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } 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); }); }; } /** * 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( /*#__PURE__*/regeneratorRuntime.mark(function _callee(author) { var res, body, page, ldJsonList, newAuthor, main, authorJson, personJson; return regeneratorRuntime.wrap(function _callee$(_context) { while (1) { switch (_context.prev = _context.next) { case 0: _context.prev = 0; if (author.url) { _context.next = 3; break; } return _context.abrupt("return", author); case 3: _context.next = 5; return (0, _nodeFetch["default"])(author.url); case 5: res = _context.sent; _context.next = 8; return res.text(); case 8: body = _context.sent; page = (0, _cheerio.load)(body); ldJsonList = []; page('script[type="application/ld+json"]').each(function (i, elSel) { // Use dirty-json because the description section has line breaks instead of \n characters like it should var jsonObj = _dirtyJson["default"].parse(page(elSel).text()); ldJsonList.push(jsonObj); }); ldJsonList = ldJsonList.flat(); newAuthor = _objectSpread({}, author); // Get Author's Images 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 authorJson = ldJsonList.find(function (jsonItem) { return 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; } personJson = ldJsonList.find(function (jsonItem) { return jsonItem["@type"] === "Person"; }); if (personJson && typeof personJson !== "string") { newAuthor.imageUrl = personJson.image; } return _context.abrupt("return", newAuthor); case 23: _context.prev = 23; _context.t0 = _context["catch"](0); return _context.abrupt("return", author); case 26: case "end": return _context.stop(); } } }, _callee, null, [[0, 23]]); })); 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 */ function getAudibleBook(_x2) { return _getAudibleBook.apply(this, arguments); } function _getAudibleBook() { _getAudibleBook = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee2(asin) { var opts, site, getAuthors, _ref, baseUrl, bookUrl, res, body, page, main, book, series, seriesArr, copyright, ldJsonList, breadcrumbJson, itemListElement, newGenres, productJson, bookJson, language, _ref2, ratingValue, ratingCount, _ref3, lowPrice, highPrice, priceCurrency, durationStr, _durationStr$match, _durationStr$match2, hours, minutes, _args2 = arguments; return regeneratorRuntime.wrap(function _callee2$(_context2) { while (1) { switch (_context2.prev = _context2.next) { case 0: opts = _args2.length > 1 && _args2[1] !== undefined ? _args2[1] : {}; _context2.prev = 1; site = opts.site || "us"; getAuthors = opts.getAuthors || false; _ref = _audibleSearchFields.siteCountries[site] || _audibleSearchFields.siteCountries.us, baseUrl = _ref.url; bookUrl = "".concat(baseUrl, "/pd/").concat(asin, "?ipRedirectOverride=true"); _context2.next = 8; return (0, _nodeFetch["default"])(bookUrl); case 8: res = _context2.sent; _context2.next = 11; return res.text(); case 11: body = _context2.sent; page = (0, _cheerio.load)(body); main = 'div[role="main"]'; // Get URL book = { url: page('link[rel="canonical"]').attr("href"), authors: [], narrators: [] }; // Get Authors page(".authorLabel a", main).each(function (i, elSel) { var el = page(elSel); var newAuthor = { name: el.text().trim() }; var authorUrlPath = el.attr("href"); if (authorUrlPath) { newAuthor.url = (0, _string.cleanUrl)(new _url.URL(authorUrlPath, baseUrl).href); } book.authors.push(newAuthor); }); // Get extended author information if (!getAuthors) { _context2.next = 20; break; } _context2.next = 19; return Promise.all(book.authors.map(function (author) { return parseAuthorInfo(author); })); case 19: book.authors = _context2.sent; case 20: // Get narrators page(".narratorLabel a", main).each(function (i, elSel) { var el = page(elSel); var newNarrator = { name: el.text().trim() }; var narratorUrlPath = el.attr("href"); if (narratorUrlPath) { newNarrator.url = (0, _string.cleanNarratorUrl)(new _url.URL(narratorUrlPath, baseUrl).href); } book.narrators.push(newNarrator); }); // Get series and series part try { series = []; page(".seriesLabel a", main).each(function (i, elSel) { var el = page(elSel); var newSeries = { name: el.text().trim() }; var seriesUrlPath = el.attr("href"); if (seriesUrlPath) { newSeries.url = (0, _string.cleanUrl)(new _url.URL(seriesUrlPath, baseUrl).href); } series.push(newSeries); }); seriesArr = page(".seriesLabel").text().replace(/\n/g, "").trim() // remove the beginning text from the series .replace("Series: ", "").split(", ").map(function (item) { return item.trim(); }); seriesArr.forEach(function (seriesStr, i) { if (seriesStr.includes("Book")) { var seriesPart = Number(seriesStr.replace("Book ", "")); if (seriesPart) { var matchingBookIndex = series.findIndex(function (item) { return 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 { 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 = (0, _string.getCopyrightYear)(copyright); } } catch (err) { console.warn("Error parsing copyright", err); } ldJsonList = []; page('script[type="application/ld+json"]').each(function (i, elSel) { var jsonObj; try { jsonObj = JSON.parse(page(elSel).text()); } catch (err) { jsonObj = _dirtyJson["default"].parse(page(elSel).text()); } ldJsonList.push(jsonObj); }); ldJsonList = ldJsonList.flat(); // parse useful ld+json from the dom of the page // "@type": "BreadcrumbList" breadcrumbJson = ldJsonList.find(function (jsonItem) { return jsonItem["@type"] === "BreadcrumbList"; }); // Get Genres if (breadcrumbJson) { itemListElement = breadcrumbJson.itemListElement; newGenres = []; itemListElement.slice(1).forEach(function (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" productJson = ldJsonList.find(function (jsonItem) { return jsonItem["@type"] === "Product"; }); if (productJson) { // Get ASIN book.asin = productJson.productID; // Get SKU book.sku = productJson.sku; } // "@type": "Audiobook" bookJson = ldJsonList.find(function (jsonItem) { return jsonItem["@type"] === "Audiobook"; }); if (bookJson) { // Get title book.title = bookJson.name; // Get clean title, without series part ("Book N") or "(Unabridged)" book.cleanTitle = (0, _string.cleanTitle)(bookJson.name); // Get Publisher book.publisher = bookJson.publisher; // Get language and language codes language = (0, _language.getLanguageByName)(bookJson.inLanguage); if (language) { book.language = language; } // Get full description (without any html) book.description = (0, _string.cleanDescription)(bookJson.description); // Get the date the book was published book.datePublished = new Date(bookJson.datePublished); // Get user rating if (bookJson.aggregateRating) { _ref2 = bookJson.aggregateRating, ratingValue = _ref2.ratingValue, ratingCount = _ref2.ratingCount; book.rating = { value: parseFloat(ratingValue), count: Number(ratingCount) }; } // Get pricing if (bookJson.offers) { _ref3 = bookJson.offers, lowPrice = _ref3.lowPrice, highPrice = _ref3.highPrice, priceCurrency = _ref3.priceCurrency; book.price = { low: Number(lowPrice), high: Number(highPrice), currency: priceCurrency }; } // Get Abridgement book.isAbridged = bookJson.abridged === "true"; // Get duration in seconds durationStr = bookJson.duration; if (durationStr) { hours = Number(((_durationStr$match = durationStr.match(/\d+(?=H)/)) === null || _durationStr$match === void 0 ? void 0 : _durationStr$match[0]) || 0); minutes = Number(((_durationStr$match2 = durationStr.match(/\d+(?=M)/)) === null || _durationStr$match2 === void 0 ? void 0 : _durationStr$match2[0]) || 0); book.duration = hours * _time.SECONDS_IN_HOUR + minutes * _time.SECONDS_IN_MINUTE; } // Get cover image URL book.coverUrl = bookJson.image; } return _context2.abrupt("return", book); case 35: _context2.prev = 35; _context2.t0 = _context2["catch"](1); console.error("ERROR PARSING AUDIBLE BOOK FROM ASIN: ".concat(asin)); throw _context2.t0; case 39: case "end": return _context2.stop(); } } }, _callee2, null, [[1, 35]]); })); return _getAudibleBook.apply(this, arguments); } //# sourceMappingURL=get-audible-book.js.map