// ==UserScript== // @name Stash Batch Result Toggle // @namespace https://github.com/7dJx1qP/stash-userscripts // @description Batch toggle scene tagger search result fields // @version 0.4.1 // @author 7dJx1qP // @match http://localhost:9999/* // @grant unsafeWindow // @grant GM.getValue // @grant GM.setValue // @require https://raw.githubusercontent.com/7dJx1qP/stash-userscripts/master/src\StashUserscriptLibrary.js // ==/UserScript== (function() { 'use strict'; const { stash, Stash, waitForElementId, waitForElementClass, waitForElementByXpath, getElementByXpath, getClosestAncestor, createElementFromHTML, updateTextInput, sortElementChildren, } = unsafeWindow.stash; let running = false; const buttons = []; let maxCount = 0; function resolveToggle(el) { let button = null; if (el?.classList.contains('optional-field-content')) { button = el.previousElementSibling; } else if (el?.tagName === 'SPAN' && el?.classList.contains('ml-auto')) { button = el.querySelector('.optional-field button'); } else if (el?.parentElement?.classList.contains('optional-field-content')) { button = el.parentElement.previousElementSibling; } const state = button?.classList.contains('text-success'); return { button, state }; } function toggleSearchItem(searchItem, toggleMode) { const searchResultItem = searchItem.querySelector('li.search-result.selected-result.active'); if (!searchResultItem) return; const { urlNode, url, id, data, nameNode, name, queryInput, performerNodes } = stash.parseSearchItem(searchItem); const { remoteUrlNode, remoteId, remoteUrl, remoteData, urlNode: matchUrlNode, detailsNode, imageNode, titleNode, dateNode, studioNode, performerNodes: matchPerformerNodes, matches } = stash.parseSearchResultItem(searchResultItem); const studioMatchNode = matches.find(o => o.matchType === 'studio')?.matchNode; const performerMatchNodes = matches.filter(o => o.matchType === 'performer').map(o => o.matchNode); const includeTitle = document.getElementById('result-toggle-title').checked; const includeDate = document.getElementById('result-toggle-date').checked; const includeCover = document.getElementById('result-toggle-cover').checked; const includeStashID = document.getElementById('result-toggle-stashid').checked; const includeURL = document.getElementById('result-toggle-url').checked; const includeDetails = document.getElementById('result-toggle-details').checked; const includeStudio = document.getElementById('result-toggle-studio').checked; const includePerformers = document.getElementById('result-toggle-performers').checked; let options = []; options.push(['title', includeTitle, titleNode, resolveToggle(titleNode)]); options.push(['date', includeDate, dateNode, resolveToggle(dateNode)]); options.push(['cover', includeCover, imageNode, resolveToggle(imageNode)]); options.push(['stashid', includeStashID, remoteUrlNode, resolveToggle(remoteUrlNode)]); options.push(['url', includeURL, matchUrlNode, resolveToggle(matchUrlNode)]); options.push(['details', includeDetails, detailsNode, resolveToggle(detailsNode)]); options.push(['studio', includeStudio, studioMatchNode, resolveToggle(studioMatchNode)]); options = options.concat(performerMatchNodes.map(o => ['performer', includePerformers, o, resolveToggle(o)])); for (const [optionType, optionValue, optionNode, { button, state }] of options) { let wantedState = optionValue; if (toggleMode === 1) { wantedState = true; } else if (toggleMode === -1) { wantedState = false; } if (optionNode && wantedState !== state) { button.click(); } } } function run() { if (!running) return; const button = buttons.pop(); stash.setProgress((maxCount - buttons.length) / maxCount * 100); if (button) { const searchItem = getClosestAncestor(button, '.search-item'); let toggleMode = 0; if (btn === btnOn) { toggleMode = 1; } else if (btn === btnOff) { toggleMode = -1; } else if (btn === btnMixed) { toggleMode = 0; } toggleSearchItem(searchItem, toggleMode); setTimeout(run, 0); } else { stop(); } } const btnGroup = document.createElement('div'); const btnGroupId = 'batch-result-toggle'; btnGroup.setAttribute('id', btnGroupId); btnGroup.classList.add('btn-group', 'ml-3'); const checkLabel = ''; const timesLabel = ''; const startLabel = ''; let btn; const btnOffId = 'batch-result-toggle-off'; const btnOff = document.createElement("button"); btnOff.setAttribute("id", btnOffId); btnOff.title = 'Result Toggle All Off'; btnOff.classList.add('btn', 'btn-primary'); btnOff.innerHTML = timesLabel; btnOff.onclick = () => { if (running) { stop(); } else { btn = btnOff; start(); } }; btnGroup.appendChild(btnOff); const btnMixedId = 'batch-result-toggle-mixed'; const btnMixed = document.createElement("button"); btnMixed.setAttribute("id", btnMixedId); btnMixed.title = 'Result Toggle All'; btnMixed.classList.add('btn', 'btn-primary'); btnMixed.innerHTML = startLabel; btnMixed.onclick = () => { if (running) { stop(); } else { btn = btnMixed; start(); } }; btnGroup.appendChild(btnMixed); const btnOnId = 'batch-result-toggle-on'; const btnOn = document.createElement("button"); btnOn.setAttribute("id", btnOnId); btnOn.title = 'Result Toggle All On'; btnOn.classList.add('btn', 'btn-primary'); btnOn.innerHTML = checkLabel; btnOn.onclick = () => { if (running) { stop(); } else { btn = btnOn; start(); } }; btnGroup.appendChild(btnOn); function start() { // btn.innerHTML = stopLabel; btn.classList.remove('btn-primary'); btn.classList.add('btn-danger'); btnMixed.disabled = true; btnOn.disabled = true; btnOff.disabled = true; btn.disabled = false; running = true; stash.setProgress(0); buttons.length = 0; for (const button of document.querySelectorAll('.btn.btn-primary')) { if (button.innerText === 'Search') { buttons.push(button); } } maxCount = buttons.length; run(); } function stop() { // btn.innerHTML = startLabel; btn.classList.remove('btn-danger'); btn.classList.add('btn-primary'); running = false; stash.setProgress(0); btnMixed.disabled = false; btnOn.disabled = false; btnOff.disabled = false; } stash.addEventListener('tagger:mutations:header', evt => { const el = getElementByXpath("//button[text()='Scrape All']"); if (el && !document.getElementById(btnGroupId)) { const container = el.parentElement; container.appendChild(btnGroup); sortElementChildren(container); el.classList.add('ml-3'); } }); const resultToggleConfigId = 'result-toggle-config'; stash.addEventListener('tagger:configuration', evt => { const el = evt.detail; if (!document.getElementById(resultToggleConfigId)) { const configContainer = el.parentElement; const resultToggleConfig = createElementFromHTML(`
Result Toggle ${startLabel} Configuration
`); configContainer.appendChild(resultToggleConfig); loadSettings(); } }); async function loadSettings() { for (const input of document.querySelectorAll(`#${resultToggleConfigId} input`)) { input.checked = await GM.getValue(input.id, input.dataset.default === 'true'); input.addEventListener('change', async () => { await GM.setValue(input.id, input.checked); }); } } stash.addEventListener('tagger:mutation:add:remoteperformer', evt => toggleSearchItem(getClosestAncestor(evt.detail.node, '.search-item'), 0)); stash.addEventListener('tagger:mutation:add:remotestudio', evt => toggleSearchItem(getClosestAncestor(evt.detail.node, '.search-item'), 0)); stash.addEventListener('tagger:mutation:add:local', evt => toggleSearchItem(getClosestAncestor(evt.detail.node, '.search-item'), 0)); stash.addEventListener('tagger:mutation:add:container', evt => toggleSearchItem(getClosestAncestor(evt.detail.node, '.search-item'), 0)); stash.addEventListener('tagger:mutation:add:subcontainer', evt => toggleSearchItem(getClosestAncestor(evt.detail.node, '.search-item'), 0)); function checkSaveButtonDisplay() { const taggerContainer = document.querySelector('.tagger-container'); const saveButton = getElementByXpath("//button[text()='Save']", taggerContainer); btnGroup.style.display = saveButton ? 'inline-block' : 'none'; } stash.addEventListener('tagger:mutations:searchitems', checkSaveButtonDisplay); })();