Use direct HTTP API for IPFS instead of ipfshttpclient

Replace ipfshttpclient library with direct HTTP requests to IPFS API.
This fixes compatibility with newer Kubo versions (0.39.0+) which are
not supported by the outdated ipfshttpclient library.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
gilesb
2026-01-08 19:40:15 +00:00
parent fd239d99f9
commit 49c622c2cd
2 changed files with 55 additions and 24 deletions

View File

@@ -3,14 +3,15 @@
IPFS client for Art DAG L2 server. IPFS client for Art DAG L2 server.
Provides functions to fetch and pin content from IPFS. Provides functions to fetch and pin content from IPFS.
L2 uses IPFS to retrieve content from the federated network. Uses direct HTTP API calls for compatibility with all Kubo versions.
""" """
import logging import logging
import os import os
import re
from typing import Optional from typing import Optional
import ipfshttpclient import requests
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -21,9 +22,26 @@ IPFS_API = os.getenv("IPFS_API", "/ip4/127.0.0.1/tcp/5001")
IPFS_TIMEOUT = int(os.getenv("IPFS_TIMEOUT", "60")) IPFS_TIMEOUT = int(os.getenv("IPFS_TIMEOUT", "60"))
def get_client(): def _multiaddr_to_url(multiaddr: str) -> str:
"""Get an IPFS client connection.""" """Convert IPFS multiaddr to HTTP URL."""
return ipfshttpclient.connect(IPFS_API, timeout=IPFS_TIMEOUT) # Handle /dns/hostname/tcp/port format
dns_match = re.match(r"/dns[46]?/([^/]+)/tcp/(\d+)", multiaddr)
if dns_match:
return f"http://{dns_match.group(1)}:{dns_match.group(2)}"
# Handle /ip4/address/tcp/port format
ip4_match = re.match(r"/ip4/([^/]+)/tcp/(\d+)", multiaddr)
if ip4_match:
return f"http://{ip4_match.group(1)}:{ip4_match.group(2)}"
# Fallback: assume it's already a URL or use default
if multiaddr.startswith("http"):
return multiaddr
return "http://127.0.0.1:5001"
# Base URL for IPFS API
IPFS_BASE_URL = _multiaddr_to_url(IPFS_API)
def get_bytes(cid: str) -> Optional[bytes]: def get_bytes(cid: str) -> Optional[bytes]:
@@ -37,10 +55,15 @@ def get_bytes(cid: str) -> Optional[bytes]:
Content as bytes or None on failure Content as bytes or None on failure
""" """
try: try:
with get_client() as client: url = f"{IPFS_BASE_URL}/api/v0/cat"
data = client.cat(cid) params = {"arg": cid}
logger.info(f"Retrieved from IPFS: {cid} ({len(data)} bytes)")
return data response = requests.post(url, params=params, timeout=IPFS_TIMEOUT)
response.raise_for_status()
data = response.content
logger.info(f"Retrieved from IPFS: {cid} ({len(data)} bytes)")
return data
except Exception as e: except Exception as e:
logger.error(f"Failed to get from IPFS: {e}") logger.error(f"Failed to get from IPFS: {e}")
return None return None
@@ -57,10 +80,14 @@ def pin(cid: str) -> bool:
True on success, False on failure True on success, False on failure
""" """
try: try:
with get_client() as client: url = f"{IPFS_BASE_URL}/api/v0/pin/add"
client.pin.add(cid) params = {"arg": cid}
logger.info(f"Pinned on IPFS: {cid}")
return True response = requests.post(url, params=params, timeout=IPFS_TIMEOUT)
response.raise_for_status()
logger.info(f"Pinned on IPFS: {cid}")
return True
except Exception as e: except Exception as e:
logger.error(f"Failed to pin on IPFS: {e}") logger.error(f"Failed to pin on IPFS: {e}")
return False return False
@@ -77,10 +104,14 @@ def unpin(cid: str) -> bool:
True on success, False on failure True on success, False on failure
""" """
try: try:
with get_client() as client: url = f"{IPFS_BASE_URL}/api/v0/pin/rm"
client.pin.rm(cid) params = {"arg": cid}
logger.info(f"Unpinned from IPFS: {cid}")
return True response = requests.post(url, params=params, timeout=IPFS_TIMEOUT)
response.raise_for_status()
logger.info(f"Unpinned from IPFS: {cid}")
return True
except Exception as e: except Exception as e:
logger.error(f"Failed to unpin from IPFS: {e}") logger.error(f"Failed to unpin from IPFS: {e}")
return False return False
@@ -94,9 +125,9 @@ def is_available() -> bool:
True if IPFS is available, False otherwise True if IPFS is available, False otherwise
""" """
try: try:
with get_client() as client: url = f"{IPFS_BASE_URL}/api/v0/id"
client.id() response = requests.post(url, timeout=5)
return True return response.status_code == 200
except Exception: except Exception:
return False return False
@@ -109,9 +140,10 @@ def get_node_id() -> Optional[str]:
Peer ID string or None on failure Peer ID string or None on failure
""" """
try: try:
with get_client() as client: url = f"{IPFS_BASE_URL}/api/v0/id"
info = client.id() response = requests.post(url, timeout=IPFS_TIMEOUT)
return info.get("ID") response.raise_for_status()
return response.json().get("ID")
except Exception as e: except Exception as e:
logger.error(f"Failed to get node ID: {e}") logger.error(f"Failed to get node ID: {e}")
return None return None

View File

@@ -7,4 +7,3 @@ python-jose[cryptography]>=3.3.0
markdown>=3.5.0 markdown>=3.5.0
python-multipart>=0.0.6 python-multipart>=0.0.6
asyncpg>=0.29.0 asyncpg>=0.29.0
ipfshttpclient>=0.7.0