Add defaultDataForPath plugin (#53)

* Add defaultDataForPath plugin
This commit is contained in:
TheSinfulKing
2022-07-13 16:20:34 -07:00
committed by GitHub
parent d0c6a34914
commit a4ccb04af7
4 changed files with 611 additions and 0 deletions

View File

@@ -0,0 +1,151 @@
# Path Default Tags
Define default tags/performers/studio for Scenes, Images, and Galleries by file path.
Big thanks to @WithoutPants - I based this entire script off of yours (markerTagToScene) and learned about Stash plugins during the process :)
## Requirements
- Stash
## Installation
- Download the whole folder 'defaultDataForPath' (defaultDataForPath.js, defaultDataForPath.yml)
- Place it in your **plugins** folder
- Reload plugins (Settings > Plugins)
- Default Data For Path (1.0) should appear.
## Usage
- This plugin will execute on Tasks->Scan. Any new file added to Stash will be created with the specified data if configured.
## Configuration
- Edit **_jsonData_** array. Refer to Examples.
## Notes
- Remember to escape file paths!
- Note this script only works on NEWLY created Scenes/Images/Galleries. To run on existing content, the content will need to be removed from Stash and then rescanned.
- There is NO validation of tags/performers/studios being performed. They must exist in Stash and be spelled exactly the same to work. These values are not updated when they are updated in Stash. They will have to manually be configured. If there is a mismatch, an error will be logged and the affected file will not have any default data added. The Scan task will continue to execute however.
- If you misconfigure the script, the Task->Scan will complete and files will be created, but you can remove those files from Stash, fix the script, and try again.
- I recommend using VS Code but any text editor should do. I especially recommend an editor with collapse functionality as your config JSON grows.
- This requires a decent bit of manual effort and verbosity to configure, but only needs to be maintained after that.
- This may slow down your Task->Scan. This script is probably sloppily written, I was not sober when I wrote it, and haven't looked much at it since it works ¯\\\_(ツ)_/¯
## Examples
> Here is a simple config Object. It defines data for any Scene/Image/Gallery found within the paths listed (it includes all subfolders). Matching files will be assigned Studio **'Brazzers'** and Tag **'Default_Data_For_Path_Tagged'** assuming that Studio and Tag exist in Stash and are spelled this way.
<br>**_'name'_** is optional and not used by the script. Feel free to include it for the purposes of organization.
<br>**_'paths'_** is optional and defines what file paths the current Object config should apply to. If it is not included, then no files will be matched to this config, unless **_'children'_** is used, in which case, those files in **_'children'_** will be matched to this config. See next example.
<br>**_'studio'_** is optional and defines a Studio to apply to file matches. The Studio must exist in Stash and be spelled the same.
<br>**_'tags'_** is optional and defines Tags to apply to file matches. The Tags must exist in Stash and be spelled the same.
```
var jsonData = [
{
"name": "OPTIONAL NAME - NOT USED IN SCRIPT",
"paths": [
"C:\\Users\\UserName\\Desktop\\NOTPORN\\Brazzers",
"D:\\SecretStorage\\Porn\\Brazzers"
],
"studio": "Brazzers",
"tags": [
"Default_Data_For_Path_Tagged"
]
}
];
```
> This config introduces a new concept. Note the _'Instagram Root'_ config object has no paths. It defines a studio and then children. This means all child config object of this will recieve the Studio _'Instagram'_ (it will overwrite any child config object studio definitions if different). You may also specify Performers and Tags in this way, those will be appended to child config objects definitions. See the _'Celebrities_' config object is used in a similar way to add the tag _'PERFORMER - Celebrity_' to its underlying children (which also recieve the _Instagram_ studio as it is their ancestor). It saves you from having to add the tag to each config object seperately and allows for more logical config groupings to be created.
> If you also add a **_'paths'_** value to _'Instagram Root'_, then the data specified on _'Instagram Root'_ config object will be applied to files in that path as well. Data from children will not be carried over. For example, _'PornHub Root'_ applies studio PornHub to all files in **_"C:\\Users\\UserName\\Desktop\\Pornhub"_**, and has children objects with more specific config. Instagram Root does not have such a paths specification. So a file in path **_"C:\\Users\\UserName\\Desktop\\Pornhub\\SweetBunny"_** will have Studio PornHub added while a file in **_"C:\\Users\\UserName\\Desktop\\Instagram\\Kylie Jenner"_** will not have Studio Instagram added.
> So say a file is scanned that has file path **_"C:\\Users\\UserName\\Desktop\\Instagram\\alexisfawx\\video1.mp4"_**. The data added will be:
<br /> **Studio:** _Instagram_ - because the "Alexis Fawx" Config object is a descendant of the Instagram config object, and the scanned file matches "Alexis Fawx" Config object paths.
<br /> **Tag:** _ORGANIZED - Unorganized_ - because the scanned file matches "Default Tag - Matches all scanned files" Config object paths.
<br /> **Tag:** _PERFORMER - Pornstar_ - because the "Alexis Fawx" Config object is a child of the Pornstars config object, and the scanned file matches "Alexis Fawx" Config object paths.
<br /> **Tag:** _PERFORMER - Caucasian_ - beacause the scanned file matches "Alexis Fawx" Config object paths.
<br /> **Tag:** _PERFORMER - Fake Tits_ - beacause the scanned file matches "Alexis Fawx" Config object paths.
<br /> **Performer:** _Alexis Fawx_ - beacause the scanned file matches "Alexis Fawx" Config object paths.
<br />
```
var jsonData = [
{
"name": "Default Tag - Matches all scanned files",
"paths": [
""
],
"tags": [
"ORGANIZED - Unorganized"
]
},
{
"name": "Instagram Root",
"studio": "Instagram",
"children": [
{
"name": "Celebrities",
"tags": [
"PERFORMER - Celebrity"
],
"children": [
{
"name": "Kim Kardashian",
"paths": [
"C:\\Users\\UserName\\Desktop\\Instagram\\kimkardashian"
],
"performers": [
"Kim Kardashian"
],
"tags": [
"PERFORMER - Armenian",
"PERFORMER - Big Ass"
]
},
{
"name": "Katy Perry",
"paths": [
"C:\\Users\\UserName\\Desktop\\Instagram\\katyperry"
],
"performers": [
"Katy Perry"
],
"tags": [
"PERFORMER - Caucasian,
"PERFORMER - Big Tits"
]
}
]
},
{
"name": "Pornstars",
"tags": [
"PERFORMER - Pornstar
],
"children": [
{
"name": "Alexis Fawx",
"paths": [
"C:\\Users\\UserName\\Desktop\\Instagram\\alexisfawx"
],
"performers": [
"Alexis Fawx"
],
"tags": [
"PERFORMER - Caucasian",
"PERFORMER - Fake Tits"
]
}
]
}
]
},
{
"name": "PornHub Root",
"paths": [
"C:\\Users\\UserName\\Desktop\\PornHub"
]
"studio": "PornHub",
"children": [
(etc...)
]
}
];
```

