function ok() { return { output: "ok", }; } function main() { var hookContext = input.Args.hookContext; var type = hookContext.type; var ID = hookContext.ID; if (!ID) { return ok(); } var filenameFetcher; var saver; if (type === "Scene.Create.Post") { filenameFetcher = getSceneFilename; saver = updateScene; } else if (type === "Gallery.Create.Post") { filenameFetcher = getGalleryFilename; saver = updateGallery; } else { return ok(); } var filename = filenameFetcher(ID); if (!filename) { return ok(); } filename = cleanFilename(filename); var parseResult = parseFilename(filename); saver(ID, parseResult); return ok(); } function getSceneFilename(sceneID) { var query = "\ query findScene($id: ID) {\ findScene(id: $id) {\ files {\ path\ }\ }\ }"; var variables = { id: sceneID, }; var result = gql.Do(query, variables); var findScene = result.findScene; if (!findScene) { return null; } var path = findScene.files[0].path; return path.substring(path.lastIndexOf("/") + 1); } function updateScene(sceneID, parseResult) { var query = "\ mutation SceneUpdate($input: SceneUpdateInput!) {\ sceneUpdate(input: $input) {\ id\ }\ }"; var variables = { input: parseResult, }; variables.input.id = sceneID; gql.Do(query, variables); } function getGalleryFilename(galleryID) { var query = "\ query findGallery($id: ID!) {\ findGallery(id: $id) {\ files {\ path\ }\ }\ }"; var variables = { id: galleryID, }; var result = gql.Do(query, variables); var findGallery = result.findGallery; if (!findGallery) { return null; } var path = findGallery.files[0].path; return path.substring(path.lastIndexOf("/") + 1); } function updateGallery(galleryID, parseResult) { var query = "\ mutation GalleryUpdate($input: GalleryUpdateInput!) {\ galleryUpdate(input: $input) {\ id\ }\ }"; var variables = { input: parseResult, }; variables.input.id = galleryID; gql.Do(query, variables); } function matchNames(parts, name, aliases) { var names = [name].concat(aliases); var partRegexes = []; for (var i = 0; i < parts.length; i++) { partRegexes[i] = new RegExp("^" + parts[i].toLowerCase() + "[. \\-_]*"); } var cleanRegex = /[. \-_]/g; var longestMatch = 0; for (var i = 0; i < names.length; i++) { var name = names[i].replace(cleanRegex, "").toLowerCase(); for (var j = 0; j < partRegexes.length; j++) { if (!partRegexes[j].test(name)) { break; } name = name.replace(partRegexes[j], ""); if (name.length === 0) { if (j + 1 > longestMatch) { longestMatch = j + 1; } } } } return longestMatch; } function cleanFilename(name) { name = name // remove imageset-...[rarbg] .replace(/imageset-[\w\d]+\[rarbg]/i, "") // replace [...] with just ... .replace(/\[(.*?)]/g, "$1") // replace (...) with just ... .replace(/\((.*?)\)/g, "$1") // replace {...} with just ... .replace(/{(.*?)}/g, "$1"); var blockList = [ "mp4", "mov", "mkv", "zip", "cbz", "cbr", "xxx", "4k", "4096x2160", "3840x2160", "2160p", "1080p", "1920x1080", "60fps", "30fps", "repack", "ktr", ]; var regExp = new RegExp( "(_|[^\\w\\d]|^)(" + blockList.join("|") + ")(_|[^\\w\\d]|$)", "i" ); while (regExp.test(name)) { name = name.replace(regExp, "$1$3"); } // If name starts with <...>.com remove the .com (sometimes names include studio name as site/domain) name = name.replace(/^([\w\d-]+?)\.com/, "$1"); name = name // Remove everything except letters and digits at the start .replace(/^(_|[^\w\d])+/, "") // Remove everything except letters and digits at the end .replace(/(_|[^\w\d])+$/, ""); return name; } function matchStudio(parts, result) { var query = "\ query findStudios($studio_filter: StudioFilterType, $filter: FindFilterType!) {\ findStudios(studio_filter: $studio_filter, filter: $filter) {\ studios {\ id\ name\ aliases\ }\ }\ }"; var searchTerm = parts[0].substring(0, 2); if (parts[0].substring(0, 1) === "a") { searchTerm = parts[0].substring(1, 3); } var variables = { filter: { per_page: -1, }, studio_filter: { name: { modifier: "INCLUDES", value: searchTerm, }, OR: { aliases: { modifier: "INCLUDES", value: searchTerm, }, }, }, }; var queryResult = gql.Do(query, variables); var studios = queryResult.findStudios.studios; if (!studios.length && parts[0].substring(0, 1) === "a") { variables.studio_filter.name.value = variables.studio_filter.OR.aliases.value = parts[0].substring(1, 3); queryResult = gql.Do(query, variables); studios = queryResult.findStudios.studios; } var matchingParts = 0; for (var i = 0; i < studios.length; i++) { var studio = studios[i]; matchingParts = matchNames(parts, studio.name, studio.aliases); if (matchingParts === 0) { continue; } result.studio_id = studio.id; break; } return matchingParts; } function matchDate(parts, result) { if ( parts.length < 3 || !/^(\d{2}|\d{4})$/.test(parts[0]) || !/^\d{2}$/.test(parts[1]) || !/^\d{2}$/.test(parts[2]) ) { return 0; } var year = parseInt(parts[0], 10); var month = parseInt(parts[1], 10); var day = parseInt(parts[2], 10); if (year < 100) { year += 2000; } if ( year < 2000 || year > 2100 || month < 1 || month > 12 || day < 1 || day > 31 ) { return 0; } result.date = year + "-" + (month < 10 ? "0" + month : month) + "-" + (day < 10 ? "0" + day : day); return 3; } function matchPerformers(parts, result) { var query = "\ query findPerformers($performer_filter: PerformerFilterType, $filter: FindFilterType!) {\ findPerformers(performer_filter: $performer_filter, filter: $filter) {\ performers {\ id\ name\ alias_list\ }\ }\ }"; var variables = { filter: { per_page: -1, }, performer_filter: { name: { modifier: "INCLUDES", }, OR: { aliases: { modifier: "INCLUDES", }, }, }, }; var totalMatchingParts = 0; result.performer_ids = []; do { variables.performer_filter.name.value = variables.performer_filter.OR.aliases.value = parts[0].substring(0, 2); var queryResult = gql.Do(query, variables); var performers = queryResult.findPerformers.performers; if (!performers.length) { parts.shift(); continue; } var maxMatchLength = 0; var matches = []; for (var i = 0; i < performers.length; i++) { var performer = performers[i]; var aliases = performer.aliases ? performer.aliases.split(/\s*[,;]+\s*/) : []; var matchingParts = matchNames(parts, performer.name, aliases); if (matchingParts === 0) { continue; } if (matchingParts > maxMatchLength) { maxMatchLength = matchingParts; matches = [performer.id]; } else if (matchingParts === maxMatchLength) { matches.push(performer.id); } } if (maxMatchLength === 0) { break; } result.performer_ids = result.performer_ids.concat(matches); totalMatchingParts += maxMatchLength; parts = parts.slice(maxMatchLength); while ( parts.length > 0 && (parts[0].toLowerCase() === "and" || parts[0] === "&") ) { parts.shift(); totalMatchingParts += 1; } } while (parts.length > 0); return totalMatchingParts; } function parseFilename(name) { var parts = name.split(/[. \-_,]+/); var matchers = [matchStudio, matchDate, matchPerformers]; var result = {}; var hasMatched = false; for ( var matchTries = 0; matchTries < 3 && !hasMatched && parts.length; matchTries++ ) { for (var i = 0; i < matchers.length && parts.length > 0; i++) { var matchedParts = matchers[i](parts, result); if (matchedParts > 0) { hasMatched = true; parts = parts.slice(matchedParts); } } // If no matchers worked remove a part. Maybe the format is correct but studio isn't found? etc if (!hasMatched) { parts.shift(); } } if (hasMatched && parts.length > 0) { var title = parts.join(" "); // Look behind assertions are not supported, so can't use `replace(/(?<=.)([A-Z]/g, ' $1')` so instead have to do a loop. Otherwise for example 'FooABar' will become 'Foo ABar' instead of 'Foo A Bar' while (/[^\s][A-Z]/.test(title)) { title = title.replace(/([^\s])([A-Z])/g, "$1 $2"); } result.title = title.trim(); } return result; } main();