// ==UserScript== // @name Stash StashID Input // @namespace https://github.com/7dJx1qP/stash-userscripts // @description Adds input for entering new stash id to performer details page and studio page // @version 0.4.0 // @author 7dJx1qP // @match http://localhost:9999/* // @grant unsafeWindow // @grant GM_setClipboard // @require https://raw.githubusercontent.com/7dJx1qP/stash-userscripts/master/src\StashUserscriptLibrary.js // ==/UserScript== (function () { 'use strict'; const { stash, Stash, waitForElementId, waitForElementClass, waitForElementByXpath, getElementByXpath, getElementsByXpath, createElementFromHTML, } = unsafeWindow.stash; async function updatePerformerStashIDs(performerId, stash_ids) { const reqData = { "variables": { "input": { "stash_ids": stash_ids, "id": performerId } }, "query": `mutation PerformerUpdate($input: PerformerUpdateInput!) { performerUpdate(input: $input) { ...PerformerData } } fragment PerformerData on Performer { id favorite stash_ids { stash_id endpoint } }` }; await stash.callGQL(reqData); } async function getPerformerStashIDs(performerId) { const reqData = { "variables": { "id": performerId }, "query": `query FindPerformer($id: ID!) { findPerformer(id: $id) { ...PerformerData } } fragment PerformerData on Performer { id stash_ids { endpoint stash_id } }` }; return stash.callGQL(reqData); } async function getStudioStashIDs(studioId) { const reqData = { "variables": { "id": studioId }, "query": `query FindStudio($id: ID!) { findStudio(id: $id) { ...StudioData } } fragment StudioData on Studio { id stash_ids { endpoint stash_id } }` }; return stash.callGQL(reqData); } async function updateStudioStashIDs(studioId, stash_ids) { const reqData = { "variables": { "input": { "stash_ids": stash_ids, "id": studioId } }, "query": `mutation StudioUpdate($input: StudioUpdateInput!) { studioUpdate(input: $input) { ...StudioData } } fragment StudioData on Studio { id stash_ids { stash_id endpoint } }` }; await stash.callGQL(reqData); } function toUrl(string) { let url; try { url = new URL(string); } catch (_) { return null; } if (url.protocol === "http:" || url.protocol === "https:") return url; return null; } function createTooltipElement() { const copyTooltip = document.createElement('span'); copyTooltip.setAttribute('id', 'copy-tooltip'); copyTooltip.innerText = 'Copied!'; copyTooltip.classList.add('fade', 'hide'); copyTooltip.style.position = "absolute"; copyTooltip.style.left = '0px'; copyTooltip.style.top = '0px'; copyTooltip.style.marginLeft = '40px'; copyTooltip.style.padding = '5px 12px'; copyTooltip.style.backgroundColor = '#000000df'; copyTooltip.style.borderRadius = '4px'; copyTooltip.style.color = '#fff'; document.body.appendChild(copyTooltip); return copyTooltip; } function createCopyButton(copyTooltip, copyText) { const copyBtn = document.createElement('button'); copyBtn.title = 'Copy to clipboard'; copyBtn.innerHTML = ``; copyBtn.classList.add('btn', 'btn-secondary', 'btn-sm', 'minimal', 'ml-1'); copyBtn.addEventListener('click', copyHandler(copyTooltip, copyText)); return copyBtn; } function copyHandler(copyTooltip, copyText) { return evt => { GM_setClipboard(copyText); const rect = document.body.getBoundingClientRect(); const rect2 = evt.currentTarget.getBoundingClientRect(); const x = rect2.left - rect.left; const y = rect2.top - rect.top; copyTooltip.classList.add('show'); copyTooltip.style.left = `${x}px`; copyTooltip.style.top = `${y}px`; setTimeout(() => { copyTooltip.classList.remove('show'); }, 500); } } function createDownloadButton(studioId, endpoint, remoteSiteId) { const downloadBtn = document.createElement('button'); downloadBtn.title = 'Download studio image and set parent studio'; downloadBtn.innerHTML = `` downloadBtn.classList.add('btn', 'btn-secondary', 'btn-sm', 'minimal', 'ml-1'); downloadBtn.addEventListener('click', downloadImageHandler(studioId, endpoint, remoteSiteId)); return downloadBtn; } function downloadImageHandler(studioId, endpoint, remoteSiteId) { return evt => { if (!confirm(`Download studio image and set parent studio?`)) return; const callback = () => { document.body.style.cursor = 'auto'; window.location.reload(); } document.body.style.cursor = 'wait'; stash.dispatchEvent(new CustomEvent('userscript_functions:update_studio', { 'detail': { studioId, endpoint, remoteSiteId, callback, errCallback: callback } })); } } stash.addEventListener('page:performer:details', function () { waitForElementId('performer-details-tabpane-details', async function (elementId, el) { if (!document.getElementById('update-stashids-endpoint')) { const detailsList = el.querySelector('.details-list'); const stashboxInputContainer = document.createElement('dt'); const stashboxInput = document.createElement('select'); stashboxInput.setAttribute('id', 'update-stashids-endpoint'); stashboxInput.classList.add('form-control', 'input-control'); stashboxInputContainer.appendChild(stashboxInput); detailsList.appendChild(stashboxInputContainer); const data = await stash.getStashBoxes(); let i = 0; for (const { name, endpoint } of data.data.configuration.general.stashBoxes) { i++; const option = document.createElement('option'); option.innerText = name || `stash-box: #${i}` option.value = endpoint; stashboxInput.appendChild(option); } const performerId = window.location.pathname.replace('/performers/', ''); const stashIdInputContainer = document.createElement('dd'); const stashIdInput = document.createElement('input'); stashIdInput.classList.add('query-text-field', 'bg-secondary', 'text-white', 'border-secondary', 'form-control'); stashIdInput.setAttribute('id', 'update-stashids'); stashIdInput.setAttribute('placeholder', 'Add StashID…'); stashIdInput.addEventListener('change', () => { const url = toUrl(stashIdInput.value); let newEndpoint; let newStashId; if (url) { for (const option of stashboxInput.options) { if (option.value === url.origin + '/graphql') { newEndpoint = option.value; } } if (!newEndpoint || !url.pathname.startsWith('/performers/')) { alert('Unknown stashbox url.'); return; } newStashId = url.pathname.replace('/performers/', ''); } else { newEndpoint = stashboxInput.options[stashboxInput.selectedIndex].value; newStashId = stashIdInput.value; } stashIdInput.value = ''; if (!newStashId) return; getPerformerStashIDs(performerId).then(data => { const stash_ids = data.data.findPerformer.stash_ids; if (stash_ids.find(({endpoint, stash_id }) => endpoint === newEndpoint && stash_id === newStashId)) return; if (!confirm(`Add StashID ${newStashId}?`)) return; return updatePerformerStashIDs(performerId, stash_ids.concat([{ endpoint: newEndpoint, stash_id: newStashId }])); }).then(() => window.location.reload()); }); stashIdInputContainer.appendChild(stashIdInput); detailsList.appendChild(stashIdInputContainer); const copyTooltip = createTooltipElement(); const stashIdsResult = getElementsByXpath("//dl[@class='details-list']//dt[text()='StashIDs']/following-sibling::dd/ul/li/a") const stashIds = []; let node = null; while (node = stashIdsResult.iterateNext()) { stashIds.push(node); } for (const stashId of stashIds) { const copyBtn = createCopyButton(copyTooltip, stashId.innerText); stashId.parentElement.appendChild(copyBtn); } } }); }); stash.addEventListener('page:studio:scenes', function () { waitForElementByXpath("//div[contains(@class, 'studio-details')]", async function (xpath, el) { if (!document.getElementById('studio-stashids')) { const container = document.createElement('div'); container.setAttribute('id', 'studio-stashids'); container.classList.add('row', 'pl-3'); el.appendChild(container); const stashboxInput = document.createElement('select'); stashboxInput.setAttribute('id', 'update-stashids-endpoint'); stashboxInput.classList.add('form-control', 'input-control', 'mt-2', 'col-md-4'); const data = await stash.getStashBoxes(); let i = 0; for (const { name, endpoint } of data.data.configuration.general.stashBoxes) { i++; const option = document.createElement('option'); option.innerText = name || `stash-box: #${i}` option.value = endpoint; stashboxInput.appendChild(option); } const studioId = window.location.pathname.replace('/studios/', ''); const stashIdInput = document.createElement('input'); stashIdInput.classList.add('query-text-field', 'bg-secondary', 'text-white', 'border-secondary', 'form-control', 'mt-2', 'col-md-8'); stashIdInput.setAttribute('id', 'update-stashids'); stashIdInput.setAttribute('placeholder', 'Add StashID…'); stashIdInput.addEventListener('change', () => { const url = toUrl(stashIdInput.value); let newEndpoint; let newStashId; if (url) { for (const option of stashboxInput.options) { if (option.value === url.origin + '/graphql') { newEndpoint = option.value; } } if (!newEndpoint || !url.pathname.startsWith('/studios/')) { alert('Unknown stashbox url.'); return; } newStashId = url.pathname.replace('/studios/', ''); } else { newEndpoint = stashboxInput.options[stashboxInput.selectedIndex].value; newStashId = stashIdInput.value; } stashIdInput.value = ''; if (!newStashId) return; getStudioStashIDs(studioId).then(data => { const stash_ids = data.data.findStudio.stash_ids; if (stash_ids.find(({endpoint, stash_id }) => endpoint === newEndpoint && stash_id === newStashId)) return; if (!confirm(`Add StashID ${newStashId}?`)) return; return updateStudioStashIDs(studioId, stash_ids.concat([{ endpoint: newEndpoint, stash_id: newStashId }])); }).then(() => window.location.reload()); }); container.appendChild(stashIdInput); container.appendChild(stashboxInput); const copyTooltip = createTooltipElement(); getStudioStashIDs(studioId).then(data => { for (const { endpoint, stash_id } of data.data.findStudio.stash_ids) { const url = endpoint.replace(/graphql$/, 'studios/') + stash_id const row = document.createElement('div'); row.classList.add('col-md-12', 'pl-1', 'mt-1'); row.innerHTML = `${stash_id}`; container.appendChild(row); const copyBtn = createCopyButton(copyTooltip, stash_id); row.appendChild(copyBtn); if (stash.userscripts.indexOf('Stash Studio Image And Parent On Create') !== -1) { const downloadBtn = createDownloadButton(studioId, endpoint, stash_id); row.appendChild(downloadBtn); } } }); } }); }); })();