Skip to content

info

info

NIP-11 metadata container with HTTP info retrieval capabilities.

Pairs Nip11InfoData with Nip11InfoLogs and provides the execute() class method that performs the actual HTTP request to retrieve a relay's NIP-11 information document.

Note

The HTTP request converts the relay's WebSocket URL scheme (wss -> https, ws -> http) and sends the Accept: application/nostr+json header as required by the NIP-11 specification. Responses larger than 64 KB are rejected to guard against resource exhaustion.

The SSL fallback strategy mirrors connect_relay: clearnet relays try verified SSL first, then fall back to CERT_NONE if allow_insecure=True. Overlay networks always use an insecure SSL context because the overlay provides its own encryption layer.

See Also

bigbrotr.nips.nip11.nip11.Nip11: Top-level model that wraps this container. bigbrotr.nips.base.BaseNipMetadata: Base class providing from_dict() / to_dict() interface. bigbrotr.models.metadata.MetadataType: The NIP11_INFO variant used when storing these results.

Classes

Nip11InfoMetadata

Bases: BaseNipMetadata

Container for NIP-11 info data and operation logs.

Provides the execute() class method for retrieving a relay's NIP-11 document over HTTP(S). The result always contains both a Nip11InfoData object and a Nip11InfoLogs object -- check logs.success for the operation status.

Warning

The execute() method never raises exceptions. All errors are captured in the logs.reason field. Callers must always check logs.success before accessing data fields.

See Also

bigbrotr.nips.nip11.nip11.Nip11.create: Factory method that delegates to execute().

Functions
execute async classmethod
execute(
    relay: Relay,
    timeout: float | None = None,
    max_size: int | None = None,
    proxy_url: str | None = None,
    session: ClientSession | None = None,
    *,
    allow_insecure: bool = False,
) -> Self

Fetch the NIP-11 information document from a relay.

Connects via HTTP(S) with the Accept: application/nostr+json header per the NIP-11 specification.

For clearnet HTTPS, verifies the certificate first and falls back to insecure if allow_insecure is True. Overlay networks always use an insecure SSL context (the overlay provides encryption). Plain HTTP connections use no SSL.

This method never raises and never returns None. Check logs.success for the operation outcome.

Parameters:

  • relay (Relay) –

    Relay to fetch from.

  • timeout (float | None, default: None ) –

    Request timeout in seconds (default: 10.0).

  • max_size (int | None, default: None ) –

    Maximum response size in bytes (default: 64 KB).

  • proxy_url (str | None, default: None ) –

    Optional SOCKS5 proxy URL for overlay networks.

  • session (ClientSession | None, default: None ) –

    Optional shared aiohttp.ClientSession. When provided, the session is reused and the caller retains ownership. When None, a per-request session is created and closed automatically.

  • allow_insecure (bool, default: False ) –

    Fall back to unverified SSL on certificate errors (default: False).

Returns:

  • Self

    An Nip11InfoMetadata instance with data and logs.

Source code in src/bigbrotr/nips/nip11/info.py
@classmethod
async def execute(  # noqa: PLR0913
    cls,
    relay: Relay,
    timeout: float | None = None,  # noqa: ASYNC109
    max_size: int | None = None,
    proxy_url: str | None = None,
    session: aiohttp.ClientSession | None = None,
    *,
    allow_insecure: bool = False,
) -> Self:
    """Fetch the NIP-11 information document from a relay.

    Connects via HTTP(S) with the ``Accept: application/nostr+json``
    header per the NIP-11 specification.

    For clearnet HTTPS, verifies the certificate first and falls back to
    insecure if *allow_insecure* is True. Overlay networks always use an
    insecure SSL context (the overlay provides encryption). Plain HTTP
    connections use no SSL.

    This method never raises and never returns None. Check
    ``logs.success`` for the operation outcome.

    Args:
        relay: Relay to fetch from.
        timeout: Request timeout in seconds (default: 10.0).
        max_size: Maximum response size in bytes (default: 64 KB).
        proxy_url: Optional SOCKS5 proxy URL for overlay networks.
        session: Optional shared ``aiohttp.ClientSession``. When
            provided, the session is reused and the caller retains
            ownership. When ``None``, a per-request session is created
            and closed automatically.
        allow_insecure: Fall back to unverified SSL on certificate
            errors (default: False).

    Returns:
        An ``Nip11InfoMetadata`` instance with data and logs.
    """
    timeout = timeout if timeout is not None else DEFAULT_TIMEOUT
    max_size = max_size if max_size is not None else cls._INFO_MAX_SIZE

    # Build the HTTP URL from the relay's WebSocket URL components
    protocol = "https" if relay.scheme == "wss" else "http"
    formatted_host = f"[{relay.host}]" if ":" in relay.host else relay.host
    default_port = 443 if protocol == "https" else 80
    port_suffix = f":{relay.port}" if relay.port and relay.port != default_port else ""
    http_url = f"{protocol}://{formatted_host}{port_suffix}{relay.path or ''}"

    headers = {"Accept": "application/nostr+json"}
    is_overlay = relay.network in (NetworkType.TOR, NetworkType.I2P, NetworkType.LOKI)

    data: dict[str, Any] = {}
    logs: dict[str, Any] = {"success": False, "reason": None}
    ssl_fallback = False

    try:
        if is_overlay:
            ctx = ssl.create_default_context()
            ctx.check_hostname = False
            ctx.verify_mode = ssl.CERT_NONE
            data = await cls._info(
                http_url,
                headers,
                timeout,
                max_size,
                ctx,
                proxy_url,
                session=session,
            )

        elif protocol == "http":
            data = await cls._info(
                http_url,
                headers,
                timeout,
                max_size,
                ssl_context=False,
                proxy_url=proxy_url,
                session=session,
            )

        else:
            # HTTPS: try verified first, optionally fall back to insecure
            try:
                data = await cls._info(
                    http_url,
                    headers,
                    timeout,
                    max_size,
                    ssl_context=True,
                    proxy_url=proxy_url,
                    session=session,
                )
            except aiohttp.ClientConnectorCertificateError:
                if not allow_insecure:
                    raise
                ssl_fallback = True
                ctx = ssl.create_default_context()
                ctx.check_hostname = False
                ctx.verify_mode = ssl.CERT_NONE
                data = await cls._info(
                    http_url,
                    headers,
                    timeout,
                    max_size,
                    ctx,
                    proxy_url,
                    session=session,
                )

        logs["success"] = True

    except (asyncio.CancelledError, KeyboardInterrupt, SystemExit):
        raise
    except (OSError, TimeoutError, aiohttp.ClientError, ValueError) as e:
        logs["success"] = False
        logs["reason"] = str(e) or type(e).__name__

    result = cls(
        data=Nip11InfoData.model_validate(Nip11InfoData.parse(data)),
        logs=Nip11InfoLogs.model_validate(logs),
    )

    if logs["success"]:
        logger.debug(
            "nip11_info_succeeded relay=%s name=%s ssl_fallback=%s",
            relay.url,
            result.data.name,
            ssl_fallback,
        )
    else:
        logger.debug("nip11_failed relay=%s error=%s", relay.url, logs["reason"])

    return result

Functions