mirror of
https://github.com/stashapp/CommunityScripts.git
synced 2026-04-30 16:27:11 -05:00
init tagGraph (#22)
This commit is contained in:
59
plugins/tagGraph/log.py
Normal file
59
plugins/tagGraph/log.py
Normal file
@@ -0,0 +1,59 @@
|
||||
import re, sys, copy
|
||||
|
||||
|
||||
# Log messages sent from a plugin 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, e, or p - corresponding to trace, debug, info,
|
||||
# warning, error and progress levels respectively), then special character
|
||||
# STX.
|
||||
#
|
||||
# The LogTrace, LogDebug, LogInfo, LogWarning, and LogError methods, and their equivalent
|
||||
# formatted methods are intended for use by plugin instances to transmit log
|
||||
# messages. The LogProgress method is also intended for sending progress data.
|
||||
#
|
||||
|
||||
def __prefix(level_char):
|
||||
start_level_char = b'\x01'
|
||||
end_level_char = b'\x02'
|
||||
|
||||
ret = start_level_char + level_char + end_level_char
|
||||
return ret.decode()
|
||||
|
||||
|
||||
|
||||
def __log(levelChar, s):
|
||||
|
||||
s_out = copy.deepcopy(s)
|
||||
if not isinstance(s_out, str):
|
||||
s_out = str(s_out)
|
||||
s_out = re.sub(r'(?<=")(data:image.+?;base64).+?(?=")', r'\1;truncated', s_out)
|
||||
|
||||
if levelChar == "":
|
||||
return
|
||||
|
||||
print(__prefix(levelChar) + s_out + "\n", file=sys.stderr, flush=True)
|
||||
|
||||
|
||||
def trace(s):
|
||||
__log(b't', s)
|
||||
|
||||
|
||||
def debug(s):
|
||||
__log(b'd', s)
|
||||
|
||||
|
||||
def info(s):
|
||||
__log(b'i', s)
|
||||
|
||||
|
||||
def warning(s):
|
||||
__log(b'w', s)
|
||||
|
||||
|
||||
def error(s):
|
||||
__log(b'e', s)
|
||||
|
||||
|
||||
def progress(p):
|
||||
progress = min(max(0, p), 1)
|
||||
__log(b'p', str(progress))
|
||||
2
plugins/tagGraph/requirements.txt
Normal file
2
plugins/tagGraph/requirements.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
pyvis==0.1.9
|
||||
requests==2.25.1
|
||||
112
plugins/tagGraph/stash_interface.py
Normal file
112
plugins/tagGraph/stash_interface.py
Normal file
@@ -0,0 +1,112 @@
|
||||
import requests
|
||||
import sys
|
||||
import log
|
||||
import re
|
||||
|
||||
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={}):
|
||||
self.port = conn['Port'] if conn.get('Port') else '9999'
|
||||
scheme = conn['Scheme'] if conn.get('Scheme') else 'http'
|
||||
|
||||
# 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'
|
||||
log.debug(f"Using stash GraphQl endpoint at {self.url}")
|
||||
|
||||
self.fragments = fragments
|
||||
|
||||
def __resolveFragments(self, query):
|
||||
|
||||
fragmentRefrences = list(set(re.findall(r'(?<=\.\.\.)\w+', query)))
|
||||
fragments = []
|
||||
for ref in fragmentRefrences:
|
||||
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.debug(f"GraphQL error: {error}")
|
||||
if result.get("error"):
|
||||
for error in result["error"]["errors"]:
|
||||
log.debug(f"GraphQL error: {error}")
|
||||
if result.get("data"):
|
||||
return result['data']
|
||||
elif response.status_code == 401:
|
||||
sys.exit("HTTP Error 401, Unauthorised. 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_db_path(self):
|
||||
query = """
|
||||
query Configuration {
|
||||
configuration {
|
||||
general{
|
||||
databasePath
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
result = self.__callGraphQL(query)
|
||||
return result['configuration']['general']['databasePath']
|
||||
63
plugins/tagGraph/tag_graph.py
Normal file
63
plugins/tagGraph/tag_graph.py
Normal file
@@ -0,0 +1,63 @@
|
||||
|
||||
import os, sys, json, sqlite3
|
||||
|
||||
# local deps
|
||||
import log
|
||||
from stash_interface import StashInterface
|
||||
|
||||
# external deps
|
||||
from pyvis.network import Network
|
||||
|
||||
class ro_stash_db:
|
||||
def __init__(self, db_path):
|
||||
self.conn = sqlite3.connect(f'file:{db_path}?mode=ro', uri=True)
|
||||
|
||||
def get_tag_relations(self):
|
||||
cur = self.conn.cursor()
|
||||
cur.execute("SELECT parent_id, child_id FROM tags_relations")
|
||||
return cur.fetchall()
|
||||
|
||||
def get_tag(self, tag_id):
|
||||
cur = self.conn.cursor()
|
||||
cur.execute(f"SELECT id, name FROM tags WHERE id={tag_id}")
|
||||
return cur.fetchone()
|
||||
|
||||
|
||||
|
||||
def main():
|
||||
global stash, stash_db
|
||||
|
||||
json_input = json.loads(sys.stdin.read())
|
||||
|
||||
stash = StashInterface(json_input["server_connection"])
|
||||
stash_db = ro_stash_db(stash.get_db_path())
|
||||
|
||||
create_graph()
|
||||
|
||||
print(json.dumps({"output":"ok"}))
|
||||
|
||||
|
||||
|
||||
def create_graph():
|
||||
G = Network(height="1080px",width="1080px")
|
||||
|
||||
for relation in stash_db.get_tag_relations():
|
||||
parent, child = relation
|
||||
|
||||
parent_id, parent_name = stash_db.get_tag(parent)
|
||||
child_id, child_name = stash_db.get_tag(child)
|
||||
|
||||
G.add_node(parent_id, label=parent_name)
|
||||
G.add_node(child_id, label=child_name)
|
||||
|
||||
G.add_edge(parent_id, child_id)
|
||||
|
||||
curr_path = os.path.dirname(os.path.abspath(__file__))
|
||||
save_path = os.path.join(curr_path, "tag_graph.html")
|
||||
|
||||
G.save_graph(save_path)
|
||||
log.info(f"saved graph to {save_path}")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
12
plugins/tagGraph/tag_graph.yml
Normal file
12
plugins/tagGraph/tag_graph.yml
Normal file
@@ -0,0 +1,12 @@
|
||||
name: Tag Graph
|
||||
description: Creates a visual of the Tag relations
|
||||
version: 0.1
|
||||
exec:
|
||||
- python
|
||||
- "{pluginDir}/tag_graph.py"
|
||||
interface: raw
|
||||
tasks:
|
||||
- name: Generate Graph
|
||||
description: generates graph from current tag data
|
||||
defaultArgs:
|
||||
mode: gen
|
||||
Reference in New Issue
Block a user