// ==UserScript==
// @name Audible to MAM JSON Converter (Full API Version)
// @namespace https://greasyfork.org/en/scripts/511491
// @version 2.0.3
// @license MIT
// @description Complete API-based solution with full metadata cleaning and category mapping
// @author SnowmanNurse (Special thanks to Dr.Blank, DeepSpaceDark, and Audnexus for their contributions)
// @include https://www.audible.*/pd/*
// @include https://www.audible.*/ac/*
// @grant GM_xmlhttpRequest
// @grant GM_setClipboard
// @grant GM_openInTab
// ==/UserScript==
// Configuration Constants
const RIPPER = "MusicFab"; // Name of the ripping software used
const CHAPTERIZED = true; // Whether the audiobook is chapterized (true/false)
const CATEGORY_SELECTION_METHOD = 2; // Category selection method: 1 = Direct mapping, 2 = Keyword matching, 3 = Use AI (ChatGPT)
const CHATGPT_API_KEY = "your-api-key-here"; // Replace with your OpenAI API key
const AVAILABLE_CATEGORIES = [
"Audiobooks - Art", "Audiobooks - Biographical", "Audiobooks - Business", "Audiobooks - Crafts",
"Audiobooks - Fantasy", "Audiobooks - Food", "Audiobooks - History", "Audiobooks - Horror",
"Audiobooks - Humor", "Audiobooks - Instructional", "Audiobooks - Juvenile", "Audiobooks - Language",
"Audiobooks - Medical", "Audiobooks - Mystery", "Audiobooks - Nature", "Audiobooks - Philosophy",
"Audiobooks - Recreation", "Audiobooks - Romance", "Audiobooks - Self-Help", "Audiobooks - Western",
"Audiobooks - Young Adult", "Audiobooks - Historical Fiction", "Audiobooks - Literary Classics",
"Audiobooks - Science Fiction", "Audiobooks - True Crime", "Audiobooks - Urban Fantasy",
"Audiobooks - Action/Adventure", "Audiobooks - Computer/Internet", "Audiobooks - Crime/Thriller",
"Audiobooks - Home/Garden", "Audiobooks - Math/Science/Tech", "Audiobooks - Travel/Adventure",
"Audiobooks - Pol/Soc/Relig", "Audiobooks - General Fiction", "Audiobooks - General Non-Fic"
];
const AUDIBLE_TO_MAM_CATEGORY_MAP = {
"Arts & Entertainment": "Audiobooks - Art",
"Biographies & Memoirs": "Audiobooks - Biographical",
"Business & Careers": "Audiobooks - Business",
"Children's Audiobooks": "Audiobooks - Juvenile",
"Comedy & Humor": "Audiobooks - Humor",
"Computers & Technology": "Audiobooks - Computer/Internet",
"Education & Learning": "Audiobooks - Instructional",
"Erotica": "Audiobooks - Romance",
"Health & Wellness": "Audiobooks - Medical",
"History": "Audiobooks - History",
"Home & Garden": "Audiobooks - Home/Garden",
"LGBTQ+": "Audiobooks - General Fiction",
"Literature & Fiction": "Audiobooks - General Fiction",
"Money & Finance": "Audiobooks - Business",
"Mystery, Thriller & Suspense": "Audiobooks - Mystery",
"Politics & Social Sciences": "Audiobooks - Pol/Soc/Relig",
"Relationships, Parenting & Personal Development": "Audiobooks - Self-Help",
"Religion & Spirituality": "Audiobooks - Pol/Soc/Relig",
"Romance": "Audiobooks - Romance",
"Science & Engineering": "Audiobooks - Math/Science/Tech",
"Science Fiction & Fantasy": "Audiobooks - Science Fiction",
"Sports & Outdoors": "Audiobooks - Recreation",
"Teen & Young Adult": "Audiobooks - Young Adult",
"Travel & Tourism": "Audiobooks - Travel/Adventure"
};
const KEYWORD_TO_MAM_CATEGORY_MAP = {
"science fiction": "Audiobooks - Science Fiction",
"sci-fi": "Audiobooks - Science Fiction",
"fantasy": "Audiobooks - Fantasy",
"magic": "Audiobooks - Fantasy",
"mystery": "Audiobooks - Mystery",
"detective": "Audiobooks - Mystery",
"crime": "Audiobooks - Crime/Thriller",
"thriller": "Audiobooks - Crime/Thriller",
"suspense": "Audiobooks - Crime/Thriller",
"horror": "Audiobooks - Horror",
"romance": "Audiobooks - Romance",
"love story": "Audiobooks - Romance",
"historical": "Audiobooks - Historical Fiction",
"history": "Audiobooks - History",
"biography": "Audiobooks - Biographical",
"memoir": "Audiobooks - Biographical",
"business": "Audiobooks - Business",
"finance": "Audiobooks - Business",
"self-help": "Audiobooks - Self-Help",
"personal development": "Audiobooks - Self-Help",
"science": "Audiobooks - Math/Science/Tech",
"technology": "Audiobooks - Math/Science/Tech",
"computer": "Audiobooks - Computer/Internet",
"programming": "Audiobooks - Computer/Internet",
"travel": "Audiobooks - Travel/Adventure",
"adventure": "Audiobooks - Travel/Adventure",
"cooking": "Audiobooks - Food",
"recipe": "Audiobooks - Food",
"health": "Audiobooks - Medical",
"wellness": "Audiobooks - Medical",
"fitness": "Audiobooks - Medical",
"sports": "Audiobooks - Recreation",
"outdoor": "Audiobooks - Recreation",
"philosophy": "Audiobooks - Philosophy",
"religion": "Audiobooks - Pol/Soc/Relig",
"spirituality": "Audiobooks - Pol/Soc/Relig",
"politics": "Audiobooks - Pol/Soc/Relig",
"social science": "Audiobooks - Pol/Soc/Relig"
};
(function() {
'use strict';
// Main initialization
window.addEventListener('load', () => {
const asin = extractASIN();
if (asin) {
createFloatingButton();
addKeyframeAnimations();
}
});
function createFloatingButton() {
const btn = document.createElement('button');
btn.id = 'mam-float-btn';
btn.textContent = CATEGORY_SELECTION_METHOD === 3
? '📚 Copy MAM JSON with AI'
: '📚 Copy MAM JSON';
Object.assign(btn.style, {
position: 'fixed',
top: '100px',
right: '20px',
zIndex: 9999,
padding: '12px 18px',
backgroundColor: '#FF9900',
color: '#1a1a1a',
border: 'none',
borderRadius: '8px',
cursor: 'pointer',
boxShadow: '0 4px 12px rgba(0,0,0,0.2)',
fontSize: '14px',
fontWeight: 'bold',
transition: 'all 0.3s ease'
});
btn.addEventListener('mouseenter', () => {
btn.style.transform = 'scale(1.05)';
btn.style.boxShadow = '0 6px 16px rgba(0,0,0,0.3)';
});
btn.addEventListener('mouseleave', () => {
btn.style.transform = 'scale(1)';
btn.style.boxShadow = '0 4px 12px rgba(0,0,0,0.2)';
});
btn.addEventListener('click', handleButtonClick);
document.body.appendChild(btn);
}
async function handleButtonClick() {
const btn = document.getElementById('mam-float-btn');
const originalText = btn.textContent;
try {
btn.textContent = '🔄 Processing...';
btn.style.backgroundColor = '#ff8000';
const asin = extractASIN();
if (!asin) throw new Error('Could not find ASIN');
const apiData = await fetchBookData(asin);
const jsonData = await processApiData(apiData);
GM_setClipboard(JSON.stringify(jsonData, null, 2), 'text');
GM_openInTab('https://www.myanonamouse.net/tor/upload.php', { active: true });
showTempAlert('✅ JSON copied to clipboard!', '#4CAF50');
} catch (error) {
showTempAlert(`❌ Error: ${error.message}`, '#f44336');
console.error('Error:', error);
} finally {
btn.textContent = originalText;
btn.style.backgroundColor = '#FF9900';
}
}
function extractASIN() {
const pathMatch = window.location.pathname.match(/\/([A-Z0-9]{10})(?:[\/?]|$)/);
return pathMatch ? pathMatch[1] : null;
}
function fetchBookData(asin) {
return new Promise((resolve, reject) => {
GM_xmlhttpRequest({
method: 'GET',
url: `https://api.audnex.us/books/${asin}`,
onload: function(response) {
if (response.status === 200) {
resolve(JSON.parse(response.responseText));
} else {
reject(new Error(`API Error: ${response.status}`));
}
},
onerror: reject
});
});
}
async function processApiData(apiData) {
const editionData = extractEditionInfo(apiData.title);
return {
authors: cleanNames(apiData.authors?.map(a => a.name) || []),
narrators: cleanNames(apiData.narrators?.map(n => n.name) || []),
title: `${editionData.title}${apiData.subtitle ? ": " + apiData.subtitle : ""}`,
description: formatDescription(apiData),
language: formatLanguage(apiData.language),
series: processSeries(apiData.seriesPrimary),
category: await determineCategory(apiData),
thumbnail: apiData.image,
tags: await generateTags(apiData, editionData.editionInfo),
isbn: `ASIN:${apiData.asin}`,
editionInfo: editionData.editionInfo
};
}
function cleanNames(names) {
const patterns = {
prefixes: new RegExp(
'^\\s*' +
'(?:Dr|Mr|Mrs|Ms|Miss|Prof|Rev|Hon|Sir|Dame|Lady|Capt|Col|Gen|Lt|Cmdr|Adm|Maj|Sgt)' +
'[\\s.]*',
'i'
),
suffixes: new RegExp(
'[\\s,]+(?:' +
'Ph\\.?D|M\\.?D|J\\.?D|MBA|MSc|BSc|MS|MA|BA|BS|RN|CPA|Esq|Jr|Sr|I{1,3}|IV|VI?|' +
'1st|2nd|3rd|4th|FNP|APRN|DNP|PhD\\.?|MD\\.?|JD\\.?' +
')(?:\\.|,|$)|' +
'\\(.*?(?:certified|licensed|registered|chartered).*?\\)',
'gi'
)
};
return names.map(name => {
return name.replace(patterns.prefixes, '')
.replace(patterns.suffixes, '')
.replace(/\s{2,}/g, ' ')
.trim();
}).filter(name => name.length > 1);
}
function extractEditionInfo(title) {
const patterns = [
/(\d+(?:st|nd|rd|th)?\s*ed(?:ition)?)\b/i,
/\((?:unabridged|abridged|special edition)\)/i,
/\[.*(?:edition|version).*\]/i
];
for (const pattern of patterns) {
const match = title.match(pattern);
if (match) {
return {
title: title.replace(match[0], '').trim(),
editionInfo: match[1] || match[0]
};
}
}
return {
title: title.trim(),
editionInfo: null
};
}
function formatDescription(apiData) {
let desc = apiData.summary || 'No description available';
// Properly handle paragraph spacing
let paragraphs = desc.split(/<\/p>/gi);
paragraphs = paragraphs.map(p =>
p.replace(/<p>/gi, '') // Remove opening paragraph tags
.replace(/<\/?span[^>]*>/gi, '') // Remove span tags
.trim()
).filter(p => p.length > 0); // Remove empty paragraphs
// Join paragraphs with double line breaks for spacing
desc = paragraphs.join('<br><br>');
if (apiData.translators?.length > 0) {
desc += `<br><br><strong>Translators:</strong> ${apiData.translators.join(', ')}`;
}
return desc;
}
function formatLanguage(lang) {
return lang ? lang.charAt(0).toUpperCase() + lang.slice(1) : 'English';
}
function processSeries(seriesData) {
return seriesData ? [{
name: seriesData.name,
number: seriesData.position?.toString() || ""
}] : [];
}
async function determineCategory(apiData) {
const audibleCategories = apiData.genres?.map(g => g.name) || [];
switch (CATEGORY_SELECTION_METHOD) {
case 1:
for (const category of audibleCategories) {
if (AUDIBLE_TO_MAM_CATEGORY_MAP[category]) {
return AUDIBLE_TO_MAM_CATEGORY_MAP[category];
}
}
return '';
case 2:
return smartCategoryMatcher(audibleCategories, apiData.title, apiData.summary);
case 3:
return await getChatGptCategory(apiData.title, apiData.summary, audibleCategories);
default:
return '';
}
}
function smartCategoryMatcher(audibleCategories, title, description) {
const text = `${title} ${description}`.toLowerCase();
const scores = {};
// Score based on keywords
for (const [keyword, category] of Object.entries(KEYWORD_TO_MAM_CATEGORY_MAP)) {
const regex = new RegExp(`\\b${keyword}\\b`, 'gi');
const matches = text.match(regex);
if (matches) {
scores[category] = (scores[category] || 0) + matches.length;
}
}
// Score based on direct category mappings
for (const audibleCat of audibleCategories) {
const mappedCat = AUDIBLE_TO_MAM_CATEGORY_MAP[audibleCat];
if (mappedCat) {
scores[mappedCat] = (scores[mappedCat] || 0) + 3;
}
}
// Return highest scoring category
const sorted = Object.entries(scores).sort((a, b) => b[1] - a[1]);
return sorted[0]?.[0] || '';
}
async function getChatGptCategory(title, description, audibleCategories) {
if (CHATGPT_API_KEY === "your-api-key-here") {
throw new Error("Please put your ChatGPT API key in the script to use AI functionality or switch to the direct mapping or keyword matching methods.");
}
if (!CHATGPT_API_KEY) return '';
const prompt = `Select the most appropriate category from this list: ${AVAILABLE_CATEGORIES.join(", ")}
Title: ${title}
Description: ${description}
Audible Categories: ${audibleCategories.join(", ")}
Respond only with the exact category name.`;
try {
const response = await fetch("https://api.openai.com/v1/chat/completions", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${CHATGPT_API_KEY}`
},
body: JSON.stringify({
model: "gpt-4o-mini",
messages: [{ role: "user", content: prompt }],
temperature: 0.5,
max_tokens: 50
})
});
if (!response.ok) {
if (response.status === 401) {
throw new Error("Invalid ChatGPT API key");
}
throw new Error(`API Error: ${response.statusText}`);
}
const data = await response.json();
const category = data.choices[0].message.content.trim();
return AVAILABLE_CATEGORIES.includes(category) ? category : '';
} catch (error) {
throw new Error(error.message.includes("API key")
? "Invalid ChatGPT API key"
: "AI Category Error");
}
}
async function generateAITags(title, description) {
const prompt = `For the audiobook with title "${title}" and description "${description}", generate 3-4 relevant descriptive tags.
Tags should be single words or short phrases that describe the book's themes, setting, or notable elements.
Avoid generic terms like "fiction", "non-fiction", or basic genre names.
Respond with just the tags, separated by commas.`;
try {
const response = await fetch("https://api.openai.com/v1/chat/completions", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${CHATGPT_API_KEY}`
},
body: JSON.stringify({
model: "gpt-4o-mini",
messages: [{ role: "user", content: prompt }],
temperature: 0.7,
max_tokens: 50
})
});
if (!response.ok) {
return [];
}
const data = await response.json();
return data.choices[0].message.content.split(',').map(tag => tag.trim());
} catch (error) {
console.error('Error generating AI tags:', error);
return [];
}
}
function formatRuntime(minutes) {
const hours = Math.floor(minutes / 60);
const mins = minutes % 60;
return `${hours} hrs ${mins} mins`;
}
function formatDate(dateString) {
const date = new Date(dateString);
return date.toLocaleDateString('en-US', {
month: 'long',
day: 'numeric',
year: 'numeric'
});
}
async function generateTags(apiData, editionInfo) {
const tags = [
`${formatDate(apiData.releaseDate)}`,
CHAPTERIZED && 'Chapterized',
RIPPER
].filter(Boolean);
// Add category-based or AI-generated tags
if (CATEGORY_SELECTION_METHOD === 3) {
const aiTags = await generateAITags(apiData.title, apiData.summary);
if (aiTags.length > 0) {
tags.push(...aiTags);
}
} else {
const category = await determineCategory(apiData);
if (category) {
// Remove "Audiobooks - " prefix and add as tag
const categoryTag = category.replace('Audiobooks - ', '');
tags.push(categoryTag);
}
}
// Add duration and publisher
tags.push(
`${formatRuntime(apiData.runtimeLengthMin)}`,
`${apiData.publisherName}`
);
if (editionInfo) tags.push(`Edition: ${editionInfo}`);
return tags.join(' | ');
}
function showTempAlert(message, color) {
const alert = document.createElement('div');
Object.assign(alert.style, {
position: 'fixed',
top: '20px',
right: '20px',
padding: '15px 25px',
backgroundColor: color,
color: 'white',
borderRadius: '8px',
boxShadow: '0 4px 12px rgba(0,0,0,0.2)',
zIndex: 10000,
animation: 'slideIn 0.3s ease-out'
});
alert.textContent = message;
document.body.appendChild(alert);
setTimeout(() => {
alert.style.animation = 'slideOut 0.3s ease-in';
setTimeout(() => alert.remove(), 300);
}, 3000);
}
function addKeyframeAnimations() {
const style = document.createElement('style');
style.textContent = `
@keyframes slideIn {
from { transform: translateX(100%); }
to { transform: translateX(0); }
}
@keyframes slideOut {
from { transform: translateX(0); }
to { transform: translateX(100%); }
}
`;
document.head.appendChild(style);
}
})();