packages feed

nostr-1.3.0.0: lib/Nostr/Nip11.hs

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE DeriveGeneric #-}

{-|
Module      : Nostr.Nip11
Description : NIP-11 Relay Information Document
Copyright   : (c) Emre YILMAZ, 2026
License     : MIT
Maintainer  : z@emre.xyz

This module implements NIP-11, allowing clients to fetch relay capabilities
and information.
-}

module Nostr.Nip11
  ( RelayInfo(..)
  , fetchRelayInfo
  ) where

import Data.Aeson (FromJSON(..), ToJSON(..), withObject, (.:?))
import qualified Data.Aeson as A
import Data.Text (Text)
import qualified Data.Text as T
import qualified Data.Text.Encoding as TE
import GHC.Generics (Generic)
import Network.HTTP.Simple
import Control.Exception (try, SomeException)

-- | Information about a relay
data RelayInfo = RelayInfo
  { riName          :: Maybe Text
  , riDescription   :: Maybe Text
  , riPubkey        :: Maybe Text
  , riContact       :: Maybe Text
  , riSupportedNips :: Maybe [Int]
  , riSoftware      :: Maybe Text
  , riVersion       :: Maybe Text
  } deriving (Show, Eq, Generic)

instance FromJSON RelayInfo where
  parseJSON = withObject "RelayInfo" $ \v -> RelayInfo
    <$> v .:? "name"
    <*> v .:? "description"
    <*> v .:? "pubkey"
    <*> v .:? "contact"
    <*> v .:? "supported_nips"
    <*> v .:? "software"
    <*> v .:? "version"

instance ToJSON RelayInfo where
  toJSON ri = A.object $ catMaybes
    [ ("name" A..=) <$> riName ri
    , ("description" A..=) <$> riDescription ri
    , ("pubkey" A..=) <$> riPubkey ri
    , ("contact" A..=) <$> riContact ri
    , ("supported_nips" A..=) <$> riSupportedNips ri
    , ("software" A..=) <$> riSoftware ri
    , ("version" A..=) <$> riVersion ri
    ]
    where
      catMaybes = foldr (\mx xs -> maybe xs (:xs) mx) []

-- | Convert a websocket URL (wss:// or ws://) to HTTP (https:// or http://)
toHttpUrl :: Text -> Text
toHttpUrl url
  | "wss://" `T.isPrefixOf` url = "https://" <> T.drop 6 url
  | "ws://" `T.isPrefixOf` url = "http://" <> T.drop 5 url
  | otherwise = url

-- | Fetch relay information document
fetchRelayInfo :: Text -> IO (Either String RelayInfo)
fetchRelayInfo relayUrl = do
  let httpUrl = T.unpack $ toHttpUrl relayUrl
  req <- parseRequest httpUrl
  let reqWithHeaders = setRequestHeader "Accept" ["application/nostr+json"] req
  
  result <- try $ httpJSON reqWithHeaders :: IO (Either SomeException (Response RelayInfo))
  case result of
    Left err -> return $ Left $ "HTTP request failed: " ++ show err
    Right response -> return $ Right $ getResponseBody response