package main import ( "encoding/json" "fmt" "github.com/gorilla/mux" "log" "math" "net/http" "net/url" "os" "regexp" "strings" "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 or update latest 5 article for _, article := range (*articles)[0:int(math.Min(float64(len(*articles)-1), 4))] { err = actor.PostArticle(article) if err != nil { fmt.Println("Posting", article, "failed") } } } } 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] if actor == nil && len(defaultActor) > 0 { // return default actor actor = actors[defaultActor] } else if actor == nil { // error out if this actor does not exist 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 _ = json.NewEncoder(w).Encode(responseMap) } inboxHandler := func(w http.ResponseWriter, r *http.Request) { activity := make(map[string]interface{}) err := json.NewDecoder(r.Body).Decode(&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 } 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, hasReplyToString := object["inReplyTo"].(string) id, hasId := object["id"].(string) if hasReplyToString && hasId && len(inReplyTo) > 0 && len(id) > 0 && strings.Contains(inReplyTo, actor.iri) { // It's an ActivityPub reply fmt.Println("Received reply to:", inReplyTo) webmentionClient := webmention.New(nil) sendWebmention(webmentionClient, actor, id, inReplyTo) } else if hasId && len(id) > 0 { // May be a mention webmentionClient := webmention.New(nil) dl, err := webmentionClient.DiscoverLinks(id, "") if err != nil { return } // Send Webmentions for _, link := range dl { sendWebmention(webmentionClient, actor, id, link) } } } } case "Delete": { if object, ok := activity["object"].(string); ok && len(object) > 0 && activity["actor"] == object { _ = actor.RemoveFollower(object) } } default: // Log inbox request logInbox(actor, &activity) } // 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)) } func logInbox(actor *Actor, activity *map[string]interface{}) { 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 { return } defer func() { _ = f.Close() }() _ = json.NewEncoder(f).Encode(activity) _, _ = f.WriteString("\n---\n") } func sendWebmention(client *webmention.Client, actor *Actor, mentioningLink, mentionedLink string) { if !strings.Contains(mentionedLink, actor.iri) { // Not mention of blog return } endpoint, err := client.DiscoverEndpoint(mentionedLink) if err != nil || len(endpoint) < 1 { return } _, err = client.SendWebmention(endpoint, mentioningLink, mentionedLink) if err != nil { log.Println("Sending webmention to " + mentionedLink + " failed") return } log.Println("Sent webmention to " + mentionedLink) }