Something went wrong. Try again.
/x/perimental project monorepo
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147// Package nodeinfo contains types and a simple client for reading the standard NodeInfo protocol described by Diaspora[1].//// This package supports version 2.0[2] because that is the one most commonly used.//// [1]: http://nodeinfo.diaspora.software/// [2]: http://nodeinfo.diaspora.software/docson/index.html#/ns/schema/2.0#$$expandpackage nodeinfo
import ( "context" "encoding/json" "fmt" "io" "net/http" "net/url"
"within.website/x/web" "within.website/x/web/useragent")
const schema2point0 = "http://nodeinfo.diaspora.software/ns/schema/2.0"const twoMebibytes = 1024 * 1024 * 2
// Node is the node information you are looking for.type Node struct { Version string `json:"version"` Software Software `json:"software"` Protocols []string `json:"protocols"` Services Services `json:"services"` OpenRegistration bool `json:"openRegistration"` Usage Usage `json:"usage"` Metadata any `json:"metadata"`}
// Software contains metadata about the server software in use.type Software struct { Name string `json:"name"` Version string `json:"version"`}
// Services lists the third party services that this server can connect to with their application API.type Services struct { Inbound []string `json:"inbound"` Outbound []string `json:"outbound"`}
// Usage statistics for this server.type Usage struct { Users Users `json:"users"` LocalPosts int64 `json:"localPosts"`}
// Users contains statistics about the users of this server.type Users struct { Total int64 `json:"total"` ActiveHalfYear int64 `json:"activeHalfYear"` ActiveMonth int64 `json:"activeMonth"`}
type wellKnownLinks struct { Links []wellKnownLink `json:"links"`}
type wellKnownLink struct { Rel string `json:"rel"` Href string `json:"href"`}
// FetchWithClient uses the provided HTTP client to fetch node information.func FetchWithClient(ctx context.Context, cli *http.Client, nodeURL string) (*Node, error) { u, err := url.Parse(nodeURL) if err != nil { return nil, fmt.Errorf("nodeinfo: can't parse nodeURL: %w", err) }
u.Path = "/.well-known/nodeinfo" req, err := http.NewRequest(http.MethodGet, u.String(), nil) if err != nil { return nil, fmt.Errorf("nodeinfo: can't create HTTP request: %w", err) }
resp, err := cli.Do(req) if err != nil { return nil, fmt.Errorf("nodeinfo: can't read HTTP response: %w", err) }
if resp.StatusCode != http.StatusOK { return nil, web.NewError(http.StatusOK, resp) }
defer resp.Body.Close()
data, err := io.ReadAll(io.LimitReader(resp.Body, twoMebibytes)) if err != nil { return nil, fmt.Errorf("nodeinfo: can't read from HTTP response body: %w", err) }
var niw wellKnownLinks err = json.Unmarshal(data, &niw) if err != nil { return nil, fmt.Errorf("nodeinfo: can't unmarshal discovery metadata: %w", err) }
var targetURL string
for _, link := range niw.Links { if link.Rel == schema2point0 { targetURL = link.Href } }
if targetURL == "" { return nil, fmt.Errorf("nodeinfo: can't find schema 2.0 nodeinfo for %s", nodeURL) }
req, err = http.NewRequest(http.MethodGet, targetURL, nil) if err != nil { return nil, fmt.Errorf("nodeinfo: can't create HTTP request: %w", err) }
resp, err = cli.Do(req) if err != nil { return nil, fmt.Errorf("nodeinfo: can't read HTTP response: %w", err) }
data, err = io.ReadAll(io.LimitReader(resp.Body, twoMebibytes)) if err != nil { return nil, fmt.Errorf("nodeinfo: can't read from HTTP response body: %w", err) }
var result Node err = json.Unmarshal(data, &result) if err != nil { return nil, fmt.Errorf("nodeinfo: can't unmarshal nodeinfo: %w", err) }
return &result, nil}
// Fetch uses the standard library HTTP client to fetch node information.func Fetch(ctx context.Context, nodeURL string) (*Node, error) { cli := &http.Client{ Transport: useragent.Transport("within.website/x/web/nodeinfo", "https://within.website/.x.botinfo", http.DefaultTransport), } return FetchWithClient(ctx, cli, nodeURL)}