Skip to content

Instantly share code, notes, and snippets.

@DontTalkToMeThx
Last active April 20, 2024 20:01
Show Gist options
  • Save DontTalkToMeThx/00b47382b7ddc54e01c58a23ef4b7d15 to your computer and use it in GitHub Desktop.
Save DontTalkToMeThx/00b47382b7ddc54e01c58a23ef4b7d15 to your computer and use it in GitHub Desktop.
// ==UserScript==
// @name e621 Set and Pool Favoriter
// @version 0.2
// @description Lets you favorite pools and sets.
// @author DefinitelyNotAFurry4
// @match https://e621.net/post_sets*
// @match https://e621.net/pools*
// @icon https://www.google.com/s2/favicons?sz=64&domain=e621.net
// @grant GM_addElement
// @grant GM_setValue
// @grant GM_getValue
// @run-at document-end
// ==/UserScript==
const POOLS_URL = "https://e621.net/pools.json?limit=100&search[id]="
const SETS_URL = "https://e621.net/post_sets.json?limit=100&search[id]="
// Ruby's time_ago_in_words in javascript
// https://gist.github.com/deadkarma/1989808
const DateHelper = {
// Takes a timestamp and converts it to a relative time
// DateHelper.timeAgoInWords(1331079503000)
timeAgoInWords: function (from) {
return this.distanceOfTimeInWords(new Date, from);
},
distanceOfTimeInWords: function (to, from) {
var distanceInSeconds = ((to - from) / 1000);
var distanceInMinutes = Math.floor(distanceInSeconds / 60);
var tense = distanceInSeconds < 0 ? " from now" : " ago";
distanceInMinutes = Math.abs(distanceInMinutes);
if (distanceInMinutes == 0) { return 'less than a minute' + tense; }
if (distanceInMinutes == 1) { return 'a minute' + tense; }
if (distanceInMinutes < 45) { return distanceInMinutes + ' minutes' + tense; }
if (distanceInMinutes < 90) { return 'an hour' + tense; }
if (distanceInMinutes < 1440) { return Math.floor(distanceInMinutes / 60) + ' hours' + tense; }
if (distanceInMinutes < 2880) { return 'a day' + tense; }
if (distanceInMinutes < 43200) { return Math.floor(distanceInMinutes / 1440) + ' days' + tense; }
if (distanceInMinutes < 86400) { return 'a month' + tense; }
if (distanceInMinutes < 525960) { return Math.floor(distanceInMinutes / 43200) + ' months' + tense; }
if (distanceInMinutes < 1051199) { return 'a year' + tense; }
return 'over ' + Math.floor(distanceInMinutes / 525960) + ' years';
}
};
/**
* Fetches data for favorited content.
* @param {Array} favoriteIds The favorite id array.
* @param {boolean} isPools Whether or not the data being fetched is pools or sets.
* @param {boolean} raw Whether or not to return raw data
* @returns An array of objects, where the object reprensts a pool or set
*/
async function fetchData(favoriteIds, isPools, raw = false) {
// The max you can get by id seems to be 100, so we need to request in batches of 100.
let data = []
for (let i = 0; i < favoriteIds.length / 100; i++) {
let idSlice = favoriteIds.slice(i * 100, i * 100 + 100)
let res = await fetch((isPools ? POOLS_URL : SETS_URL) + idSlice.join(","))
if (res.ok) data.push(...(await res.json()))
else {
console.error(await res.text())
alert("Unable to load favorites")
return null
}
}
// Preserve favorite order
let realData = []
for (let id of favoriteIds) {
let d = data.find(f => f.id == id)
if (!raw) {
let easyData = []
if (isPools) {
easyData.push("", { html: `<a href="/pools/${d.id}">${d.name.replaceAll("_", " ")}</a>` }, d.post_count)
} else {
easyData.push({ html: `<a href="/post_sets/${d.id}">${d.name}</a>` }, { html: `<a href="/posts?tags=set:${d.shortname}">${d.shortname}</a>` }, { html: `<a href="https://e621.new/users/${d.creator_id}">https://e621.new/users/${d.creator_id}</a>` }, d.post_count, { isDate: true, value: d.created_at }, { isDate: true, value: d.updated_at }, d.is_public ? "Public" : "Private")
}
realData.push(easyData)
} else {
realData.push(d)
}
}
return realData
}
async function getFavorites(isPools) {
return isPools ? await GM_getValue("favoritePools", []) : await GM_getValue("favoriteSets", [])
}
async function getHideShowButton(table, showing) {
let button = document.createElement("button")
button.innerText = showing ? "Hide" : "Show"
if (showing) button.classList.add("button", "btn-danger")
else button.classList.add("button", "btn-success")
button.addEventListener("click", () => {
if (showing) {
showing = false
button.classList.remove("btn-danger")
button.classList.add("btn-success")
} else {
showing = true
button.classList.remove("btn-success")
button.classList.add("btn-danger")
}
GM_setValue("showingFavorites", showing)
button.innerText = showing ? "Hide" : "Show"
table.style.display = showing ? null : "none"
})
return button
}
async function showFavorites(isPools, afterElement) {
let title = document.createElement("h2")
title.innerText = "Favorites"
afterElement.after(title)
let loading = document.createElement("h3")
loading.innerText = "Loading"
loading.style.marginBottom = "3em"
title.after(loading)
let favoriteIds = await getFavorites(isPools)
if (favoriteIds.length > 0) {
let favortieData = await fetchData(favoriteIds, isPools)
let table = document.createElement("table")
table.style.marginBottom = "3em"
table.classList.add("striped")
if (isPools) {
let tHead = document.createElement("thead")
let tr = document.createElement("tr")
tHead.appendChild(tr)
let padding = document.createElement("th")
padding.setAttribute("width", "5%")
tr.appendChild(padding)
let name = document.createElement("th")
name.innerText = "Name"
name.setAttribute("width", "60%")
tr.appendChild(name)
let count = document.createElement("th")
count.innerText = "Count"
count.setAttribute("width", "10%")
tr.appendChild(count)
table.appendChild(tHead)
} else {
let tHead = document.createElement("thead")
let tr = document.createElement("tr")
tHead.appendChild(tr)
let name = document.createElement("th")
name.innerText = "Name"
name.setAttribute("width", "30%")
tr.appendChild(name)
let shortName = document.createElement("th")
shortName.innerText = "Short Name"
shortName.setAttribute("width", "20%")
tr.appendChild(shortName)
let creator = document.createElement("th")
creator.innerText = "Creator"
creator.setAttribute("width", "15%")
tr.appendChild(creator)
let posts = document.createElement("th")
posts.innerText = "Posts"
posts.setAttribute("width", "5%")
tr.appendChild(posts)
let created = document.createElement("th")
created.innerText = "Created"
created.setAttribute("width", "10%")
tr.appendChild(created)
let updated = document.createElement("th")
updated.innerText = "Updated"
updated.setAttribute("width", "10%")
tr.appendChild(updated)
let status = document.createElement("th")
status.innerText = "Status"
status.setAttribute("width", "10%")
tr.appendChild(status)
table.appendChild(tHead)
}
let tBody = document.createElement("tbody")
table.appendChild(tBody)
for (let data of favortieData) {
let tr = document.createElement("tr")
for (let d of data) {
let td = document.createElement("td")
if (d.html) td.innerHTML = d.html
else if (d.isDate) {
let date = new Date(d.value)
let timeAgo = DateHelper.timeAgoInWords(date)
let time = document.createElement("time")
time.dateTime = date.toISOString()
time.title = date.toString()
time.innerText = timeAgo
td.appendChild(time)
} else {
td.innerText = d
}
tr.appendChild(td)
}
tBody.appendChild(tr)
}
let showing = await GM_getValue("showingFavorites", true)
if (!showing) table.style.display = "none"
loading.remove()
title.after(table)
title.after(await getHideShowButton(table, showing))
} else {
loading.remove()
let emptyNode = document.createElement("h3")
emptyNode.style.marginBottom = "3em"
emptyNode.innerText = "Empty"
title.after(emptyNode)
}
}
async function showPoolGalleryFavorites(afterElement) {
// let title = document.createElement("h2")
// title.innerText = "Favorites"
// afterElement.after(title)
// let loading = document.createElement("h3")
// loading.innerText = "Loading"
// loading.style.marginBottom = "3em"
// title.after(loading)
// let favoriteIds = await getFavorites(true)
}
async function addFavorite(id, isPools) {
let favoriteIds = await getFavorites(isPools)
favoriteIds.push(id)
GM_setValue(isPools ? "favoritePools" : "favoriteSets", favoriteIds)
}
async function removeFavorite(id, isPools) {
let favoriteIds = await getFavorites(isPools)
let index = favoriteIds.indexOf(id)
if (index != -1) {
favoriteIds.splice(index, 1)
GM_setValue(isPools ? "favoritePools" : "favoriteSets", favoriteIds)
}
}
async function addButtons(id, isPools) {
let favoriteIds = await getFavorites(isPools)
let isFavorited = favoriteIds.includes(id)
let after = isPools ? document.getElementById("description") : document.getElementsByClassName("set-description-bottom")[0]
let favoriteButton = document.createElement("button")
favoriteButton.innerText = isFavorited ? "Unfavorite" : "Favorite"
if (isFavorited) favoriteButton.classList.add("button", "btn-danger")
else favoriteButton.classList.add("button", "btn-success")
favoriteButton.addEventListener("click", () => {
if (isFavorited) {
isFavorited = false
removeFavorite(id, isPools)
favoriteButton.classList.remove("btn-danger")
favoriteButton.classList.add("btn-success")
} else {
isFavorited = true
addFavorite(id, isPools)
favoriteButton.classList.remove("btn-success")
favoriteButton.classList.add("btn-danger")
}
favoriteButton.innerText = isFavorited ? "Unfavorite" : "Favorite"
})
after.after(favoriteButton)
}
(async function () {
'use strict';
let isPools = window.location.pathname.startsWith("/pools")
if (window.location.pathname == "/post_sets" || window.location.pathname == "/pools") {
let searchForm = document.getElementById("searchform")
showFavorites(isPools, searchForm)
} else if (window.location.pathname == "/pools/gallery") {
// Unfortunately this isn't really possible without massive load times.
// let searchForm = document.getElementById("searchform")
// showPoolGalleryFavorites(searchForm.nextElementSibling)
} else {
let id = parseInt(window.location.pathname.split("/").at(-1))
if (isNaN(id)) return
addButtons(id, isPools)
}
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment