MAM Audible Upload

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

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 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(),
    })
  );
});