Amazon Book Metadata with Multiple AI Options

Copies Amazon book metadata to JSON for both physical and ebooks, with multiple AI-powered category selection options

  1. // ==UserScript==
  2. // @name Amazon Book Metadata with Multiple AI Options
  3. // @namespace https://greatest.deepsurf.us/en/scripts/513462
  4. // @version 1.0.3
  5. // @license MIT
  6. // @description Copies Amazon book metadata to JSON for both physical and ebooks, with multiple AI-powered category selection options
  7. // @author SnowmanNurse
  8. // @match https://www.amazon.com/*
  9. // @match https://www.amazon.in/*
  10. // @grant none
  11. // ==/UserScript==
  12. (function() {
  13. 'use strict';
  14.  
  15. const CATEGORY_SELECTION_METHOD = 2; // 1 for direct mapping, 2 for scoring method, 3 for AI-based selection
  16.  
  17. // AI model selection (only used if CATEGORY_SELECTION_METHOD is 3)
  18. // 0: None (fall back to direct mapping), 1: ChatGPT (OpenAI), 2: Meta Llama, 3: Google Gemini 128K,
  19. // 4: Mistral AI, 5: Google Gemini 1M, 6: Anthropic Claude 3
  20. const AI_MODEL = 1;
  21.  
  22. // API keys for different AI models (Replace with your actual API keys)
  23. const CHATGPT_API_KEY = "your_chatgpt_api_key_here";
  24. const META_API_KEY = "your_meta_api_key_here";
  25. const GOOGLE_API_KEY = "your_google_api_key_here";
  26. const MISTRAL_API_KEY = "your_mistral_api_key_here";
  27. const ANTHROPIC_API_KEY = "your_anthropic_api_key_here";
  28.  
  29. const AVAILABLE_CATEGORIES = [
  30. "Ebooks - Art", "Ebooks - Biographical", "Ebooks - Business", "Ebooks - Computers",
  31. "Ebooks - Cooking", "Ebooks - Crafts", "Ebooks - Fantasy", "Ebooks - Fiction",
  32. "Ebooks - Health", "Ebooks - History", "Ebooks - Home & Garden", "Ebooks - Horror",
  33. "Ebooks - Humor", "Ebooks - Juvenile", "Ebooks - Language", "Ebooks - Literature",
  34. "Ebooks - Mathematics", "Ebooks - Medical", "Ebooks - Mystery", "Ebooks - Nature",
  35. "Ebooks - Philosophy", "Ebooks - Politics", "Ebooks - Reference", "Ebooks - Religion",
  36. "Ebooks - Romance", "Ebooks - Science", "Ebooks - Science Fiction", "Ebooks - Self-Help",
  37. "Ebooks - Social Science", "Ebooks - Sports", "Ebooks - Technology", "Ebooks - Travel",
  38. "Ebooks - True Crime", "Ebooks - Young Adult"
  39. ];
  40.  
  41. const AMAZON_TO_MAM_CATEGORY_MAP = {
  42. "Arts & Photography": "Ebooks - Art",
  43. "Biographies & Memoirs": "Ebooks - Biographical",
  44. "Business & Money": "Ebooks - Business",
  45. "Computers & Technology": "Ebooks - Computers",
  46. "Cookbooks, Food & Wine": "Ebooks - Cooking",
  47. "Crafts, Hobbies & Home": "Ebooks - Crafts",
  48. "Science Fiction & Fantasy": "Ebooks - Fantasy",
  49. "Literature & Fiction": "Ebooks - Fiction",
  50. "Health, Fitness & Dieting": "Ebooks - Health",
  51. "History": "Ebooks - History",
  52. "Horror": "Ebooks - Horror",
  53. "Humor & Entertainment": "Ebooks - Humor",
  54. "Children's Books": "Ebooks - Juvenile",
  55. "Reference": "Ebooks - Reference",
  56. "Romance": "Ebooks - Romance",
  57. "Science & Math": "Ebooks - Science",
  58. "Self-Help": "Ebooks - Self-Help",
  59. "Sports & Outdoors": "Ebooks - Sports",
  60. "Teen & Young Adult": "Ebooks - Young Adult"
  61. };
  62.  
  63. // SVG logo
  64. const svgLogo = `<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 59.16 69.57'>
  65. <g transform='translate(-110.73,-61.49)'>
  66. <path d='m 130.15,99.28 c 2.9,-1.3 4.93,-4.22 4.94,-7.6 0,-4.61 -3.74,-8.35 -8.35,-8.35 -4.61,0 -8.35,3.74 -8.35,8.35 0,4.6 3.74,8.34 8.35,8.34 0.72,0 1.41,-0.1 2.08,-0.27 0.41,-0.18 0.85,-0.33 1.32,-0.46 z m -7.55,-6.13 c 0,-2.3 1.87,-4.17 4.18,-4.17 0.58,0 1.15,0.12 1.65,0.34 -0.11,0.31 -0.18,0.65 -0.18,1.0 0,1.53 1.19,2.79 2.7,2.91 -0.04,2.26 -1.89,4.09 -4.17,4.09 -2.3,0 -4.18,-1.87 -4.18,-4.17' style='fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.03' />
  67. <path d='m 146.01,83.31 c -4.61,0 -8.35,3.74 -8.35,8.35 0,3.35 1.97,6.23 4.82,7.56 0.55,0.14 1.08,0.32 1.56,0.54 0.63,0.15 1.28,0.24 1.96,0.24 4.6,0 8.34,-3.74 8.35,-8.35 0,-4.61 -3.74,-8.35 -8.35,-8.35 z m 0.04,14.01 c -2.3,0 -4.17,-1.87 -4.17,-4.17 0,-2.3 1.87,-4.17 4.17,-4.17 0.51,0 1.01,0.09 1.46,0.26 -0.13,0.33 -0.21,0.7 -0.21,1.08 0,1.61 1.3,2.92 2.92,2.92 -0.05,2.26 -1.9,4.08 -4.17,4.08' style='fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.03' />
  68. </g>
  69. </svg>`;
  70.  
  71. function cleanName(name) {
  72. const titlesToRemove = [
  73. "PhD", "MD", "JD", "MBA", "MA", "MS", "MSc", "MFA", "MEd", "ScD", "DrPH", "MPH", "LLM", "DDS", "DVM",
  74. "EdD", "PsyD", "ThD", "DO", "PharmD", "DSc", "DBA", "RN", "CPA", "Esq.", "LCSW", "PE", "AIA", "FAIA",
  75. "CSP", "CFP", "Jr.", "Sr.", "I", "II", "III", "IV", "Dr.", "Mr.", "Mrs.", "Ms.", "Prof.", "Rev.", "Fr.",
  76. "Sr.", "Capt.", "Col.", "Gen.", "Lt.", "Cmdr.", "Adm.", "Sir", "Dame", "Hon.", "Amb.", "Gov.", "Sen.",
  77. "Rep.", "BSN", "MSN", "RN", "MS", "MN", "CNE", "CNEcl", "ANEF", "FAADN", "COI", "DNP"
  78. ];
  79.  
  80. let cleanedName = name.trim();
  81.  
  82. titlesToRemove.forEach(title => {
  83. const regexBefore = new RegExp(`^${title}\\b`, 'i');
  84. const regexAfter = new RegExp(`\\b${title}$`, 'i');
  85. cleanedName = cleanedName.replace(regexBefore, '').replace(regexAfter, '');
  86. });
  87.  
  88. titlesToRemove.forEach(title => {
  89. const regexMiddle = new RegExp(`\\s${title}\\s`, 'gi');
  90. cleanedName = cleanedName.replace(regexMiddle, ' ');
  91. });
  92.  
  93. cleanedName = cleanedName.replace(/,\s*([A-Z]+\s*)+$/, '');
  94. cleanedName = cleanedName.replace(/\s+/g, ' ').trim();
  95.  
  96. return cleanedName;
  97. }
  98.  
  99. function cleanText(text) {
  100. return text
  101. .replace(/‎/g, '') // Remove special characters
  102. .replace(/\s+/g, ' ') // Replace multiple spaces with single space
  103. .replace(/›.*$/g, '') // Remove everything after '›' character
  104. .replace(/‹.*$/g, '') // Remove everything after '‹' character
  105. .replace(/Back to results.*$/i, '') // Remove "Back to results" text
  106. .trim();
  107. }
  108.  
  109.  
  110. function showOverlay() {
  111. const overlayHtml = `
  112. <div id="mam-overlay" style="display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.5); backdrop-filter: blur(5px); z-index: 9999;">
  113. <div style="position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); background: white; padding: 20px; border-radius: 5px; text-align: center;">
  114. <p style="font-size: 18px; margin: 0;">Processing JSON data...</p>
  115. <p style="font-size: 16px; margin: 10px 0 0;">Please wait...</p>
  116. <div id="ai-gif" style="display: none; margin-top: 20px;">
  117. <img src="https://c.tenor.com/JDV9WN1QC3kAAAAC/tenor.gif" alt="AI Processing" style="max-width: 200px;">
  118. </div>
  119. </div>
  120. </div>`;
  121.  
  122. if (!document.getElementById('mam-overlay')) {
  123. document.body.insertAdjacentHTML('beforeend', overlayHtml);
  124. }
  125. document.getElementById("mam-overlay").style.display = "block";
  126.  
  127. // Show GIF if AI model is selected
  128. if (CATEGORY_SELECTION_METHOD === 3 && AI_MODEL !== 0) {
  129. document.getElementById("ai-gif").style.display = "block";
  130. }
  131. }
  132.  
  133. function hideOverlay() {
  134. const overlay = document.getElementById("mam-overlay");
  135. if (overlay) {
  136. overlay.style.display = "none";
  137. const aiGif = document.getElementById("ai-gif");
  138. if (aiGif) {
  139. aiGif.style.display = "none";
  140. }
  141. }
  142. }
  143.  
  144. function getAuthors() {
  145. let authorElements = document.querySelectorAll('#bylineInfo .author .a-link-normal');
  146. let authors = [];
  147. for (let element of authorElements) {
  148. if (element) {
  149. let authorName = element.textContent.trim();
  150. authorName = cleanName(authorName);
  151. if (authorName && !authors.includes(authorName)) {
  152. authors.push(authorName);
  153. }
  154. }
  155. }
  156. return authors;
  157. }
  158.  
  159. function getTitle() {
  160. let titleElement = document.getElementById("productTitle");
  161. return titleElement ? titleElement.textContent.trim() : "";
  162. }
  163.  
  164. function getDescription() {
  165. let descriptionElement = document.querySelector('#bookDescription_feature_div .a-expander-content');
  166. return descriptionElement ? descriptionElement.innerHTML.trim() : "No description available";
  167. }
  168.  
  169. function getThumbnail() {
  170. // First try for normal book layout
  171. var thumbnailElement = document.querySelector("#imgBlkFront");
  172. if (!thumbnailElement) {
  173. // Try for ebook layout
  174. thumbnailElement = document.querySelector("#ebooksImgBlkFront");
  175. }
  176. if (!thumbnailElement) {
  177. // Try for alternate layout
  178. thumbnailElement = document.querySelector("#imgTagWrapperId img");
  179. }
  180.  
  181. if (thumbnailElement) {
  182. // Try to get the high-resolution version first
  183. var thumbnail = thumbnailElement.getAttribute("data-a-dynamic-image");
  184. if (thumbnail) {
  185. try {
  186. // Get the first URL from the dynamic image JSON
  187. var dynamicImageData = JSON.parse(thumbnail);
  188. return Object.keys(dynamicImageData)[0];
  189. } catch (e) {
  190. console.error('Error parsing dynamic image data:', e);
  191. }
  192. }
  193.  
  194. // Fallback to data-old-hires or src
  195. return thumbnailElement.getAttribute("data-old-hires") || thumbnailElement.src;
  196. }
  197.  
  198. return "";
  199. }
  200.  
  201. function getLanguage() {
  202. let languageElement = document.querySelector("#rpi-attribute-language .rpi-attribute-value span");
  203. return languageElement ? languageElement.textContent.trim() : "Unknown";
  204. }
  205.  
  206. function getSeriesInfo() {
  207. let seriesElement = document.querySelector("#seriesBulletWidget_feature_div");
  208. let seriesInfo = [];
  209.  
  210. if (seriesElement) {
  211. let seriesLink = seriesElement.querySelector("a");
  212. if (seriesLink) {
  213. let seriesName = seriesLink.textContent.trim();
  214. let bookNumberMatch = seriesElement.textContent.match(/Book\s*?(\d+\.?\d*-?\d*\.?\d*)/);
  215. let bookNumber = bookNumberMatch ? bookNumberMatch[1] : "";
  216.  
  217. seriesInfo.push({ name: seriesName, number: bookNumber });
  218. }
  219. }
  220.  
  221. return seriesInfo;
  222. }
  223.  
  224. function getEdition() {
  225. let detailsElement = document.querySelector('#detailBullets_feature_div');
  226. if (detailsElement) {
  227. let items = detailsElement.querySelectorAll('li');
  228. for (let item of items) {
  229. if (item.textContent.includes('Publisher')) {
  230. let editionMatch = item.textContent.match(/;\s*(\d+(?:st|nd|rd|th)\s+edition)/i);
  231. if (editionMatch) {
  232. return cleanText(editionMatch[1]);
  233. }
  234. }
  235. }
  236. }
  237. return "";
  238. }
  239.  
  240. function getPageCount() {
  241. let detailsElement = document.querySelector('#detailBullets_feature_div');
  242. if (detailsElement) {
  243. let items = detailsElement.querySelectorAll('li');
  244. for (let item of items) {
  245. if (item.textContent.includes('pages')) {
  246. let pageCount = item.textContent.match(/(\d+)\s*pages/);
  247. return pageCount ? pageCount[1] : "Unknown";
  248. }
  249. }
  250. }
  251.  
  252. // Try alternate location
  253. let printLengthElement = document.querySelector('#rpi-attribute-book_details-fiona_pages .rpi-attribute-value span');
  254. if (printLengthElement) {
  255. let pageCount = printLengthElement.textContent.match(/(\d+)/);
  256. return pageCount ? pageCount[1] : "Unknown";
  257. }
  258.  
  259. return "Unknown";
  260. }
  261.  
  262. function getPublicationDate() {
  263. let detailsElement = document.querySelector('#detailBullets_feature_div');
  264. if (detailsElement) {
  265. let items = detailsElement.querySelectorAll('li');
  266. for (let item of items) {
  267. if (item.textContent.includes('Publisher') || item.textContent.includes('Publication date')) {
  268. let dateMatch = item.textContent.match(/\((.*?)\)/);
  269. return dateMatch ? cleanText(dateMatch[1]) : "";
  270. }
  271. }
  272. }
  273.  
  274. // Try alternate location
  275. let pubDateElement = document.querySelector('#rpi-attribute-book_details-publication_date .rpi-attribute-value span');
  276. if (pubDateElement) {
  277. return cleanText(pubDateElement.textContent);
  278. }
  279.  
  280. return "";
  281. }
  282.  
  283. function getPublisher() {
  284. let detailsElement = document.querySelector('#detailBullets_feature_div');
  285. if (detailsElement) {
  286. let items = detailsElement.querySelectorAll('li');
  287. for (let item of items) {
  288. if (item.textContent.includes('Publisher')) {
  289. let publisherText = item.textContent.split(':')[1];
  290. if (publisherText) {
  291. let publisher = publisherText.split('(')[0];
  292. return cleanText(publisher).replace(/;\s*\d+(?:st|nd|rd|th)\s+edition/i, '');
  293. }
  294. }
  295. }
  296. }
  297.  
  298. // Try alternate location
  299. let publisherElement = document.querySelector('#rpi-attribute-book_details-publisher .rpi-attribute-value span');
  300. if (publisherElement) {
  301. return cleanText(publisherElement.textContent).replace(/;\s*\d+(?:st|nd|rd|th)\s+edition/i, '');
  302. }
  303.  
  304. return "Unknown Publisher";
  305. }
  306.  
  307. function getAmazonCategory() {
  308. let categoryElement = document.querySelector("#wayfinding-breadcrumbs_feature_div");
  309. if (categoryElement) {
  310. let categories = categoryElement.textContent.trim().split("›");
  311. // Get the last category but make sure it's not a navigation element
  312. let lastCategory = categories[categories.length - 1];
  313. if (lastCategory && !lastCategory.toLowerCase().includes('back to')) {
  314. return cleanText(lastCategory);
  315. }
  316. // If the last item is navigation, try the second to last
  317. if (categories.length > 1) {
  318. return cleanText(categories[categories.length - 2]);
  319. }
  320. }
  321. return "";
  322. }
  323.  
  324. function getISBN() {
  325. let isbn10Element = document.querySelector('#rpi-attribute-book_details-isbn10 .rpi-attribute-value span');
  326. let isbn10 = isbn10Element ? isbn10Element.textContent.trim() : '';
  327. let isbn13Element = document.querySelector('#rpi-attribute-book_details-isbn13 .rpi-attribute-value span');
  328. let isbn13 = isbn13Element ? isbn13Element.textContent.trim() : '';
  329.  
  330. if (!isbn10 || !isbn13) {
  331. let detailsElement = document.querySelector('#detailBullets_feature_div');
  332. if (detailsElement) {
  333. let items = detailsElement.querySelectorAll('li');
  334. items.forEach(function(item) {
  335. let text = item.textContent;
  336. if (text.includes('ISBN-10')) {
  337. isbn10 = text.split(':')[1].trim();
  338. } else if (text.includes('ISBN-13')) {
  339. isbn13 = text.split(':')[1].trim();
  340. }
  341. });
  342. }
  343. }
  344.  
  345. return { isbn10, isbn13 };
  346. }
  347.  
  348. function getASIN() {
  349. let asinElement = document.querySelector("#detailsRichBullets_feature_div");
  350. if (asinElement) {
  351. let asinText = asinElement.textContent.trim();
  352. let match = asinText.match(/ASIN\s*:\s*([\w\d]+)/);
  353. return match ? match[1] : "";
  354. }
  355. return "";
  356. }
  357.  
  358. function getTags() {
  359. let tags = [];
  360. let edition = getEdition();
  361. let pageCount = getPageCount();
  362. let publicationDate = getPublicationDate();
  363. let publisher = getPublisher();
  364. let amazonCategory = getAmazonCategory();
  365.  
  366. // Add edition first if it exists
  367. if (edition) tags.push(edition);
  368. if (pageCount && pageCount !== "Unknown") tags.push(`Pages: ${pageCount}`);
  369. if (publicationDate) tags.push(`Published: ${publicationDate}`);
  370. if (publisher && publisher !== "Unknown Publisher") tags.push(`Publisher: ${publisher}`);
  371. if (amazonCategory) {
  372. let cleanCategory = cleanText(amazonCategory);
  373. if (cleanCategory) tags.push(cleanCategory);
  374. }
  375.  
  376. return tags.filter(tag => tag && tag.trim()).join(" | ");
  377. }
  378.  
  379. async function getMAMCategory(title, description, amazonCategory) {
  380. switch (CATEGORY_SELECTION_METHOD) {
  381. case 1: // Direct mapping
  382. return AMAZON_TO_MAM_CATEGORY_MAP[amazonCategory] || "";
  383. case 2: // Scoring method
  384. return smartCategoryMatcher(amazonCategory, title, description);
  385. case 3: // AI-based selection
  386. return await getCategoryFromAI(title, description, amazonCategory);
  387. default:
  388. return "";
  389. }
  390. }
  391.  
  392. async function getCategoryFromAI(title, description, amazonCategory) {
  393. const prompt = `Given the following book information, select the most appropriate category from this list: ${AVAILABLE_CATEGORIES.join(", ")}
  394.  
  395. Title: ${title}
  396. Description: ${description}
  397. Amazon Category: ${amazonCategory}
  398.  
  399. Please respond with only the category name, nothing else.`;
  400.  
  401. switch (AI_MODEL) {
  402. case 1: return await getCategoryFromChatGPT(prompt);
  403. case 2: return await getCategoryFromMetaLlama(prompt);
  404. case 3: return await getCategoryFromGoogleGemini(prompt, false);
  405. case 4: return await getCategoryFromMistralAI(prompt);
  406. case 5: return await getCategoryFromGoogleGemini(prompt, true);
  407. case 6: return await getCategoryFromAnthropicClaude(prompt);
  408. default: return null;
  409. }
  410. }
  411.  
  412. async function getCategoryFromChatGPT(prompt) {
  413. if (!CHATGPT_API_KEY) return null;
  414. const response = await fetch("https://api.openai.com/v1/chat/completions", {
  415. method: "POST",
  416. headers: {
  417. "Content-Type": "application/json",
  418. "Authorization": `Bearer ${CHATGPT_API_KEY}`
  419. },
  420. body: JSON.stringify({
  421. model: "gpt-3.5-turbo",
  422. messages: [{ role: "user", content: prompt }],
  423. temperature: 0.7,
  424. max_tokens: 50
  425. })
  426. });
  427. const data = await response.json();
  428. return data.choices[0].message.content.trim();
  429. }
  430.  
  431. async function getCategoryFromMetaLlama(prompt) {
  432. if (!META_API_KEY) return null;
  433. const response = await fetch("https://api.meta.com/v1/text/completions", {
  434. method: "POST",
  435. headers: {
  436. "Content-Type": "application/json",
  437. "Authorization": `Bearer ${META_API_KEY}`
  438. },
  439. body: JSON.stringify({
  440. model: "llama-2-13b-chat",
  441. prompt: prompt,
  442. max_tokens: 50
  443. })
  444. });
  445. const data = await response.json();
  446. return data.choices[0].text.trim();
  447. }
  448.  
  449. async function getCategoryFromGoogleGemini(prompt, isLargeModel) {
  450. if (!GOOGLE_API_KEY) return null;
  451. const model = isLargeModel ? "gemini-1.5-flash-1m" : "gemini-1.5-flash";
  452. const response = await fetch(`https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent?key=${GOOGLE_API_KEY}`, {
  453. method: "POST",
  454. headers: { "Content-Type": "application/json" },
  455. body: JSON.stringify({
  456. contents: [{ parts: [{ text: prompt }] }]
  457. })
  458. });
  459. const data = await response.json();
  460. return data.candidates[0].content.parts[0].text.trim();
  461. }
  462.  
  463. async function getCategoryFromMistralAI(prompt) {
  464. if (!MISTRAL_API_KEY) return null;
  465. const response = await fetch("https://api.mistral.ai/v1/chat/completions", {
  466. method: "POST",
  467. headers: {
  468. "Content-Type": "application/json",
  469. "Authorization": `Bearer ${MISTRAL_API_KEY}`
  470. },
  471. body: JSON.stringify({
  472. model: "mistral-large-latest",
  473. messages: [{ role: "user", content: prompt }],
  474. max_tokens: 50
  475. })
  476. });
  477. const data = await response.json();
  478. return data.choices[0].message.content.trim();
  479. }
  480.  
  481. async function getCategoryFromAnthropicClaude(prompt) {
  482. if (!ANTHROPIC_API_KEY) return null;
  483. const response = await fetch("https://api.anthropic.com/v1/messages", {
  484. method: "POST",
  485. headers: {
  486. "Content-Type": "application/json",
  487. "x-api-key": ANTHROPIC_API_KEY
  488. },
  489. body: JSON.stringify({
  490. model: "claude-3-haiku-20240307",
  491. max_tokens: 50,
  492. messages: [{ role: "user", content: prompt }]
  493. })
  494. });
  495. const data = await response.json();
  496. return data.content[0].text.trim();
  497. }
  498.  
  499. async function copyToClipboard(text) {
  500. try {
  501. await navigator.clipboard.writeText(text);
  502. console.log('Copied to clipboard successfully!');
  503. window.open("https://www.myanonamouse.net/tor/upload.php", "_blank");
  504. } catch (err) {
  505. console.error('Failed to copy: ', err);
  506. } finally {
  507. hideOverlay();
  508. }
  509. }
  510.  
  511. async function generateJson() {
  512. try {
  513. var title = getTitle();
  514. var authors = getAuthors();
  515. var description = getDescription();
  516. var thumbnail = getThumbnail();
  517. var language = getLanguage();
  518. var seriesInfo = getSeriesInfo();
  519. var amazonCategory = getAmazonCategory();
  520. var mamCategory = await getMAMCategory(title, description, amazonCategory);
  521. var tags = getTags();
  522. var { isbn10, isbn13 } = getISBN();
  523. var asin = getASIN();
  524.  
  525. // Append ISBNs to the description
  526. if (isbn10 || isbn13) {
  527. description += '<br><br>';
  528. if (isbn13) description += `ISBN-13: ${isbn13}<br>`;
  529. if (isbn10) description += `ISBN-10: ${isbn10}`;
  530. }
  531.  
  532. var json = {
  533. "authors": authors,
  534. "description": description,
  535. "tags": tags,
  536. "thumbnail": thumbnail,
  537. "title": title,
  538. "language": language,
  539. "series": seriesInfo,
  540. "category": mamCategory,
  541. "isbn": isbn13 || isbn10 || asin || ''
  542. };
  543.  
  544. var strJson = JSON.stringify(json, null, 2);
  545. await copyToClipboard(strJson);
  546. } catch (error) {
  547. console.error('Error generating JSON:', error);
  548. hideOverlay();
  549. }
  550. }
  551.  
  552. function addButtonAndLogo() {
  553. // Determine button text based on AI settings
  554. const isAISelected = CATEGORY_SELECTION_METHOD === 3 && AI_MODEL !== 0;
  555. const buttonText = isAISelected ? "Copy Book info to JSON with AI" : "Copy Book info to JSON";
  556.  
  557. let container = document.createElement("div");
  558. container.innerHTML = `
  559. <div id="metadataButtonContainer" style="position:fixed;bottom:10px;right:10px;z-index:9999;display:flex;align-items:center;">
  560. <div style="width:30px;height:30px;margin-right:10px;">${svgLogo}</div>
  561. <button id="copyMetadataButton" style="padding:10px;background-color:#008CBA;color:white;border:none;border-radius:5px;font-size:14px;">
  562. ${buttonText}
  563. </button>
  564. </div>`;
  565. document.body.appendChild(container);
  566.  
  567. document.getElementById("copyMetadataButton").addEventListener("click", async function(event) {
  568. event.preventDefault();
  569. showOverlay();
  570. await generateJson();
  571. });
  572. }
  573.  
  574. function main() {
  575. if (document.querySelector('#productTitle')) {
  576. console.log("Amazon Book Metadata script is running on a book page.");
  577. addButtonAndLogo();
  578. } else {
  579. console.log("Not a book page. Amazon Book Metadata script will not run.");
  580. }
  581. }
  582.  
  583. // Run the script
  584. if (document.readyState === "complete" || document.readyState === "interactive") {
  585. setTimeout(main, 1000); // Delay execution by 1 second
  586. } else {
  587. window.addEventListener("DOMContentLoaded", function() {
  588. setTimeout(main, 1000); // Delay execution by 1 second
  589. });
  590. }
  591. })();