您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Complete API-based solution with full metadata cleaning and category mapping
当前为
// ==UserScript== // @name Audible to MAM JSON Converter (Full API Version) // @namespace https://greasyfork.org/en/scripts/511491 // @version 2.0.1 // @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, 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') .replace(/<p>/g, '<br>') .replace(/<\/p>/g, '') .trim(); 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 = [ `Pub.: ${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( `Duration: ${formatRuntime(apiData.runtimeLengthMin)}`, `Publisher: ${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); } })();