// ==UserScript==
// @name MAM Audible Upload
// @namespace https://greasyfork.org/en/users/886084
// @version 1.0.6
// @license MIT
// @description Adds button to copy audiobook data as json to Audible page and opens MAM upload
// @author Originally by DrBlank (Edited by SnowmanNurse)
// @include https://www.audible.com/pd/*
// @include https://www.audible.in/pd/*
// @include https://www.audible.com/ac/*
// @include https://www.audible.in/ac/*
// @grant none
// ==/UserScript==
const RIPPER = "MusicFab"; // yours can be InAudible or Libation or OpenAudible or Blank if Encoded
const CHAPTERIZED = true; // yours will be false if not properly ripped
const AVAILABLE_CATEGORIES = [
"Art",
"Biographical",
"Business",
"Crafts",
"Fantasy",
"Food",
"History",
"Horror",
"Humor",
"Instructional",
"Juvenile",
"Language",
"Medical",
"Mystery",
"Nature",
"Philosophy",
"Recreation",
"Romance",
"Self-Help",
"Western",
"Young Adult",
"Historical Fiction",
"Literary Classics",
"Science Fiction",
"True Crime",
"Urban Fantasy",
"Action/Adventure",
"Computer/Internet",
"Crime/Thriller",
"Home/Garden",
"Math/Science/Tech",
"Travel/Adventure",
"Pol/Soc/Relig",
"General Fiction",
"General Non-Fic",
];
function cleanName(name) {
const titlesToRemove = [
"PhD", "MD", "JD", "MBA", "MA", "MS", "MSc", "MFA", "MEd", "MPH", "LLM", "DDS", "DVM", "EdD", "PsyD", "ThD", "DO", "PharmD", "DSc", "DBA", "RN", "CPA", "Esq.", "LCSW", "PE", "AIA", "FAIA", "CSP", "CFP", "Jr.", "Sr.", "I", "II", "III", "IV", "Dr.", "Mr.", "Mrs.", "Ms.", "Prof.", "Rev.", "Fr.", "Sr.", "Capt.", "Col.", "Gen.", "Lt.", "Cmdr.", "Adm.", "Sir", "Dame", "Hon.", "Amb.", "Gov.", "Sen.", "Rep."
];
let cleanedName = name.trim();
titlesToRemove.forEach(title => {
const regexBefore = new RegExp(`^${title}\\s+`, 'i');
const regexAfter = new RegExp(`\\s*,?\\s*${title}$`, 'i');
cleanedName = cleanedName.replace(regexBefore, '').replace(regexAfter, '');
});
return cleanedName.trim();
}
function cleanSeriesName(seriesName) {
const wordsToRemove = ["series", "an", "the", "novel"];
let cleanedName = seriesName.toLowerCase();
wordsToRemove.forEach(word => {
const regex = new RegExp(`\\b${word}\\b`, 'gi');
cleanedName = cleanedName.replace(regex, '');
});
// Remove extra spaces and trim
cleanedName = cleanedName.replace(/\s+/g, ' ').trim();
// Capitalize the first letter of each word
return cleanedName.replace(/\b\w/g, l => l.toUpperCase());
}
function getTitle() {
let title = document.getElementsByTagName("h1")[0].innerText;
return title;
}
function getSubtitle() {
let sLoggedIn = document.querySelector(".subtitle");
let sLoggedOut = document.querySelector("span.bc-size-medium");
let subtitle = "";
if (sLoggedIn) {
subtitle = sLoggedIn.innerText;
} else if (sLoggedOut) {
subtitle = sLoggedOut.innerText;
}
if (!subtitle) return "";
let series = getSeriesName().toLowerCase();
let isSubtitleSeries = Boolean(
series && subtitle.toLocaleLowerCase().includes(series)
);
if (isSubtitleSeries) return "";
return subtitle;
}
function getTitleAndSubtitle() {
let subtitle = getSubtitle();
if (subtitle) {
return `${getTitle()}: ${subtitle}`;
}
return getTitle();
}
function getAuthors() {
let authorElements = document.querySelectorAll(".authorLabel a");
let authors = [];
let uniqueAuthors = new Set(); // To store only unique authors
for (let index = 0; index < authorElements.length; index++) {
let authorName = authorElements[index].innerHTML.replace(
/ - (foreword|afterword|translator|editor)/gi,
""
).trim();
authorName = cleanName(authorName);
// Exclude entries that contain "Author", "Title", or are clearly not the author's name
if (!authorName.toLowerCase().includes("author") && !authorName.toLowerCase().includes("title") && !uniqueAuthors.has(authorName)) {
authors.push(authorName);
uniqueAuthors.add(authorName); // Add to Set to track unique authors
}
}
return authors;
}
function getNarrators() {
let narratorElements = document.querySelectorAll(".narratorLabel a");
let narrators = [];
for (let index = 0; index < narratorElements.length; index++) {
let narratorName = narratorElements[index].innerHTML;
narratorName = cleanName(narratorName);
narrators.push(narratorName);
}
return narrators;
}
function getSeriesName() {
let series = "";
let seriesElement = document.querySelector(".seriesLabel");
if (seriesElement) {
series = seriesElement.querySelector("a").innerHTML;
series = cleanSeriesName(series);
}
return series;
}
function getSeriesBookNumber() {
let bookNumber = "";
if (!getSeriesName()) {
return "";
}
let seriesElement = document.querySelector(".seriesLabel");
let patt = /Book\s*?(\d+\.?\d*-?\d*\.?\d*)/g;
bookNumber = patt.exec(seriesElement.innerHTML);
if (!bookNumber) {
return "";
}
return bookNumber[1];
}
function getLanguage() {
let languageElement = document.querySelector(".languageLabel");
let patt = /\s*(\w+)$/g;
let matches = patt.exec(languageElement.innerHTML.trim());
return matches[1];
}
function getRunTime() {
let runtimeElement = document.querySelector(".runtimeLabel").textContent;
/* clean up unnecessary parts of string */
let patt = new RegExp("Length:\n\\s+(\\d[^\n]+)");
let matches = patt.exec(runtimeElement);
/* The first capture group contains the actual runtime */
return matches[1];
}
function getBookCover() {
return document.querySelector(".bc-image-inset-border").src;
}
function getAudibleCategory() {
let categoryElement = document.querySelector(".categoriesLabel");
if (categoryElement) return categoryElement.innerText;
categoryElement = document.querySelector("nav.bc-breadcrumb");
if (categoryElement) return categoryElement.innerText;
return "";
}
function getMAMCategory() {
/* TODO: Implement guessing of categories */
let audibleCategory = getAudibleCategory().toLowerCase();
let guesses = [];
AVAILABLE_CATEGORIES.forEach((category) => {
if (audibleCategory.includes(category.toLowerCase())) {
guesses.push(`Audiobooks - ${category}`);
return;
}
let separators = ["/", " "];
separators.forEach((separator) => {
let splits = category.split(separator);
splits.forEach((split) => {
if (audibleCategory.includes(split.toLowerCase())) {
guesses.push(`Audiobooks - ${category}`);
return;
}
});
});
});
if (guesses.length) return guesses[0];
return "";
}
function getDescription() {
let d = document.querySelector(
".productPublisherSummary>div>div>span"
).innerHTML;
/* In order: Remove excess whitespace, replace double quotes, remove empty p elements, add line break after every paragraph, and every list */
return d
.replace(/\s+/g, " ")
.replace(/"/g, "'")
.replace(/<p><\/p>/g, "")
.replace(/<\/p>/g, "</p><br>")
.replace(/<\/ul>/g, "</ul><br>");
}
function getReleaseDate() {
let element = document.querySelector(".releaseDateLabel");
let patt = /\d{2}-\d{2}-\d{2}/;
let matches = patt.exec(element.innerText);
return matches ? matches[0] : "";
}
function getPublisher() {
return document.querySelector(".publisherLabel>a").innerText;
}
function getAdditionalTags() {
let raw_tags = [];
raw_tags.push(`Duration: ${getRunTime()}`);
if (CHAPTERIZED) raw_tags.push("Chapterized");
if (RIPPER) raw_tags.push(`${RIPPER}`);
raw_tags.push(
`Audible Release: ${getReleaseDate()}`,
`Publisher: ${getPublisher()}`,
getAudibleCategory()
);
return raw_tags.join(" | ");
}
function getSeries() {
let seriesName = getSeriesName();
if (seriesName) {
let bookNumber = getSeriesBookNumber();
return [{ name: seriesName, number: bookNumber }];
}
return [];
}
function fallbackCopyTextToClipboard(text) {
var textArea = document.createElement("textarea");
textArea.value = text;
// Avoid scrolling to bottom
textArea.style.top = "0";
textArea.style.left = "0";
textArea.style.position = "fixed";
document.body.appendChild(textArea);
textArea.focus();
textArea.select();
try {
var successful = document.execCommand("copy");
var msg = successful ? "successful" : "unsuccessful";
console.log("Fallback: Copying text command was " + msg);
} catch (err) {
console.error("Fallback: Oops, unable to copy", err);
}
document.body.removeChild(textArea);
}
function copyTextToClipboard(text) {
if (!navigator.clipboard) {
fallbackCopyTextToClipboard(text);
return;
}
navigator.clipboard.writeText(text).then(
function () {
window.open("https://www.myanonamouse.net/tor/upload.php", "_blank");
},
function (err) {
console.error("Async: Could not copy text: ", err);
}
);
}
let buttonStr = `<div id="" class="bc-row bc-spacing-top-s1">
<div class="bc-row">
<div class="bc-trigger bc-pub-block">
<span class="bc-button bc-button-primary">
<button
id="upload-to-mam"
class="bc-button-text"
type="button"
tabindex="0" title="Copy book details as JSON"
>
<span class="bc-text bc-button-text-inner bc-size-action-large">
Copy as JSON
</span>
</button>
</span>
</div>
</div>
</div>
`;
let foo = document.createElement("foo");
foo.innerHTML = buttonStr.trim();
let button = foo.firstChild;
document.querySelector("#adbl-buy-box").appendChild(button);
button.addEventListener("click", function (event) {
copyTextToClipboard(
JSON.stringify({
authors: getAuthors(),
description: getDescription(),
narrators: getNarrators(),
tags: getAdditionalTags(),
thumbnail: getBookCover(),
title: getTitleAndSubtitle(),
language: getLanguage(),
series: getSeries(),
category: getMAMCategory(),
})
);
});