Skip to content

Instantly share code, notes, and snippets.

@Maista6969
Last active August 8, 2023 02:50
Show Gist options
  • Save Maista6969/124d0ddfd5fc05ec8cce7cf21e46b9dd to your computer and use it in GitHub Desktop.
Save Maista6969/124d0ddfd5fc05ec8cce7cf21e46b9dd to your computer and use it in GitHub Desktop.
Shows the summarized O-counter and scene ratings for studio cards in Stash
// ==UserScript==
// @name Rating stats for studios
// @author Maista
// @namespace com.maista.userscripts
// @version 1.0
// @require https://gist.githubusercontent.com/Maista6969/aaf8f04b1af7603ed27866e9e7f46a02/raw/53f00a1ccc3370e65af4735c46bcc0a71ac5ef77/onElementReady.js
// @include http://localhost:9999/*
// @grant GM_xmlhttpRequest
// @icon http://localhost:9999/favicon.ico
// ==/UserScript==
const script_name = GM_info.script.name.toLowerCase().split(' ').join('-');
onElementReady('.studio-card', showStats, script_name);
async function showStats(studioCard) {
const studioId = studioCard.querySelector('a[href*=studios]').href.split('/').pop();
const name = studioCard.querySelector('h5').innerText;
let popoverRow = studioCard.querySelector('.card-popovers');
// Some studios only have child studios and no scenes of their own but we summarize
// the stats for all children so we need to create the popover row for icons ourselves
if (popoverRow === null) {
studioCard.insertAdjacentHTML('beforeend', '<hr><div role="group" class="card-popovers btn-group"></div>');
popoverRow = studioCard.querySelector('.card-popovers');
};
const newElems = await getStudioData(studioId).then(summarizeSceneData).then((data) => makeIcons({name, id: studioId, ...data}));
newElems.forEach(el => popoverRow.appendChild(el));
}
function makeIcons({id, name, o_total, avgRating, numScenes}) {
const elems = [];
const baseUrl = `http://localhost:9999/scenes?c=(%22type%22:%22studios%22,%22value%22:(%22items%22:%5B(%22id%22:%22${id}%22,%22label%22:%22${name}%22)%5D,%22depth%22:-1),%22modifier%22:%22INCLUDES%22)`;
if (o_total > 0) {
const oEl = document.createElement('a');
oEl.title = `${o_total} total Os`;
oEl.href = `${baseUrl}&sortby=o_counter&sortdir=desc`;
const oButton = document.createElement('button');
oButton.classList.add("minimal", "btn", "btn-primary");
oButton.innerHTML = `<svg class="svg-inline--fa fa-icon" aria-hidden="true" focusable="false" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="currentColor" d="M22.855.758L7.875 7.024l12.537 9.733c2.633 2.224 6.377 2.937 9.77 1.518c4.826-2.018 7.096-7.576 5.072-12.413C33.232 1.024 27.68-1.261 22.855.758zm-9.962 17.924L2.05 10.284L.137 23.529a7.993 7.993 0 0 0 2.958 7.803a8.001 8.001 0 0 0 9.798-12.65zm15.339 7.015l-8.156-4.69l-.033 9.223c-.088 2 .904 3.98 2.75 5.041a5.462 5.462 0 0 0 7.479-2.051c1.499-2.644.589-6.013-2.04-7.523z"></path></svg><span>${o_total}</span>`;
oEl.appendChild(oButton);
elems.push(oEl);
}
if (!avgRating.includes("NaN")) {
const arEl = document.createElement('a');
arEl.title = `${avgRating} average rating based on ${numScenes} rated scenes`;
arEl.href = `${baseUrl}&sortby=rating&sortdir=desc`;
let arButton = document.createElement('button');
arButton.classList.add("minimal", "btn", "btn-primary");
arButton.innerHTML = `<svg class="svg-inline--fa fa-star fa-icon unset" aria-hidden="true" focusable="false" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><path fill="currentColor" d="M287.9 0C297.1 0 305.5 5.25 309.5 13.52L378.1 154.8L531.4 177.5C540.4 178.8 547.8 185.1 550.7 193.7C553.5 202.4 551.2 211.9 544.8 218.2L433.6 328.4L459.9 483.9C461.4 492.9 457.7 502.1 450.2 507.4C442.8 512.7 432.1 513.4 424.9 509.1L287.9 435.9L150.1 509.1C142.9 513.4 133.1 512.7 125.6 507.4C118.2 502.1 114.5 492.9 115.1 483.9L142.2 328.4L31.11 218.2C24.65 211.9 22.36 202.4 25.2 193.7C28.03 185.1 35.5 178.8 44.49 177.5L197.7 154.8L266.3 13.52C270.4 5.249 278.7 0 287.9 0L287.9 0zM287.9 78.95L235.4 187.2C231.9 194.3 225.1 199.3 217.3 200.5L98.98 217.9L184.9 303C190.4 308.5 192.9 316.4 191.6 324.1L171.4 443.7L276.6 387.5C283.7 383.7 292.2 383.7 299.2 387.5L404.4 443.7L384.2 324.1C382.9 316.4 385.5 308.5 391 303L476.9 217.9L358.6 200.5C350.7 199.3 343.9 194.3 340.5 187.2L287.9 78.95z"></path></svg><span>${avgRating}</span>`;
arEl.appendChild(arButton);
elems.push(arEl);
}
return elems;
}
function summarizeSceneData({scenes, ...rest}) {
const o_total = scenes.map(({o_counter}) => o_counter).reduce((a, b) => a+b, 0);
const ratings = scenes.map(({rating100}) => rating100).filter(rating => rating !== null);
const numScenes = ratings.length;
const avgRating = (ratings.reduce((a, b) => a+b, 0) / numScenes / 20).toPrecision(2);
return { o_total, avgRating, numScenes, ...rest };
}
function getStudioData(id) {
let query = JSON.stringify(
{
"query": "query FindScenes($scene_filter: SceneFilterType) { findScenes(scene_filter: $scene_filter, filter: { per_page: -1 }) { scenes { o_counter rating100 } } }",
"variables": {
"scene_filter": {
"studios": {
"modifier": "INCLUDES",
"value": [id],
"depth": -1, // Remove this if you do not want to summarize data for all child studios
}
}
}
});
return new Promise((resolve, reject) => {
GM_xmlhttpRequest({
method: "POST",
responseType: "json",
url: "http://localhost:9999/graphql",
headers: { "Content-Type": "application/json" },
data: query,
onload: (res) => {
try {
if (res.status != 200) {
let errors = res.response.errors.map((e) => e.message).join('\n');
return reject(`Query for '${id}' failed: ${errors}`);
}
resolve(res.response.data.findScenes);
} catch (error) {
reject(error.message);
}
}
});
});
}
@echo6ix
Copy link

echo6ix commented Aug 8, 2023

I know you made this for your own use only, but now you've let the cat out of the bag. 😁

Would be neat if you modified the script and it's corresponding performers script in four ways

  1. add a stash_ip variable instead of hardcoding localhost
  2. add the average rating on the details pages for performers, tags, and studios. located within the <span class="name-icons"> element with the other icon buttons
  3. detect the rating star precision settings and average the rating accordingly for rating consistency. if a user has their precision set to full, seeing a rating > 5 is confusing imo.
  4. remove the o-counter totals for performers (i am sure you're aware that's a stock feature now).

p.s. this really should be built in to stash. very elegant idea and presentation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment