mirror of
https://github.com/stashapp/CommunityScripts.git
synced 2026-02-04 10:49:10 -06:00
[userscripts][stashdb submission helper] Add userscripts section and stashdb submission helper userscript (#63)
This commit is contained in:
parent
466c4689a2
commit
ffbd8d81a3
@ -34,6 +34,12 @@ Theme Name|Description |
|
||||
|
||||
## Utility Scripts
|
||||
|
||||
|Category|Userscript Name|Description|
|
||||
---------|---------------|-----------|
|
||||
StashDB |[StashDB Submission Helper](/userscripts/StashDB_Submission_Helper)|Adds handy functions for StashDB submissions like buttons to add aliases in bulk to a performer|
|
||||
|
||||
## Utility Scripts
|
||||
|
||||
Category|Plugin Name|Description|Minimum Stash version
|
||||
--------|-----------|-----------|---------------------
|
||||
Kodi|[Kodi Helper](scripts/kodi-helper)|Generates `nfo` and `strm` for use with Kodi.|v0.7
|
||||
|
||||
32
userscripts/StashDB_Submission_Helper/README.md
Normal file
32
userscripts/StashDB_Submission_Helper/README.md
Normal file
@ -0,0 +1,32 @@
|
||||
# StashDB Submission Helper
|
||||
|
||||
- Adds button to add all unmatched aliases to performer
|
||||
- Adds button to add all unmatched urls to performer
|
||||
- Adds button to add all unmatched measurements to performer (if they match expected formats)
|
||||
- Convert unmatched urls from regular strings to linked strings
|
||||
|
||||
## [**INSTALL USERSCRIPT**](https://raw.githubusercontent.com/stashapp/CommunityScripts/main/userscripts/StashDB_Submission_Helper/stashdb_submission_helper.user.js)
|
||||
|
||||
Installation requires a browser extension such as [Violentmonkey](https://violentmonkey.github.io/) / [Tampermonkey](https://www.tampermonkey.net/) / [Greasemonkey](https://www.greasespot.net/).
|
||||
|
||||
### Screenshot
|
||||

|
||||
|
||||
### Demo GIF:
|
||||

|
||||
|
||||
## Changelog
|
||||
|
||||
### 0.7
|
||||
- Allow alias separator to also be `/` or ` or ` (space on either side of the or).
|
||||
- Allow measurements to be added without the cup size
|
||||
- Support full current list of sites for adding URLS (previously only IAFD, DATA18, Indexxx, and Twitter were supported because I forgot to add the others)
|
||||
|
||||
### 0.6
|
||||
- Add input field / button to performer edit pages to add a comma separated list of aliases to a performer
|
||||

|
||||
- Cleaned up code so that it doesn't run on non-performer drafts
|
||||
- Added performer add and edit pages to the pages it runs on (since alias function isn't just draft related)
|
||||
|
||||
### 0.5
|
||||
Public Release
|
||||
@ -0,0 +1,360 @@
|
||||
// ==UserScript==
|
||||
// @name StashDB Submission Helper
|
||||
// @author halorrr
|
||||
// @version 0.7
|
||||
// @description Adds button to add all unmatched aliases, measurements, and urls to a performer.
|
||||
// @icon https://raw.githubusercontent.com/stashapp/stash/develop/ui/v2.5/public/favicon.png
|
||||
// @namespace https://github.com/halorrr
|
||||
// @match https://stashdb.org/drafts/*
|
||||
// @match https://stashdb.org/performers/*/edit
|
||||
// @match https://stashdb.org/performers/add
|
||||
// @homepageURL https://github.com/stashapp/CommunityScripts/tree/main/userscripts/StashDB_Submission_Helper
|
||||
// @downloadURL https://raw.githubusercontent.com/stashapp/CommunityScripts/main/userscripts/StashDB_Submission_Helper/stashdb_submission_helper.user.js
|
||||
// @updateURL https://raw.githubusercontent.com/stashapp/CommunityScripts/main/userscripts/StashDB_Submission_Helper/stashdb_submission_helper.user.js
|
||||
// ==/UserScript==
|
||||
|
||||
function setNativeValue(element, value) {
|
||||
const valueSetter = Object.getOwnPropertyDescriptor(element, 'value')?.set;
|
||||
const prototype = Object.getPrototypeOf(element);
|
||||
const prototypeValueSetter = Object.getOwnPropertyDescriptor(prototype, 'value')?.set;
|
||||
|
||||
if (prototypeValueSetter && valueSetter !== prototypeValueSetter) {
|
||||
prototypeValueSetter.call(element, value);
|
||||
} else if (valueSetter) {
|
||||
valueSetter.call(element, value);
|
||||
} else {
|
||||
throw new Error('The given element does not have a value setter');
|
||||
};
|
||||
|
||||
const eventName = element instanceof HTMLSelectElement ? 'change' : 'input';
|
||||
element.dispatchEvent(new Event(eventName, { bubbles: true }));
|
||||
};
|
||||
|
||||
function waitForElm(selector) {
|
||||
return new Promise(resolve => {
|
||||
if (document.querySelector(selector)) {
|
||||
return resolve(document.querySelector(selector));
|
||||
};
|
||||
|
||||
const observer = new MutationObserver(mutations => {
|
||||
if (document.querySelector(selector)) {
|
||||
resolve(document.querySelector(selector));
|
||||
observer.disconnect();
|
||||
};
|
||||
});
|
||||
|
||||
observer.observe(document.body, {
|
||||
childList: true,
|
||||
subtree: true
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const aliasInputSelector = 'label[for="aliases"] + div input';
|
||||
|
||||
function unmatchedTargetElement(targetProperty) {
|
||||
var targetRegex = '//h6/following-sibling::ul/li[b[contains(text(), "' + targetProperty + '")]]/span/text()';
|
||||
var targetElement = document.evaluate(targetRegex, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
|
||||
return targetElement;
|
||||
};
|
||||
|
||||
function unmatchedTargetValue(targetProperty) {
|
||||
var targetElement = unmatchedTargetElement(targetProperty)
|
||||
if (targetElement == null) {
|
||||
return;
|
||||
}
|
||||
return targetElement.data;
|
||||
};
|
||||
|
||||
function unmatchedTargetButton(targetProperty) {
|
||||
var targetRegex = '//h6/following-sibling::ul/li[b[contains(text(), "' + targetProperty + '")]]';
|
||||
var targetElement = document.evaluate(targetRegex, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
|
||||
return targetElement;
|
||||
};
|
||||
|
||||
function wrapUrlTag(url) {
|
||||
return "<a href='" + url + "' target='_blank'>" + url + "</a>";
|
||||
};
|
||||
|
||||
function makeUrlLink(element) {
|
||||
const currentUrls = element.data.split(", ");
|
||||
|
||||
const wrappedUrls = currentUrls.map(url => {
|
||||
return wrapUrlTag(url);
|
||||
});
|
||||
|
||||
element.parentElement.innerHTML = wrappedUrls.join(", ");
|
||||
};
|
||||
|
||||
function formTab(tabName) {
|
||||
const tabRegex = '//ul[@role="tablist"]/li/button[contains(text(), "' + tabName + '")]';
|
||||
return document.evaluate(tabRegex, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
|
||||
};
|
||||
|
||||
function addAlias(alias) {
|
||||
alias = alias.trim()
|
||||
const existingAliases = Array.from(document.querySelectorAll('label[for="aliases"] + div .react-select__multi-value__label'));
|
||||
let aliasMatch = existingAliases.find(element => { return element.innerText == alias; });
|
||||
if (typeof aliasMatch !== 'undefined') {
|
||||
console.warn("Skipping alias '" + alias + "' as it is already added to this performer.");
|
||||
return;
|
||||
};
|
||||
const aliasInput = document.querySelector(aliasInputSelector);
|
||||
setNativeValue(aliasInput, alias);
|
||||
var addButton = document.querySelector('label[for="aliases"] + div .react-select__option');
|
||||
formTab("Personal Information").click();
|
||||
addButton.click();
|
||||
};
|
||||
|
||||
function existingUrlObjects() {
|
||||
const existingUrls = Array.from(document.querySelectorAll('.URLInput ul .input-group'));
|
||||
const urlObjects = existingUrls.map(urlGroup => {
|
||||
let site = urlGroup.childNodes[1].innerText;
|
||||
let url = urlGroup.childNodes[2].innerText;
|
||||
let urlObject = {
|
||||
site: site,
|
||||
url: url
|
||||
};
|
||||
return urlObject;
|
||||
});
|
||||
return urlObjects;
|
||||
};
|
||||
|
||||
function urlSite(url) {
|
||||
let site;
|
||||
if (/(^https?:\/\/(?:www\.)?adultfilmdatabase\.com\/(?:video|studio|actor)\/.+)\??/.test(url)) {
|
||||
site = 'AFDB'
|
||||
} else if (/(https?:\/\/www.babepedia.com\/babe\/[^?]+)\??/.test(url)) {
|
||||
site = 'Babepedia'
|
||||
} else if (/(^https?:\/\/(?:www\.)?bgafd\.co\.uk\/(?:films|actresses)\/details.php\/id\/[^?]+)\??/.test(url)) {
|
||||
site = 'BGAFD'
|
||||
} else if (/(https?:\/\/www.boobpedia.com\/boobs\/[^?]+)\??/.test(url)) {
|
||||
site = 'Boobpedia'
|
||||
} else if (/(https?:\/\/www.data18.com\/[^?]+)\??/.test(url)) {
|
||||
site = 'DATA18'
|
||||
} else if (/(^https?:\/\/(?:www\.)?egafd\.com\/(?:films|actresses)\/details.php\/id\/[^?]+)\??/.test(url)) {
|
||||
site = 'EGAFD'
|
||||
} else if (/(https?:\/\/(www\.)?eurobabeindex.com\/sbandoindex\/.*?.html)/.test(url)) {
|
||||
site = 'Eurobabeindex'
|
||||
} else if (/(^https?:\/\/(?:www.)?facebook\.com\/[^?]+)/.test(url)) {
|
||||
site = 'Facebook'
|
||||
} else if (/(https?:\/\/www.freeones.com\/[^/?]+)\??/.test(url)) {
|
||||
site = 'FreeOnes'
|
||||
} else if (/(https?:\/\/www.iafd.com\/[^?]+)\??/.test(url)) {
|
||||
site = 'IAFD'
|
||||
} else if (/(^https?:\/\/(?:www\.)?imdb\.com\/(?:name|title)\/[^?]+)\/?/.test(url)) {
|
||||
site = 'IMDB'
|
||||
} else if (/(https?:\/\/www.indexxx.com\/[^?]+)\??/.test(url)) {
|
||||
site = 'Indexxx'
|
||||
} else if (/(https?:\/\/www.instagram.com\/[^/?]+)\??/.test(url)) {
|
||||
site = 'Instagram'
|
||||
} else if (/(https?:\/\/www.manyvids.com\/[^?]+)\??/.test(url)) {
|
||||
site = 'ManyVids'
|
||||
} else if (/(^https?:\/\/(?:www.)?minnano-av\.com\/actress\d+.html)/.test(url)) {
|
||||
site = 'Minnano-av'
|
||||
} else if (/(^https?:\/\/(?:www.)?myspace\.com\/[^?]+)/.test(url)) {
|
||||
site = 'Myspace'
|
||||
} else if (/(https?:\/\/onlyfans.com\/[^?]+)\??/.test(url)) {
|
||||
site = 'OnlyFans'
|
||||
} else if (/(https?:\/\/www.thenude.com\/[^?]+\.htm)/.test(url)) {
|
||||
site = 'theNude'
|
||||
} else if (/(^https?:\/\/(?:www.)?tiktok\.com\/@[^?]+)/.test(url)) {
|
||||
site = 'TikTok'
|
||||
} else if (/(https?:\/\/twitter.com\/[^?]+)\??/.test(url)) {
|
||||
site = 'Twitter'
|
||||
} else if (/(^https?:\/\/(www\.)?wikidata.org\/wiki\/[^?]+)/.test(url)) {
|
||||
site = 'Wikidata'
|
||||
} else if (/(^https?:\/\/(?:\w+\.)?wikipedia\.org\/wiki\/[^?]+)/.test(url)) {
|
||||
site = 'Wikipedia'
|
||||
} else if (/(^https?:\/\/xslist\.org\/en\/model\/\d+\.html)/.test(url)) {
|
||||
site = 'XsList'
|
||||
} else if (/(^https?:\/\/(?:www.)?youtube\.com\/(?:c(?:hannel)?|user)\/[^?]+)/.test(url)) {
|
||||
site = 'YouTube'
|
||||
} else {
|
||||
return;
|
||||
};
|
||||
|
||||
return site;
|
||||
};
|
||||
|
||||
function siteMatch(url, selections) {
|
||||
const match = Array.from(selections.options).find((option) => option.text == urlSite(url));
|
||||
|
||||
return match;
|
||||
}
|
||||
|
||||
function addUrl(url) {
|
||||
const existingUrls = existingUrlObjects();
|
||||
let urlMatch = existingUrls.find(element => { return element.url == url; });
|
||||
if (typeof urlMatch !== 'undefined') {
|
||||
console.warn("Skipping url '" + url + "' as it is already added to this performer.");
|
||||
return;
|
||||
};
|
||||
|
||||
const urlForm = document.querySelector('form .URLInput');
|
||||
const urlInput = urlForm.querySelector(':scope > .input-group');
|
||||
const selections = (urlInput.children[1]);
|
||||
const inputField = (urlInput.children[2]);
|
||||
const addButton = (urlInput.children[3]);
|
||||
|
||||
const selection = siteMatch(url, selections);
|
||||
setNativeValue(selections, selection.value);
|
||||
setNativeValue(inputField, url);
|
||||
if (addButton.disabled) {
|
||||
console.warn("Unable to add url (Add button is disabled)");
|
||||
};
|
||||
|
||||
formTab("Links").click();
|
||||
addButton.click();
|
||||
};
|
||||
|
||||
function setStyles(element, styles) {
|
||||
Object.assign(element.style, styles);
|
||||
return element;
|
||||
};
|
||||
|
||||
function baseButtonContainer() {
|
||||
const container = document.createElement("span");
|
||||
return container;
|
||||
};
|
||||
|
||||
function baseButtonSet(name) {
|
||||
const set = document.createElement("a");
|
||||
set.innerText = "add " + name;
|
||||
set.classList.add("fw-bold");
|
||||
setStyles(set, { color: "var(--bs-yellow)", cursor: "pointer", "margin-left": "0.5em", });
|
||||
return set;
|
||||
};
|
||||
|
||||
function insertButton(action, element, name) {
|
||||
const container = baseButtonContainer();
|
||||
const set = baseButtonSet(name);
|
||||
set.addEventListener("click", action);
|
||||
container.append(set);
|
||||
element.appendChild(container);
|
||||
};
|
||||
|
||||
function addMeasurements(measurements) {
|
||||
const splitMeasurements = measurements.split("-");
|
||||
|
||||
if (splitMeasurements.length > 0) {
|
||||
const braSize = splitMeasurements[0].trim();
|
||||
const braInput = document.querySelector('input[name="braSize"]');
|
||||
setNativeValue(braInput, braSize);
|
||||
};
|
||||
|
||||
if (splitMeasurements.length > 1) {
|
||||
const waistSize = splitMeasurements[1].trim();
|
||||
const waistInput = document.querySelector('input[name="waistSize"]');
|
||||
setNativeValue(waistInput, waistSize);
|
||||
};
|
||||
|
||||
if (splitMeasurements.length > 2) {
|
||||
const hipSize = splitMeasurements[2].trim();
|
||||
const hipInput = document.querySelector('input[name="hipSize"]');
|
||||
setNativeValue(hipInput, hipSize);
|
||||
};
|
||||
|
||||
formTab("Personal Information").click();
|
||||
}
|
||||
|
||||
function createAliasButton(unmatched, element) {
|
||||
const addAliases = () => unmatched.forEach(addAlias);
|
||||
insertButton(addAliases, element, "aliases");
|
||||
};
|
||||
|
||||
function createMeasurementsButton(unmatched, element) {
|
||||
const insertMeasurements = () => addMeasurements(unmatched);
|
||||
insertButton(insertMeasurements, element, "measurements");
|
||||
};
|
||||
|
||||
function createUrlsButton(unmatched, element) {
|
||||
const addUrls = () => unmatched.forEach(addUrl);
|
||||
insertButton(addUrls, element, "urls");
|
||||
};
|
||||
|
||||
function isValidMeasurements(measurements) {
|
||||
const measurementsRegex = /(\d\d\w?\w?\w?\s?)(-\s?\d\d\s?)?(-\s?\d\d)?/;
|
||||
const isValid = measurementsRegex.test(measurements);
|
||||
if (!isValid) { console.warn("Measurement format '" + measurements + "' is invalid and cannot be automatically added.") };
|
||||
return measurementsRegex.test(measurements);
|
||||
};
|
||||
|
||||
function addAliasInputContainer() {
|
||||
const performerForm = document.querySelector(".PerformerForm");
|
||||
const aliasContainer = document.createElement ('div');
|
||||
aliasContainer.innerHTML = '<button id="aliasButton">Add Aliases</button>';
|
||||
aliasContainer.setAttribute ('id', 'aliasContainer');
|
||||
performerForm.prepend(aliasContainer);
|
||||
|
||||
const aliasButton = document.createElement("input");
|
||||
aliasButton.innerText = "Add Aliases";
|
||||
aliasButton.setAttribute("id", "aliasButton");
|
||||
aliasButton.setAttribute("style", "border-radius: 0.25rem;")
|
||||
|
||||
const aliasField = document.createElement("input");
|
||||
aliasField.setAttribute("id", "aliasField");
|
||||
aliasField.setAttribute("placeholder", " Comma separated aliases");
|
||||
aliasField.setAttribute("size", "50px");
|
||||
aliasField.setAttribute("style", "border-radius: 0.25rem; margin-right: 0.5rem;");
|
||||
|
||||
document.getElementById("aliasContainer").prepend(aliasField);
|
||||
const enteredAliases = document.getElementById("aliasField").value;
|
||||
|
||||
document.getElementById("aliasButton").addEventListener('click', function handleClick(event) {
|
||||
event.preventDefault();
|
||||
const aliasField = document.getElementById("aliasField");
|
||||
if (aliasField.value != '') {
|
||||
aliasField.value.split(/,|\/|\sor\s/).forEach(addAlias);
|
||||
aliasField.value = "";
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
function performerEditPage() {
|
||||
const aliasValues = unmatchedTargetValue("Aliases");
|
||||
if (aliasValues != null) {
|
||||
const unmatchedAliases = aliasValues.split(/,|\/|\sor\s/);
|
||||
const aliasElement = unmatchedTargetButton("Aliases");
|
||||
createAliasButton(unmatchedAliases, aliasElement);
|
||||
};
|
||||
|
||||
const urlsValues = unmatchedTargetValue("URLs");
|
||||
if (urlsValues != null) {
|
||||
const unmatchedUrls = urlsValues.split(", ");
|
||||
if (unmatchedUrls) {
|
||||
const umatchedUrlsElement = unmatchedTargetElement("URLs")
|
||||
makeUrlLink(umatchedUrlsElement);
|
||||
};
|
||||
const urlsElement = unmatchedTargetButton("URLs");
|
||||
createUrlsButton(unmatchedUrls, urlsElement);
|
||||
};
|
||||
|
||||
const unmatchedMeasurements = unmatchedTargetValue("Measurements");
|
||||
if (unmatchedMeasurements != null) {
|
||||
if (isValidMeasurements(unmatchedMeasurements)) {
|
||||
const measurementsElement = unmatchedTargetButton("Measurements");
|
||||
createMeasurementsButton(unmatchedMeasurements, measurementsElement);
|
||||
};
|
||||
};
|
||||
|
||||
addAliasInputContainer();
|
||||
};
|
||||
|
||||
function sceneEditPage() {
|
||||
return;
|
||||
};
|
||||
|
||||
function pageType() {
|
||||
return document.querySelector(".NarrowPage form").className.replace("Form", "");
|
||||
};
|
||||
|
||||
waitForElm(aliasInputSelector).then(() => {
|
||||
if (pageType() == "Performer") {
|
||||
performerEditPage();
|
||||
} else if (pageType() == "Scene") {
|
||||
sceneEditPage();
|
||||
} else {
|
||||
return;
|
||||
};
|
||||
});
|
||||
Loading…
x
Reference in New Issue
Block a user