Greasy Fork

Ao3 Auto Bookmarker

Allows for autofilled bookmark summary and tags on Ao3.

目前为 2025-03-31 提交的版本。查看 最新版本

// ==UserScript==
// @name        Ao3 Auto Bookmarker
// @description Allows for autofilled bookmark summary and tags on Ao3.
// @namespace   Ao3
// @match       http*://archiveofourown.org/works/*
// @match       http*://archiveofourown.org/series/*
// @grant       none
// @version     1.0
// @author      Legovil
// @license     MIT
// @downloadURL
// ==/UserScript==

// Set the true or false value of each setting to change whether the bookmark auto generates the option.
const Settings = new Map([
  ["GenerateTitleNote", true],
  ["GenerateSummaryNote", true],
  ["CheckRecBox", false],
  ["CheckPrivateBox", false],
  ["GetRating", true],
  ["GetArchiveWarnings", true],
  ["GetCategoryTags", true],
  ["GetFandomTags", true],
  ["GetRelationshipTags", true],
  ["GetCharacterTags", true],
  ["GetAdditionalTags", true],
  ["GenerateWordCountTag", true],
  ["AppendToExistingNote", true],
  ["AppendToExistingTags", false],
  ["UsingAo3Extensions", true]
]);

// Edit these comma delimited numbers to change the auto word count tag boundaries (e.g. 1000 - 5000).
const WordCountBounds = [1000, 5000, 10000, 50000, 100000, 500000];

// Bookmark type enum.
const BookmarkType = Object.freeze({
  Work: 'Work',
  Series: 'Series'
});

(function() {
  'use strict';

  const bookmarkType = CheckIfWorkOrSeries(window.location.href);

  if (Object.is(bookmarkType, BookmarkType.Series)) {
      console.error("Not generating bookmark. Series bookmarks are not yet implemented.");
      return;
    }

  if (Object.is(bookmarkType, null)) {
    console.error("Bookmark type not found. Cancelling bookmark generation.");
    return;
  }

  GenerateNotes();
  GenerateTags();
  HandleCheckBoxes();
})();

function CheckIfWorkOrSeries(url) {
  if (url.includes("/works/")) {
    console.log("Found Work Bookmark.");
    return BookmarkType.Work;
  }
  if (url.includes("/series/")) {
    console.log("Found Series Bookmark.");
    return BookmarkType.Series;
  }
  return null;
}

function GenerateNotes() {
  const notesElement = document.getElementById("bookmark_notes");
  let note = "";

  if (Object.is(notesElement, null)) {
    console.error("Notes element not found. Cancelling notes generation.");
    return;
  }

  note += GenerateTitleNote();
  note += GenerateSummaryNote();
  if (Settings.get("AppendToExistingNote")) {
    notesElement.value += `\n\n${note}`;
  } else {
    notesElement.value = note;
  }
}

function GenerateTitleNote() {
  if (!Settings.get("GenerateTitleNote")) {
    console.log("Not generating title note.");
    return "";
  }

  console.log("Generating title note.");
  const title = document.getElementsByClassName("title heading")[0];
  if (Object.is(title, undefined)) {
    console.warn("Title not found. Cancelling Title Note generation.");
    return "";
  }

  const byline = document.getElementsByClassName("byline heading")[0].getElementsByTagName('a')[0];
  if (Object.is(byline, undefined)) {
    console.warn("Byline not found. Cancelling Title Note generation.");
    return "";
  }
  return `${title.innerHTML.link(window.location.href)} by ${byline.outerHTML}.`;
}

function GenerateSummaryNote() {
  if (!Settings.get("GenerateSummaryNote")) {
    console.log("Not generating summary note.");
    return;
  }

  const summary = document.getElementsByClassName("summary module")[0].getElementsByClassName("userstuff")[0];
  if (Object.is(summary, undefined)) {
    console.warn("No summary found. Cancelling summary note generation.");
    return "";
  }
  return `\n\nSummary: ${summary.innerText}`;
}

function GenerateTags() {
  let tagsElement = document.getElementById("bookmark_tag_string_autocomplete");
  if (Object.is(tagsElement, null)) {
    console.error("Tags element not found. Cancelling bookmark tag generation.");
    return;
  }
  let tags = Settings.get("AppendToExistingTags") ? `${tagsElement.value}, ` : "";
  tags += Settings.get("GetArchiveWarnings") ? GetTagsFromString("warning tags") : "";
  tags += Settings.get("GetCategoryTags") ? GetTagsFromString("category tags") : "";
  tags += Settings.get("GetFandomTags") ? GetTagsFromString("fandom tags") : "";
  tags += Settings.get("GetRelationshipTags") ? GetTagsFromString("relationship tags") : "";
  tags += Settings.get("GetCharacterTags") ? GetTagsFromString("character tags") : "";
  tags += Settings.get("GetAdditionalTags") ? GetTagsFromString("freeform tags") : "";
  tags += GenerateWordCountTag();
  tagsElement.value = tags;
}

function GetTagsFromString(tagClassName) {
  const tagList = document.getElementsByClassName(tagClassName)[1].getElementsByClassName("tag");
  if (Object.is(tagList, null) || tagList.length == 0) {
    console.error(`Tags element not found. Cancelling ${tagClassName} generation.`);
    return "";
  }
  let tags = "";
  for (const tag of tagList) {
    tags += `${tag.text}, `;
  }
  return tags;
}

function GenerateWordCountTag() {
  if (!Settings.get("GenerateWordCountTag")) {
    console.log("Not generating word count tag.")
    return;
  }

  const wordCountElement = Settings.get("UsingAo3Extensions") ? document.getElementsByClassName("words")[2] : document.getElementsByClassName("words")[1];
  if (Object.is(wordCountElement, null) || wordCountElement.innerText === "Words:") {
    console.error("Word count not found. Cancelling word count tag generation. Check to see if Ao3 Extensions setting is toggled correctly.");
    return;
  }
  const wordCount = wordCountElement.innerText.replaceAll(",", "");
  let lowerBound = WordCountBounds[0];
  if (wordCount < lowerBound) {
    return `< ${lowerBound}`;
  }

  for (const upperBound of WordCountBounds) {
    if (wordCount < upperBound) {
      return `${lowerBound} - ${upperBound - 1}`
    }
    lowerBound = upperBound;
  }
  return `> ${WordCountBounds[WordCountBounds.length - 1]}`;
}

function HandleCheckBoxes() {
  const recBox = document.getElementById("bookmark_rec");
  if (Settings.get("CheckRecBox") && !Object.is(recBox, undefined)) {
    console.log("Checking rec box.");
    recBox.checked = true;
  }

  const privateBox = document.getElementById("bookmark_private");
  if (Settings.get("CheckPrivateBox") && !Object.is(privateBox, undefined)) {
    console.log("Checking private box.");
    privateBox.checked = true;
  }
}