// ==UserScript==
// @name AO3 Match Preference
// @version 1.0.1
// @description See how likely you are to enjoy a story based on your preferred tags.
// @namespace https://greasyfork.org/en/users/1353885-akira123
// @author Akira123
// @match http*://archiveofourown.org/*
// @license MIT
// @grant none
// ==/UserScript==
(function() {
'use strict'
// SETTINGS
// ====================
// Keywords are case insensitive. If a tag contains equal amounts of like and dislike keywords (e.g. Fluff and Angst), it will be considered neutral.
const likes = {
fandoms: ['Mashle'],
warnings: [],
relationships: ['Lance Crown/Mash Burnedead'],
characters: ['Lance Crown', 'Mash Burnedead'],
freeforms: ['Fluff', 'Slow Burn']
}
const dislikes = {
fandoms: [],
warnings: ['Major Character Death'],
relationships: ['Finn Ames/Carpaccio Luo-Yang'],
characters: ['Carpaccio Luo-Yang'],
freeforms: ['Angst', 'Alpha/Beta/Omega Dynamics', 'Violence']
}
const highlightTags = {
likes: true,
neutrals: true,
dislikes: true
}
const highlightMatch = true
/*
Default:
const green = 'rgba(139, 195, 74, .5)'
const yellow = 'rgba(255, 215, 0, .5)'
const red = 'rgba(255, 111, 111, .5)'
*/
const green = 'rgba(139, 195, 74, .5)'
const yellow = 'rgba(255, 215, 0, .5)'
const red = 'rgba(255, 111, 111, .5)'
/*
Match >= likePercentage will have green highlight
Match >= neutralPercentage and < likePercentage will have yellow highlight
Match < neutralPercentage will have red highlight
Default:
const likePercentage = 60
const neutralPercentage = 30
*/
const likePercentage = 60
const neutralPercentage = 30
// ====================
// END SETTINGS
function calculateMatch(tagElements) {
const storyScores = {
likes: 0,
neutrals: 0,
dislikes: 0
}
for (const [type, typeElements] of Object.entries(tagElements)) {
typeElements.forEach(typeElement => {
const tag = typeElement.textContent.toLowerCase()
let tagLikes = 0, tagDislikes = 0
likes[type].forEach(like => {
if (tag.includes(like.toLowerCase())) tagLikes++
})
dislikes[type].forEach(dislike => {
if (tag.includes(dislike.toLowerCase())) tagDislikes++
})
if (tagLikes === 0 && tagDislikes === 0) return
analyzeTag(tagLikes, tagDislikes, storyScores, typeElement)
})
}
const storyTotal = storyScores.likes + storyScores.neutrals + storyScores.dislikes
if (storyTotal === 0) return -1
return Math.floor((storyScores.likes + storyScores.neutrals / 2) / storyTotal * 100)
}
function analyzeTag(tagLikes, tagDislikes, storyScores, typeElement) {
if (tagLikes > tagDislikes) {
storyScores.likes++
if (highlightTags.likes) typeElement.style.backgroundColor = green
} else if (tagLikes === tagDislikes) {
storyScores.neutrals++
if (highlightTags.neutrals) typeElement.style.backgroundColor = yellow
} else {
storyScores.dislikes++
if (highlightTags.dislikes) typeElement.style.backgroundColor = red
}
}
function addMatch(storyTagElements, stats) {
const match = calculateMatch(storyTagElements)
const matchDt = document.createElement('dt')
const matchDd = document.createElement('dd')
matchDt.textContent = 'Match:'
matchDd.textContent = `${match === -1 ? 'N/A' : `${match}%`}`
if (highlightMatch) {
if (match >= likePercentage) {
matchDd.style.backgroundColor = green
} else if (match >= neutralPercentage) {
matchDd.style.backgroundColor = yellow
} else if (match >= 0) {
matchDd.style.backgroundColor = red
}
}
stats.appendChild(matchDt)
stats.appendChild(matchDd)
}
if (window.location.href.match(/works\/\d+/)) {
const storyTagElements = {
fandoms: document.querySelectorAll('.meta .fandom a'),
warnings: document.querySelectorAll('.meta .warning a'),
relationships: document.querySelectorAll('.meta .relationship a'),
characters: document.querySelectorAll('.meta .character a'),
freeforms: document.querySelectorAll('.meta .freeform a')
}
const stats = document.querySelector('.meta dl.stats')
addMatch(storyTagElements, stats)
} else {
const storyElements = document.querySelectorAll('.blurb')
storyElements.forEach(storyElement => {
const storyId = storyElement.getAttribute('id')
const storyTagElements = {
fandoms: document.querySelectorAll(`#${storyId} .fandoms a`),
warnings: document.querySelectorAll(`#${storyId} .warnings a`),
relationships: document.querySelectorAll(`#${storyId} .relationships a`),
characters: document.querySelectorAll(`#${storyId} .characters a`),
freeforms: document.querySelectorAll(`#${storyId} .freeforms a`)
}
const stats = document.querySelector(`#${storyId} .stats`)
addMatch(storyTagElements, stats)
})
}
})()