MAM Audible Upload

Adds button to copy audiobook data as json to Audible page and opens MAM upload

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         MAM Audible Upload
// @namespace    https://greasyfork.org/en/users/886084
// @version      1.0.1
// @license      MIT
// @description  Adds button to copy audiobook data as json to Audible page and opens MAM upload
// @author       DrBlank
// @include      https://www.audible.com/pd/*
// @include      https://www.audible.in/pd/*
// @include      https://www.audible.tld/pd/*
// @grant        none
// ==/UserScript==

const RIPPER = "Libation"; // yours can be InAudible 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 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 = [];
  for (let index = 0; index < authorElements.length; index++) {
    authors[index] = authorElements[index].innerHTML.replace(
      / - (foreword|afterword|translator|editor)/gi,
      ""
    );
  }
  return authors;
}

function getNarrators() {
  let narratorElements = document.querySelectorAll(".narratorLabel a");
  let narrators = [];
  for (let index = 0; index < narratorElements.length; index++) {
    narrators[index] = narratorElements[index].innerHTML;
  }
  return narrators;
}

function getSeriesName() {
  let series = "";
  let seriesElement = document.querySelector(".seriesLabel");
  if (seriesElement) {
    series = seriesElement.querySelector("a").innerHTML;
  }
  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} True Decrypt`);

  raw_tags.push(
    `Audible Release: ${getReleaseDate()}`,
    `Publisher: ${getPublisher()}`,
    getAudibleCategory()
  );
  // let tags = `Duration: ${getRunTime()} | Chapterized | Libation True Decrypt | ${getAudibleCategory()} | Audible Release: ${getReleaseDate()} | Publisher: ${getPublisher()}`;
  // return tags;
  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-common-buybox").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(),
    })
  );
});