diff --git a/plugins/VideoScrollWheel/VideoScrollWheel.yml b/plugins/VideoScrollWheel/VideoScrollWheel.yml index bd54484..bc978a4 100644 --- a/plugins/VideoScrollWheel/VideoScrollWheel.yml +++ b/plugins/VideoScrollWheel/VideoScrollWheel.yml @@ -1,6 +1,6 @@ name: VideoScrollWheel +# requires: CommunityScriptsUILibrary description: Adds functionality to change volume/time in scene video player by hovering over left/right side of player and scrolling with mouse scrollwheel. Scroll while hovering on left side to adjust volume, scroll on right side to skip forward/back. -#requires: CommunityScriptsUILibrary version: 0.2 settings: allowVolumeChange: diff --git a/plugins/defaultDataForPath/defaultDataForPath.js b/plugins/defaultDataForPath/defaultDataForPath.js index 4318c42..13e47e8 100644 --- a/plugins/defaultDataForPath/defaultDataForPath.js +++ b/plugins/defaultDataForPath/defaultDataForPath.js @@ -19,7 +19,7 @@ function ok() { function main() { var hookContext = input.Args.hookContext; var type = hookContext.type; - var ID = hookContext.ID; + var ID = hookContext.id; if (!type || !ID) { // just return @@ -183,7 +183,11 @@ function getImagePath(ID) { "\ query findImage($id: ID) {\ findImage(id: $id) {\ - path\ + visual_files {\ + ... on ImageFile {\ + path\ + }\ + }\ }\ }"; @@ -197,7 +201,7 @@ query findImage($id: ID) {\ return null; } - var path = findImage.path; + var path = findImage["visual_files"][0].path; return path; } @@ -241,7 +245,7 @@ function getTagId(tagName) { }, }; - result = gql.Do(query, variables); + var result = gql.Do(query, variables); if (result.findTags.tags[0]) { return result.findTags.tags[0].id; } else { @@ -267,7 +271,7 @@ function getPerformerId(performerName) { }, }; - result = gql.Do(query, variables); + var result = gql.Do(query, variables); if (result.findPerformers.performers[0]) { return result.findPerformers.performers[0].id; } else { @@ -293,7 +297,7 @@ function getStudioId(studioName) { }, }; - result = gql.Do(query, variables); + var result = gql.Do(query, variables); if (result.findStudios.studios[0]) { return result.findStudios.studios[0].id; } else { diff --git a/plugins/discordPresence/discordPresence.yml b/plugins/discordPresence/discordPresence.yml index 67de7f6..1889e3c 100644 --- a/plugins/discordPresence/discordPresence.yml +++ b/plugins/discordPresence/discordPresence.yml @@ -1,7 +1,7 @@ name: Discord Presence description: Sets currently playing scene data as your Discord status. See README for prerequisites and config options (blue hyperlink next to enable/disable button) url: https://github.com/stashapp/CommunityScripts/tree/main/plugins/discordPresence -#requires: CommunityScriptsUILibrary +# requires: CommunityScriptsUILibrary version: 1.0 settings: discordClientId: diff --git a/plugins/markerTagToScene/markerTagToScene.js b/plugins/markerTagToScene/markerTagToScene.js index 2010672..c256adf 100644 --- a/plugins/markerTagToScene/markerTagToScene.js +++ b/plugins/markerTagToScene/markerTagToScene.js @@ -4,28 +4,139 @@ function ok() { }; } -function main() { - var hookContext = input.Args.hookContext; - var opInput = hookContext.Input; - var primaryTagID = opInput.PrimaryTagID; - var sceneID = opInput.SceneID; +function includes(haystack, needle) { + for (var i = 0; i < haystack.length; ++i) { + if (haystack[i] == needle) return true; + } + return false; +} + +function mapSceneTagsToIds(sceneTags) { + var ret = []; + for (var i = 0; i < sceneTags.length; ++i) { + ret.push(sceneTags[i].id); + } + return ret; +} + +function shouldHandleAllTags() { + var query = + "query Query {\ + configuration {\ + plugins\ + }\ +}"; + + var result = gql.Do(query); + //log.Info("Config is " + JSON.stringify(result.configuration)); + if (!result.configuration) { + throw "Unable to get library paths"; + } + if (result.configuration.plugins.hasOwnProperty("markerTagToScene")) { + //log.Info("allTags is " + result.configuration.plugins.markerTagToScene.allTags); + return !!result.configuration.plugins.markerTagToScene.allTags; + } else { + //log.Info("all tags wasn't found. defaulting to false"); + return false; + } +} + +function processMarker(marker, shouldHandleAllTags) { + log.Debug("processMarker (allTags = " + shouldHandleAllTags + ") " + marker); + var primaryTagID = marker.primary_tag_id; + var sceneID = marker.scene_id; + + var tagIDsToCheck = []; + if (primaryTagID != null) tagIDsToCheck.push(primaryTagID); + + if (shouldHandleAllTags && marker.tag_ids != null) + tagIDsToCheck = tagIDsToCheck.concat(marker.tag_ids); // we can't currently find scene markers. If it's not in the input // then just return - if (!primaryTagID || !sceneID) { + if (tagIDsToCheck.length == 0) { // just return return ok(); } // get the existing scene tags - var sceneTags = getSceneTags(sceneID); + var sceneTags = mapSceneTagsToIds(getSceneTags(sceneID)); + var newTags = []; + for (var i = 0; i < tagIDsToCheck.length; ++i) { + var tag = tagIDsToCheck[i]; + if (!includes(sceneTags, tag)) newTags.push(tag); + } - // Combine all tags from scene and the new marker - var allTags = [...new Set([...sceneTags, primaryTagID, ...opInput.TagIds])]; - var newTags = allTags.filter((t) => sceneTags.includes(t)); + if (newTags.length == 0) { + // All tags were present; don't do anything + log.Debug("All tags were already present on scene " + sceneID); + return ok(); + } + var tagIDs = sceneTags.concat(newTags); + setSceneTags(sceneID, tagIDs); + log.Info("adding tags " + newTags + " to scene " + sceneID); +} - setSceneTags(sceneID, allTags); - log.Debug("added new tags " + newTags.join(", ") + " to scene " + sceneID); +function main() { + log.Info(JSON.stringify(input)); + if (input.Args.mode == "processMarkers") { + var allTags = shouldHandleAllTags(); + log.Trace("Mode is processMarkers, allTags is " + allTags); + var allMarkers = getAllMarkers(); + //The markers come back as {primary_tag: { id: 600 } } + //but processMarker (because of the hook) expects 'primary_tag_id', so transform it here + log.Info( + "markerTagToScene has " + allMarkers.length + " markers to process" + ); + for (var i = 0; i < allMarkers.length; ++i) { + var marker = allMarkers[i]; + var sceneMarker = {}; + sceneMarker.id = marker.id; + sceneMarker.scene_id = marker.scene.id; + sceneMarker.primary_tag_id = marker.primary_tag.id; + var tag_ids = []; + for (var j = 0; j < marker.tags.length; ++j) { + tag_ids.push(marker.tags[j].id); + } + sceneMarker.tag_ids = tag_ids; + //log.Info(sceneMarker); + processMarker(sceneMarker, allTags); + log.Progress(i / allMarkers.length); + } + log.Progress("Finished processing markers"); + } else if (input.Args.mode == "hook") { + log.Info("Mode is hook"); + processMarker(input.Args.hookContext.input, shouldHandleAllTags()); + } else { + log.Error("Unknown mode"); + } +} + +function getAllMarkers() { + var query = + "\ +query Query($filter: FindFilterType) {\ + findSceneMarkers (filter: $filter) {\ + scene_markers {\ + id,\ + primary_tag {\ + id\ + }\ + tags {\ + id\ + }\ + scene {\ + id\ + }\ + }\ + }\ +}"; + var variables = { filter: { per_page: -1 } }; + var result = gql.Do(query, variables); + var findSceneMarkers = result.findSceneMarkers; + if (findSceneMarkers) { + return findSceneMarkers.scene_markers; + } } function getSceneTags(sceneID) { @@ -46,7 +157,7 @@ query findScene($id: ID) {\ var result = gql.Do(query, variables); var findScene = result.findScene; if (findScene) { - return findScene.tags.map((t) => t.id); + return findScene.tags; } return []; diff --git a/plugins/markerTagToScene/markerTagToScene.yml b/plugins/markerTagToScene/markerTagToScene.yml index f5973e5..a0097d8 100644 --- a/plugins/markerTagToScene/markerTagToScene.yml +++ b/plugins/markerTagToScene/markerTagToScene.yml @@ -6,9 +6,21 @@ version: 1.0 exec: - markerTagToScene.js interface: js +settings: + allTags: + displayName: All Tags + description: Add all scene tags instead of just the primary scene tag. + type: BOOLEAN hooks: - name: Update scene with scene marker tag description: Adds primary tag of Scene Marker to the Scene on marker create/update. triggeredBy: - SceneMarker.Create.Post - SceneMarker.Update.Post + defaultArgs: + mode: hook +tasks: + - name: Process all markers + description: Add tags from all markers to scenes + defaultArgs: + mode: processMarkers diff --git a/plugins/miscTags/miscTags.py b/plugins/miscTags/miscTags.py index 574ab95..9851d9d 100644 --- a/plugins/miscTags/miscTags.py +++ b/plugins/miscTags/miscTags.py @@ -9,7 +9,16 @@ per_page = 100 skip_tag = "[MiscTags: Skip]" # Defaults if nothing has changed in the stash ui -settings = {"addStashVrCompanionTags": False, "addVrTags": False,"addVSoloTags": True,"addVThreesomeTags":True, "flatStudio": ""} +settings = {"addStashVrCompanionTags": False, + "addVRTags": False, + "addSoloTags": True, + "addThreesomeTags": True, + "addFoursomeTags": True, + "addFivesomeTags": True, + "addSixsomeTags": True, + "addSevensomeTags": True, + "assumeMissingMale": True, + "flatStudio": ""} VRCTags = { "flat": {"VRCTags": ["FLAT"], "projTags": []}, @@ -20,7 +29,7 @@ VRCTags = { "180_lr": {"VRCTags": ["DOME", "SBS"], "projTags": ["180°"]}, "180_3dh_lr": {"VRCTags": ["DOME", "SBS"], "projTags": ["180°"]}, "360_tb": {"VRCTags": ["SPHERE", "TB"], "projTags": ["360°"]}, - "mkx200": {"VRCTags": ["MKX200", "FISHEYE", "SBS"], "projTags": ["220°"]}, + "mkx200": {"VRCTags": ["MKX200", "FISHEYE", "SBS"], "projTags": ["200°"]}, "mkx220": {"VRCTags": ["MKX220", "FISHEYE", "SBS"], "projTags": ["220°"]}, "vrca220": {"VRCTags": ["VRCA220", "FISHEYE", "SBS"], "projTags": ["220°"]}, "rf52": {"VRCTags": ["RF52", "FISHEYE", "SBS"], "projTags": ["190°"]}, @@ -32,6 +41,7 @@ VRCTags = { "5k": {"VRCTags": [], "projTags": ["5K"]}, } tags_cache = {} +performer_cache = {} def processScene(scene): @@ -45,12 +55,21 @@ def processScene(scene): if settings["addStashVrCompanionTags"]: processStashVRCompanionTags(scene, tags) log.debug(tags) - if settings["addVrTags"]: + if settings["addVRTags"]: processVRTags(scene, tags) - if settings["addVSoloTags"]: - soloTag(scene,tags) - if settings["addVThreesomeTags"]: - threesomeTag(scene, tags) + if settings["addSoloTags"]: + soloTag(scene, tags) + if settings["addThreesomeTags"]: + processGroupMakeup(['threesome'], 'Threesome', 3, scene, tags) + if settings["addFoursomeTags"]: + processGroupMakeup(['foursome', '4some'], 'Foursome', 4, scene, tags) + if settings["addFivesomeTags"]: + processGroupMakeup(['fivesome', 'fiveway'], 'Fivesome', 5, scene, tags) + if settings["addSixsomeTags"]: + processGroupMakeup(['sixsome'], 'Sixsome', 6, scene, tags) + if settings["addSevensomeTags"]: + processGroupMakeup(['sevensome'], 'Sevensome', 7, scene, tags) + if len(settings["flatStudio"]) > 0: processFlatStudio(scene, tags) if len(tags) > 0: @@ -135,58 +154,69 @@ def processFlatStudio(scene, tags): def soloTag(scene,tags): """Add Solo Female, Solo male, Solo Trans where there is a single performer and the solo tag""" - for name in ['solo', 'solo model','solo models']: + for name in ['solo', 'solo model', 'solo models']: if name in [x["name"].lower() for x in scene['tags']]: - if len(scene['performers']) ==1: - p=stash.find_performer(scene['performers'][0]) - if p['gender']=='FEMALE': + if len(scene['performers']) == 1: + p=getPerformer(scene['performers'][0]) + if p['gender'] == 'FEMALE': tags.append('Solo Female') elif p['gender'] == 'MALE': tags.append('Solo Male') - elif p['gender'] =='TRANSGENDER_MALE': + elif p['gender'] == 'TRANSGENDER_MALE': tags.append('Solo Trans') - elif p['gender'] =='TRANSGENDER_FEMALE': + elif p['gender'] == 'TRANSGENDER_FEMALE': tags.append('Solo Trans') -def threesomeTag(scene, tags): - """Add Threesome (XXX) tags based on group makeup""" - for name in ['threesome']: + +def getPerformer(p): + if p['id'] not in performer_cache: + p2 = stash.find_performer(p) + performer_cache[p['id']] = p2 + log.debug(performer_cache) + return p2 + return performer_cache[p] + + +def processGroupMakeup(tag_strings, makeup_label, count, scene, tags): + for name in tag_strings: if name in [x["name"].lower() for x in scene['tags']]: - if len(scene['performers']) == 3: - makeup=[] - for p in scene['performers']: - p2=stash.find_performer(p) - makeup.append(p2['gender']) + makeup = [] + for p in scene['performers']: + p2 = getPerformer(p) + if p2['gender'] == 'FEMALE': + makeup.append('G') + elif p2['gender'] == 'MALE': + makeup.append('B') + elif p2['gender'] == 'TRANSGENDER_FEMALE': + makeup.append('T') + elif p2['gender'] == 'TRANSGENDER_MALE': + makeup.append('T') + elif p2['gender'] == 'INTERSEX': + makeup.append('I') + elif p2['gender'] == 'NON_BINARY': + makeup.append('I') + else: + makeup.append('U') + # unknown or not set yet + # + if settings["assumeMissingMale"]: + # If there is a mising performer + for x in range(len(makeup), count): + makeup.append('B') + + if len(makeup) == count: makeup.sort() - log.debug(makeup) - if makeup[0]=='FEMALE' and makeup[1]=='FEMALE' and makeup[2]=='FEMALE': - tags.append('Threesome (Lesbian)') - elif makeup[0]=='FEMALE' and makeup[1]=='FEMALE' and makeup[2]=='MALE': - tags.append('Threesome (BGG)') - elif makeup[0]=='FEMALE' and makeup[1]=='MALE' and makeup[2]=='MALE': - tags.append('Threesome (BBG)') - elif makeup[0]=='FEMALE' and makeup[1]=='FEMALE' and ( makeup[2]=='TRANSGENDER_FEMALE' or makeup[2]=='TRANSGENDER_MALE'): - tags.append('Threesome (GGT)') - elif makeup[0]=='FEMALE' and ( makeup[1]=='TRANSGENDER_FEMALE' or makeup[1]=='TRANSGENDER_MALE') and ( makeup[2]=='TRANSGENDER_FEMALE' or makeup[2]=='TRANSGENDER_MALE'): - tags.append('Threesome (GTT)') - elif makeup[0]=='FEMALE' and makeup[1]=='MALE' and ( makeup[2]=='TRANSGENDER_FEMALE' or makeup[2]=='TRANSGENDER_MALE'): - tags.append('Threesome (BGT)') - elif makeup[0]=='MALE' and makeup[1]=='MALE' and makeup[2]=='MALE': - tags.append('Threesome (Gay)') - elif makeup[0]=='MALE' and makeup[1]=='MALE' and ( makeup[2]=='TRANSGENDER_FEMALE' or makeup[2]=='TRANSGENDER_MALE'): - tags.append('Threesome (BBT)') - elif makeup[0]=='MALE' and ( makeup[1]=='TRANSGENDER_FEMALE' or makeup[1]=='TRANSGENDER_MALE') and ( makeup[2]=='TRANSGENDER_FEMALE' or makeup[2]=='TRANSGENDER_MALE'): - tags.append('Threesome (BTT)') - elif ( makeup[0]=='TRANSGENDER_FEMALE' or makeup[0]=='TRANSGENDER_MALE') and ( makeup[1]=='TRANSGENDER_FEMALE' or makeup[1]=='TRANSGENDER_MALE') and ( makeup[2]=='TRANSGENDER_FEMALE' or makeup[2]=='TRANSGENDER_MALE'): - tags.append('Threesome (Trans)') - - - - - - - - + makeup_str = ''.join(makeup) + if makeup_str == ('G' * count): + tags.append('%s (Lesbian)' % (makeup_label, )) + elif makeup_str == ('B' * count): + tags.append('%s (Gay)' % (makeup_label, )) + elif makeup_str == ('G' * count): + tags.append('%s (Lesbian)' % (makeup_label, )) + else: + tags.append('%s (%s)' % (makeup_label, makeup_str, ) ) + else: + log.debug('missing performers for group makeup %s, have %s performers instead' % (makeup_label, len(makeup), )) def processScenes(): diff --git a/plugins/miscTags/miscTags.yml b/plugins/miscTags/miscTags.yml index 8ae3c5e..3f8bbc3 100644 --- a/plugins/miscTags/miscTags.yml +++ b/plugins/miscTags/miscTags.yml @@ -1,6 +1,6 @@ name: Misc Tags description: Add extra tags for VR and other ues -version: 0.3 +version: 0.4 url: https://github.com/stashapp/CommunityScripts/ exec: - python @@ -15,14 +15,34 @@ settings: displayName: Add VR related tags description: Add projection based tags 180°, 200°, VR type: BOOLEAN - addVSoloTags: + addSoloTags: displayName: Add solo tags description: if there is the "solo" tag and 1 performer add the "Solo Female" tag type: BOOLEAN - addVThreesomeTags: - displayName: Add threesome tags + addThreesomeTags: + displayName: Add threesome (xxx) description: if there is the "threesome" tag with 3 tagged performers add a group makeup tag eg Threesome (BGG) type: BOOLEAN + addFoursomeTags: + displayName: Add Foursome (XXXX) + description: if there is the "foursome" tag with 4 tagged performers add a group makeup tag eg Threesome (BGG) + type: BOOLEAN + addFivesomeTags: + displayName: Add Fivesome (xxxxx) tags + description: if there is the "fivesome" tag with 5 tagged performers add a group makeup tag eg Threesome (BGG) + type: BOOLEAN + addSixsomeTags: + displayName: Add Sixsome (XXXXXX) + description: if there is the "sixsome" tag with 6 tagged performers add a group makeup tag eg Threesome (BGG) + type: BOOLEAN + addSevensomeTags: + displayName: Add Sevensome (XXXXXXX) + description: if there is the "sevensome" tag with 7 tagged performers add a group makeup tag eg Threesome (BGG) + type: BOOLEAN + assumeMissingMale: + displayName: Assume missing male performer + description: For the group makeup tag assume the missing performer is male if there are unknown performers + type: BOOLEAN flatStudio: displayName: 2d Studio for stash-vr-companion diff --git a/plugins/stats/stats.yml b/plugins/stats/stats.yml index ce10cae..ce055c0 100644 --- a/plugins/stats/stats.yml +++ b/plugins/stats/stats.yml @@ -1,5 +1,5 @@ name: Extended Stats -#requires: CommunityScriptsUILibrary +# requires: CommunityScriptsUILibrary description: Adds new stats to the stats page version: 1.1 ui: diff --git a/plugins/tagGraph/README.md b/plugins/tagGraph/README.md deleted file mode 100644 index c09bbf4..0000000 --- a/plugins/tagGraph/README.md +++ /dev/null @@ -1,30 +0,0 @@ - -# Tag Graph Generator - -## Requirements - * python >= 3.7.X - * `pip install -r requirements.txt` - ---- - -## Usage - -### Running as a plugin -move the `tagGraph` directory into Stash's plugins directory, reload plugins and you can run the **Generate Graph** task - -### Running as a script -> **⚠️ Note:** use this if you are connecting to a remote instance of stash - -ensure `STASH_SETTINGS` is configured properly, you will likely need to change it - -run `python .\tag_graph.py -script` - -### View graph -a `tag_graph.html` file will be generated inside the tagGraph directory, open it with a browser to view/interact with the graph - ---- - -## Customizing the graph -set `SHOW_OPTIONS` to `True` and you will get an interface to play around with that will affect what the graph looks like. - -for more info see [pyvis docs](https://pyvis.readthedocs.io/en/latest/tutorial.html#using-the-configuration-ui-to-dynamically-tweak-network-settings) diff --git a/plugins/tagGraph/config.py b/plugins/tagGraph/config.py deleted file mode 100644 index e08ed10..0000000 --- a/plugins/tagGraph/config.py +++ /dev/null @@ -1,7 +0,0 @@ -STASH_SETTINGS = { - "Scheme": "http", - "Domain": "localhost", - "Port": "9999", - "ApiKey": "YOUR_API_KEY_HERE", -} -SHOW_OPTIONS = False diff --git a/plugins/tagGraph/requirements.txt b/plugins/tagGraph/requirements.txt deleted file mode 100644 index 2e17f0e..0000000 --- a/plugins/tagGraph/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -pyvis==0.1.9 -requests==2.25.1 \ No newline at end of file diff --git a/plugins/tagGraph/tag_graph.py b/plugins/tagGraph/tag_graph.py deleted file mode 100644 index f5dae2e..0000000 --- a/plugins/tagGraph/tag_graph.py +++ /dev/null @@ -1,272 +0,0 @@ -import os, re, sys, copy, json, requests - -# local dependencies -import config - -# external dependencies -from pyvis.network import Network - - -class StashLogger: - # Log messages sent from a script scraper instance are transmitted via stderr and are - # encoded with a prefix consisting of special character SOH, then the log - # level (one of t, d, i, w or e - corresponding to trace, debug, info, - # warning and error levels respectively), then special character - # STX. - # - # The log.trace, log.debug, log.info, log.warning, and log.error methods, and their equivalent - # formatted methods are intended for use by script scraper instances to transmit log - # messages. - # - def __log(self, level_char: bytes, s): - if level_char: - lvl_char = "\x01{}\x02".format(level_char.decode()) - s = re.sub(r"data:image.+?;base64(.+?')", "[...]", str(s)) - for x in s.split("\n"): - print(lvl_char, x, file=sys.stderr, flush=True) - - def trace(self, s): - self.__log(b"t", s) - - def debug(self, s): - self.__log(b"d", s) - - def info(self, s): - self.__log(b"i", s) - - def warning(self, s): - self.__log(b"w", s) - - def error(self, s): - self.__log(b"e", s) - - def progress(self, p): - progress = min(max(0, p), 1) - self.__log(b"p", str(progress)) - - -class StashInterface: - port = "" - url = "" - headers = { - "Accept-Encoding": "gzip, deflate", - "Content-Type": "application/json", - "Accept": "application/json", - "Connection": "keep-alive", - "DNT": "1", - } - cookies = {} - - def __init__(self, conn, fragments={}): - global log - - if conn.get("Logger"): - log = conn.get("Logger") - else: - raise Exception("No logger passed to StashInterface") - - self.port = conn["Port"] if conn.get("Port") else "9999" - scheme = conn["Scheme"] if conn.get("Scheme") else "http" - - api_key = conn.get("ApiKey") - if api_key: - self.headers["ApiKey"] = api_key - - # Session cookie for authentication - self.cookies = {} - if conn.get("SessionCookie"): - self.cookies.update({"session": conn["SessionCookie"]["Value"]}) - - domain = conn["Domain"] if conn.get("Domain") else "localhost" - - # Stash GraphQL endpoint - self.url = f"{scheme}://{domain}:{self.port}/graphql" - - try: - self.get_stash_config() - except Exception: - log.error(f"Could not connect to Stash at {self.url}") - sys.exit() - - log.info(f"Using Stash's GraphQl endpoint at {self.url}") - - self.fragments = fragments - - def __resolveFragments(self, query): - - fragmentReferences = list(set(re.findall(r"(?<=\.\.\.)\w+", query))) - fragments = [] - for ref in fragmentReferences: - fragments.append( - { - "fragment": ref, - "defined": bool(re.search("fragment {}".format(ref), query)), - } - ) - - if all([f["defined"] for f in fragments]): - return query - else: - for fragment in [f["fragment"] for f in fragments if not f["defined"]]: - if fragment not in self.fragments: - raise Exception(f'GraphQL error: fragment "{fragment}" not defined') - query += self.fragments[fragment] - return self.__resolveFragments(query) - - def __callGraphQL(self, query, variables=None): - - query = self.__resolveFragments(query) - - json_request = {"query": query} - if variables is not None: - json_request["variables"] = variables - - response = requests.post( - self.url, json=json_request, headers=self.headers, cookies=self.cookies - ) - - if response.status_code == 200: - result = response.json() - - if result.get("errors"): - for error in result["errors"]: - log.error(f"GraphQL error: {error}") - if result.get("error"): - for error in result["error"]["errors"]: - log.error(f"GraphQL error: {error}") - if result.get("data"): - return result["data"] - elif response.status_code == 401: - sys.exit( - "HTTP Error 401, Unauthorized. Cookie authentication most likely failed" - ) - else: - raise ConnectionError( - "GraphQL query failed:{} - {}. Query: {}. Variables: {}".format( - response.status_code, response.content, query, variables - ) - ) - - def __match_alias_item(self, search, items): - item_matches = {} - for item in items: - if re.match(rf"{search}$", item.name, re.IGNORECASE): - log.debug( - f'matched "{search}" to "{item.name}" ({item.id}) using primary name' - ) - item_matches[item.id] = item - if not item.aliases: - continue - for alias in item.aliases: - if re.match(rf"{search}$", alias.strip(), re.IGNORECASE): - log.debug( - f'matched "{search}" to "{alias}" ({item.id}) using alias' - ) - item_matches[item.id] = item - return list(item_matches.values()) - - def get_stash_config(self): - query = """ - query Configuration { - configuration { general { stashes{ path } } } - } - """ - result = self.__callGraphQL(query) - return result["configuration"] - - def get_tags_with_relations(self): - query = """ - query FindTags($filter: FindFilterType, $tag_filter: TagFilterType) { - findTags(filter: $filter, tag_filter: $tag_filter) { - count - tags { - id - name - parents { id } - children { id } - } - } - } - """ - - variables = { - "tag_filter": { - "child_count": {"modifier": "GREATER_THAN", "value": 0}, - "OR": {"parent_count": {"modifier": "GREATER_THAN", "value": 0}}, - }, - "filter": {"q": "", "per_page": -1}, - } - result = self.__callGraphQL(query, variables) - return result["findTags"]["tags"] - - -def script_init(): - import logging as log - - log.basicConfig(level=log.INFO, format="%(levelname)s: %(message)s") - stash_connection = config.STASH_SETTINGS - stash_connection["Logger"] = log - generate_graph(stash_connection) - - -def plugin_init(): - log = StashLogger() - stash_connection = json.loads(sys.stdin.read())["server_connection"] - stash_connection["Logger"] = log - generate_graph(stash_connection) - print(json.dumps({"output": "ok"})) - - -def generate_graph(stash_connection): - log = stash_connection["Logger"] - - stash = StashInterface(stash_connection) - log.info("getting tags from stash...") - tags = stash.get_tags_with_relations() - - log.info("generating graph...") - if config.SHOW_OPTIONS: - G = Network( - directed=True, - height="100%", - width="66%", - bgcolor="#202b33", - font_color="white", - ) - G.show_buttons() - else: - G = Network( - directed=True, - height="100%", - width="100%", - bgcolor="#202b33", - font_color="white", - ) - - node_theme = { - "border": "#adb5bd", - "background": "#394b59", - "highlight": {"border": "#137cbd", "background": "#FFFFFF"}, - } - edge_theme = {"color": "#FFFFFF", "highlight": "#137cbd"} - - # create all nodes - for tag in tags: - G.add_node(tag["id"], label=tag["name"], color=node_theme) - # create all edges - for tag in tags: - for child in tag["children"]: - G.add_edge(tag["id"], child["id"], color=edge_theme) - - current_abs_path = os.path.dirname(os.path.abspath(__file__)) - save_path = os.path.join(current_abs_path, "tag_graph.html") - - G.save_graph(save_path) - log.info(f'saved graph to "{save_path}"') - - -if __name__ == "__main__": - if len(sys.argv) > 1: - script_init() - else: - plugin_init() diff --git a/plugins/tagGraph/tag_graph.yml b/plugins/tagGraph/tag_graph.yml deleted file mode 100644 index 60c9c9d..0000000 --- a/plugins/tagGraph/tag_graph.yml +++ /dev/null @@ -1,12 +0,0 @@ -name: Tag Graph -description: Creates a visual of the Tag relations -version: 0.2 -exec: - - python - - "{pluginDir}/tag_graph.py" -interface: raw -tasks: - - name: Generate Graph - description: generates graph from current tag data - defaultArgs: - mode: generate