This only applies to the auto fetch tag version, the old version won't be updated:
-
-
Save DontTalkToMeThx/74a4a271e19cbe14195cd2c6671736ba to your computer and use it in GitHub Desktop.
e621 Tag Helper
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ==UserScript== | |
// @name e621 Tag Helper | |
// @version 0.2 | |
// @description Provies a useful UI for tagging common things on e621 | |
// @author DefinitelyNotAFurry4 | |
// @match https://e621.net/uploads/new | |
// @icon https://www.google.com/s2/favicons?sz=64&domain=e621.net | |
// @grant GM_addElement | |
// @grant GM_addStyle | |
// @grant GM.xmlHttpRequest | |
// @connect e621.net | |
// @run-at document-end | |
// ==/UserScript== | |
/* | |
This allows the definition of starting tags for a tag, these are added regardless of whether they are implied or not | |
ex: | |
let manualOverrides = { | |
animal_penis: ["some","tags","you","want","to","start","with"] | |
} | |
*/ | |
let manualOverrides = {} | |
/* | |
This allows the definition of replacement tags under a parent: | |
ex: | |
let replacements = { | |
animal_penis: { // only affects when the parent is animal_penis | |
anteater_penis: "whatever_you_want_to_replace_anteater_penis_with" | |
} | |
} | |
*/ | |
let replacements = {} | |
let lastRequestTime = 0 | |
function wait(ms) { return new Promise(r => setTimeout(r, ms)) } | |
let queued = 0 | |
async function fetchAllImplicationsTo(tag) { | |
let tags = manualOverrides[tag] || [] | |
let waitTime = 500 - (Date.now() - lastRequestTime) | |
if (waitTime > 0) await wait(waitTime * ++queued) | |
lastRequestTime = Date.now() | |
return new Promise(resolve => { | |
GM.xmlHttpRequest({ | |
method: "GET", | |
url: `https://e621.net/tag_implications.json?search%5Bconsequent_name%5D=${tag}`, | |
onload: async (response) => { | |
queued-- | |
let data = JSON.parse(response.responseText) | |
if (data.tag_implications) return resolve(tags) | |
for (let implication of data) { | |
if (implication.status == "active") { | |
if (!replacements[tag] || replacements[tag][implication.antecedent_name] === undefined) tags.push(implication.antecedent_name) | |
else if (replacements[tag][implication.antecedent_name]) tags.push(replacements[tag][implication.antecedent_name]) | |
} | |
} | |
resolve(tags.sort((a, b) => { | |
let startingDigitsA = a.match(/^\d+/) | |
let startingDigitsB = b.match(/^\d+/) | |
if (startingDigitsA && startingDigitsB) { | |
return parseInt(startingDigitsA[0]) - parseInt(startingDigitsB[0]) | |
} | |
return a.localeCompare(b) | |
})) | |
} | |
}) | |
}) | |
} | |
function toTitle(text) { | |
return text.toLowerCase().split("_").map(s => s.charAt(0).toUpperCase() + s.substring(1)).join(" ") | |
} | |
function removeFromText(text, toRemove) { | |
return text.split(" ").filter(t => t != toRemove).join(" ") | |
} | |
function dispatchEvents(target) { | |
target.dispatchEvent(new Event("input")) | |
} | |
async function buildGroup(group, parentTag, parent = null, hr = null) { | |
if (group[0] === true) group = await fetchAllImplicationsTo(parentTag) | |
if (group.length == 0) return [] | |
let nodes = [] | |
let topDiv = parent | |
if (!topDiv) { | |
let hr = document.createElement("hr") | |
hr.classList.add("hidden") | |
nodes.push(hr) | |
topDiv = document.createElement("div") | |
topDiv.classList.add("flex-wrap", "hidden") | |
nodes.push(topDiv) | |
} else { | |
nodes.push(hr) | |
nodes.push(topDiv) | |
} | |
let postTags = document.getElementById("post_tags") | |
for (let i = 0; i < group.length; i++) { | |
let nextTag = group[i] | |
let button = document.createElement("button") | |
button.classList.add("toggle-button") | |
button.setAttribute("deta-tag", nextTag) | |
button.innerText = toTitle(nextTag) | |
topDiv.appendChild(button) | |
let possibleHr = document.createElement("hr") | |
possibleHr.classList.add("hidden") | |
let possibleDiv = document.createElement("div") | |
possibleDiv.id = `${nextTag}-button-container` | |
possibleDiv.classList.add("flex-wrap", "hidden") | |
let fetchedChildren = false | |
let hasChildren = false | |
button.addEventListener("click", async (e) => { | |
e.preventDefault() | |
button.classList.toggle("active") | |
if (button.classList.contains("active")) { | |
postTags.value += ` ${nextTag}` | |
postTags.value = postTags.value.trim() | |
} else { | |
if (hasChildren) { | |
for (let child of possibleDiv.children) { | |
if (child.classList.contains("active")) { | |
child.click() | |
} | |
} | |
} | |
postTags.value = removeFromText(postTags.value, nextTag) | |
} | |
if (!fetchedChildren) { | |
let implications = await fetchAllImplicationsTo(nextTag) | |
if (implications.length > 0) { | |
hasChildren = true | |
let nodes = (await buildGroup(implications, nextTag, possibleDiv, possibleHr)).reverse() | |
for (let node of nodes) { | |
let nextTagDiv = topDiv.nextSibling | |
if (i != 0) { | |
nextTagDiv = document.getElementById(`${group[i - 1]}-button-container`) | |
let x = i - 1 | |
while (nextTagDiv == null && x > 0) { | |
nextTagDiv = document.getElementById(`${group[--x]}-button-container`) | |
} | |
if (!nextTagDiv) nextTagDiv = topDiv.nextSibling | |
else nextTagDiv = nextTagDiv.nextSibling | |
} | |
topDiv.parentElement.insertBefore(node, nextTagDiv) | |
} | |
} | |
fetchedChildren = true | |
} | |
if (hasChildren) { | |
possibleHr.classList.toggle("hidden") | |
possibleDiv.classList.toggle("hidden") | |
} | |
dispatchEvents(postTags) | |
}) | |
} | |
return nodes | |
} | |
async function buildButtons(startingPoint) { | |
let nodes = [] | |
let topDiv = document.createElement("div") | |
topDiv.classList.add("flex-wrap") | |
nodes.push(topDiv) | |
let postTags = document.getElementById("post_tags") | |
for (let group of startingPoint) { | |
let button = document.createElement("button") | |
button.classList.add("toggle-button") | |
button.setAttribute("deta-tag", group[0]) | |
button.innerText = toTitle(group[0]) | |
topDiv.appendChild(button) | |
let nodeGroup = await buildGroup(group[1], group[0]) | |
button.addEventListener("click", (e) => { | |
e.preventDefault() | |
if (nodeGroup.length > 0) { | |
nodeGroup[0].classList.toggle("hidden") | |
nodeGroup[1].classList.toggle("hidden") | |
} | |
button.classList.toggle("active") | |
if (!button.classList.contains("active")) { | |
postTags.value = removeFromText(postTags.value, group[0]) | |
for (let node of nodeGroup) { | |
for (let child of node.children) { | |
if (child.classList.contains("active")) child.click() | |
} | |
} | |
} else { | |
postTags.value += ` ${group[0]}` | |
postTags.value = postTags.value.trim() | |
} | |
dispatchEvents(postTags) | |
}) | |
nodes.push(...nodeGroup) | |
} | |
return nodes | |
} | |
async function buildGrid(options) { | |
let grid = document.createElement("div") | |
grid.classList.add("flex-grid", "border-bottom") | |
let left = document.createElement("div") | |
left.classList.add("col") | |
grid.appendChild(left) | |
let label = document.createElement("label") | |
label.classList.add("section-label") | |
label.innerText = options.title//"Common Explicit Tags" | |
left.appendChild(label) | |
let explanationDiv = document.createElement("div") | |
explanationDiv.innerText = options.description//"What common explicit tags are present in the image, if any?" | |
left.appendChild(explanationDiv) | |
let right = document.createElement("div") | |
right.classList.add("col2") | |
grid.appendChild(right) | |
right.append(...(await buildButtons(options.startingPoint))) | |
return { grid, options } | |
} | |
function toggleGrids(grids, activeType) { | |
for (let data of grids) { | |
if (data.options.ratingTypes.includes(activeType)) { | |
data.grid.classList.remove("hidden") | |
} else { | |
data.grid.classList.add("hidden") | |
for (let button of data.grid.getElementsByClassName("toggle-button")) { | |
if (button.classList.contains("active")) button.click() | |
} | |
} | |
} | |
} | |
(async function () { | |
'use strict' | |
GM_addStyle(".hidden { display:none !important; }") | |
/* | |
If you want to add your own, here's a template of what to add to the array: | |
await buildGrid( | |
{ | |
title: "This is what shows in bold on the left", | |
description: "This shows under the title", | |
startingPoint: [ | |
["these", ["these"]], | |
["are", ["are"]], | |
["top", ["second-level"]], | |
["tags", ["tags"]], // Tags after the second level are automatically fetched by implication, | |
// if you don't want any second-level tags, use an empty array: `[]` | |
// Alternatively, you can use `["tag", [true]],` which will auto fetch implications in place of the tags, notice it's still in an array. | |
], | |
ratingTypes: ["explicit", "questionable", "safe"] // Remove any that don't apply to the tags | |
} | |
), | |
If the above was added, the array would look like this: | |
let grids = [ | |
await buildGrid( | |
{ | |
title: "Media Tags", | |
description: "What kind of media is this?", | |
startingPoint: [ | |
["digital_media_(artwork)", [true]], | |
["photography_(artwork)", [true]], | |
["traditional_media_(artwork)", [true]] | |
], | |
ratingTypes: ["explicit", "questionable", "safe"] | |
} | |
), | |
await buildGrid( | |
{ | |
title: "Common Explicit Tags", | |
description: "What common explicit tags are present in the image, if any?", | |
startingPoint: [ | |
["anus", []], | |
["cloaca", ["horizontal_cloaca", "round_cloaca", "triangular_cloaca", "vertical_cloaca"]], | |
["genital_slit", []], | |
["penis", ["animal_penis", "humanoid_penis", "hybrid_penis", "mechanical_penis", "multi_penis", "prehensile_penis", "unusual_penis", "vacuum_penis"]], | |
["pussy", ["animal_pussy", "humanoid_pussy", "hybrid_pussy", "mechanical_pussy", "unusual_pussy"]] | |
], | |
ratingTypes: ["explicit"] | |
} | |
), | |
await buildGrid( | |
{ | |
title: "This is what shows in bold on the left", | |
description: "This shows under the title", | |
startingPoint: [ | |
["these", ["these"]], | |
["are", ["are"]], | |
["top", ["second-level"]], | |
["tags", ["tags"]], | |
], | |
ratingTypes: ["explicit", "questionable", "safe"] | |
} | |
), | |
] | |
*/ | |
let grids = [ | |
await buildGrid( | |
{ | |
title: "Media Tags", | |
description: "What kind of media is this?", | |
startingPoint: [ | |
["digital_media_(artwork)", [true]], | |
["mixed_media", [true]], | |
["photography_(artwork)", [true]], | |
["traditional_media_(artwork)", [true]] | |
], | |
ratingTypes: ["explicit", "questionable", "safe"] | |
} | |
), | |
await buildGrid( | |
{ | |
title: "Common Explicit Tags", | |
description: "What common explicit tags are present in the image, if any?", | |
startingPoint: [ | |
["anus", []], | |
["balls", ["glowing_balls", "lemon_testicles", "marsupial_balls", "multi_balls", "translucent_balls", "udder_balls", "uniball", "unusual_balls"]], | |
["clitoris", ["animal_clitoris", "multi_clitoris", "prehensile_clitoris", "tapering_clitoris"]], | |
["cloaca", ["horizontal_cloaca", "round_cloaca", "triangular_cloaca", "vertical_cloaca"]], | |
["genital_slit", []], | |
["penis", ["animal_penis", "humanoid_penis", "hybrid_penis", "mechanical_penis", "multi_penis", "prehensile_penis", "unusual_penis", "vacuum_penis"]], | |
["pussy", ["animal_pussy", "humanoid_pussy", "hybrid_pussy", "mechanical_pussy", "unusual_pussy"]], | |
["sheath", ["attached_sheath", "detached_sheath", "fully_sheathed", "glowing_sheath", "knot_in_sheath", "multi_sheath", "retracted_sheath", "sheathed_humanoid_penis", "unusual_sheath"]] | |
], | |
ratingTypes: ["explicit"] | |
} | |
), | |
] | |
let prevChild = null | |
for (let i = 0; i < grids.length; i++) { | |
let data = grids[i] | |
data.grid.classList.add("hidden") | |
let box = document.querySelector(".col.box-section") | |
box.insertBefore(data.grid, i == 0 ? box.children[8] : prevChild.nextSibling) | |
prevChild = data.grid | |
} | |
prevChild = null | |
let explicitRatingToggler = document.querySelector(".toggle-button.rating-e") | |
let questionableRatingToggler = document.querySelector(".toggle-button.rating-q") | |
let safeRatingToggler = document.querySelector(".toggle-button.rating-s") | |
explicitRatingToggler.addEventListener("click", () => { | |
toggleGrids(grids, "explicit") | |
}) | |
questionableRatingToggler.addEventListener("click", () => { | |
toggleGrids(grids, "questionable") | |
}) | |
safeRatingToggler.addEventListener("click", () => { | |
toggleGrids(grids, "safe") | |
}) | |
})() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ==UserScript== | |
// @name e621 Genitalia Tag Helper | |
// @version 0.1 | |
// @description Provies a useful UI for tagging genitals on e621 | |
// @author DefinitelyNotAFurry4 | |
// @match https://e621.net/uploads/new | |
// @icon https://www.google.com/s2/favicons?sz=64&domain=e621.net | |
// @grant GM_addElement | |
// @grant GM_addStyle | |
// @run-at document-end | |
// ==/UserScript== | |
/* Simple function to get the structure of the below array after you add the id... | |
let types = document.getElementById("types") | |
function recurse(topChild) { | |
let arr = [] | |
for (let child of topChild.children) { | |
if (child.tagName == "UL") { | |
arr[arr.length - 1].push(recurse(child)) | |
} else if (child.tagName == "LI") { | |
arr.push([child.innerText]) | |
} | |
} | |
return arr | |
} | |
console.log(recurse(types)) | |
*/ | |
let penises = [["animal_penis", [["avian_penis"], ["bovine_penis"], ["canine_penis"], ["caprine_penis"], ["cervine_penis"], ["tapering_penis", [["cetacean_penis"]]], ["crocodilian_penis"], ["echidna_penis"], ["equine_penis", [["knotted_equine_penis"]]], ["feline_penis"], ["fossa_penis"], ["lagomorph_penis"], ["marsupial_penis"], ["mustelid_penis"], ["porcine_penis"], ["raccoon_penis"], ["turtle_penis"], ["ursine_penis"]]], ["humanoid_penis"], ["hybrid_penis"], ["mechanical_penis"], ["unusual_penis"], ["vacuum_penis"]]; | |
let pussies = [["animal_pussy", [["bovine_pussy"], ["canine_pussy"], ["caprine_pussy"], ["cervine_pussy"], ["cephalopussy"], ["cetacean_pussy"], ["feline_pussy"], ["equine_pussy"], ["lagomorph_pussy"], ["marsupial_pussy"], ["mustelid_pussy"], ["porcine_pussy"], ["raccoon_pussy"], ["ursine_pussy"]]], ["humanoid_pussy"], ["hybrid_pussy"], ["mechanical_pussy"], ["unusual_pussy"]]; | |
function toTitle(text) { | |
return text.toLowerCase().split("_").map(s => s.charAt(0).toUpperCase() + s.substring(1)).join(" ") | |
} | |
function removeFromText(text, toRemove) { | |
return text.split(" ").filter(t => t != toRemove).join(" ") | |
} | |
function buildGroup(group) { | |
let nodes = [] | |
let hr = document.createElement("hr") | |
hr.classList.add("hidden") | |
nodes.push(hr) | |
let topDiv = document.createElement("div") | |
topDiv.classList.add("flex-wrap", "hidden") | |
nodes.push(topDiv) | |
for (let nextGroup of group) { | |
let postTags = document.getElementById("post_tags") | |
let button = document.createElement("button") | |
button.classList.add("toggle-button") | |
button.setAttribute("deta-tag", nextGroup[0]) | |
button.innerText = toTitle(nextGroup[0]) | |
topDiv.appendChild(button) | |
if (nextGroup.length == 2) { | |
let nextNodes = buildGroup(nextGroup[1]) | |
nodes.push(...nextNodes) | |
button.addEventListener("click", (e) => { | |
e.preventDefault() | |
let inputEvent = new Event("input") | |
nextNodes[0].classList.toggle("hidden") | |
nextNodes[1].classList.toggle("hidden") | |
button.classList.toggle("active") | |
if (!button.classList.contains("active")) { | |
postTags.value = removeFromText(postTags.value, nextGroup[0]) | |
for (let node of nextNodes) { | |
for (let child of node.children) { | |
if (child.classList.contains("active")) child.click() | |
} | |
} | |
} else { | |
postTags.value += ` ${nextGroup[0]}` | |
postTags.value = postTags.value.trim() | |
} | |
postTags.dispatchEvent(inputEvent) | |
}) | |
} else { | |
button.addEventListener("click", (e) => { | |
e.preventDefault() | |
let inputEvent = new Event("input") | |
button.classList.toggle("active") | |
if (button.classList.contains("active")) { | |
postTags.value += ` ${nextGroup[0]}` | |
postTags.value = postTags.value.trim() | |
} else { | |
postTags.value = removeFromText(postTags.value, nextGroup[0]) | |
} | |
postTags.dispatchEvent(inputEvent) | |
}) | |
} | |
} | |
return nodes | |
} | |
function buildButtons() { | |
let nodes = [] | |
let topDiv = document.createElement("div") | |
topDiv.classList.add("flex-wrap") | |
nodes.push(topDiv) | |
let penisButton = document.createElement("button") | |
penisButton.classList.add("toggle-button", "top-level-genital-toggle-button") | |
penisButton.setAttribute("deta-tag", "penis") | |
penisButton.innerText = "Penis" | |
topDiv.appendChild(penisButton) | |
let pussyButton = document.createElement("button") | |
pussyButton.classList.add("toggle-button", "top-level-genital-toggle-button") | |
pussyButton.setAttribute("deta-tag", "pussy") | |
pussyButton.innerText = "Pussy" | |
topDiv.appendChild(pussyButton) | |
let penisGroup = buildGroup(penises) | |
nodes.push(...penisGroup) | |
let postTags = document.getElementById("post_tags") | |
penisButton.addEventListener("click", (e) => { | |
e.preventDefault() | |
let inputEvent = new Event("input") | |
penisGroup[0].classList.toggle("hidden") | |
penisGroup[1].classList.toggle("hidden") | |
penisButton.classList.toggle("active") | |
if (!penisButton.classList.contains("active")) { | |
postTags.value = removeFromText(postTags.value, "penis") | |
for (let node of penisGroup) { | |
for (let child of node.children) { | |
if (child.classList.contains("active")) child.click() | |
} | |
} | |
} else { | |
postTags.value += " penis" | |
postTags.value = postTags.value.trim() | |
} | |
postTags.dispatchEvent(inputEvent) | |
}) | |
let pussyGroup = buildGroup(pussies) | |
nodes.push(...pussyGroup) | |
pussyButton.addEventListener("click", (e) => { | |
e.preventDefault() | |
let inputEvent = new Event("input") | |
pussyGroup[0].classList.toggle("hidden") | |
pussyGroup[1].classList.toggle("hidden") | |
pussyButton.classList.toggle("active") | |
if (!pussyButton.classList.contains("active")) { | |
postTags.value = removeFromText(postTags.value, "pussy") | |
for (let node of pussyGroup) { | |
for (let child of node.children) { | |
if (child.classList.contains("active")) child.click() | |
} | |
} | |
} else { | |
postTags.value += " pussy" | |
postTags.value = postTags.value.trim() | |
} | |
postTags.dispatchEvent(inputEvent) | |
}) | |
return nodes | |
} | |
function buildGrid() { | |
let genitaliaGrid = document.createElement("div") | |
genitaliaGrid.classList.add("flex-grid", "border-bottom") | |
let left = document.createElement("div") | |
left.classList.add("col") | |
genitaliaGrid.appendChild(left) | |
let label = document.createElement("label") | |
label.classList.add("section-label") | |
label.innerText = "Genitalia" | |
left.appendChild(label) | |
let explanationDiv = document.createElement("div") | |
explanationDiv.innerText = "What genitalia is present in the image, if any?" | |
left.appendChild(explanationDiv) | |
let right = document.createElement("div") | |
right.classList.add("col2") | |
genitaliaGrid.appendChild(right) | |
right.append(...buildButtons()) | |
return genitaliaGrid | |
} | |
(function () { | |
'use strict' | |
GM_addStyle(".hidden { display:none !important; }") | |
let grid = buildGrid() | |
grid.classList.add("hidden") | |
let box = document.querySelector(".col.box-section") | |
box.insertBefore(grid, box.children[8]) | |
let explicitRatingToggler = document.querySelector(".toggle-button.rating-e") | |
let questionableRatingToggler = document.querySelector(".toggle-button.rating-q") | |
let safeRatingToggler = document.querySelector(".toggle-button.rating-s") | |
explicitRatingToggler.addEventListener("click", () => { | |
grid.classList.remove("hidden") | |
}) | |
questionableRatingToggler.addEventListener("click", () => { | |
grid.classList.add("hidden") | |
for (let button of document.getElementsByClassName("top-level-genital-toggle-button")) { | |
if (button.classList.contains("active")) button.click() | |
} | |
}) | |
safeRatingToggler.addEventListener("click", () => { | |
grid.classList.add("hidden") | |
for (let button of document.getElementsByClassName("top-level-genital-toggle-button")) { | |
if (button.classList.contains("active")) button.click() | |
} | |
}) | |
})() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment