From 703a6a4ea0eabecdf00273ca43b271146d078085 Mon Sep 17 00:00:00 2001 From: Will Miao Date: Sat, 27 Jun 2026 22:22:48 +0800 Subject: [PATCH] fix(import): request withMeta=true from CivitAI API, fix checkpoint type guard and CivArchive version lookup - Add &withMeta=true to image info URL so API returns full generation metadata (resources with hash/type) instead of null meta - Fix checkpoint assignment guard: check modelId instead of id so non- checkpoint types (upscaler) are not wrongly set as recipe checkpoint - Skip modelVersionIds loop when resources/civitaiResources already provided LoRAs, preventing hash-resolved duplicates - Fix int/str type comparison in CivArchive get_model_version so version ID matching works correctly --- py/recipes/parsers/civitai_image.py | 21 ++++++++++++++++----- py/services/civarchive_client.py | 2 +- py/services/civitai_client.py | 2 +- tests/services/test_civitai_client.py | 6 +++--- 4 files changed, 21 insertions(+), 10 deletions(-) diff --git a/py/recipes/parsers/civitai_image.py b/py/recipes/parsers/civitai_image.py index b78e7c74..1c0efc96 100644 --- a/py/recipes/parsers/civitai_image.py +++ b/py/recipes/parsers/civitai_image.py @@ -514,11 +514,21 @@ class CivitaiApiMetadataParser(RecipeMetadataParser): result["loras"].append(lora_entry) - # Process modelVersionIds from Civitai image API - # These are model version IDs returned at root level when meta doesn't contain resources - if "modelVersionIds" in metadata and isinstance( - metadata["modelVersionIds"], list + # Process modelVersionIds from Civitai image API. + # These are version IDs returned at root level of the API response. + # When resources or civitaiResources are already present in metadata + # (which they are when ?withMeta=true is passed), those sections have + # complete hash/type information — modelVersionIds is a fallback for + # when meta is null and only the flat ID list is available. Skipping + # it here avoids duplicates: the same file hash often resolves to + # different version IDs via hash lookup (resources) vs the original + # version ID in modelVersionIds, and both paths would create entries. + if ( + "modelVersionIds" in metadata + and isinstance(metadata["modelVersionIds"], list) + and not result.get("loras") ): + for version_id in metadata["modelVersionIds"]: version_id_str = str(version_id) @@ -594,11 +604,12 @@ class CivitaiApiMetadataParser(RecipeMetadataParser): checkpoint_entry, civitai_info ) ) - if cp_populated.get("id"): + if cp_populated.get("modelId"): result["model"] = cp_populated continue # Not a LoRA, don't add to loras lora_entry = populated_entry + except Exception as e: logger.error( f"Error fetching Civitai info for model version {version_id}: {e}" diff --git a/py/services/civarchive_client.py b/py/services/civarchive_client.py index 4445b5ee..9f063d08 100644 --- a/py/services/civarchive_client.py +++ b/py/services/civarchive_client.py @@ -417,7 +417,7 @@ class CivArchiveClient: if version_id is not None: raw_id = version_data.get("id") - if raw_id != version_id: + if raw_id is not None and str(raw_id) != str(version_id): logger.warning( "Requested version %s doesn't match default version %s for model %s", version_id, diff --git a/py/services/civitai_client.py b/py/services/civitai_client.py index 65a31078..788927c7 100644 --- a/py/services/civitai_client.py +++ b/py/services/civitai_client.py @@ -56,7 +56,7 @@ class CivitaiClient: self._MAX_CACHE_ENTRIES = 500 def _build_image_info_url(self, image_id: str) -> str: - return f"{self.base_url}/images?imageId={image_id}&nsfw=X" + return f"{self.base_url}/images?imageId={image_id}&nsfw=X&withMeta=true" async def _make_request( self, diff --git a/tests/services/test_civitai_client.py b/tests/services/test_civitai_client.py index 2712264a..0203b457 100644 --- a/tests/services/test_civitai_client.py +++ b/tests/services/test_civitai_client.py @@ -568,7 +568,7 @@ async def test_get_image_info_prefers_red_host_for_red_source(monkeypatch, downl assert result == {"id": 124950237, "name": "target"} assert requested_urls == [ - "https://civitai.red/api/v1/images?imageId=124950237&nsfw=X" + "https://civitai.red/api/v1/images?imageId=124950237&nsfw=X&withMeta=true" ] @@ -589,7 +589,7 @@ async def test_get_image_info_uses_red_host_even_for_red_source(monkeypatch, dow assert result == {"id": 124950237, "name": "target"} assert requested_urls == [ - "https://civitai.red/api/v1/images?imageId=124950237&nsfw=X", + "https://civitai.red/api/v1/images?imageId=124950237&nsfw=X&withMeta=true", ] @@ -610,7 +610,7 @@ async def test_get_image_info_does_not_fall_back_after_request_failure(monkeypat assert result is None assert requested_urls == [ - "https://civitai.red/api/v1/images?imageId=124950237&nsfw=X", + "https://civitai.red/api/v1/images?imageId=124950237&nsfw=X&withMeta=true", ]