Greasy Fork is available in English.
Allows for autofilled bookmark summary and tags on Ao3.
当前为
// ==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.2
// @author Legovil
// @license MIT
// ==/UserScript==
// Settings for the script, allowing customization of which features are enabled.
const settings = {
generateTitleNote: true,
generateSummaryNote: true,
checkRecBox: false,
checkPrivateBox: false,
getRating: true,
getArchiveWarnings: false,
getCategoryTags: true,
getFandomTags: false,
getRelationshipTags: false,
getCharacterTags: false,
getAdditionalTags: false,
generateWordCountTag: true,
appendToExistingNote: false,
appendToExistingTags: false,
usingAo3Extensions: true,
};
// Word count boundaries for generating word count tags.
const wordCountBounds = [1000, 5000, 10000, 50000, 100000, 500000];
// Enum for bookmark types.
const BookmarkType = Object.freeze({
WORK: 'Work',
SERIES: 'Series',
});
(function() {
'use strict';
// Determine the type of bookmark based on the URL.
const bookmarkType = checkBookmarkType(window.location.href);
// If the bookmark type is not found, cancel the generation process.
if (bookmarkType === null) {
console.error('Bookmark type not found. Cancelling bookmark generation.');
return;
}
// Generate notes and tags, and handle checkboxes based on the bookmark type.
generateNotes(bookmarkType);
generateTags();
handleCheckBoxes();
})();
/**
* Checks the type of bookmark based on the URL.
* @param {string} url The URL to check.
* @return {string|null} The bookmark type or null if not found.
*/
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;
}
/**
* Generates notes for the bookmark.
* @param {string} bookmarkType The type of bookmark.
*/
function generateNotes(bookmarkType) {
const notesElement = document.getElementById('bookmark_notes');
if (!notesElement) {
console.error('Notes element not found. Cancelling notes generation.');
return;
}
// Generate title and summary notes based on settings.
const notes = [
settings.generateTitleNote && generateTitleNote(bookmarkType),
settings.generateSummaryNote && generateSummaryNote(bookmarkType),
].filter(Boolean).join('\n\n');
// Append or replace existing notes based on settings.
notesElement.value = settings.appendToExistingNote ? `${notesElement.value}\n\n${notes}` : notes;
}
/**
* Generates the title note for the bookmark.
* @param {string} bookmarkType The type of bookmark.
* @return {string} The generated title note.
*/
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}.`;
}
/**
* Generates the summary note for the bookmark.
* @param {string} bookmarkType The type of bookmark.
* @return {string} The generated summary note.
*/
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}`;
}
/**
* Generates tags for the bookmark.
*/
function generateTags() {
const tagsElement = document.getElementById('bookmark_tag_string_autocomplete');
if (!tagsElement) {
console.error('Tags input element not found. Cancelling bookmark tag generation.');
return;
}
// Remove existing tags if the setting is not to append to them.
if (!settings.appendToExistingTags) {
document.querySelectorAll('.added.tag a').forEach(tagLink => tagLink.click());
}
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(Boolean).join(', ');
}
/**
* Gets tags from a specific class name.
* @param {string} tagClassName The class name to get tags from.
* @return {string} The generated tags.
*/
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(', ');
}
/**
* Generates a word count tag based on the word count boundaries.
* @return {string} The generated word count tag.
*/
function generateWordCountTag() {
const wordCountElement = settings.usingAo3Extensions ? document.getElementsByClassName('words')[2] : document.getElementsByClassName('words')[1];
if (!wordCountElement || 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]}`;
}
/**
* Handles the state of checkboxes based on settings.
*/
function handleCheckBoxes() {
const recBox = document.getElementById('bookmark_rec');
if (settings.checkRecBox && recBox) {
console.log('Checking rec box.');
recBox.checked = true;
}
const privateBox = document.getElementById('bookmark_private');
if (settings.checkPrivateBox && privateBox) {
console.log('Checking private box.');
privateBox.checked = true;
}
}