mirror of
https://github.com/stashapp/CommunityScripts.git
synced 2025-12-13 20:46:08 -06:00
667 lines
14 KiB
JavaScript
667 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 = [];
|
|
var subMatches;
|
|
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 null;
|
|
}
|
|
|
|
return result.findTags.tags[0].id;
|
|
}
|
|
|
|
var DEBUG = false;
|
|
var BASE_PATHS = [];
|
|
var bufferedOutput = "";
|
|
main();
|