// 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();