@@ -0,0 +1,80 @@ | |||||
package nodeinfo | |||||
type ( | |||||
NodeProtocol string | |||||
NodeService string | |||||
) | |||||
const ( | |||||
// Protocols that can be supported by this node. | |||||
ProtocolActivityPub NodeProtocol = "activitypub" | |||||
ProtocolOStatus = "ostatus" | |||||
// Services that can be supported (inbound or outbound) by this node's API. | |||||
ServiceTwitter NodeService = "twitter" | |||||
ServiceTumblr = "tumblr" | |||||
) | |||||
type Config struct { | |||||
InfoURL string | |||||
Metadata Metadata | |||||
Protocols []NodeProtocol | |||||
Services Services | |||||
Software SoftwareInfo | |||||
} | |||||
type ( | |||||
// NodeInfo includes all required node info. | |||||
NodeInfo struct { | |||||
Metadata Metadata `json:"metadata"` | |||||
OpenRegistrations bool `json:"openRegistrations"` | |||||
Protocols []NodeProtocol `json:"protocols"` | |||||
Services Services `json:"services"` | |||||
Software SoftwareInfo `json:"software"` | |||||
Usage Usage `json:"usage"` | |||||
Version string `json:"version"` | |||||
} | |||||
// Metadata for nodeinfo. Properties are based on what Pleroma uses. | |||||
// | |||||
// From the spec: Free form key value pairs for software specific values. | |||||
// Clients should not rely on any specific key present. | |||||
Metadata struct { | |||||
NodeName string `json:"nodeName,omitempty"` | |||||
NodeDescription string `json:"nodeDescription,omitempty"` | |||||
Private bool `json:"private,omitempty"` | |||||
} | |||||
Services struct { | |||||
Inbound []NodeService `json:"inbound"` | |||||
Outbound []NodeService `json:"outbound"` | |||||
} | |||||
SoftwareInfo struct { | |||||
// Name (canonical) of this server software. | |||||
Name string `json:"name"` | |||||
// Version of this server software. | |||||
Version string `json:"version"` | |||||
} | |||||
// Usage is usage statistics for this server. | |||||
Usage struct { | |||||
Users UsageUsers `json:"users"` | |||||
LocalPosts int `json:"localPosts,omitempty"` | |||||
LocalComments int `json:"localComments,omitempty"` | |||||
} | |||||
UsageUsers struct { | |||||
Total int `json:"total,omitempty"` | |||||
ActiveHalfYear int `json:"activeHalfyear,omitempty"` | |||||
ActiveMonth int `json:"activeMonth,omitempty"` | |||||
} | |||||
) | |||||
func (s Service) BuildInfo() NodeInfo { | |||||
ni := s.Info | |||||
ni.OpenRegistrations, _ = s.resolver.IsOpenRegistration() | |||||
ni.Usage, _ = s.resolver.Usage() | |||||
ni.Version = profileVer | |||||
return ni | |||||
} |
@@ -0,0 +1,8 @@ | |||||
package nodeinfo | |||||
type Resolver interface { | |||||
// IsOpenRegistration returns whether or not registration is open on this node. | |||||
IsOpenRegistration() (bool, error) | |||||
// Usage returns usage stats for this node. | |||||
Usage() (Usage, error) | |||||
} |
@@ -0,0 +1,64 @@ | |||||
package nodeinfo | |||||
import ( | |||||
"encoding/json" | |||||
"github.com/writeas/go-webfinger" | |||||
"log" | |||||
"net/http" | |||||
) | |||||
// NodeInfoPath defines the default path of the nodeinfo handler. | |||||
const NodeInfoPath = "/.well-known/nodeinfo" | |||||
type discoverInfo struct { | |||||
Links []webfinger.Link `json:"links"` | |||||
} | |||||
func (s *Service) NodeInfoDiscover(w http.ResponseWriter, r *http.Request) { | |||||
w.Header().Set("Content-Type", "application/json; charset=UTF-8") | |||||
i := discoverInfo{ | |||||
Links: []webfinger.Link{ | |||||
{ | |||||
Rel: profile, | |||||
HRef: s.InfoURL, | |||||
}, | |||||
}, | |||||
} | |||||
body, err := json.Marshal(i) | |||||
if err != nil { | |||||
log.Printf("Unable to marshal nodeinfo discovery: %v", err) | |||||
w.WriteHeader(http.StatusInternalServerError) | |||||
return | |||||
} | |||||
w.WriteHeader(http.StatusOK) | |||||
_, err = w.Write(body) | |||||
if err != nil { | |||||
log.Printf("Unable to write body: %v", err) | |||||
return | |||||
} | |||||
} | |||||
func (s *Service) NodeInfo(w http.ResponseWriter, r *http.Request) { | |||||
w.Header().Set("Content-Type", "application/json; profile="+profile+"#") | |||||
i := s.BuildInfo() | |||||
body, err := json.Marshal(i) | |||||
if err != nil { | |||||
log.Printf("Unable to marshal nodeinfo: %v", err) | |||||
w.WriteHeader(http.StatusInternalServerError) | |||||
return | |||||
} | |||||
w.WriteHeader(http.StatusOK) | |||||
_, err = w.Write(body) | |||||
if err != nil { | |||||
log.Printf("Unable to write body: %v", err) | |||||
return | |||||
} | |||||
} |
@@ -0,0 +1,26 @@ | |||||
package nodeinfo | |||||
const ( | |||||
profileVer = "2.0" | |||||
profile = "http://nodeinfo.diaspora.software/ns/schema/" + profileVer | |||||
) | |||||
type Service struct { | |||||
InfoURL string | |||||
Info NodeInfo | |||||
resolver Resolver | |||||
} | |||||
func NewService(cfg Config, r Resolver) *Service { | |||||
return &Service{ | |||||
InfoURL: cfg.InfoURL, | |||||
Info: NodeInfo{ | |||||
Metadata: cfg.Metadata, | |||||
Protocols: cfg.Protocols, | |||||
Services: cfg.Services, | |||||
Software: cfg.Software, | |||||
}, | |||||
resolver: r, | |||||
} | |||||
} |