@@ -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, | |||
} | |||
} |