init tagGraph (#22)

This commit is contained in:
stg-annon
2021-10-31 21:42:08 -04:00
committed by GitHub
parent 52cc6cbfc2
commit 8b435ee758
5 changed files with 248 additions and 0 deletions

59
plugins/tagGraph/log.py Normal file
View 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))

View File

@@ -0,0 +1,2 @@
pyvis==0.1.9
requests==2.25.1

View 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']

View 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()

View 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