Skip to content

Instantly share code, notes, and snippets.

@QxxxGit
Last active August 19, 2024 05:08
Show Gist options
  • Save QxxxGit/7d6e5759814b1ce84cb7535173415eff to your computer and use it in GitHub Desktop.
Save QxxxGit/7d6e5759814b1ce84cb7535173415eff to your computer and use it in GitHub Desktop.
// ==UserScript==
// @name Performer Suggestions
// @namespace https://github.com/7dJx1qP/stash-userscripts
// @version 0.3
// @description Adds performer suggestions based on similar tags.
// @author QxxxGit
// @match http://localhost:9999/*
// @require https://raw.githubusercontent.com/7dJx1qP/stash-userscripts/master/src\StashUserscriptLibrary.js
// @grant unsafeWindow
// @grant GM_getResourceText
// @grant GM_addStyle
// ==/UserScript==
(function() {
'use strict';
const Settings = {
// The methodology for displaying the suggestions.
// 0 = show performer names only
// 1 = show performer images only
// 2 = show both performer images and name badges
display: 2,
// The amount of tags randomly selected to find matching performers.
tagSelectCount: 3,
// Amount of performers to query from the backend
performersQueryCount: 25,
// The amount of performers returned as suggestions
performerSuggestionMaxCount: 5,
// Maximum amount of times to query for performers when no matches are found.
queryRepeat: 3
};
// Unique id for the container of suggestions.
// This is used to prevent duplication of suggestions when changing tabs.
const suggestionRowId = 'suggestion-row';
const {
stash,
Stash,
waitForElementId,
waitForElementClass,
} = unsafeWindow.stash;
const getPerformerIdFromUrl = function() {
return window.location.pathname.replace('/performers/', '').split('/')[0];
};
const queryCurrentPerformerTags = async function() {
const performerId = getPerformerIdFromUrl();
const gqlQuery = {
'query': `query {
findPerformer(id: ${performerId}) {
tags {
id
}
}
}`
};
const results = await stash.callGQL(gqlQuery);
if(results === undefined || results === null) return;
const tags = results.data.findPerformer.tags;
if(tags === undefined || tags === null) return;
let tagIds = tags.map((tag) => tag.id);
tagIds.sort(() => 0.5 - Math.random());
tagIds = tagIds.slice(0, Settings.tagSelectCount);
return tagIds;
};
const queryPerformersFromSimilarTags = async function(tags) {
const performerId = getPerformerIdFromUrl();
const gqlQuery = {
'variables': {
'performerFilter': {
'tags': {
'value': tags,
'modifier': 'INCLUDES_ALL'
}
},
'filter': {
'per_page': Settings.performersQueryCount
}
},
'query': `query findPerformersFromSimilarTags($performerFilter: PerformerFilterType!, $filter: FindFilterType!) {
findPerformers(performer_filter: $performerFilter, filter: $filter) {
performers {
id,
name,
image_path
}
}
}`
};
const results = await stash.callGQL(gqlQuery);
if(results === undefined || results === null) return;
let performers = results.data.findPerformers.performers;
if(performers === undefined || performers === null) return;
performers = performers.filter(item => item.id != performerId);
performers = performers.sort(() => 0.5 - Math.random());
return performers;
};
const wrapElementWithAnchor = function(element, performerId) {
if(performerId === undefined || performerId === null) return;
const anchor = document.createElement('a');
anchor.innerHTML = element.outerHTML;
anchor.setAttribute('href', `/performers/${performerId}`);
return anchor;
};
const createPerformerBadge = function(performerName) {
const performerBadge = document.createElement('span');
performerBadge.classList.add('tag-item', (Settings.display === 2) ? 'd-block' : null, 'badge', 'secondary-badge');
performerBadge.innerText = performerName;
return performerBadge;
};
const createPerformerPicture = function(performerImageUrl) {
const performerPicture = document.createElement('img');
performerPicture.classList.add('image-thumbnail');
performerPicture.setAttribute('src', performerImageUrl);
return performerPicture;
};
let currentQueryCount = 1;
const run = async function() {
if(currentQueryCount > Settings.queryRepeat) return;
if(!document.getElementById(suggestionRowId)) {
const performerHeaderContainer = document.getElementsByClassName('performer-head col')[0];
const performerNameHeader = performerHeaderContainer.getElementsByTagName('h2')[0];
const buttonContainer = document.getElementsByClassName('nav-tabs')[0];
const suggestionContainer = document.createElement('div');
suggestionContainer.setAttribute('id', suggestionRowId);
const tags = await queryCurrentPerformerTags();
const performers = await queryPerformersFromSimilarTags(tags);
let performerCount = 1;
for(let performer in performers) {
if(performerCount > Settings.performerSuggestionMaxCount) break;
const performerData = performers[performer];
switch(Settings.display) {
case 0: {
const badge = createPerformerBadge(performerData.name);
const link = wrapElementWithAnchor(badge, performerData.id);
suggestionContainer.appendChild(link);
}
break;
case 1: {
const picture = createPerformerPicture(performerData.image_path);
const link = wrapElementWithAnchor(picture, performerData.id);
suggestionContainer.appendChild(link);
}
break;
case 2: {
const performerContainer = document.createElement('div');
performerContainer.classList.add('performer-tag-container', 'row');
const picture = createPerformerPicture(performerData.image_path);
const pictureLink = wrapElementWithAnchor(picture, performerData.id);
pictureLink.classList.add('performer-tag', 'col', 'm-auto', 'zoom-2');
const badge = createPerformerBadge(performerData.name);
const badgeLink = wrapElementWithAnchor(badge, performerData.id);
performerContainer.appendChild(pictureLink);
performerContainer.appendChild(badgeLink);
suggestionContainer.appendChild(performerContainer);
}
break;
}
performerCount++;
}
if(performers.length > 0) {
const detailsList = document.getElementsByClassName('details-list')[0];
const suggestionDescriptionTerm = document.createElement('dt');
suggestionDescriptionTerm.innerText = 'Similar Performers';
const suggestionDescriptionDetails = document.createElement('dd');
const suggestionUl = document.createElement('ul');
suggestionUl.classList.add('pl-0');
detailsList.appendChild(suggestionDescriptionTerm);
suggestionDescriptionDetails.appendChild(suggestionUl);
detailsList.appendChild(suggestionDescriptionDetails);
suggestionUl.appendChild(suggestionContainer);
} else {
currentQueryCount++;
await run();
}
}
};
stash.addEventListener('page:performer', function () {
waitForElementClass("performer-body", async function() {
await run();
});
});
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment