diff --git a/server.py b/server.py index d457720..726ec18 100644 --- a/server.py +++ b/server.py @@ -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,22 +2008,19 @@ class RobustProxyHandler(http.server.SimpleHTTPRequestHandler): scanGenerateSprites = payload.get('scanGenerateSprites', False) scanGeneratePhashes = payload.get('scanGeneratePhashes', False) - mutation = ''' - mutation MetadataScan($input: ScanMetadataInput!) { - metadataScan(input: $input) + input_obj = { + "paths": paths if paths else None, + "rescan": bool(rescan), + "scanGenerateCovers": bool(scanGenerateCovers), + "scanGeneratePreviews": bool(scanGeneratePreviews), + "scanGenerateSprites": bool(scanGenerateSprites), + "scanGeneratePhashes": bool(scanGeneratePhashes) } - ''' - variables = { - "input": { - "paths": paths if paths else None, - "rescan": bool(rescan), - "scanGenerateCovers": bool(scanGenerateCovers), - "scanGeneratePreviews": bool(scanGeneratePreviews), - "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) + 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 None, - "performers": bool(payload.get('performers', True)), - "studios": bool(payload.get('studios', True)), - "tags": bool(payload.get('tags', 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("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": { - "paths": paths if paths else None, - "options": { - "setCoverImage": True, - "setOrganized": True - } + 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,38 +2073,24 @@ 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) + input_gen = { + "dryRun": dry_run, + "blobFiles": True, + "sprites": True, + "screenshots": True, + "transcodes": True, + "markers": True, + "imageThumbnails": True } - ''' - variables_gen = { - "input": { - "dryRun": dry_run, - "blobFiles": True, - "sprites": True, - "screenshots": True, - "transcodes": True, - "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,25 +2104,20 @@ 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) + input_gen = { + "covers": bool(payload.get('covers', True)), + "sprites": bool(payload.get('sprites', True)), + "previews": bool(payload.get('previews', True)), + "imagePreviews": bool(payload.get('imagePreviews', True)), + "markers": bool(payload.get('markers', True)), + "transcodes": bool(payload.get('transcodes', False)), + "phashes": bool(payload.get('phashes', True)), + "overwrite": bool(payload.get('overwrite', False)), + "paths": payload.get('paths') if payload.get('paths') else None } - ''' - variables = { - "input": { - "covers": bool(payload.get('covers', True)), - "sprites": bool(payload.get('sprites', True)), - "previews": bool(payload.get('previews', True)), - "imagePreviews": bool(payload.get('imagePreviews', True)), - "markers": bool(payload.get('markers', True)), - "transcodes": bool(payload.get('transcodes', False)), - "phashes": bool(payload.get('phashes', True)), - "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) + input_obj = { + "id": str(scene_id), + "delete_file": delete_file, + "delete_generated": delete_generated } - ''' - variables = { - "input": { - "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 - } + input_obj = { + "source": source_ids, + "destination": dest_id } - ''' - variables = { - "input": { - "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 - } + input_obj = { + "source": source_ids, + "destination": dest_id } - ''' - variables = { - "input": { - "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}")