Greasy Fork

Greasy Fork is available in English.

Ao3 Auto Bookmarker

Allows for autofilled bookmark summary and tags on Ao3.

当前为 2025-04-01 提交的版本,查看 最新版本

// ==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.1
// @author      Legovil
// @license     MIT
// ==/UserScript==

// Set the true or false value of each setting to change whether the bookmark auto generates the option.
const settings = {
  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: false,
  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 = CheckBookmarkType(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(bookmarkType);
  GenerateTags();
  HandleCheckBoxes();
})();

function CheckBookmarkType(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(bookmarkType) {
  const notesElement = document.getElementById("bookmark_notes");
  if (!notesElement) {
    console.error("Notes element not found. Cancelling notes generation.");
    return;
  }

  console.log("Notes element found.");
  const notes = [
    settings.GenerateTitleNote && GenerateTitleNote(bookmarkType),
    settings.GenerateSummaryNote && GenerateSummaryNote(bookmarkType)
  ].filter(item => Boolean(item)).join("\n\n");

  notesElement.value = settings.AppendToExistingNote ? `${notesElement.value}\n\n${notes}` : notes;
}

function GenerateTitleNote(bookmarkType) {
  const titleQuery = (bookmarkType === BookmarkType.Work) ? ".title.heading" : "ul.series a";
  const title = document.querySelector(titleQuery);
  if (!title) {
    console.warn("Title not found. Cancelling Title Note generation.");
    return "";
  }
  const bylineQuery = (bookmarkType === BookmarkType.Work) ? ".byline.heading a" : ".series.meta.group a";
  const byline = document.querySelector(bylineQuery);
  if (!byline) {
    console.warn("Byline not found. Cancelling Title Note generation.");
    return "";
  }
  return `${title.innerHTML.link(window.location.href)} by ${byline.outerHTML}.`;
}

function GenerateSummaryNote(bookmarkType) {
  const summaryQuery = (bookmarkType === BookmarkType.Work) ? ".summary.module .userstuff" : ".series.meta.group .userstuff"
  const summary = document.querySelector(summaryQuery);
  if (!summary) {
    console.warn("No summary found. Cancelling summary note generation.");
    return "";
  }
  return `Summary: ${summary.innerText}`;
}

function GenerateTags(bookmarkType) {
  let tagsElement = document.getElementById("bookmark_tag_string_autocomplete");
  if (!tagsElement) {
    console.error("Tags input element not found. Cancelling bookmark tag generation.");
    return;
  }
  console.log("Tags input element found.");
  tagsElement.value = [
    settings.GetArchiveWarnings && GetTagsFromString("warning tags"),
    settings.GetCategoryTags && GetTagsFromString("category tags"),
    settings.GetFandomTags && GetTagsFromString("fandom tags"),
    settings.GetRelationshipTags && GetTagsFromString("relationship tags"),
    settings.GetCharacterTags && GetTagsFromString("character tags"),
    settings.GetAdditionalTags && GetTagsFromString("freeform tags"),
    settings.GenerateWordCountTag && GenerateWordCountTag()
  ].filter(item => Boolean(item)).join(', ');
}

function GetTagsFromString(tagClassName) {
  const tagList = document.getElementsByClassName(tagClassName)[1]?.getElementsByClassName("tag");
  if (!tagList || tagList.length == 0) {
    console.error(`Tags element not found. Cancelling ${tagClassName} generation.`);
    return "";
  }
  return Array.from(tagList).map(tag => tag.text).join(', ');
}

function GenerateWordCountTag() {
  const wordCountElement = settings.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(",", "").replaceAll(" ","");
  console.log(`Word count: ${wordCount}`);
  let lowerBound = wordCountBounds[0];
  if (wordCount < lowerBound) {
    return `< ${lowerBound}`;
  }

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

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

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