Some scripts to do some stuff on AO3. These are not very good, but it works good enough for me, so whatever.
Also for these you will need to open AO3, make sure you're logged in, open a specific page (the history page for example), and then open and run the script in the Developer Tools console.
Scrapes a bunch of data from works in your history. You need to be in the history page
scrapped = (await Promise.all(Array.apply(undefined, Array(+Array.from(document.querySelectorAll("ol.pagination>li")).slice(-2,-1)[0].textContent)).map((c,i) => new Promise((rs, rej) => fetch(`https://archiveofourown.org/users/${document.querySelector("a.dropdown-toggle").textContent.split(" ")[1].slice(0, -1)}/readings?page=${i+1}`).then(
(res) => res.text().then((text) => {
let aopage = document.createElement("div")
aopage.innerHTML = text
rs(Array.from(aopage.querySelectorAll("ol.reading.work.index.group > li")).map(e => {return {
id: e.id.split('_')[1],
title: Array.from(e.querySelectorAll("h4.heading > a")).map(e=>e.textContent)[0],
authors: Array.from(e.querySelectorAll('h4.heading > a[rel~="author"]')).map(el => {return {text: el.textContent.trim(), href: el.attributes.href.value}}),
language: Array.from(e.querySelectorAll("dl.stats > dd.language")).map(v => v.textContent)[0],
wordcount: +(Array.from(e.querySelectorAll("dl.stats > dd.words")).map(v => v.textContent)[0] || "").replace(",", ""),
chapters: (Array.from(e.querySelectorAll("dl.stats > dd.chapters")).map(v => v.textContent)[0] || "?/?").split("/"),
visited: e.querySelector("h4.viewed").innerText.trim().split(" ").at(-2) == "Visited" ? 1 : parseInt(e.querySelector("h4.viewed").innerText.trim().split(" ").at(-2)),
lastvisit: e.querySelector("h4.viewed").innerText,
rating: Array.from(e.querySelectorAll("span.rating")).map(v => v.classList[0])[0],
tags: {
warnings: Array.from(e.querySelectorAll("li.warnings")).map(e=>e.textContent),
relationships: Array.from(e.querySelectorAll("li.relationships")).map(e=>e.textContent),
characters: Array.from(e.querySelectorAll("li.characters")).map(e=>e.textContent),
freeforms: Array.from(e.querySelectorAll("li.freeforms")).map(e=>e.textContent)
},
fandom: Array.from(e.querySelectorAll("h5.fandoms.heading > a.tag")).map(e=>e.textContent),
updated: Array.from(e.querySelectorAll("p.datetime")).map(v => v.textContent)[0],
kudos: +Array.from(e.querySelectorAll("dd.kudos")).map(v => v.textContent)[0],
onpage: i + 1,
summary: Array.from(e.querySelectorAll("blockquote.userstuff.summary > p")).map(v => v.textContent)
}}))
})
))))).flat()
Filter works with updates available:
scrapped.filter(e => e.lastvisit.includes("Update"))
Sort by how many times you have re-read a work:
scrapped.sort((a,b) => (b.visited/+b.chapters[0])-(a.visited/+a.chapters[0]))
Counts all the tags from your history and sorts them:
// tags = scrapped.reduce((t,v) => {Object.values(v.tags).reduce((t,v) => t.concat(v),[]).forEach(v => t[v] = (t[v] || 0) + 1); return t},{}) // Combine all tags
tags = scrapped.reduce((t,v) => {v.tags.freeforms.forEach(v => t[v] = (t[v] || 0) + 1); return t},{}) // Freeform tags
tlist = Object.keys(tags).map((key) => [key, tags[key]])
tlist.sort((a,b) => b[1] - a[1])
Word Counter:
console.log("%c Words: " + Math.floor(scrapped.reduce((t, c) => t + (((+c.wordcount || 0) / (+c.chapters[0] || 1)) * (+c.visited || 1)), 0)), "font-size: 4rem")
Simple Filter and Sort Combination:
scrapped.filter(e=> +e.chapters[0] < 3 && e.kudos > 1000 && e.fandom.includes("The Owl House (Cartoon)")).sort((a,b) => b.kudos-a.kudos)
Render the results back to the page (also clears the original page)
document.body.innerHTML = ""
filtered.forEach(e=> document.body.innerHTML += `
<h2><a href="https://archiveofourown.org/works/${e.id}">${e.title}</a> by ${e.authors.map(author => `<a href="https://archiveofourown.org${author.href}"> ${author.text}</a>`).join(" ")}</h2>
<p><strong>(${e.fandom.join(", ")})</strong></p>
<p><strong>${e.tags.warnings.join(", ")}</strong></p>
<p>${e.tags.relationships.join(", ")}</p>
<p>${e.tags.characters.join(", ")}</p>
<p><span style="font-size:9px">${e.tags.freeforms.join(", ")}</span></p>
<blockquote>
<p>${e.summary.join("</br>")}</p>
</blockquote>
<p><strong>Kudos:</strong> ${e.kudos} | <strong>Chapters: </strong>${e.chapters.join("/")} | <strong>Words: </strong>${e.wordcount} | Updated: ${e.updated} | Visited: ${e.visited}x | Read Count: ${e.visited/+e.chapters[0]}</p>
<hr />
`)
Scrapes your history and estimates how many words you have read. You need to be in the history page.
I don't think Archive of Our Own has any Reading Statistics feature that can show how many words you have read. So this is useful to calculate an estimate of how many words you have read.
console.log("%c Words: " + Math.floor((await Promise.all(Array.apply(undefined, Array(+Array.from(document.querySelectorAll("ol.pagination>li")).slice(-2,-1)[0].textContent)).map((c,i) => new Promise((rs, rej) => fetch(`https://archiveofourown.org/users/${document.querySelector("a.dropdown-toggle").textContent.split(" ")[1].slice(0, -1)}/readings?page=${i+1}`).then(
(res) => res.text().then((text) => {
let aopage = document.createElement("div")
aopage.innerHTML = text
rs(Array.from(aopage.querySelectorAll("ol.reading.work.index.group > li")).map(e => {return {
wordcount: +(Array.from(e.querySelectorAll("dl.stats > dd.words")).map(v => v.textContent)[0] || "").replace(",", ""),
chapters: (Array.from(e.querySelectorAll("dl.stats > dd.chapters")).map(v => v.textContent)[0] || "?/?").split("/"),
visited: e.querySelector("h4.viewed").innerText.trim().split(" ").at(-2) == "Visited" ? 1 : parseInt(e.querySelector("h4.viewed").innerText.trim().split(" ").at(-2))
}}))
})
))))).flat().reduce((t, c) => t + (((+c.wordcount || 0) / (+c.chapters[0] || 1)) * (+c.visited || 1)), 0)), "font-size: 4rem")