audible-api
Version:
A Node.js API for searching the audible website
312 lines (240 loc) • 11.6 kB
JavaScript
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