package main import ( "encoding/json" "fmt" "github.com/gorilla/mux" "io/ioutil" "log" "net/http" "net/url" "os" "regexp" "time" "willnorris.com/go/webmention" ) func Serve() { hookHandler := func(w http.ResponseWriter, r *http.Request) { fmt.Println("Fetch feeds: ", time.Now().Format(time.RFC3339)) for _, actor := range actors { fmt.Println(actor.feed) articles, err := allFeedItems(actor.feed) if err != nil { fmt.Println(actor.feed, err.Error()) continue } // Post latest article if articles[0] != actor.lastItem { err = actor.PostArticle(articles[0]) if err != nil { fmt.Println("Posting", articles[0], "failed") } else { actor.lastItem = articles[0] _ = actor.save() } } } } webfingerHandler := func(w http.ResponseWriter, r *http.Request) { w.Header().Set("content-type", "application/jrd+json; charset=utf-8") name := r.URL.Query().Get("resource") // should be something like acct:user@example.com urlBaseUrl, _ := url.Parse(baseURL) re := regexp.MustCompile(`^acct:(.*)@` + regexp.QuoteMeta(urlBaseUrl.Host) + `$`) name = re.ReplaceAllString(name, "$1") actor := actors[name] // error out if this actor does not exist if actor == nil { w.WriteHeader(http.StatusNotFound) return } responseMap := make(map[string]interface{}) responseMap["subject"] = "acct:" + actor.Name + "@" + urlBaseUrl.Host // links is a json array with a single element var links [1]map[string]string link1 := make(map[string]string) link1["rel"] = "self" link1["type"] = ContentTypeAs2 link1["href"] = actor.iri links[0] = link1 responseMap["links"] = links response, _ := json.Marshal(responseMap) _, _ = w.Write(response) } inboxHandler := func(w http.ResponseWriter, r *http.Request) { b, err := ioutil.ReadAll(r.Body) if err != nil { panic(err) } activity := make(map[string]interface{}) err = json.Unmarshal(b, &activity) if err != nil { w.WriteHeader(http.StatusInternalServerError) return } actor := actors[mux.Vars(r)["actor"]] if actor == nil { // actor doesn't exist w.WriteHeader(http.StatusNotFound) return } f, err := os.OpenFile(storage+slash+"actors"+slash+actor.Name+slash+"inbox", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) if err != nil { w.WriteHeader(http.StatusInternalServerError) return } defer func() { _ = f.Close() }() _, _ = f.Write(b) _, _ = f.WriteString("\n---\n") switch activity["type"] { case "Follow": actor.Accept(activity) case "Undo": { if object, ok := activity["object"].(map[string]interface{}); ok { if objectType, ok := object["type"].(string); ok && objectType == "Follow" { if iri, ok := object["actor"].(string); ok && iri == activity["actor"] { _ = actor.RemoveFollower(iri) fmt.Println(iri, "unfollowed") } } } } case "Create": { if object, ok := activity["object"].(map[string]interface{}); ok { inReplyTo, ok := object["inReplyTo"].(string) id, ok2 := object["id"].(string) if ok && ok2 && len(inReplyTo) > 0 && len(id) > 0 { webmentionClient := webmention.New(nil) endpoint, err := webmentionClient.DiscoverEndpoint(inReplyTo) if err != nil || len(endpoint) < 1 { return } _, err = webmentionClient.SendWebmention(endpoint, id, inReplyTo) if err != nil { log.Println("Sending webmention to " + inReplyTo + " failed") } else { log.Println("Sent webmention to " + inReplyTo) } } } } case "Delete": { if object, ok := activity["object"].(string); ok && len(object) > 0 && activity["actor"] == object { _ = actor.RemoveFollower(object) fmt.Println("Deleted", object) } } } // Return 201 w.WriteHeader(http.StatusCreated) } // Add the handlers to a HTTP server gorilla := mux.NewRouter() gorilla.HandleFunc("/hook", hookHandler).Methods(http.MethodPost) gorilla.HandleFunc("/.well-known/webfinger", webfingerHandler) gorilla.PathPrefix("/{actor}/inbox").HandlerFunc(inboxHandler).Methods(http.MethodPost) http.Handle("/", gorilla) log.Fatal(http.ListenAndServe(":8081", nil)) }