fix: Resolve Stash v0.31+ GraphQL mutation parse error for Scan and AutoTag tasks
This commit is contained in:
@@ -211,6 +211,27 @@ def execute_stash_graphql(query_str, stash_url=None, variables=None, timeout=4):
|
||||
with urllib.request.urlopen(req, timeout=timeout) as res:
|
||||
return json.loads(res.read().decode())
|
||||
|
||||
def build_stash_mutation(mutation_name, input_obj=None, fields=None, args_dict=None):
|
||||
call_parts = []
|
||||
if input_obj is not None:
|
||||
raw_json = json.dumps(input_obj)
|
||||
gql_input = re.sub(r'\"([a-zA-Z_][a-zA-Z0-9_]*)\"\s*:', r'\1:', raw_json)
|
||||
call_parts.append(f"input: {gql_input}")
|
||||
if args_dict:
|
||||
for k, v in args_dict.items():
|
||||
if isinstance(v, bool):
|
||||
call_parts.append(f"{k}: {'true' if v else 'false'}")
|
||||
elif isinstance(v, (int, float)):
|
||||
call_parts.append(f"{k}: {v}")
|
||||
else:
|
||||
v_str = str(v).replace('"', '\\"')
|
||||
call_parts.append(f'{k}: "{v_str}"')
|
||||
|
||||
call_str = f"{mutation_name}({', '.join(call_parts)})" if call_parts else mutation_name
|
||||
if fields:
|
||||
return f"mutation {{ {call_str} {{ {fields} }} }}"
|
||||
return f"mutation {{ {call_str} }}"
|
||||
|
||||
def parse_stash_scene_data(scene, match_strategy='exact_path'):
|
||||
if not scene:
|
||||
return None
|
||||
@@ -1987,13 +2008,7 @@ class RobustProxyHandler(http.server.SimpleHTTPRequestHandler):
|
||||
scanGenerateSprites = payload.get('scanGenerateSprites', False)
|
||||
scanGeneratePhashes = payload.get('scanGeneratePhashes', False)
|
||||
|
||||
mutation = '''
|
||||
mutation MetadataScan($input: ScanMetadataInput!) {
|
||||
metadataScan(input: $input)
|
||||
}
|
||||
'''
|
||||
variables = {
|
||||
"input": {
|
||||
input_obj = {
|
||||
"paths": paths if paths else None,
|
||||
"rescan": bool(rescan),
|
||||
"scanGenerateCovers": bool(scanGenerateCovers),
|
||||
@@ -2001,8 +2016,11 @@ class RobustProxyHandler(http.server.SimpleHTTPRequestHandler):
|
||||
"scanGenerateSprites": bool(scanGenerateSprites),
|
||||
"scanGeneratePhashes": bool(scanGeneratePhashes)
|
||||
}
|
||||
}
|
||||
res = execute_stash_graphql(mutation, stash_url, variables=variables, timeout=15)
|
||||
# Remove None values
|
||||
input_obj = {k: v for k, v in input_obj.items() if v is not None}
|
||||
|
||||
mutation = build_stash_mutation("metadataScan", input_obj=input_obj)
|
||||
res = execute_stash_graphql(mutation, stash_url, timeout=15)
|
||||
self.send_json_response(200, {"success": True, "result": res.get('data', {}).get('metadataScan')})
|
||||
except Exception as e:
|
||||
self.send_json_response(500, {"error": str(e)})
|
||||
@@ -2017,37 +2035,27 @@ class RobustProxyHandler(http.server.SimpleHTTPRequestHandler):
|
||||
mode = payload.get('mode', 'autotag')
|
||||
|
||||
if mode == 'autotag':
|
||||
mutation = '''
|
||||
mutation MetadataAutoTag($input: AutoTagMetadataInput!) {
|
||||
metadataAutoTag(input: $input)
|
||||
}
|
||||
'''
|
||||
variables = {
|
||||
"input": {
|
||||
input_obj = {
|
||||
"paths": paths if paths else None,
|
||||
"performers": bool(payload.get('performers', True)),
|
||||
"studios": bool(payload.get('studios', True)),
|
||||
"tags": bool(payload.get('tags', True))
|
||||
"performers": ["*"] if bool(payload.get('performers', True)) else None,
|
||||
"studios": ["*"] if bool(payload.get('studios', True)) else None,
|
||||
"tags": ["*"] if bool(payload.get('tags', True)) else None
|
||||
}
|
||||
}
|
||||
res = execute_stash_graphql(mutation, stash_url, variables=variables, timeout=15)
|
||||
input_obj = {k: v for k, v in input_obj.items() if v is not None}
|
||||
mutation = build_stash_mutation("metadataAutoTag", input_obj=input_obj)
|
||||
res = execute_stash_graphql(mutation, stash_url, timeout=15)
|
||||
self.send_json_response(200, {"success": True, "job_id": res.get('data', {}).get('metadataAutoTag')})
|
||||
else:
|
||||
mutation = '''
|
||||
mutation MetadataIdentify($input: IdentifyMetadataInput!) {
|
||||
metadataIdentify(input: $input)
|
||||
}
|
||||
'''
|
||||
variables = {
|
||||
"input": {
|
||||
input_obj = {
|
||||
"paths": paths if paths else None,
|
||||
"options": {
|
||||
"setCoverImage": True,
|
||||
"setOrganized": True
|
||||
}
|
||||
}
|
||||
}
|
||||
res = execute_stash_graphql(mutation, stash_url, variables=variables, timeout=15)
|
||||
input_obj = {k: v for k, v in input_obj.items() if v is not None}
|
||||
mutation = build_stash_mutation("metadataIdentify", input_obj=input_obj)
|
||||
res = execute_stash_graphql(mutation, stash_url, timeout=15)
|
||||
self.send_json_response(200, {"success": True, "job_id": res.get('data', {}).get('metadataIdentify')})
|
||||
except Exception as e:
|
||||
self.send_json_response(500, {"error": str(e)})
|
||||
@@ -2065,28 +2073,14 @@ class RobustProxyHandler(http.server.SimpleHTTPRequestHandler):
|
||||
results = {}
|
||||
|
||||
if clean_type in ['metadata', 'both']:
|
||||
mutation = '''
|
||||
mutation MetadataClean($input: CleanMetadataInput!) {
|
||||
metadataClean(input: $input)
|
||||
}
|
||||
'''
|
||||
variables = {
|
||||
"input": {
|
||||
"dryRun": dry_run,
|
||||
"paths": paths if paths else None
|
||||
}
|
||||
}
|
||||
res = execute_stash_graphql(mutation, stash_url, variables=variables, timeout=15)
|
||||
input_clean = {"dryRun": dry_run, "paths": paths if paths else None}
|
||||
input_clean = {k: v for k, v in input_clean.items() if v is not None}
|
||||
mutation = build_stash_mutation("metadataClean", input_obj=input_clean)
|
||||
res = execute_stash_graphql(mutation, stash_url, timeout=15)
|
||||
results['metadataClean'] = res.get('data', {}).get('metadataClean')
|
||||
|
||||
if clean_type in ['generated', 'both']:
|
||||
mutation_gen = '''
|
||||
mutation MetadataCleanGenerated($input: CleanGeneratedInput!) {
|
||||
metadataCleanGenerated(input: $input)
|
||||
}
|
||||
'''
|
||||
variables_gen = {
|
||||
"input": {
|
||||
input_gen = {
|
||||
"dryRun": dry_run,
|
||||
"blobFiles": True,
|
||||
"sprites": True,
|
||||
@@ -2095,8 +2089,8 @@ class RobustProxyHandler(http.server.SimpleHTTPRequestHandler):
|
||||
"markers": True,
|
||||
"imageThumbnails": True
|
||||
}
|
||||
}
|
||||
res_gen = execute_stash_graphql(mutation_gen, stash_url, variables=variables_gen, timeout=15)
|
||||
mutation_gen = build_stash_mutation("metadataCleanGenerated", input_obj=input_gen)
|
||||
res_gen = execute_stash_graphql(mutation_gen, stash_url, timeout=15)
|
||||
results['metadataCleanGenerated'] = res_gen.get('data', {}).get('metadataCleanGenerated')
|
||||
|
||||
self.send_json_response(200, {"success": True, "results": results})
|
||||
@@ -2110,13 +2104,7 @@ class RobustProxyHandler(http.server.SimpleHTTPRequestHandler):
|
||||
payload = json.loads(post_data.decode('utf-8'))
|
||||
stash_url = payload.get('stash_url') or DEFAULT_STASH_URL
|
||||
|
||||
mutation = '''
|
||||
mutation MetadataGenerate($input: GenerateMetadataInput!) {
|
||||
metadataGenerate(input: $input)
|
||||
}
|
||||
'''
|
||||
variables = {
|
||||
"input": {
|
||||
input_gen = {
|
||||
"covers": bool(payload.get('covers', True)),
|
||||
"sprites": bool(payload.get('sprites', True)),
|
||||
"previews": bool(payload.get('previews', True)),
|
||||
@@ -2127,8 +2115,9 @@ class RobustProxyHandler(http.server.SimpleHTTPRequestHandler):
|
||||
"overwrite": bool(payload.get('overwrite', False)),
|
||||
"paths": payload.get('paths') if payload.get('paths') else None
|
||||
}
|
||||
}
|
||||
res = execute_stash_graphql(mutation, stash_url, variables=variables, timeout=15)
|
||||
input_gen = {k: v for k, v in input_gen.items() if v is not None}
|
||||
mutation = build_stash_mutation("metadataGenerate", input_obj=input_gen)
|
||||
res = execute_stash_graphql(mutation, stash_url, timeout=15)
|
||||
self.send_json_response(200, {"success": True, "job_id": res.get('data', {}).get('metadataGenerate')})
|
||||
except Exception as e:
|
||||
self.send_json_response(500, {"error": str(e)})
|
||||
@@ -2143,20 +2132,12 @@ class RobustProxyHandler(http.server.SimpleHTTPRequestHandler):
|
||||
stop_all = payload.get('stop_all', False)
|
||||
|
||||
if stop_all or not job_id:
|
||||
mutation = '''
|
||||
mutation StopAllJobs {
|
||||
stopAllJobs
|
||||
}
|
||||
'''
|
||||
mutation = "mutation { stopAllJobs }"
|
||||
res = execute_stash_graphql(mutation, stash_url, timeout=10)
|
||||
self.send_json_response(200, {"success": True, "result": res.get('data', {}).get('stopAllJobs')})
|
||||
else:
|
||||
mutation = '''
|
||||
mutation StopJob($job_id: ID!) {
|
||||
stopJob(job_id: $job_id)
|
||||
}
|
||||
'''
|
||||
res = execute_stash_graphql(mutation, stash_url, variables={"job_id": str(job_id)}, timeout=10)
|
||||
mutation = f'mutation {{ stopJob(job_id: "{job_id}") }}'
|
||||
res = execute_stash_graphql(mutation, stash_url, timeout=10)
|
||||
self.send_json_response(200, {"success": True, "result": res.get('data', {}).get('stopJob')})
|
||||
except Exception as e:
|
||||
self.send_json_response(500, {"error": str(e)})
|
||||
@@ -2300,12 +2281,8 @@ class RobustProxyHandler(http.server.SimpleHTTPRequestHandler):
|
||||
self.send_json_response(400, {"error": "Missing plugin_id or task_name"})
|
||||
return
|
||||
|
||||
mutation = '''
|
||||
mutation RunPluginTask($plugin_id: ID!, $task_name: String!) {
|
||||
runPluginTask(plugin_id: $plugin_id, task_name: $task_name)
|
||||
}
|
||||
'''
|
||||
res = execute_stash_graphql(mutation, stash_url, variables={"plugin_id": plugin_id, "task_name": task_name}, timeout=15)
|
||||
mutation = f'mutation {{ runPluginTask(plugin_id: "{plugin_id}", task_name: "{task_name}") }}'
|
||||
res = execute_stash_graphql(mutation, stash_url, timeout=15)
|
||||
self.send_json_response(200, {"success": True, "job_id": res.get('data', {}).get('runPluginTask')})
|
||||
except Exception as e:
|
||||
self.send_json_response(500, {"error": str(e)})
|
||||
@@ -2324,19 +2301,13 @@ class RobustProxyHandler(http.server.SimpleHTTPRequestHandler):
|
||||
self.send_json_response(400, {"error": "Missing scene_id"})
|
||||
return
|
||||
|
||||
mutation = '''
|
||||
mutation SceneDestroy($input: SceneDestroyInput!) {
|
||||
sceneDestroy(input: $input)
|
||||
}
|
||||
'''
|
||||
variables = {
|
||||
"input": {
|
||||
input_obj = {
|
||||
"id": str(scene_id),
|
||||
"delete_file": delete_file,
|
||||
"delete_generated": delete_generated
|
||||
}
|
||||
}
|
||||
res = execute_stash_graphql(mutation, stash_url, variables=variables, timeout=15)
|
||||
mutation = build_stash_mutation("sceneDestroy", input_obj=input_obj)
|
||||
res = execute_stash_graphql(mutation, stash_url, timeout=15)
|
||||
self.send_json_response(200, {"success": True, "result": res.get('data', {}).get('sceneDestroy')})
|
||||
except Exception as e:
|
||||
self.send_json_response(500, {"error": str(e)})
|
||||
@@ -2424,22 +2395,12 @@ class RobustProxyHandler(http.server.SimpleHTTPRequestHandler):
|
||||
self.send_json_response(400, {"error": "Missing source_ids or destination_id"})
|
||||
return
|
||||
|
||||
mutation = '''
|
||||
mutation PerformerMerge($input: PerformerMergeInput!) {
|
||||
performerMerge(input: $input) {
|
||||
id
|
||||
name
|
||||
scene_count
|
||||
}
|
||||
}
|
||||
'''
|
||||
variables = {
|
||||
"input": {
|
||||
input_obj = {
|
||||
"source": source_ids,
|
||||
"destination": dest_id
|
||||
}
|
||||
}
|
||||
res = execute_stash_graphql(mutation, stash_url, variables=variables, timeout=20)
|
||||
mutation = build_stash_mutation("performerMerge", input_obj=input_obj, fields="id name scene_count")
|
||||
res = execute_stash_graphql(mutation, stash_url, timeout=20)
|
||||
self.send_json_response(200, {"success": True, "merged_performer": res.get('data', {}).get('performerMerge')})
|
||||
except Exception as e:
|
||||
self.send_json_response(500, {"error": str(e)})
|
||||
@@ -2458,21 +2419,14 @@ class RobustProxyHandler(http.server.SimpleHTTPRequestHandler):
|
||||
|
||||
merged_count = 0
|
||||
errors = []
|
||||
mutation = '''
|
||||
mutation PerformerMerge($input: PerformerMergeInput!) {
|
||||
performerMerge(input: $input) {
|
||||
id
|
||||
name
|
||||
}
|
||||
}
|
||||
'''
|
||||
for c in clusters:
|
||||
source_ids = [str(sid) for sid in c.get('source_ids', [])]
|
||||
dest_id = str(c.get('destination_id'))
|
||||
if not source_ids or not dest_id:
|
||||
continue
|
||||
try:
|
||||
res = execute_stash_graphql(mutation, stash_url, variables={"input": {"source": source_ids, "destination": dest_id}}, timeout=20)
|
||||
mutation = build_stash_mutation("performerMerge", input_obj={"source": source_ids, "destination": dest_id}, fields="id name")
|
||||
res = execute_stash_graphql(mutation, stash_url, timeout=20)
|
||||
if res.get('data', {}).get('performerMerge'):
|
||||
merged_count += 1
|
||||
elif res.get('errors'):
|
||||
@@ -2561,24 +2515,16 @@ class RobustProxyHandler(http.server.SimpleHTTPRequestHandler):
|
||||
payload = json.loads(post_data.decode('utf-8'))
|
||||
stash_url = payload.get('stash_url') or DEFAULT_STASH_URL
|
||||
paths = payload.get('paths', [])
|
||||
tag_performers = bool(payload.get('performers', True))
|
||||
tag_studios = bool(payload.get('studios', True))
|
||||
tag_tags = bool(payload.get('tags', True))
|
||||
|
||||
mutation = '''
|
||||
mutation AutoTagScenes($input: AutoTagMetadataInput!) {
|
||||
metadataAutoTag(input: $input)
|
||||
input_obj = {
|
||||
"paths": paths if paths else None,
|
||||
"performers": ["*"] if bool(payload.get('performers', True)) else None,
|
||||
"studios": ["*"] if bool(payload.get('studios', True)) else None,
|
||||
"tags": ["*"] if bool(payload.get('tags', True)) else None
|
||||
}
|
||||
'''
|
||||
variables = {
|
||||
"input": {
|
||||
"paths": paths if paths else [],
|
||||
"performers": tag_performers,
|
||||
"studios": tag_studios,
|
||||
"tags": tag_tags
|
||||
}
|
||||
}
|
||||
res = execute_stash_graphql(mutation, stash_url, variables=variables, timeout=20)
|
||||
input_obj = {k: v for k, v in input_obj.items() if v is not None}
|
||||
mutation = build_stash_mutation("metadataAutoTag", input_obj=input_obj)
|
||||
res = execute_stash_graphql(mutation, stash_url, timeout=20)
|
||||
self.send_json_response(200, {"success": True, "job_id": res.get('data', {}).get('metadataAutoTag')})
|
||||
except Exception as e:
|
||||
self.send_json_response(500, {"error": str(e)})
|
||||
@@ -2652,22 +2598,12 @@ class RobustProxyHandler(http.server.SimpleHTTPRequestHandler):
|
||||
self.send_json_response(400, {"error": "Missing source_ids or destination_id"})
|
||||
return
|
||||
|
||||
mutation = '''
|
||||
mutation TagsMerge($input: TagsMergeInput!) {
|
||||
tagsMerge(input: $input) {
|
||||
id
|
||||
name
|
||||
scene_count
|
||||
}
|
||||
}
|
||||
'''
|
||||
variables = {
|
||||
"input": {
|
||||
input_obj = {
|
||||
"source": source_ids,
|
||||
"destination": dest_id
|
||||
}
|
||||
}
|
||||
res = execute_stash_graphql(mutation, stash_url, variables=variables, timeout=20)
|
||||
mutation = build_stash_mutation("tagsMerge", input_obj=input_obj, fields="id name scene_count")
|
||||
res = execute_stash_graphql(mutation, stash_url, timeout=20)
|
||||
self.send_json_response(200, {"success": True, "merged_tag": res.get('data', {}).get('tagsMerge')})
|
||||
except Exception as e:
|
||||
self.send_json_response(500, {"error": str(e)})
|
||||
@@ -2686,21 +2622,14 @@ class RobustProxyHandler(http.server.SimpleHTTPRequestHandler):
|
||||
|
||||
merged_count = 0
|
||||
errors = []
|
||||
mutation = '''
|
||||
mutation TagsMerge($input: TagsMergeInput!) {
|
||||
tagsMerge(input: $input) {
|
||||
id
|
||||
name
|
||||
}
|
||||
}
|
||||
'''
|
||||
for c in clusters:
|
||||
source_ids = [str(sid) for sid in c.get('source_ids', [])]
|
||||
dest_id = str(c.get('destination_id'))
|
||||
if not source_ids or not dest_id:
|
||||
continue
|
||||
try:
|
||||
res = execute_stash_graphql(mutation, stash_url, variables={"input": {"source": source_ids, "destination": dest_id}}, timeout=20)
|
||||
mutation = build_stash_mutation("tagsMerge", input_obj={"source": source_ids, "destination": dest_id}, fields="id name")
|
||||
res = execute_stash_graphql(mutation, stash_url, timeout=20)
|
||||
if res.get('data', {}).get('tagsMerge'):
|
||||
merged_count += 1
|
||||
elif res.get('errors'):
|
||||
@@ -2907,12 +2836,8 @@ class RobustProxyHandler(http.server.SimpleHTTPRequestHandler):
|
||||
try:
|
||||
parent_dir = os.path.dirname(orig_local_path)
|
||||
stash_parent = parent_dir.replace('/mnt/isolation/videos', '/data')
|
||||
mutation = '''
|
||||
mutation MetadataScan($input: ScanMetadataInput!) {
|
||||
metadataScan(input: $input)
|
||||
}
|
||||
'''
|
||||
execute_stash_graphql(mutation, stash_url, variables={"input": {"paths": [stash_parent], "rescan": True}}, timeout=10)
|
||||
mutation = build_stash_mutation("metadataScan", input_obj={"paths": [stash_parent], "rescan": True})
|
||||
execute_stash_graphql(mutation, stash_url, timeout=10)
|
||||
except Exception as e:
|
||||
print(f"Background Stash rescan trigger error: {e}")
|
||||
|
||||
@@ -2991,12 +2916,8 @@ class RobustProxyHandler(http.server.SimpleHTTPRequestHandler):
|
||||
if trigger_rescan and rescanned_dirs:
|
||||
try:
|
||||
stash_dirs = [d.replace('/mnt/isolation/videos', '/data') for d in rescanned_dirs]
|
||||
mutation = '''
|
||||
mutation MetadataScan($input: ScanMetadataInput!) {
|
||||
metadataScan(input: $input)
|
||||
}
|
||||
'''
|
||||
execute_stash_graphql(mutation, stash_url, variables={"input": {"paths": stash_dirs, "rescan": True}}, timeout=15)
|
||||
mutation = build_stash_mutation("metadataScan", input_obj={"paths": stash_dirs, "rescan": True})
|
||||
execute_stash_graphql(mutation, stash_url, timeout=15)
|
||||
except Exception as e:
|
||||
print(f"Batch Stash rescan trigger error: {e}")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user