mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-08-16 02:33:21 -03:00
feat(update): detect CivitAI paidAccess versions and add hide paid updates (#1060)
CivitAI's PaidAccess cutover deprecated the availability=EarlyAccess and earlyAccessEndsAt signals; gated versions now report availability=Public with a paidAccess DTO that LoRA Manager previously ignored, so "Hide Early Access Updates" missed paid/early-access models and downloads failed with 401. Parse and persist paidAccess from model-level, bulk, and by-hash responses; treat timed paid gates as early access and permanent paid versions as a distinct is_paid state; add a hide_paid_updates setting with a "Paid" badge in the versions tab; warn before downloading gated versions. Includes SQLite migration, i18n for all locales, and backend/frontend tests.
This commit is contained in:
@@ -593,3 +593,103 @@ async def test_fetch_missing_license_data_filters_model_ids(monkeypatch):
|
||||
assert len(payload["updated"]) == 1
|
||||
assert provider_calls == [[20]]
|
||||
assert len(saved) == 1
|
||||
|
||||
|
||||
def test_serialize_version_permanent_paid_is_not_early_access():
|
||||
"""Permanent paid versions (is_paid, no end date) must not be flagged as
|
||||
early access, mirroring _is_early_access_active in the update service."""
|
||||
version = ModelVersionRecord(
|
||||
version_id=7, name="v7", base_model=None, released_at=None, size_bytes=None,
|
||||
preview_url=None, is_in_library=False, should_ignore=False,
|
||||
early_access_ends_at=None, is_early_access=True, usage_control="Download",
|
||||
paid_access=json.dumps({"permanent": True, "endsAt": None}), is_paid=True,
|
||||
)
|
||||
serialized = ModelUpdateHandler._serialize_version(version, None)
|
||||
assert serialized["isEarlyAccess"] is False
|
||||
assert serialized["isPaid"] is True
|
||||
assert serialized["paidAccess"] == {"permanent": True, "endsAt": None}
|
||||
|
||||
|
||||
def test_serialize_version_timed_paid_is_early_access():
|
||||
"""Timed paid gates (endsAt in the future) stay flagged as early access."""
|
||||
version = ModelVersionRecord(
|
||||
version_id=8, name="v8", base_model=None, released_at=None, size_bytes=None,
|
||||
preview_url=None, is_in_library=False, should_ignore=False,
|
||||
early_access_ends_at="2099-01-01T00:00:00.000Z", is_early_access=True,
|
||||
usage_control="Download",
|
||||
paid_access=json.dumps({"permanent": False, "endsAt": "2099-01-01T00:00:00.000Z"}),
|
||||
is_paid=False,
|
||||
)
|
||||
serialized = ModelUpdateHandler._serialize_version(version, None)
|
||||
assert serialized["isEarlyAccess"] is True
|
||||
assert serialized["isPaid"] is False
|
||||
|
||||
|
||||
def test_serialize_version_malformed_paid_access_does_not_crash():
|
||||
"""A malformed paid_access row must degrade to None instead of failing
|
||||
the whole versions-list response."""
|
||||
version = ModelVersionRecord(
|
||||
version_id=10, name="v10", base_model=None, released_at=None, size_bytes=None,
|
||||
preview_url=None, is_in_library=False, should_ignore=False,
|
||||
early_access_ends_at=None, is_early_access=True, usage_control=None,
|
||||
paid_access="{not json", is_paid=False,
|
||||
)
|
||||
serialized = ModelUpdateHandler._serialize_version(version, None)
|
||||
assert serialized["paidAccess"] is None
|
||||
assert serialized["isEarlyAccess"] is True
|
||||
|
||||
|
||||
async def test_enrich_early_access_details_skips_permanent_paid(monkeypatch):
|
||||
"""Permanent paid versions must not trigger per-version CivitAI fetches in
|
||||
_enrich_early_access_details: they are not early access and can never get
|
||||
an end time, so enriching them is wasted API traffic."""
|
||||
record = ModelUpdateRecord(
|
||||
model_type="lora",
|
||||
model_id=1,
|
||||
versions=[
|
||||
ModelVersionRecord(
|
||||
version_id=100, name="paid", base_model=None, released_at=None,
|
||||
size_bytes=None, preview_url=None, is_in_library=False,
|
||||
should_ignore=False, early_access_ends_at=None,
|
||||
is_early_access=True, usage_control="Download",
|
||||
paid_access='{"permanent": true, "endsAt": null}', is_paid=True,
|
||||
),
|
||||
ModelVersionRecord(
|
||||
version_id=200, name="ea", base_model=None, released_at=None,
|
||||
size_bytes=None, preview_url=None, is_in_library=False,
|
||||
should_ignore=False, early_access_ends_at=None,
|
||||
is_early_access=True, usage_control="Download",
|
||||
paid_access=None, is_paid=False,
|
||||
),
|
||||
],
|
||||
last_checked_at=1.0,
|
||||
should_ignore_model=False,
|
||||
)
|
||||
|
||||
fetched: list[int] = []
|
||||
|
||||
async def fake_version_info(version_id: str):
|
||||
fetched.append(int(version_id))
|
||||
return {"earlyAccessEndsAt": "2099-01-01T00:00:00.000Z"}, None
|
||||
|
||||
provider = SimpleNamespace(get_model_version_info=fake_version_info)
|
||||
|
||||
async def metadata_selector(name):
|
||||
assert name == "civitai_api"
|
||||
return provider
|
||||
|
||||
handler = ModelUpdateHandler(
|
||||
service=DummyService(SimpleNamespace(raw_data=[], version_index={})),
|
||||
update_service=SimpleNamespace(),
|
||||
metadata_provider_selector=metadata_selector,
|
||||
settings_service=SimpleNamespace(get=lambda *_: False),
|
||||
logger=logging.getLogger(__name__),
|
||||
)
|
||||
|
||||
enriched = await handler._enrich_early_access_details(record)
|
||||
|
||||
# Only the timed EA version (200) is fetched; the permanent paid one (100) is skipped.
|
||||
assert fetched == [200]
|
||||
enriched_map = {v.version_id: v for v in enriched.versions}
|
||||
assert enriched_map[200].early_access_ends_at == "2099-01-01T00:00:00.000Z"
|
||||
assert enriched_map[100].early_access_ends_at is None
|
||||
|
||||
Reference in New Issue
Block a user