Skip to content

Instantly share code, notes, and snippets.

@BrickGrass
Last active May 28, 2022
Embed
What would you like to do?

AO3 Bookmark wordcount autofill

This is a simple little tampermonkey/greasemonkey extension that autofills a wordcount tag whenever you open a bookmark form on ao3. It doesn't work on the dedicated bookmark edit page (eg: https://archiveofourown.org/bookmarks/<bookmark_id>/edit) since the wordcount cannot be found in the html there.

Install by clicking this https://gist.github.com/BrickGrass/6ddece6536cb1b0558cf6f4d8eeb2ad9/raw/bc52418652a0ea8c738fe86b3b50816b1e2b0305/wordcount_autofill.pub.user.js

// ==UserScript==
// @name AO3 Bookmark wordcount autofill
// @namespace https://brickgrass.uk
// @version 0.2
// @description Automatically add wordcount tags to the ao3 bookmark form whenever it is opened.
// @author BrickGrass
// @include https://archiveofourown.org/*
// @require http://code.jquery.com/jquery-3.6.0.min.js
// @grant none
// ==/UserScript==
// following two are similar, check first *then* second if failed first
const work_bookmarks_url = /https:\/\/archiveofourown\.org\/works\/\d+\/bookmarks/;
const work_url = /https:\/\/archiveofourown\.org\/works\/\d+/;
const series_url = /https:\/\/archiveofourown\.org\/series\/\d+/;
const bookmark_form_url = /https:\/\/archiveofourown\.org\/bookmarks\/\d+\/edit/;
const num_separators = /[\.,]/;
const wordcount_tags = {
"0-99": "Wordcount: 0-100",
"100-999": "Wordcount: 100-1.000",
"1000-4999": "Wordcount: 1.000-5.000",
"5000-9999": "Wordcount: 5.000-10.000",
"10000-29999": "Wordcount: 10.000-30.000",
"30000-49999": "Wordcount: 30.000-50.000",
"50000-99999": "Wordcount: 50.000-100.000",
"100000-149999": "Wordcount: 100.000-150.000",
"150000-9999999999999": "Wordcount: Over 150.000"
}
function autopopulate_wordcount() {
if (this === window) {
return; // Don't know why, but the id selector also returns window?
}
let dds = $(this).find("fieldset > fieldset > dl > dd");
let tag_dd = dds[1];
let tag_list = $(tag_dd).children("ul");
let tag_input = $(this).find("[id=bookmark_tag_string_autocomplete]");
tag_input = tag_input[0]; // get actual DOM node
// what page is this? can we find the wordcount?
let href = window.location.href;
let wordcount = null;
if (!href.match(work_bookmarks_url) && href.match(work_url)) {
// Work page, need to look in slightly different place for wordcount
wordcount = $("#main div.wrapper dd.stats dd.words");
} else if (href.match(series_url)) {
// Series page, need to look in slightly different place for wordcount
wordcount = $("#main div.wrapper dd.stats dd").first();
} else if (href.match(bookmark_form_url)) {
// Dedicated bookmark edit page, wordcount not accessable
return;
} else {
// All other pages have the bookmark form nested within a bookmark article
let bookmark_article = $(this).closest("li.bookmark[role=article]");
wordcount = $(bookmark_article).find("dl.stats > dd.words");
// Series listings have wordcount laid out differently
if (wordcount.length === 0) {
wordcount = $(bookmark_article).find("dl.stats > dd").first();
}
}
wordcount = wordcount.text();
wordcount = wordcount.replace(num_separators, "");
wordcount = parseInt(wordcount);
let tag = "";
for (const [range, tag_str] of Object.entries(wordcount_tags)) {
let [ low, high ] = range.split("-");
[ low, high ] = [ parseInt(low), parseInt(high) ];
if (low <= wordcount && wordcount <= high) {
tag = tag_str;
break;
}
}
// adding tag spoofing from: https://github.com/LazyCats-dev/ao3-podfic-posting-helper/blob/main/src/inject.js
const event = new InputEvent('input', {bubbles: true, data: tag});
tag_input.value = tag;
// Replicates the value changing.
tag_input.dispatchEvent(event);
// Replicates the user hitting comma.
tag_input.dispatchEvent(new KeyboardEvent('keydown', {'key': ','}));
}
function set_context(j_node) {
autopopulate_wordcount.call(j_node);
}
function waitForKeyElements (
selectorTxt, /* Required: The jQuery selector string that
specifies the desired element(s).
*/
actionFunction, /* Required: The code to run when elements are
found. It is passed a jNode to the matched
element.
*/
bWaitOnce, /* Optional: If false, will continue to scan for
new elements even after the first match is
found.
*/
iframeSelector /* Optional: If set, identifies the iframe to
search.
*/
) {
var targetNodes, btargetsFound;
if (typeof iframeSelector == "undefined")
targetNodes = $(selectorTxt);
else
targetNodes = $(iframeSelector).contents()
.find(selectorTxt);
if (targetNodes && targetNodes.length > 0) {
btargetsFound = true;
/*--- Found target node(s). Go through each and act if they
are new.
*/
targetNodes.each ( function () {
var jThis = $(this);
var alreadyFound = jThis.data ('alreadyFound') || false;
if (!alreadyFound) {
//--- Call the payload function.
var cancelFound = actionFunction (jThis);
if (cancelFound)
btargetsFound = false;
else
jThis.data ('alreadyFound', true);
}
} );
}
else {
btargetsFound = false;
}
//--- Get the timer-control variable for this selector.
var controlObj = waitForKeyElements.controlObj || {};
var controlKey = selectorTxt.replace (/[^\w]/g, "_");
var timeControl = controlObj [controlKey];
//--- Now set or clear the timer as appropriate.
if (btargetsFound && bWaitOnce && timeControl) {
//--- The only condition where we need to clear the timer.
clearInterval (timeControl);
delete controlObj [controlKey]
}
else {
//--- Set a timer, if needed.
if ( ! timeControl) {
timeControl = setInterval ( function () {
waitForKeyElements (selectorTxt,
actionFunction,
bWaitOnce,
iframeSelector
);
},
300
);
controlObj [controlKey] = timeControl;
}
}
waitForKeyElements.controlObj = controlObj;
}
// $("[id='idofelement']") due to multiple elements with same id being possible on ao3 bookmark page
waitForKeyElements("[id='bookmark-form']", set_context, false /*false = continue searching after first*/);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment