mirror of
https://github.com/stashapp/CommunityScripts.git
synced 2025-12-14 22:04:53 -06:00
748 lines
14 KiB
JavaScript
748 lines
14 KiB
JavaScript
// Common Patterns
|
|
var patterns = {
|
|
movieTitleAndYear: /(.+) \(\d{4}\)/,
|
|
sceneTitleAndPerformers: /(.+) - ([A-zÀ-ú, ]+)/
|
|
}
|
|
|
|
var rules = [
|
|
{
|
|
name: 'Rule 1',
|
|
pattern: [
|
|
'Specific Studio',
|
|
null,
|
|
null
|
|
],
|
|
fields: {
|
|
studio: '#0',
|
|
title: '#2',
|
|
}
|
|
},
|
|
{
|
|
name: 'Rule 2',
|
|
pattern: [
|
|
['One Studio', 'Another Studio'],
|
|
patterns.movieTitleAndYear,
|
|
patterns.sceneTitleAndPerformers
|
|
],
|
|
fields: {
|
|
title: '#2',
|
|
studio: '#0',
|
|
performers: '#3'
|
|
}
|
|
},
|
|
];
|
|
|
|
/* ----------------------------------------------------------------------------
|
|
// DO NOT EDIT BELOW!
|
|
---------------------------------------------------------------------------- */
|
|
function main()
|
|
{
|
|
try
|
|
{
|
|
switch (getTask(input.Args))
|
|
{
|
|
case 'createTags':
|
|
var runTag = getArg(input.Args, 'runTag');
|
|
var testTag = getArg(input.Args, 'testTag');
|
|
createTags([runTag, testTag]);
|
|
break;
|
|
|
|
case 'removeTags':
|
|
var runTag = getArg(input.Args, 'runTag');
|
|
var testTag = getArg(input.Args, 'testTag');
|
|
removeTags([runTag, testTag]);
|
|
break;
|
|
|
|
case 'runRules':
|
|
var runTag = getArg(input.Args, 'runTag');
|
|
initBasePaths();
|
|
runRules(runTag);
|
|
break;
|
|
|
|
case 'testRules':
|
|
DEBUG = true;
|
|
var testTag = getArg(input.Args, 'testTag');
|
|
initBasePaths();
|
|
runRules(testTag);
|
|
break;
|
|
|
|
case 'scene':
|
|
var id = getId(input.Args);
|
|
initBasePaths();
|
|
matchRuleWithSceneId(id, applyRule);
|
|
break;
|
|
|
|
case 'image':
|
|
var id = getId(input.Args);
|
|
initBasePaths();
|
|
break;
|
|
|
|
default:
|
|
throw 'Unsupported task';
|
|
}
|
|
}
|
|
catch (e)
|
|
{
|
|
return { Output: 'error', Error: e };
|
|
}
|
|
|
|
return { Output: 'ok' };
|
|
}
|
|
|
|
// Get an input arg
|
|
function getArg(inputArgs, arg)
|
|
{
|
|
if (inputArgs.hasOwnProperty(arg))
|
|
{
|
|
return inputArgs[arg];
|
|
}
|
|
|
|
throw 'Input is missing ' + arg;
|
|
}
|
|
|
|
// Determine task based on input args
|
|
function getTask(inputArgs)
|
|
{
|
|
if (inputArgs.hasOwnProperty('task'))
|
|
{
|
|
return inputArgs.task;
|
|
}
|
|
|
|
if (!inputArgs.hasOwnProperty('hookContext'))
|
|
{
|
|
return;
|
|
}
|
|
|
|
switch (inputArgs.hookContext.type)
|
|
{
|
|
case 'Scene.Create.Post':
|
|
return 'scene';
|
|
|
|
case 'Image.Create.Post':
|
|
return 'image';
|
|
}
|
|
}
|
|
|
|
// Get stash paths from configuration
|
|
function initBasePaths()
|
|
{
|
|
var query ='\
|
|
query Query {\
|
|
configuration {\
|
|
general {\
|
|
stashes {\
|
|
path\
|
|
}\
|
|
}\
|
|
}\
|
|
}';
|
|
|
|
var result = gql.Do(query);
|
|
if (!result.configuration)
|
|
{
|
|
throw 'Unable to get library paths';
|
|
}
|
|
|
|
BASE_PATHS = result.configuration.general.stashes.map(function (stash)
|
|
{
|
|
return stash.path;
|
|
});
|
|
|
|
if (BASE_PATHS == null || BASE_PATHS.length == 0)
|
|
{
|
|
throw 'Unable to get library paths';
|
|
}
|
|
}
|
|
|
|
// Create tag if it does not already exist
|
|
function createTags(tags)
|
|
{
|
|
var query ='\
|
|
mutation TagCreate($input: TagCreateInput!) {\
|
|
tagCreate(input: $input) {\
|
|
id\
|
|
}\
|
|
}';
|
|
|
|
tags.forEach(function (tag)
|
|
{
|
|
if (tryGetTag(tag) !== null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var variables = {
|
|
input: {
|
|
name: tag
|
|
}
|
|
};
|
|
|
|
var result = gql.Do(query, variables);
|
|
if (!result.tagCreate)
|
|
{
|
|
throw 'Could not create tag ' + tag;
|
|
}
|
|
});
|
|
}
|
|
|
|
// Remove tags if it already exists
|
|
function removeTags(tags)
|
|
{
|
|
tags.forEach(function (tag)
|
|
{
|
|
var tagId = tryGetTag(tag);
|
|
if (tagId === null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var query = '\
|
|
mutation TagsDestroy($ids: [ID!]!) {\
|
|
tagsDestroy(ids: $ids)\
|
|
}';
|
|
|
|
var variables = {
|
|
ids: [ tagId ]
|
|
};
|
|
|
|
var result = gql.Do(query, variables);
|
|
if (!result.tagsDestroy)
|
|
{
|
|
throw 'Unable to remove tag ' + tag;
|
|
}
|
|
});
|
|
}
|
|
|
|
// Run rules for scenes containing tag
|
|
function runRules(tag)
|
|
{
|
|
var tagId = tryGetTag(tag);
|
|
if (tagId === null)
|
|
{
|
|
throw 'Tag ' + tag + ' does not exist';
|
|
}
|
|
|
|
var query = '\
|
|
query FindScenes($sceneFilter: SceneFilterType) {\
|
|
findScenes(scene_filter: $sceneFilter) {\
|
|
scenes {\
|
|
id\
|
|
}\
|
|
}\
|
|
}';
|
|
|
|
var variables = {
|
|
sceneFilter: {
|
|
tags: {
|
|
value: tagId,
|
|
modifier: 'INCLUDES'
|
|
}
|
|
}
|
|
};
|
|
|
|
var result = gql.Do(query, variables);
|
|
if (!result.findScenes || result.findScenes.scenes.length == 0)
|
|
{
|
|
throw 'No scenes found with tag ' + tag;
|
|
}
|
|
|
|
result.findScenes.scenes.forEach(function (scene)
|
|
{
|
|
matchRuleWithSceneId(scene.id, applyRule);
|
|
});
|
|
}
|
|
|
|
// Get scene/image id from input args
|
|
function getId(inputArgs)
|
|
{
|
|
if ((id = inputArgs.hookContext.id) == null)
|
|
{
|
|
throw 'Input is missing id';
|
|
}
|
|
|
|
return id;
|
|
}
|
|
|
|
// Apply callback function to first matching rule for id
|
|
function matchRuleWithSceneId(sceneId, cb)
|
|
{
|
|
var query = '\
|
|
query FindScene($findSceneId: ID) {\
|
|
findScene(id: $findSceneId) {\
|
|
files {\
|
|
path\
|
|
}\
|
|
}\
|
|
}';
|
|
|
|
var variables = {
|
|
findSceneId: sceneId
|
|
}
|
|
|
|
var result = gql.Do(query, variables);
|
|
if (!result.findScene || result.findScene.files.length == 0)
|
|
{
|
|
throw 'Missing scene for id: ' + sceneId;
|
|
}
|
|
|
|
for (var i = 0; i < result.findScene.files.length; i++)
|
|
{
|
|
try
|
|
{
|
|
matchRuleWithPath(sceneId, result.findScene.files[i].path, cb);
|
|
|
|
if (DEBUG && bufferedOutput !== null && bufferedOutput !== '')
|
|
{
|
|
log.Info('[PathParser] ' + bufferedOutput);
|
|
}
|
|
|
|
return;
|
|
}
|
|
catch (e)
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (DEBUG && bufferedOutput !== null && bufferedOutput !== '')
|
|
{
|
|
log.Info('[PathParser] ' + bufferedOutput);
|
|
}
|
|
|
|
throw 'No rule matches id: ' + sceneId;
|
|
}
|
|
|
|
// Apply callback to first matching rule for path
|
|
function matchRuleWithPath(sceneId, path, cb)
|
|
{
|
|
// Remove base path
|
|
for (var i = 0; i < BASE_PATHS.length; i++)
|
|
{
|
|
if (path.slice(0, BASE_PATHS[i].length) === BASE_PATHS[i])
|
|
{
|
|
path = path.slice(BASE_PATHS[i].length);
|
|
}
|
|
}
|
|
|
|
if (DEBUG)
|
|
{
|
|
bufferedOutput = path + '\n';
|
|
}
|
|
|
|
// Split paths into parts
|
|
var parts = path.split(/[\\/]/);
|
|
|
|
// Remove extension from filename
|
|
parts[parts.length - 1] = parts[parts.length - 1].slice(0, parts[parts.length - 1].lastIndexOf('.'));
|
|
|
|
for (var i = 0; i < rules.length; i++)
|
|
{
|
|
var sceneData = testRule(rules[i].pattern, parts);
|
|
if (sceneData !== null)
|
|
{
|
|
if (DEBUG)
|
|
{
|
|
bufferedOutput += 'Rule: ' + rules[i].name + '\n';
|
|
}
|
|
|
|
log.Debug('[PathParser] Rule: ' + rules[i].name + '\nPath: ' + path);
|
|
cb(sceneId, rules[i].fields, sceneData);
|
|
return;
|
|
}
|
|
}
|
|
|
|
bufferedOutput += 'No matching rule!';
|
|
throw 'No matching rule for path: ' + path;
|
|
}
|
|
|
|
// Test single rule
|
|
function testRule(pattern, parts)
|
|
{
|
|
if (pattern.length !== parts.length)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
var matchedParts = [];
|
|
for (var i = 0; i < pattern.length; i++)
|
|
{
|
|
if ((subMatches = testPattern(pattern[i], parts[i])) == null)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
matchedParts = [].concat(matchedParts, subMatches);
|
|
}
|
|
|
|
return matchedParts;
|
|
}
|
|
|
|
function testPattern(pattern, part)
|
|
{
|
|
// Match anything
|
|
if (pattern == null)
|
|
{
|
|
return [part];
|
|
}
|
|
|
|
// Simple match
|
|
if (typeof pattern === 'string')
|
|
{
|
|
if (pattern === part)
|
|
{
|
|
return [part];
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
// Predicate match
|
|
if (typeof pattern == 'function')
|
|
{
|
|
try
|
|
{
|
|
var results = pattern(part);
|
|
if (results !== null)
|
|
{
|
|
return results;
|
|
}
|
|
}
|
|
catch (e)
|
|
{
|
|
throw e;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
// Array match
|
|
if (pattern instanceof Array)
|
|
{
|
|
for (var i = 0; i < pattern.length; i++)
|
|
{
|
|
if ((results = testPattern(pattern[i], part)) != null)
|
|
{
|
|
return results;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
// RegExp match
|
|
if (pattern instanceof RegExp)
|
|
{
|
|
var results = pattern.exec(part);
|
|
if (results === null)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
return results.slice(1);
|
|
}
|
|
}
|
|
|
|
// Apply rule
|
|
function applyRule(sceneId, fields, data)
|
|
{
|
|
var any = false;
|
|
var variables = {
|
|
input: {
|
|
id: sceneId
|
|
}
|
|
};
|
|
|
|
if (DEBUG)
|
|
{
|
|
for (var i = 0; i < data.length; i++)
|
|
{
|
|
bufferedOutput += '#' + i + ': ' + data[i] + '\n';
|
|
}
|
|
}
|
|
|
|
for (var field in fields)
|
|
{
|
|
var value = fields[field];
|
|
for (var i = data.length - 1; i >= 0; i--)
|
|
{
|
|
value = value.replace('#' + i, data[i]);
|
|
}
|
|
|
|
switch (field)
|
|
{
|
|
case 'title':
|
|
if (DEBUG)
|
|
{
|
|
bufferedOutput += field + ': ' + value + '\n';
|
|
}
|
|
|
|
variables.input['title'] = value;
|
|
any = true;
|
|
continue;
|
|
|
|
case 'studio':
|
|
var studioId = tryGetStudio(value);
|
|
if (studioId == null)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (DEBUG)
|
|
{
|
|
bufferedOutput += field + ': ' + value + '\n';
|
|
bufferedOutput += 'studio_id: ' + studioId + '\n';
|
|
}
|
|
|
|
variables.input['studio_id'] = studioId;
|
|
any = true;
|
|
continue;
|
|
|
|
case 'movie_title':
|
|
var movie_title = value.split(' ').join('[\\W]*');
|
|
var movieId = tryGetMovie(movie_title);
|
|
if (movieId == null)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (!variables.input.hasOwnProperty('movies'))
|
|
{
|
|
variables.input['movies'] = [{}];
|
|
}
|
|
|
|
if (DEBUG)
|
|
{
|
|
bufferedOutput += field + ': ' + value + '\n';
|
|
bufferedOutput += 'movie_id: ' + movieId + '\n';
|
|
}
|
|
|
|
variables.input['movies'][0]['movie_id'] = movieId;
|
|
any = true;
|
|
continue;
|
|
|
|
case 'scene_index':
|
|
var sceneIndex = parseInt(value);
|
|
if (isNaN(sceneIndex))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (!variables.input.hasOwnProperty('movies'))
|
|
{
|
|
variables.input['movies'] = [{}];
|
|
}
|
|
|
|
if (DEBUG)
|
|
{
|
|
bufferedOutput += 'scene_index: ' + sceneIndex + '\n';
|
|
}
|
|
|
|
variables.input['movies'][0]['scene_index'] = sceneIndex;
|
|
continue;
|
|
|
|
case 'performers':
|
|
var performers = value.split(',').map(tryGetPerformer).filter(notNull);
|
|
if (performers.length == 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (DEBUG)
|
|
{
|
|
bufferedOutput += field + ': ' + value + '\n';
|
|
bufferedOutput += 'performer_ids: ' + performers.join(', ') + '\n';
|
|
}
|
|
|
|
variables.input['performer_ids'] = performers;
|
|
any = true;
|
|
continue;
|
|
|
|
case 'tags':
|
|
var tags = value.split(',').map(tryGetTag).filter(notNull);
|
|
if (tags.length == 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (DEBUG)
|
|
{
|
|
bufferedOutput += field + ': ' + value + '\n';
|
|
bufferedOutput += 'tag_ids: ' + tags.join(', ') + '\n';
|
|
}
|
|
|
|
variables.input['tag_ids'] = tags;
|
|
any = true;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// Test only
|
|
if (DEBUG)
|
|
{
|
|
if (!any)
|
|
{
|
|
bufferedOutput += 'No fields to update!\n';
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
// Remove movies if movie_id is missing
|
|
if (variables.input.hasOwnProperty('movies') && !variables.input['movies'][0].hasOwnProperty('movie_id'))
|
|
{
|
|
delete variables.input['movies'];
|
|
}
|
|
|
|
// Apply updates
|
|
var query = '\
|
|
mutation Mutation($input: SceneUpdateInput!) {\
|
|
sceneUpdate(input: $input) {\
|
|
id\
|
|
}\
|
|
}';
|
|
|
|
if (!any)
|
|
{
|
|
throw 'No fields to update for scene ' + sceneId;
|
|
}
|
|
|
|
var result = gql.Do(query, variables);
|
|
if (!result.sceneUpdate)
|
|
{
|
|
throw 'Unable to update scene ' + sceneId;
|
|
}
|
|
}
|
|
|
|
// Returns true for not null elements
|
|
function notNull(ele)
|
|
{
|
|
return ele != null;
|
|
}
|
|
|
|
// Get studio id from studio name
|
|
function tryGetStudio(studio)
|
|
{
|
|
var query = '\
|
|
query FindStudios($studioFilter: StudioFilterType) {\
|
|
findStudios(studio_filter: $studioFilter) {\
|
|
studios {\
|
|
id\
|
|
}\
|
|
count\
|
|
}\
|
|
}';
|
|
|
|
var variables = {
|
|
studioFilter: {
|
|
name: {
|
|
value: studio.trim(),
|
|
modifier: 'EQUALS'
|
|
}
|
|
}
|
|
};
|
|
|
|
var result = gql.Do(query, variables);
|
|
if (!result.findStudios || result.findStudios.count == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
return result.findStudios.studios[0].id;
|
|
}
|
|
|
|
function tryGetMovie(movie_title)
|
|
{
|
|
var query = '\
|
|
query FindMovies($movieFilter: MovieFilterType) {\
|
|
findMovies(movie_filter: $movieFilter) {\
|
|
movies {\
|
|
id\
|
|
}\
|
|
count\
|
|
}\
|
|
}';
|
|
|
|
var variables = {
|
|
movieFilter: {
|
|
name: {
|
|
value: movie_title.trim(),
|
|
modifier: 'MATCHES_REGEX'
|
|
}
|
|
}
|
|
};
|
|
|
|
var result = gql.Do(query, variables);
|
|
if (!result.findMovies || result.findMovies.count == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
return result.findMovies.movies[0].id;
|
|
}
|
|
|
|
// Get performer id from performer name
|
|
function tryGetPerformer(performer)
|
|
{
|
|
var query = '\
|
|
query FindPerformers($performerFilter: PerformerFilterType) {\
|
|
findPerformers(performer_filter: $performerFilter) {\
|
|
performers {\
|
|
id\
|
|
}\
|
|
count\
|
|
}\
|
|
}';
|
|
|
|
var variables = {
|
|
performerFilter: {
|
|
name: {
|
|
value: performer.trim(),
|
|
modifier: 'EQUALS'
|
|
}
|
|
}
|
|
};
|
|
|
|
var result = gql.Do(query, variables);
|
|
if (!result.findPerformers || result.findPerformers.count == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
return result.findPerformers.performers[0].id;
|
|
}
|
|
|
|
// Get tag id from tag name
|
|
function tryGetTag(tag)
|
|
{
|
|
var query ='\
|
|
query FindTags($tagFilter: TagFilterType) {\
|
|
findTags(tag_filter: $tagFilter) {\
|
|
tags {\
|
|
id\
|
|
}\
|
|
count\
|
|
}\
|
|
}';
|
|
|
|
var variables = {
|
|
tagFilter: {
|
|
name: {
|
|
value: tag.trim(),
|
|
modifier: 'EQUALS'
|
|
}
|
|
}
|
|
};
|
|
|
|
var result = gql.Do(query, variables);
|
|
if (!result.findTags || result.findTags.count == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
return result.findTags.tags[0].id;
|
|
}
|
|
|
|
var DEBUG = false;
|
|
var BASE_PATHS = [];
|
|
var bufferedOutput = '';
|
|
main(); |