Commit Diff


commit - 345d2260d11d054c199c7a16e48ad84d0f068b35
commit + 519acee1bbf60fee276fd734f7a8254edd803d06
blob - c2b8a3a95b8c9a282efe093aa9f1f503c884a592
blob + 37d0c0a04d9f1e59c704a55ecb56882b4873608f
--- cmd/apserve/listen.go
+++ cmd/apserve/listen.go
@@ -166,6 +166,9 @@ func main() {
 		acceptFor: acceptFor,
 	}
 	http.HandleFunc("/.well-known/webfinger", serveWebFinger)
+	http.Handle("/.well-known/nodeinfo", http.RedirectHandler("/nodeinfo/2.0", http.StatusFound))
+	http.HandleFunc("/nodeinfo/2.0", srv.serveNodeInfo)
+	http.HandleFunc("/nodeinfo/2.0.json", srv.serveNodeInfo)
 
 	for _, u := range acceptFor {
 		dataDir := path.Join(u.HomeDir, "apubtest")
blob - ac03c54af0a01bb2af85fe72f457d19eb2639415
blob + e6d123b274247d1feb5641f79307e092063476eb
--- cmd/apserve/user.go
+++ cmd/apserve/user.go
@@ -5,7 +5,9 @@ import (
 	"fmt"
 	"log"
 	"net/http"
+	"os"
 	"os/user"
+	"path"
 	"strings"
 
 	"olowe.co/apub"
@@ -57,3 +59,28 @@ func serveWebFinger(w http.ResponseWriter, req *http.R
 		log.Printf("encode webfinger response: %v", err)
 	}
 }
+
+func (srv *server) nodeInfo() (NodeInfo, error) {
+	var count int
+	for _, user := range srv.acceptFor {
+		dents, err := os.ReadDir(path.Join(user.HomeDir, "apubtest/outbox"))
+		if err != nil {
+			return NodeInfo{}, fmt.Errorf("count posts in outbox: %w", err)
+		}
+		count += len(dents)
+	}
+	return NewNodeInfo("apas", "0.0.1", len(srv.acceptFor), count), nil
+}
+
+func (srv *server) serveNodeInfo(w http.ResponseWriter, req *http.Request) {
+	info, err := srv.nodeInfo()
+	if err != nil {
+		log.Println("serve nodeinfo:", err)
+		http.Error(w, err.Error(), http.StatusInternalServerError)
+		return
+	}
+	w.Header().Set("Content-Type", "application/json")
+	if err := json.NewEncoder(w).Encode(info); err != nil {
+		log.Println("encode nodeinfo:", err)
+	}
+}
blob - /dev/null
blob + 0211664170d948005bd9d409f76b56b30e1646ea (mode 644)
--- /dev/null
+++ cmd/apserve/nodeinfo.go
@@ -0,0 +1,40 @@
+package apub
+
+// NodeInfo represents...
+type NodeInfo struct {
+	Version           string       `json:"version"`
+	Software          nodeSoftware `json:"software"`
+	Protocols         []string     `json:"protocols"`
+	Usage             nodeUsage    `json:"usage"`
+	OpenRegistrations bool         `json:"openRegistration"`
+}
+
+type nodeSoftware struct {
+	Name    string `json:"name"`
+	Version string `json:"version"`
+}
+
+type nodeUsage struct {
+	Users         nodeUserCounts `json:"users"`
+	LocalPosts    int            `json:"localPosts"`
+	LocalComments int            `json:"localComments"`
+}
+
+type nodeUserCounts struct {
+	Total          int `json:"total"`
+	ActiveHalfYear int `json:"activeHalfYear"`
+	ActiveMonth    int `json:"activetMonth"`
+}
+
+func NewNodeInfo(name, version string, users int, posts int) NodeInfo {
+	return NodeInfo{
+		Version:   "2.0",
+		Software:  nodeSoftware{name, version},
+		Protocols: []string{"activitypub"},
+		Usage: nodeUsage{
+			Users:      nodeUserCounts{users, users, users},
+			LocalPosts: posts,
+		},
+		OpenRegistrations: false,
+	}
+}