View File

@@ -0,0 +1,444 @@
var jsonData = [
{
"name": "OPTIONAL NAME - NOT USED IN SCRIPT",
"paths": [
"C:\\Users\\UserName\\Desktop\\NOTPORN\\Brazzers",
"D:\\SecretStorage\\Porn\\Brazzers"
],
"studio": "Brazzers",
"tags": [
"Default_Data_For_Path_Tagged"
]
}
];
function ok() {
return {
output: "ok"
};
}
function main() {
var hookContext = input.Args.hookContext;
var type = hookContext.type;
var ID = hookContext.ID;
if (!type || !ID) {
// just return
return ok();
}
var itemPath;
var name = "";
if (type === 'Scene.Create.Post') {
itemPath = getScenePath(ID);
name = "scene"
} else if (type === 'Gallery.Create.Post') {
itemPath = getGalleryPath(ID);
name = "gallery"
} else if(type === 'Image.Create.Post') {
itemPath = getImagePath(ID);
name = "image"
}
var defaultData = getDefaultData(itemPath)
// create tags
var defaultTags = [];
for(var p=0; p<defaultData.length; p++) {
var tags = defaultData[p].tags;
if(tags) {
for(var t=0; t<tags.length; t++) {
var tag = tags[t]
if(stringNotAlreadyPresent(tag, defaultTags))
defaultTags.push(tag)
}
}
}
// create studio
var addStudio = false;
var defaultStudioId = null;
var defaultStudio;
for(var p=0; p<defaultData.length; p++) {
var studio = defaultData[p].studio;
if(studio) {
var studioId = getStudioId(studio)
if(studioId) {
defaultStudioId = studioId;
addStudio = true;
defaultStudio = studio;
}
}
}
// create performers
var defaultPerformers = [];
for(var p=0; p<defaultData.length; p++) {
var performers = defaultData[p].performers;
if(performers) {
for(var t=0; t<performers.length; t++) {
var performer = performers[t];
if(stringNotAlreadyPresent(performer, defaultPerformers))
defaultPerformers.push(performer)
}
}
}
// convert tags to tagIds
var addTags = false;
var defaultTagIds = [];
if(defaultTags) {
for(var i=defaultTags.length-1; i>=0; i--) {
var tagId = getTagId(defaultTags[i])
tagId ? defaultTagIds.push(tagId) : defaultTags.pop();
}
if(defaultTagIds && defaultTagIds.length != 0) {
addTags = true;
}
}
// convert performers to performerIds
var addPerformers = false;
var defaultPerformerIds = [];
if(defaultPerformers) {
for(var i=defaultPerformers.length-1; i>=0; i--) {
var tagId = getPerformerId(defaultPerformers[i])
tagId ? defaultPerformerIds.push(tagId) : defaultPerformers.pop();
}
if(defaultPerformerIds && defaultPerformerIds.length != 0) {
addPerformers = true;
}
}
// Apply all and log
var tags = addTags ? defaultTagIds : null;
var studio = addStudio ? defaultStudioId : null;
var performers = addPerformers ? defaultPerformerIds : null;
if (type === 'Scene.Create.Post') {
setSceneData(ID, tags, studio, performers)
} else if (type === 'Gallery.Create.Post') {
setGalleryData(ID, tags, studio, performers)
} else if(type === 'Image.Create.Post') {
setImageData(ID, tags, studio, performers)
}
for(var o=0;o<defaultTags.length;o++) {
log.Info("[DefaultDataForPath]: Added tag " + defaultTags[o] + " to " + name + " " + ID);
}
for(var o=0;o<defaultPerformers.length;o++) {
log.Info("[DefaultDataForPath]: Added performer " + defaultPerformers[o] + " to " + name + " " + ID);
}
addStudio ? log.Info("[DefaultDataForPath]: Added studio " + defaultStudio + " to " + name + " " + ID) : "";
}
function getScenePath(ID) {
var query = "\
query findScene($id: ID) {\
findScene(id: $id) {\
path\
}\
}";
var variables = {
id: ID
};
var result = gql.Do(query, variables);
var findScene = result.findScene;
if (!findScene) {
return null;
}
var path = findScene.path;
return path;
}
function getImagePath(ID) {
var query = "\
query findImage($id: ID) {\
findImage(id: $id) {\
path\
}\
}";
var variables = {
id: ID
};
var result = gql.Do(query, variables);
var findImage = result.findImage;
if (!findImage) {
return null;
}
var path = findImage.path;
return path;
}
function getGalleryPath(ID) {
var query = "\
query findGallery($id: ID) {\
findGallery(id: $id) {\
path\
}\
}";
var variables = {
id: ID
};
var result = gql.Do(query, variables);
var findGallery = result.findGallery;
if (!findGallery) {
return null;
}
var path = findGallery.path;
return path;
}
function getTagId(tagName) {
var query = "\
query findTagId($filter: FindFilterType!) {\
findTags(filter: $filter) {\
tags {\
id\
}\
}\
}";
var variables = {
filter: {
q: tagName
}
};
result = gql.Do(query, variables)
if(result.findTags.tags[0]) {
return result.findTags.tags[0].id;
}
else {
log.Error("TAG " + tagName + " DOES NOT EXIST IN STASH!")
return null;
}
}
function getPerformerId(performerName) {
var query = "\
query findPerformerId($filter: FindFilterType!) {\
findPerformers(filter: $filter) {\
performers {\
id\
}\
}\
}";
var variables = {
filter: {
q: performerName
}
};
result = gql.Do(query, variables)
if(result.findPerformers.performers[0]) {
return result.findPerformers.performers[0].id;
}
else {
log.Error("PERFORMER " + performerName + " DOES NOT EXIST IN STASH!")
return null;
}
}
function getStudioId(studioName) {
var query = "\
query findStudioId($filter: FindFilterType!) {\
findStudios(filter: $filter) {\
studios {\
id\
}\
}\
}";
var variables = {
filter: {
q: studioName
}
};
result = gql.Do(query, variables)
if(result.findStudios.studios[0]) {
return result.findStudios.studios[0].id
}
else {
log.Error("STUDIO " + studioName + " DOES NOT EXIST IN STASH!")
return null;
}
}
function stringNotAlreadyPresent(text, itemArray) {
for(var i=0;i < itemArray.length; i++) {
if(itemArray[i] === text) {
return false;
}
}
return true;
}
function addAllData(obj, lowerItemPath, defaultData, pTags, pPerformers, pStudio) {
if(obj) {
if(obj.paths) {
var paths = obj.paths;
if(containsPath(paths, lowerItemPath)) {
// inject data from parent if avail
if(pTags) {
obj.tags = obj.tags.concat(pTags)
}
if(pPerformers) {
obj.performers = obj.performers.concat(pPerformers)
}
if(pStudio) {
obj.studio = pStudio
}
defaultData.push(obj)
}
}
else {
// add defaults to children
if(obj.tags) {
if(!pTags) {
pTags = obj.tags;
}
else {
pTags = pTags.concat(obj.tags)
}
}
if(obj.performers) {
if(!pPerformers) {
pPerformers = obj.performers
}
else {
pPerformers = pPerformers.concat(obj.performers)
}
}
if(obj.studio) {
pStudio = obj.studio;
}
}
if(obj.children) {
for(var o=0;o<obj.children.length;o++) {
defaultData = addAllData(obj.children[o], lowerItemPath, defaultData, pTags, pPerformers, pStudio)
}
}
}
return defaultData
}
function getDefaultData(itemPath) {
var defaultData = [];
var lowerItemPath = itemPath.toLowerCase();
for(var i=0;i<jsonData.length;i++) {
var obj = jsonData[i];
defaultData = addAllData(obj, lowerItemPath, defaultData, null, null, null)
}
return defaultData;
}
function containsPath(paths, inputPath) {
for(var p=0;p<paths.length;p++) {
var path = paths[p].toLowerCase() + '';
if(stringContains(inputPath, path)) {
log.Info("[DefaultDataForPath]: " + inputPath + " MATCH " + path)
return true;
}
}
return false;
}
function stringContains(value, searchFor)
{
var v = (value || '').toLowerCase();
var v2 = searchFor;
if (v2) {
v2 = v2.toLowerCase();
}
return v.indexOf(v2) > -1;
}
function containsElem(items, elem) {
for(var i=0;i<items.length;i++) {
var item = items[i].toLowerCase();
if(item.equals(elem)) {
return true;
}
}
return false;
}
function setSceneData(sceneID, tagIDs, studioID, performerIds) {
var mutation = "\
mutation sceneUpdate($input: SceneUpdateInput!) {\
sceneUpdate(input: $input) {\
id\
}\
}";
var variables = {
input: {
id: sceneID,
tag_ids: tagIDs,
studio_id: studioID,
performer_ids: performerIds
}
};
gql.Do(mutation, variables);
}
function setImageData(sceneID, tagIDs, studioID, performerIds) {
var mutation = "\
mutation imageUpdate($input: ImageUpdateInput!) {\
imageUpdate(input: $input) {\
id\
}\
}";
var variables = {
input: {
id: sceneID,
tag_ids: tagIDs,
studio_id: studioID,
performer_ids: performerIds
}
};
gql.Do(mutation, variables);
}
function setGalleryData(sceneID, tagIDs, studioID, performerIds) {
var mutation = "\
mutation galleryUpdate($input: GalleryUpdateInput!) {\
galleryUpdate(input: $input) {\
id\
}\
}";
var variables = {
input: {
id: sceneID,
tag_ids: tagIDs,
studio_id: studioID,
performer_ids: performerIds
}
};
gql.Do(mutation, variables);
}
main();

View File

@@ -0,0 +1,15 @@
# example plugin config
name: Default Data For Path
description: Adds configured Tags, Performers and/or Studio to all newly scanned Scenes, Images and Galleries.
url: https://github.com/stashapp/CommunityScripts
version: 1.0
exec:
- defaultDataForPath.js
interface: js
hooks:
- name: Add Configured Data on Scan
description: Adds configured tags/performers/studio on Task->Scan creation.
triggeredBy:
- Scene.Create.Post
- Gallery.Create.Post
- Image.Create.Post