Skip to content

Commit

Permalink
add stashbox scene count plugin
Browse files Browse the repository at this point in the history
  • Loading branch information
7dJx1qP committed Feb 12, 2024
1 parent a7aedb5 commit e752e68
Show file tree
Hide file tree
Showing 7 changed files with 441 additions and 0 deletions.
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,16 @@ Sync Stashbox favorite performers whenever a stash performer is favorited or unf

![Performers page](images/Stash%20Set%20Stashbox%20Favorite%20Performers/performers-page.png?raw=true "Performers page")

### Stash Stashbox Scene Count

Adds stashbox scene counts to performers and studios

#### Requirements

* Python 3.9+
* Requests (https://pypi.org/project/requests/)
* PyStashLib (https://pypi.org/project/pystashlib/)

### Stash StashID Icon

Adds checkmark icon to performer and studio cards that have a stashid
Expand Down
39 changes: 39 additions & 0 deletions plugins/stashStashboxSceneCount/log.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import sys
import re
# Log messages sent from a script scraper instance are transmitted via stderr and are
# encoded with a prefix consisting of special character SOH, then the log
# level (one of t, d, i, w or e - corresponding to trace, debug, info,
# warning and error levels respectively), then special character
# STX.
#
# The log.trace, log.debug, log.info, log.warning, and log.error methods, and their equivalent
# formatted methods are intended for use by script scraper instances to transmit log
# messages.
#

def __log(level_char: bytes, s):
if level_char:
lvl_char = "\x01{}\x02".format(level_char.decode())
s = re.sub(r"data:image.+?;base64(.+?')","[...]",str(s))
for x in s.split("\n"):
print(lvl_char, x, file=sys.stderr, flush=True)


def trace(s):
__log(b't', s)


def debug(s):
__log(b'd', s)


def info(s):
__log(b'i', s)


def warning(s):
__log(b'w', s)


def error(s):
__log(b'e', s)
2 changes: 2 additions & 0 deletions plugins/stashStashboxSceneCount/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
requests==2.30.0
pystashlib==0.4.4
182 changes: 182 additions & 0 deletions plugins/stashStashboxSceneCount/stashStashboxSceneCount.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
(function() {
'use strict';

const {
stash,
Stash,
waitForElementId,
waitForElementClass,
waitForElementByXpath,
getElementByXpath,
getClosestAncestor,
updateTextInput,
} = window.stash7dJx1qP;

async function runGetStashboxPerformerSceneCountTask(endpoint, api_key, stashId) {
if (endpoint !== 'https://stashdb.org/graphql') return;
return stash.runPluginTask("stashStashboxSceneCount", "Get Stashbox Performer Scene Count", [{"key":"endpoint", "value":{"str": endpoint}}, {"key":"api_key", "value":{"str": api_key}}, {"key":"stash_id", "value":{"str": stashId}}]);
}

async function runGetStashboxStudioSceneCountTask(endpoint, api_key, stashId) {
if (endpoint !== 'https://stashdb.org/graphql') return;
return stash.runPluginTask("stashStashboxSceneCount", "Get Stashbox Studio Scene Count", [{"key":"endpoint", "value":{"str": endpoint}}, {"key":"api_key", "value":{"str": api_key}}, {"key":"stash_id", "value":{"str": stashId}}]);
}

async function getPerformer() {
const performerId = window.location.pathname.split('/').find((o, i, arr) => i > 1 && arr[i - 1] == 'performers');
const reqData = {
"operationName": "FindPerformer",
"variables": {
"id": performerId
},
"query": `query FindPerformer($id: ID!) {
findPerformer(id: $id) {
id
stash_ids {
endpoint
stash_id
}
}
}`
};
const result = await stash.callGQL(reqData);
return result?.data?.findPerformer;
}

async function getStudio() {
const studioId = window.location.pathname.split('/').find((o, i, arr) => i > 1 && arr[i - 1] == 'studios');
const reqData = {
"operationName": "FindStudio",
"variables": {
"id": studioId
},
"query": `query FindStudio($id: ID!) {
findStudio(id: $id) {
id
stash_ids {
endpoint
stash_id
}
}
}`
};
const result = await stash.callGQL(reqData);
return result?.data?.findStudio;
}

async function getPerformerScenes(endpoint) {
const performerId = window.location.pathname.split('/').find((o, i, arr) => i > 1 && arr[i - 1] == 'performers');
const reqData = {
"operationName": "FindScenes",
"variables": {
"filter": {
"q": "",
"page": 1,
"per_page": 20,
"sort": "random_41127446",
"direction": "DESC"
},
"scene_filter": {
"stash_id_endpoint": {
"endpoint": endpoint,
"stash_id": "",
"modifier": "NOT_NULL"
},
"performers": {
"value": [performerId],
"excludes": [],
"modifier": "INCLUDES_ALL"
}
}
},
"query": `query FindScenes($filter: FindFilterType, $scene_filter: SceneFilterType, $scene_ids: [Int!]) {
findScenes(filter: $filter, scene_filter: $scene_filter, scene_ids: $scene_ids) {
count
}
}`
};
const result = await stash.callGQL(reqData);
return result?.data?.findScenes.count;
}

async function getStudioScenes(endpoint, includeSubsidiaryStudios) {
const studioId = window.location.pathname.split('/').find((o, i, arr) => i > 1 && arr[i - 1] == 'studios');
const reqData = {
"operationName": "FindScenes",
"variables": {
"filter": {
"q": "",
"page": 1,
"per_page": 20,
"sort": "random_41127446",
"direction": "DESC"
},
"scene_filter": {
"stash_id_endpoint": {
"endpoint": endpoint,
"stash_id": "",
"modifier": "NOT_NULL"
},
"studios": {
"value": [studioId],
"excludes": [],
"modifier": "INCLUDES_ALL"
}
}
},
"query": `query FindScenes($filter: FindFilterType, $scene_filter: SceneFilterType, $scene_ids: [Int!]) {
findScenes(filter: $filter, scene_filter: $scene_filter, scene_ids: $scene_ids) {
count
}
}`
};
if (includeSubsidiaryStudios) {
reqData.variables.scene_filter.studios.depth = -1;
}
const result = await stash.callGQL(reqData);
return result?.data?.findScenes.count;
}

async function performerPageHandler() {
const settings = await stash.getPluginConfig('stashStashboxSceneCount');
if (settings?.performers) {
const performer = await getPerformer();
const data = await stash.getStashBoxes();
for (const { endpoint, stash_id } of performer.stash_ids) {
const sceneCount = await getPerformerScenes(endpoint);
const api_key = data.data.configuration.general.stashBoxes.find(o => o.endpoint = endpoint).api_key;
await runGetStashboxPerformerSceneCountTask(endpoint, api_key, stash_id);
const stashBoxSceneCount = await stash.pollLogsForMessage(`[Plugin / Stash Stashbox Scene Count] ${stash_id}: `);
const el = getElementByXpath(`//span[@class='stash-id-pill']/a[text()='${stash_id}']`);
if (el) {
//el.innerText = `${stash_id} ${sceneCount}/${stashBoxSceneCount}`;
el.innerText = `${stash_id} ${stashBoxSceneCount}`;
}
}
}
}
stash.addEventListener('page:performer:any', performerPageHandler);
stash.addEventListener('page:performer:details:expanded', performerPageHandler);

async function studioPageHandler() {
const settings = await stash.getPluginConfig('stashStashboxSceneCount');
if (settings?.studios) {
const studio = await getStudio();
const data = await stash.getStashBoxes();
for (const { endpoint, stash_id } of studio.stash_ids) {
const sceneCount = await getStudioScenes(endpoint, settings?.includeSubsidiaryStudios);
const api_key = data.data.configuration.general.stashBoxes.find(o => o.endpoint = endpoint).api_key;
await runGetStashboxStudioSceneCountTask(endpoint, api_key, stash_id);
const stashBoxSceneCount = await stash.pollLogsForMessage(`[Plugin / Stash Stashbox Scene Count] ${stash_id}: `);
const el = getElementByXpath(`//span[@class='stash-id-pill']/a[text()='${stash_id}']`);
if (el) {
//el.innerText = `${stash_id} ${sceneCount}/${stashBoxSceneCount}`;
el.innerText = `${stash_id} ${stashBoxSceneCount}`;
}
}
}
}
stash.addEventListener('page:studio:any', studioPageHandler);
stash.addEventListener('page:studio:details:expanded', studioPageHandler);

})();
86 changes: 86 additions & 0 deletions plugins/stashStashboxSceneCount/stashStashboxSceneCount.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import json
import log
import sys
from stashbox_scene_counts import stashbox_performer_scene_count, stashbox_studio_scene_count
try:
from stashlib.stash_database import StashDatabase
from stashlib.stash_interface import StashInterface
except ModuleNotFoundError:
print("If you have pip (normally installed with python), run this command in a terminal (cmd): pip install pystashlib)", file=sys.stderr)
sys.exit()

json_input = json.loads(sys.stdin.read())
name = json_input['args']['name']

client = StashInterface(json_input["server_connection"])

def get_database_config():
result = client.callGraphQL("""query Configuration { configuration { general { databasePath, blobsPath, blobsStorage } } }""")
database_path = result["configuration"]["general"]["databasePath"]
blobs_path = result["configuration"]["general"]["blobsPath"]
blobs_storage = result["configuration"]["general"]["blobsStorage"]
log.debug(f"databasePath: {database_path}")
return database_path, blobs_path, blobs_storage

settings = client.callGraphQL("""query Configuration { configuration { plugins } }""")['configuration']['plugins']
if settings and 'stashStashboxSceneCount' in settings:
pluginSettings = settings['stashStashboxSceneCount']
else:
pluginSettings = {}

try:
db = StashDatabase(*get_database_config())
except Exception as e:
log.error(str(e))
sys.exit(0)

endpoint = json_input['args']['endpoint']
api_key = json_input['args']['api_key']
stash_id = json_input['args']['stash_id']

if name == 'stashbox_performer_scene_count':
log.debug(f"stashbox_performer_scene_count: endpoint={endpoint}, stash_id={stash_id}")
scene_count = stashbox_performer_scene_count(endpoint, api_key, stash_id)
# pluginSettings['performerCount'] = scene_count
# variables = {
# "plugin_id": "stashStashboxSceneCount",
# "input": pluginSettings
# }
# # log.debug(f"{stash_id}: {scene_count}")
# client.callGraphQL("""mutation ConfigurePlugin($plugin_id: ID!, $input: Map!) { configurePlugin(plugin_id: $plugin_id, input: $input) }""", variables)
database_scene_count = db.fetchone("""SELECT COUNT(DISTINCT b.stash_id)
FROM scenes a
JOIN scene_stash_ids b ON a.id = b.scene_id
JOIN performers_scenes c ON a.id = c.scene_id
JOIN performer_stash_ids d ON c.performer_id = d.performer_id
WHERE d.stash_id = ?""", (stash_id, ))[0]
log.debug(f"{stash_id}: {database_scene_count}/{scene_count}")
elif name == 'stashbox_studio_scene_count':
log.debug(f"stashbox_studio_scene_count: endpoint={endpoint}, stash_id={stash_id}")
include_subsidiary_studios = 'includeSubsidiaryStudios' in pluginSettings and pluginSettings['includeSubsidiaryStudios']
log.debug(f"include_subsidiary_studios: {include_subsidiary_studios}")
scene_count = stashbox_studio_scene_count(endpoint, api_key, stash_id, include_subsidiary_studios)
# pluginSettings['studioCount'] = scene_count
# variables = {
# "plugin_id": "stashStashboxSceneCount",
# "input": pluginSettings
# }
# # log.debug(f"{stash_id}: {scene_count}")
# client.callGraphQL("""mutation ConfigurePlugin($plugin_id: ID!, $input: Map!) { configurePlugin(plugin_id: $plugin_id, input: $input) }""", variables)
if not include_subsidiary_studios:
database_scene_count = db.fetchone("""SELECT COUNT(DISTINCT b.stash_id)
FROM scenes a
JOIN scene_stash_ids b ON a.id = b.scene_id
JOIN studio_stash_ids c ON c.studio_id = a.studio_id
WHERE c.stash_id = ?""", (stash_id, ))[0]
else:
database_scene_count = db.fetchone("""SELECT COUNT(DISTINCT b.stash_id)
FROM scenes a
JOIN scene_stash_ids b ON a.id = b.scene_id
JOIN studio_stash_ids c ON c.studio_id = a.studio_id
JOIN studios d ON d.id = c.studio_id
JOIN studio_stash_ids e ON e.studio_id = d.parent_id
WHERE e.stash_id = ?""", (stash_id, ))[0]
log.debug(f"{stash_id}: {database_scene_count}/{scene_count}")

db.close()
38 changes: 38 additions & 0 deletions plugins/stashStashboxSceneCount/stashStashboxSceneCount.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
name: Stash Stashbox Scene Count
# requires: stashUserscriptLibrary7dJx1qP
description: Adds stashbox scene counts to performers and studios
version: 0.1.0
ui:
requires:
- stashUserscriptLibrary7dJx1qP
javascript:
- stashStashboxSceneCount.js
settings:
performers:
displayName: Display scene count on performers page
type: BOOLEAN
studios:
displayName: Display scene count on studios page
type: BOOLEAN
includeSubsidiaryStudios:
displayName: Include subsidiary studios
type: BOOLEAN
exec:
- python
- "{pluginDir}/stashStashboxSceneCount.py"
interface: raw
tasks:
- name: Get Stashbox Performer Scene Count
description: Gets stashbox performer scene count
defaultArgs:
name: stashbox_performer_scene_count
endpoint: null
api_key: null
stash_id: null
- name: Get Stashbox Studio Scene Count
description: Gets stashbox studio scene count
defaultArgs:
name: stashbox_studio_scene_count
endpoint: null
api_key: null
stash_id: null
Loading

0 comments on commit e752e68

Please sign in to comment.