170 lines
4.9 KiB
Go
170 lines
4.9 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"github.com/gorilla/mux"
|
|
"log"
|
|
"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))
|
|
go func() {
|
|
for _, actor := range actors {
|
|
fmt.Println(actor.feed)
|
|
articles, err := allFeedItems(actor.feed)
|
|
if err != nil {
|
|
fmt.Println(actor.feed, err.Error())
|
|
continue
|
|
}
|
|
if len(*articles) < 1 {
|
|
fmt.Println(actor.feed, "Empty feed")
|
|
continue
|
|
}
|
|
err = actor.PostArticle((*articles)[0])
|
|
if err != nil {
|
|
fmt.Println("Posting", (*articles)[0], "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)
|
|
}
|