// ==UserScript==
// @name Audible Metadata to MAM JSON script (fully supports banner pages)
// @namespace https://greasyfork.org/en/scripts/511491
// @version 1.0.5
// @license MIT
// @description Copies audiobook metadata to JSON and opens MAM upload page. Supports multiple series and works on all Audible domains.
// @author SnowmanNurse (Parts from and inspired by script by Dr.Blank)
// @include https://www.audible.*/pd/*
// @include https://www.audible.*/ac/*
// @grant none
// ==/UserScript==
const RIPPER = "MusicFab"; // yours can be Libation, OpenAudible, MusicFab, InAudible etc. 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 copyToClipboard(text) {
navigator.clipboard.writeText(text).then(function () {
console.log('Copied to clipboard successfully!');
window.open("https://www.myanonamouse.net/tor/upload.php", "_blank");
}, function (err) {
console.error('Could not copy text: ', err);
alert('Failed to copy metadata. Please check the console for errors.');
});
}
function getLanguage() {
let languageElement = document.querySelector(".languageLabel");
let patt = /\s*(\w+)$/g;
let matches = patt.exec(languageElement.innerHTML.trim());
return matches[1];
}
function getSeriesInfo() {
let seriesElements = document.querySelectorAll(".seriesLabel");
let seriesInfo = [];
seriesElements.forEach(element => {
let seriesLink = element.querySelector("a");
if (seriesLink) {
let seriesName = cleanSeriesName(seriesLink.textContent);
let bookNumberMatch = element.textContent.match(/Book\s*?(\d+\.?\d*-?\d*\.?\d*)/);
let bookNumber = bookNumberMatch ? bookNumberMatch[1] : "";
seriesInfo.push({ name: seriesName, number: bookNumber });
}
});
return seriesInfo;
}
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() {
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 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 "";
if (subtitle.trim() === "+More") return "";
let seriesInfo = getSeriesInfo();
let isSubtitleSeries = seriesInfo.some(series =>
subtitle.toLowerCase().includes(series.name.toLowerCase())
);
if (isSubtitleSeries) return "";
return subtitle;
}
function getTitleAndSubtitle() {
let subtitle = getSubtitle();
if (subtitle) {
return `${getTitle()}: ${subtitle}`;
}
return getTitle();
}
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() {
let publisherElement = document.querySelector(".publisherLabel>a");
return publisherElement ? publisherElement.innerText : "Unknown Publisher";
}
function getRunTime() {
let runtimeElement = document.querySelector(".runtimeLabel");
let runtime = runtimeElement ? runtimeElement.textContent : "Unknown";
let patt = new RegExp("Length:\\n\\s+(\\d[^\n]+)");
let matches = patt.exec(runtime);
return matches ? matches[1] : "Unknown";
}
function getAdditionalTags() {
let tags = [];
tags.push(`Duration: ${getRunTime()}`);
if (CHAPTERIZED) tags.push("Chapterized");
if (RIPPER) tags.push(RIPPER);
tags.push(`Audible Release: ${getReleaseDate()}`);
tags.push(`Publisher: ${getPublisher()}`);
tags.push(getAudibleCategory());
return tags.join(" | ");
}
function getASIN() {
// Try to get ASIN from URL first
const urlMatch = window.location.pathname.match(/\/([A-Z0-9]{10})/);
if (urlMatch && urlMatch[1]) {
return "ASIN:" + urlMatch[1];
}
// If not in URL, try to find it in the page content
const productDetails = document.querySelector('#detailsproductInfoSection');
if (productDetails) {
const asinMatch = productDetails.textContent.match(/ASIN:\s*([A-Z0-9]{10})/);
if (asinMatch && asinMatch[1]) {
return "ASIN:" + asinMatch[1];
}
}
// If ASIN is not found, return an empty string
return "";
}
function getAuthors() {
var authorElements = document.querySelectorAll(".authorLabel a[data-authorid]");
var authors = [];
for (let element of authorElements) {
let authorName = element.textContent.trim();
authorName = authorName.replace(/ - (foreword|afterword|translator|editor)/gi, "");
authorName = cleanName(authorName);
if (authorName && !authors.includes(authorName)) {
authors.push(authorName);
}
}
return authors.filter(author => author !== "Prologue Projects");
}
function getNarrators() {
var narratorElements = document.querySelectorAll(".narratorLabel a");
var narrators = [];
for (let index = 0; index < narratorElements.length; index++) {
if (narratorElements[index]) {
let narratorName = narratorElements[index].textContent.trim();
narratorName = cleanName(narratorName);
if (narratorName && !narrators.includes(narratorName)) {
narrators.push(narratorName);
}
}
}
return narrators;
}
function generateJson() {
var imgElement = document.querySelector(".bc-image-inset-border");
var imageSrc = imgElement ? imgElement.src : "No image available";
var descriptionElement = document.querySelector(".productPublisherSummary>div>div>span") ||
document.querySelector("div.bc-col-6 span.bc-text") ||
document.querySelector("div.bc-text.bc-color-secondary");
var description = descriptionElement ? descriptionElement.innerHTML : "No description available";
description = description.replace(/\s+/g, " ").replace(/"/g, '\\"').replace(/<p><\/p>/g, "").replace(/<\/p>/g, "</p><br>").replace(/<\/ul>/g, "</ul><br>");
var json = {
"authors": getAuthors(),
"description": description,
"narrators": getNarrators(),
"tags": getAdditionalTags(),
"thumbnail": imageSrc,
"title": getTitleAndSubtitle(),
"language": getLanguage(),
"series": getSeriesInfo(),
"category": getMAMCategory(),
"isbn": getASIN()
};
var strJson = JSON.stringify(json);
copyToClipboard(strJson);
}
function addButton() {
const buyBoxElement = document.querySelector("#adbl-buy-box");
if (!buyBoxElement) return;
const buttonHtml = `
<div id="mam-metadata-button" 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="copyMetadataButton" 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 Book info to JSON
</span>
</button>
</span>
</div>
</div>
</div>`;
buyBoxElement.insertAdjacentHTML('beforeend', buttonHtml);
document.getElementById("copyMetadataButton").addEventListener("click", function (event) {
event.preventDefault();
generateJson();
});
}
window.onload = function () {
addButton();
};