Implement being followed.
parent
f67906c96a
commit
86eda3a0e9
13
TODO
13
TODO
|
@ -1,8 +1,3 @@
|
||||||
[ ] Load outbox of users and parse latest posts
|
|
||||||
[ ] Write these posts to local file
|
|
||||||
(normally we would need to sort by time but this is a
|
|
||||||
temporary solution until we really follow the actors
|
|
||||||
and get notifications in timely manner)
|
|
||||||
[ ] Follow users
|
[ ] Follow users
|
||||||
[ ] Announcements (read up how boost json looks like)
|
[ ] Announcements (read up how boost json looks like)
|
||||||
[ ] Federate the post to our followers (hardcoded for now)
|
[ ] Federate the post to our followers (hardcoded for now)
|
||||||
|
@ -13,14 +8,14 @@
|
||||||
[ ] Handle the /actor endpoint
|
[ ] Handle the /actor endpoint
|
||||||
[ ] Create configuration file
|
[ ] Create configuration file
|
||||||
[ ] Implement database backend
|
[ ] Implement database backend
|
||||||
[ ] Create a file with the actors we have, their following
|
[✔] Create a file with the actors we have, their following
|
||||||
and their followers.
|
and their followers.
|
||||||
[ ] `MakeActor` should create a file with that actor.
|
[✔] `MakeActor` should create a file with that actor.
|
||||||
[ ] Implement `LoadActor`
|
[✔] Implement `LoadActor`
|
||||||
[ ] All but `main.go` should run LoadActor instead of MakeActor
|
[ ] All but `main.go` should run LoadActor instead of MakeActor
|
||||||
(Actually nobody should run LoadActor except GetActor)
|
(Actually nobody should run LoadActor except GetActor)
|
||||||
[ ] `actor.Follow` should write the new following to file
|
[ ] `actor.Follow` should write the new following to file
|
||||||
[ ] Handle being followed
|
[✔] Handle being followed
|
||||||
[ ] When followed, the handler should write the new follower to file
|
[ ] When followed, the handler should write the new follower to file
|
||||||
[ ] Make sure we send our boosts to all our followers
|
[ ] Make sure we send our boosts to all our followers
|
||||||
Code is there but it works sometimes (I hate when this happens)
|
Code is there but it works sometimes (I hate when this happens)
|
||||||
|
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -57,7 +58,7 @@ func MakeActor(name, summary, actorType string) (Actor, error) {
|
||||||
following := make(map[string]interface{})
|
following := make(map[string]interface{})
|
||||||
followersIRI := baseURL + name + "/followers"
|
followersIRI := baseURL + name + "/followers"
|
||||||
publicKeyID := baseURL + name + "#main-key"
|
publicKeyID := baseURL + name + "#main-key"
|
||||||
iri := baseURL + "/" + name
|
iri := baseURL + name
|
||||||
nuIri, err := url.Parse(iri)
|
nuIri, err := url.Parse(iri)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Info("Something went wrong when parsing the local actor uri into net/url")
|
log.Info("Something went wrong when parsing the local actor uri into net/url")
|
||||||
|
@ -193,6 +194,10 @@ func LoadActor(name string) (Actor, error) {
|
||||||
return actor, nil
|
return actor, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// func LoadActorFromIRI(iri string) a Actor{
|
||||||
|
|
||||||
|
// }
|
||||||
|
|
||||||
// save the actor to file
|
// save the actor to file
|
||||||
func (a *Actor) save() error {
|
func (a *Actor) save() error {
|
||||||
|
|
||||||
|
@ -250,20 +255,22 @@ func (a *Actor) whoAmI() string {
|
||||||
}`
|
}`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *Actor) newID() string {
|
func (a *Actor) newIDhash() string {
|
||||||
return uniuri.New()
|
return uniuri.New()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *Actor) newIDurl() string {
|
||||||
|
return baseURL + a.name + "/" + a.newIDhash()
|
||||||
|
}
|
||||||
|
|
||||||
// CreateNote posts an activityPub note to our followers
|
// CreateNote posts an activityPub note to our followers
|
||||||
func (a *Actor) CreateNote(content string) {
|
func (a *Actor) CreateNote(content string) {
|
||||||
// for now I will just write this to the outbox
|
// for now I will just write this to the outbox
|
||||||
|
|
||||||
id := a.newID()
|
id := a.newIDurl()
|
||||||
create := make(map[string]interface{})
|
create := make(map[string]interface{})
|
||||||
note := make(map[string]interface{})
|
note := make(map[string]interface{})
|
||||||
context := make([]string, 1)
|
create["@context"] = context()
|
||||||
context[0] = "https://www.w3.org/ns/activitystreams"
|
|
||||||
create["@context"] = context
|
|
||||||
create["actor"] = baseURL + a.name
|
create["actor"] = baseURL + a.name
|
||||||
create["cc"] = a.followersIRI
|
create["cc"] = a.followersIRI
|
||||||
create["id"] = baseURL + a.name + "/" + id
|
create["id"] = baseURL + a.name + "/" + id
|
||||||
|
@ -279,34 +286,6 @@ func (a *Actor) CreateNote(content string) {
|
||||||
note["to"] = "https://www.w3.org/ns/activitystreams#Public"
|
note["to"] = "https://www.w3.org/ns/activitystreams#Public"
|
||||||
create["published"] = note["published"]
|
create["published"] = note["published"]
|
||||||
create["type"] = "Create"
|
create["type"] = "Create"
|
||||||
|
|
||||||
// note := `{
|
|
||||||
// "actor" : "https://` + baseURL + a.name + `",
|
|
||||||
// "cc" : [
|
|
||||||
// "https://` + baseURL + a.name + `/followers"
|
|
||||||
// ],
|
|
||||||
// "id" : "https://` + baseURL + a.name + `/` + id +`",
|
|
||||||
// "object" : {
|
|
||||||
// "attributedTo" : "https://` + baseURL + a.name + `",
|
|
||||||
// "cc" : [
|
|
||||||
// "https://` + baseURL + a.name + `/followers"
|
|
||||||
// ],
|
|
||||||
// "content" : "`+ content + `",
|
|
||||||
// "id" : "https://` + baseURL + a.name + `/` + id +`",
|
|
||||||
// "inReplyTo" : null,
|
|
||||||
// "published" : "2019-08-26T16:25:26Z",
|
|
||||||
// "to" : [
|
|
||||||
// "https://www.w3.org/ns/activitystreams#Public"
|
|
||||||
// ],
|
|
||||||
// "type" : "Note",
|
|
||||||
// "url" : "https://` + baseURL + a.name + `/` + id +`"
|
|
||||||
// },
|
|
||||||
// "published" : "2019-08-26T16:25:26Z",
|
|
||||||
// "to" : [
|
|
||||||
// "https://www.w3.org/ns/activitystreams#Public"
|
|
||||||
// ],
|
|
||||||
// "type" : "Create"
|
|
||||||
// }`
|
|
||||||
to, _ := url.Parse("https://cybre.space/inbox")
|
to, _ := url.Parse("https://cybre.space/inbox")
|
||||||
go a.send(create, to)
|
go a.send(create, to)
|
||||||
a.saveItem(id, create)
|
a.saveItem(id, create)
|
||||||
|
@ -330,6 +309,33 @@ func (a *Actor) send(content map[string]interface{}, to *url.URL) (err error) {
|
||||||
return a.signedHTTPPost(content, to.String())
|
return a.signedHTTPPost(content, to.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetFollowers returns a list of people that follow us
|
||||||
|
func (a *Actor) GetFollowers(page int) (response []byte, err error) {
|
||||||
|
// if there's no page parameter mastodon displays an
|
||||||
|
// OrderedCollection with info of where to find orderedCollectionPages
|
||||||
|
// with the actual information. We are mirroring that behavior
|
||||||
|
themap := make(map[string]interface{})
|
||||||
|
themap["@context"] = "https://www.w3.org/ns/activitystreams"
|
||||||
|
if page == 0 {
|
||||||
|
themap["first"] = baseURL + a.name + "/followers?page=1"
|
||||||
|
themap["id"] = baseURL + a.name + "/followers"
|
||||||
|
themap["totalItems"] = strconv.Itoa(len(a.followers))
|
||||||
|
themap["type"] = "OrderedCollection"
|
||||||
|
} else if page == 1 { // implement pagination
|
||||||
|
themap["id"] = baseURL + a.name + "followers?page=" + strconv.Itoa(page)
|
||||||
|
items := make([]string, 0, len(a.followers))
|
||||||
|
for k := range a.followers {
|
||||||
|
items = append(items, k)
|
||||||
|
}
|
||||||
|
themap["orderedItems"] = items
|
||||||
|
themap["partOf"] = baseURL + a.name + "/followers"
|
||||||
|
themap["totalItems"] = len(a.followers)
|
||||||
|
themap["type"] = "OrderedCollectionPage"
|
||||||
|
}
|
||||||
|
response, _ = json.Marshal(themap)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func (a *Actor) signedHTTPPost(content map[string]interface{}, to string) (err error) {
|
func (a *Actor) signedHTTPPost(content map[string]interface{}, to string) (err error) {
|
||||||
b, err := json.Marshal(content)
|
b, err := json.Marshal(content)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -357,7 +363,7 @@ func (a *Actor) signedHTTPPost(content map[string]interface{}, to string) (err e
|
||||||
}
|
}
|
||||||
req.Header.Add("Accept-Charset", "utf-8")
|
req.Header.Add("Accept-Charset", "utf-8")
|
||||||
req.Header.Add("Date", time.Now().UTC().Format("Mon, 02 Jan 2006 15:04:05")+" GMT")
|
req.Header.Add("Date", time.Now().UTC().Format("Mon, 02 Jan 2006 15:04:05")+" GMT")
|
||||||
req.Header.Add("User-Agent", fmt.Sprintf("activityserve 0.0"))
|
req.Header.Add("User-Agent", userAgent + " " + version)
|
||||||
req.Header.Add("Host", iri.Host)
|
req.Header.Add("Host", iri.Host)
|
||||||
req.Header.Add("Accept", "application/activity+json")
|
req.Header.Add("Accept", "application/activity+json")
|
||||||
sum := sha256.Sum256(b)
|
sum := sha256.Sum256(b)
|
||||||
|
@ -377,16 +383,16 @@ func (a *Actor) signedHTTPPost(content map[string]interface{}, to string) (err e
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
if !isSuccess(resp.StatusCode) {
|
if !isSuccess(resp.StatusCode) {
|
||||||
responseData, _ := ioutil.ReadAll(resp.Body)
|
responseData, _ := ioutil.ReadAll(resp.Body)
|
||||||
err = fmt.Errorf("POST request to %s failed (%d): %s\nResponse: %s \nRequest: %s \nHeaders: %s", to, resp.StatusCode, resp.Status, formatJSON(responseData), formatJSON(byteCopy), req.Header)
|
err = fmt.Errorf("POST request to %s failed (%d): %s\nResponse: %s \nRequest: %s \nHeaders: %s", to, resp.StatusCode, resp.Status, FormatJSON(responseData), FormatJSON(byteCopy), FormatHeaders(req.Header))
|
||||||
log.Info(err)
|
log.Info(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
responseData, _ := ioutil.ReadAll(resp.Body)
|
responseData, _ := ioutil.ReadAll(resp.Body)
|
||||||
fmt.Printf("POST request to %s succeeded (%d): %s \nResponse: %s \nRequest: %s \nHeaders: %s", to, resp.StatusCode, resp.Status, formatJSON(responseData), formatJSON(byteCopy), req.Header)
|
fmt.Printf("POST request to %s succeeded (%d): %s \nResponse: %s \nRequest: %s \nHeaders: %s", to, resp.StatusCode, resp.Status, FormatJSON(responseData), FormatJSON(byteCopy), FormatHeaders(req.Header))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *Actor) signedHTTPGet(address string) (string, error){
|
func (a *Actor) signedHTTPGet(address string) (string, error) {
|
||||||
req, err := http.NewRequest("GET", address, nil)
|
req, err := http.NewRequest("GET", address, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("cannot create new http.request")
|
log.Error("cannot create new http.request")
|
||||||
|
@ -424,12 +430,18 @@ func (a *Actor) signedHTTPGet(address string) (string, error){
|
||||||
if resp.StatusCode != http.StatusOK {
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
|
||||||
responseData, _ := ioutil.ReadAll(resp.Body)
|
responseData, _ := ioutil.ReadAll(resp.Body)
|
||||||
return "", fmt.Errorf("GET request to %s failed (%d): %s \n%s", iri.String(), resp.StatusCode, resp.Status, formatJSON(responseData))
|
return "", fmt.Errorf("GET request to %s failed (%d): %s \n%s", iri.String(), resp.StatusCode, resp.Status, FormatJSON(responseData))
|
||||||
}
|
}
|
||||||
|
|
||||||
responseData, _ := ioutil.ReadAll(resp.Body)
|
responseData, _ := ioutil.ReadAll(resp.Body)
|
||||||
fmt.Println("GET request succeeded:", iri.String(), req.Header, resp.StatusCode, resp.Status, "\n", formatJSON(responseData))
|
fmt.Println("GET request succeeded:", iri.String(), req.Header, resp.StatusCode, resp.Status, "\n", FormatJSON(responseData))
|
||||||
|
|
||||||
responseText := string(responseData)
|
responseText := string(responseData)
|
||||||
return responseText, nil
|
return responseText, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewFollower records a new follower to the actor file
|
||||||
|
func (a *Actor) NewFollower(iri string) error {
|
||||||
|
a.followers[iri] = struct{}{}
|
||||||
|
return a.save()
|
||||||
|
}
|
||||||
|
|
|
@ -2,7 +2,9 @@ package activityserve
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/gologme/log"
|
"github.com/gologme/log"
|
||||||
|
@ -11,7 +13,7 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
)
|
)
|
||||||
|
|
||||||
// SetupHTTP starts an http server with all the required handlers
|
// Serve starts an http server with all the required handlers
|
||||||
func Serve() {
|
func Serve() {
|
||||||
|
|
||||||
var webfingerHandler http.HandlerFunc = func(w http.ResponseWriter, r *http.Request) {
|
var webfingerHandler http.HandlerFunc = func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
@ -34,23 +36,26 @@ func Serve() {
|
||||||
responseMap := make(map[string]interface{})
|
responseMap := make(map[string]interface{})
|
||||||
|
|
||||||
responseMap["subject"] = "acct:" + actor.name + "@" + server
|
responseMap["subject"] = "acct:" + actor.name + "@" + server
|
||||||
links := make(map[string]string)
|
// links is a json array with a single element
|
||||||
links["rel"] = "self"
|
var links [1]map[string]string
|
||||||
links["type"] = "application/activity+json"
|
link1 := make(map[string]string)
|
||||||
links["href"] = baseURL + actor.name
|
link1["rel"] = "self"
|
||||||
|
link1["type"] = "application/activity+json"
|
||||||
|
link1["href"] = baseURL + actor.name
|
||||||
|
links[0] = link1
|
||||||
responseMap["links"] = links
|
responseMap["links"] = links
|
||||||
|
|
||||||
response, err := json.Marshal(responseMap)
|
response, err := json.Marshal(responseMap)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("problem creating the webfinger response json")
|
log.Error("problem creating the webfinger response json")
|
||||||
}
|
}
|
||||||
log.Info(string(response))
|
PrettyPrintJSON(response)
|
||||||
w.Write([]byte(response))
|
w.Write([]byte(response))
|
||||||
}
|
}
|
||||||
|
|
||||||
var actorHandler http.HandlerFunc = func(w http.ResponseWriter, r *http.Request) {
|
var actorHandler http.HandlerFunc = func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Header().Set("content-type", "application/activity+json; charset=utf-8")
|
w.Header().Set("content-type", "application/activity+json; charset=utf-8")
|
||||||
log.Info("Remote server just fetched our /actor endpoint")
|
log.Info("Remote server " + r.RemoteAddr + " just fetched our /actor endpoint")
|
||||||
username := mux.Vars(r)["actor"]
|
username := mux.Vars(r)["actor"]
|
||||||
log.Info(username)
|
log.Info(username)
|
||||||
if username == ".well-known" || username == "favicon.ico" {
|
if username == ".well-known" || username == "favicon.ico" {
|
||||||
|
@ -66,9 +71,13 @@ func Serve() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
fmt.Fprintf(w, actor.whoAmI())
|
fmt.Fprintf(w, actor.whoAmI())
|
||||||
log.Info(r.RemoteAddr)
|
|
||||||
log.Info(r.Body)
|
// Show some debugging information
|
||||||
log.Info(r.Header)
|
printer.Info("")
|
||||||
|
body, _ := ioutil.ReadAll(r.Body)
|
||||||
|
PrettyPrintJSON(body)
|
||||||
|
log.Info(FormatHeaders(r.Header))
|
||||||
|
printer.Info("")
|
||||||
}
|
}
|
||||||
|
|
||||||
var outboxHandler http.HandlerFunc = func(w http.ResponseWriter, r *http.Request) {
|
var outboxHandler http.HandlerFunc = func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
@ -137,13 +146,101 @@ func Serve() {
|
||||||
w.Write([]byte(response))
|
w.Write([]byte(response))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var inboxHandler http.HandlerFunc = 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 {
|
||||||
|
log.Error("Probably this request didn't have (valid) JSON inside it")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// TODO check if it's actually an activity
|
||||||
|
|
||||||
|
// check if case is going to be an issue
|
||||||
|
switch activity["type"] {
|
||||||
|
case "Follow":
|
||||||
|
// it's a follow, write it down
|
||||||
|
newFollower := activity["actor"].(string)
|
||||||
|
// check we aren't following ourselves
|
||||||
|
if newFollower == activity["object"] {
|
||||||
|
log.Info("You can't follow yourself")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// load the object as actor
|
||||||
|
actor, err := LoadActor(mux.Vars(r)["actor"]) // load the actor from disk
|
||||||
|
if err != nil {
|
||||||
|
log.Error("No such actor")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if this user is already following us
|
||||||
|
if _, ok := actor.followers[newFollower]; ok {
|
||||||
|
log.Info("You're already following us, yay!")
|
||||||
|
// do nothing, they're already following us
|
||||||
|
} else {
|
||||||
|
actor.NewFollower(newFollower)
|
||||||
|
}
|
||||||
|
// send accept anyway even if they are following us already
|
||||||
|
// this is very verbose. I would prefer creating a map by hand
|
||||||
|
|
||||||
|
// remove @context from the inner activity
|
||||||
|
delete(activity, "@context")
|
||||||
|
|
||||||
|
accept := make(map[string]interface{})
|
||||||
|
|
||||||
|
accept["@context"] = "https://www.w3.org/ns/activitystreams"
|
||||||
|
accept["to"] = activity["actor"]
|
||||||
|
accept["id"] = actor.newIDurl()
|
||||||
|
accept["actor"] = actor.iri
|
||||||
|
accept["object"] = activity
|
||||||
|
accept["type"] = "Accept"
|
||||||
|
|
||||||
|
follower, err := NewRemoteActor(activity["actor"].(string))
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Info("Couldn't retrieve remote actor info, maybe server is down?")
|
||||||
|
log.Info(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
go actor.signedHTTPPost(accept, follower.inbox)
|
||||||
|
|
||||||
|
default:
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
var followersHandler http.HandlerFunc = func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Header().Set("content-type", "application/activity+json; charset=utf-8")
|
||||||
|
username := mux.Vars(r)["actor"]
|
||||||
|
actor, err := LoadActor(username)
|
||||||
|
// error out if this actor does not exist
|
||||||
|
if err != nil {
|
||||||
|
log.Info("Can't create local actor")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var page int
|
||||||
|
pageS := r.URL.Query().Get("page")
|
||||||
|
if pageS == "" {
|
||||||
|
page = 0
|
||||||
|
} else {
|
||||||
|
page, _ = strconv.Atoi(pageS)
|
||||||
|
}
|
||||||
|
response, _ := actor.GetFollowers(page)
|
||||||
|
w.Write(response)
|
||||||
|
}
|
||||||
|
|
||||||
// Add the handlers to a HTTP server
|
// Add the handlers to a HTTP server
|
||||||
gorilla := mux.NewRouter()
|
gorilla := mux.NewRouter()
|
||||||
gorilla.HandleFunc("/.well-known/webfinger", webfingerHandler)
|
gorilla.HandleFunc("/.well-known/webfinger", webfingerHandler)
|
||||||
|
gorilla.HandleFunc("/{actor}/followers", followersHandler)
|
||||||
gorilla.HandleFunc("/{actor}/outbox", outboxHandler)
|
gorilla.HandleFunc("/{actor}/outbox", outboxHandler)
|
||||||
gorilla.HandleFunc("/{actor}/outbox/", outboxHandler)
|
gorilla.HandleFunc("/{actor}/outbox/", outboxHandler)
|
||||||
// gorilla.HandleFunc("/{actor}/inbox", inboxHandler)
|
gorilla.HandleFunc("/{actor}/inbox", inboxHandler)
|
||||||
// gorilla.HandleFunc("/{actor}/inbox/", inboxHandler)
|
gorilla.HandleFunc("/{actor}/inbox/", inboxHandler)
|
||||||
gorilla.HandleFunc("/{actor}/", actorHandler)
|
gorilla.HandleFunc("/{actor}/", actorHandler)
|
||||||
gorilla.HandleFunc("/{actor}", actorHandler)
|
gorilla.HandleFunc("/{actor}", actorHandler)
|
||||||
// gorilla.HandleFunc("/{actor}/post/{hash}", postHandler)
|
// gorilla.HandleFunc("/{actor}/post/{hash}", postHandler)
|
||||||
|
|
|
@ -0,0 +1,92 @@
|
||||||
|
package activityserve
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
|
||||||
|
"github.com/gologme/log"
|
||||||
|
|
||||||
|
// "github.com/go-fed/activity/pub"
|
||||||
|
// "github.com/go-fed/httpsig"
|
||||||
|
|
||||||
|
"net/http"
|
||||||
|
// "net/url"
|
||||||
|
|
||||||
|
"encoding/json"
|
||||||
|
|
||||||
|
"bytes"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RemoteActor is a type that holds an actor
|
||||||
|
// that we want to interact with
|
||||||
|
type RemoteActor struct {
|
||||||
|
iri, outbox, inbox string
|
||||||
|
info map[string]interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewRemoteActor returns a remoteActor which holds
|
||||||
|
// all the info required for an actor we want to
|
||||||
|
// interact with (not essentially sitting in our instance)
|
||||||
|
func NewRemoteActor(iri string) (RemoteActor, error) {
|
||||||
|
|
||||||
|
info, err := get(iri)
|
||||||
|
if err != nil {
|
||||||
|
log.Info("Couldn't get remote actor information")
|
||||||
|
log.Info(err)
|
||||||
|
return RemoteActor{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
outbox := info["outbox"].(string)
|
||||||
|
inbox := info["inbox"].(string)
|
||||||
|
|
||||||
|
return RemoteActor{
|
||||||
|
iri: iri,
|
||||||
|
outbox: outbox,
|
||||||
|
inbox: inbox,
|
||||||
|
}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ra RemoteActor) getLatestPosts(number int) (map[string]interface{}, error) {
|
||||||
|
return get(ra.outbox)
|
||||||
|
}
|
||||||
|
|
||||||
|
func get(iri string) (info map[string]interface{}, err error) {
|
||||||
|
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
|
||||||
|
req, err := http.NewRequest("GET", iri, buf)
|
||||||
|
if err != nil {
|
||||||
|
log.Info(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
req.Header.Add("Accept", "application/activity+json; profile=\"https://www.w3.org/ns/activitystreams\"")
|
||||||
|
req.Header.Add("User-Agent", userAgent+" "+version)
|
||||||
|
req.Header.Add("Accept-Charset", "utf-8")
|
||||||
|
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Info("Cannot perform the request")
|
||||||
|
log.Info(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
responseData, _ := ioutil.ReadAll(resp.Body)
|
||||||
|
|
||||||
|
if !isSuccess(resp.StatusCode) {
|
||||||
|
err = fmt.Errorf("GET request to %s failed (%d): %s\nResponse: %s \nHeaders: %s", iri, resp.StatusCode, resp.Status, FormatJSON(responseData), FormatHeaders(req.Header))
|
||||||
|
log.Info(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var e interface{}
|
||||||
|
err = json.Unmarshal(responseData, &e)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Info("something went wrong when unmarshalling the json")
|
||||||
|
log.Info(err)
|
||||||
|
}
|
||||||
|
info = e.(map[string]interface{})
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
|
@ -2,8 +2,8 @@ package activityserve
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"os"
|
||||||
|
|
||||||
"github.com/gologme/log"
|
"github.com/gologme/log"
|
||||||
"gopkg.in/ini.v1"
|
"gopkg.in/ini.v1"
|
||||||
|
@ -13,6 +13,7 @@ var slash = string(os.PathSeparator)
|
||||||
var baseURL = "http://example.com/"
|
var baseURL = "http://example.com/"
|
||||||
var storage = "storage"
|
var storage = "storage"
|
||||||
var userAgent = "activityserve"
|
var userAgent = "activityserve"
|
||||||
|
var printer *log.Logger
|
||||||
|
|
||||||
const libName = "activityserve"
|
const libName = "activityserve"
|
||||||
const version = "0.99"
|
const version = "0.99"
|
||||||
|
@ -61,7 +62,7 @@ func Setup(configurationFile string, debug bool) {
|
||||||
log.EnableLevel("warn")
|
log.EnableLevel("warn")
|
||||||
// create a logger with levels but without prefixes for easier to read
|
// create a logger with levels but without prefixes for easier to read
|
||||||
// debug output
|
// debug output
|
||||||
printer := log.New(os.Stdout, " ", 0)
|
printer = log.New(os.Stdout, " ", 0)
|
||||||
|
|
||||||
if debug == true {
|
if debug == true {
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
|
|
|
@ -0,0 +1,86 @@
|
||||||
|
## When we follow someone from pherephone 1.00
|
||||||
|
|
||||||
|
``` json
|
||||||
|
|
||||||
|
{
|
||||||
|
"@context": "https://www.w3.org/ns/activitystreams",
|
||||||
|
"actor": "https://floorb.qwazix.com/myAwesomeList1",
|
||||||
|
"id": "https://floorb.qwazix.com/myAwesomeList1/Xm9UHyJXyFYduqXz",
|
||||||
|
"object": "https://cybre.space/users/qwazix",
|
||||||
|
"to": "https://cybre.space/users/qwazix",
|
||||||
|
"type": "Follow"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
``` yaml
|
||||||
|
|
||||||
|
Accept: application/activity+json
|
||||||
|
Accept-Charset: utf-8
|
||||||
|
Date: Tue, 10 Sep 2019 05:31:22 GMT
|
||||||
|
Digest: SHA-256=uL1LvGU4+gSDm8Qci6XibZODTaNCsXWXWgkMWAqBvG8=
|
||||||
|
Host: cybre.space
|
||||||
|
Signature: keyId="https://floorb.qwazix.com/myAwesomeList1#main-key",algorithm="rsa-sha256",headers="(request-target) date host digest",signature="c6oipeXu/2zqX3qZF1x7KLNTYifcyqwwDySoslAowjpYlKWO3qAZMU1A//trYm23AtnItXkH2mY3tPq8X7fy9P1+CMFmiTzV01MGwwwJLDtEXKoq8W7L7lWuQhDD5rjiZqWyei4T13FW7MOCRbAtC4kZqkHrp5Z3l8HhPvmgUV5VOuSGWrtbmCN3hlAEHVugQTMPC6UjlaHva6Qm/SNlFmpUdG7WmUUPJIZ6a/ysBk4cLkF1+Hb03grXKexLHAU4bPIRcjwFpUl06yp8fZ8CCLhNhIsBACiizV85D3votmdxAollE5JXSwBp4f6jrZbgiJEusFoxiVKKqZRHRESQBQ=="
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
## Pherephone 1 Accept Activity
|
||||||
|
|
||||||
|
``` yaml
|
||||||
|
Accept: application/activity+json
|
||||||
|
Accept-Charset: utf-8
|
||||||
|
Date: Tue, 10 Sep 2019 07:28:49 GMT
|
||||||
|
Digest: SHA-256=GTy9bhYjOnbeCJzAzpqI/HEw/5p81NnoPLJkVAiZ4K0=
|
||||||
|
Host: cybre.space
|
||||||
|
Signature: keyId="https://floorb.qwazix.com/activityserve_test_actor_1#main-key",algorithm="rsa-sha256",headers="(request-target) date host digest",signature="jAeTEy9v1t+bCwQJB2R4Cscu/fGu5i4luHXlzJcJVyRbsHGqxbNEOxlk/G0S5BGbX3Kuoerq2oMpkFV5kCWPlpAmfhz38NKIrWhjnEUpFOfiG+ZJBpQsb3VQp7M3RGPZ9K4hmV6BSzkC8npsFGPI/HkAaj9u/txW5Cp4v6dMOYteoRLcKc3UVPK9j4hCbjq6SPhpwfM+StARSDnUFfpDe4YYQiVnO2WoINPUr4xvELmCYdBclSBCKcG66g8sBpnx4McjIlu0VISeBxzIHZYOONPteLY2uZW3Axi9JIAq88Y2Ecw4vV6Ctp7KcmD7M3kAJLqao2p/XZNZ3ExsTGfrXA=="
|
||||||
|
User-Agent: activityserve 0.0
|
||||||
|
```
|
||||||
|
|
||||||
|
``` json
|
||||||
|
{
|
||||||
|
"@context": "https://www.w3.org/ns/activitystreams",
|
||||||
|
"actor": "https://floorb.qwazix.com/myAwesomeList1",
|
||||||
|
"id": "https://floorb.qwazix.com/myAwesomeList1/SABRE7xlDAjtDcZb",
|
||||||
|
"object": {
|
||||||
|
"actor": "https://cybre.space/users/qwazix",
|
||||||
|
"id": "https://cybre.space/3e7336af-4bcd-4f77-aa69-6a145be824aa",
|
||||||
|
"object": "https://floorb.qwazix.com/myAwesomeList1",
|
||||||
|
"type": "Follow"
|
||||||
|
},
|
||||||
|
"to": "https://cybre.space/users/qwazix",
|
||||||
|
"type": "Accept"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Pherephone 2 Accept Activity
|
||||||
|
|
||||||
|
``` yaml
|
||||||
|
|
||||||
|
Accept: application/activity+json
|
||||||
|
Accept-Charset: utf-8
|
||||||
|
Date: Tue, 10 Sep 2019 07:32:08 GMT
|
||||||
|
Digest: SHA-256=yKzA6srSMx0b5GXn9DyflXVdqWd6ADBGt5hO9t/yc44=
|
||||||
|
Host: cybre.space
|
||||||
|
Signature: keyId="https://floorb.qwazix.com/myAwesomeList1#main-key",algorithm="rsa-sha256",headers="(request-target) date host digest",signature="WERXWDRFS7aGiIoz+HSujtuv9XNFBPxHkJSsCPu7PNIUDoAB2jdwW3rZc5jbrSLxi9Aqhr2BiBV/VYELQ8gITPzzIYH5sizPcPyLyARPUw37t6zA3HinahpfBKXhf73q9u+CYE/7DMKQ2Pvv2lQPaZ8hl27R2KJmcc3Jhmn5nxrQ+kxAtn6qYpNT/BqLWlXKx5rpYM2r+mHjFyYRYsjlAmi+RQNDEmv/uwn+XuNKzEtrL8Oq7mM13Lsid0a3gJi/t0b/luoyRyvi3fHUM/b1epfVogG/FulsZ0A92310v8MbastceQjjUzTzjKHILl7qNewkqtlzn2ARm3cZlAprSg=="
|
||||||
|
User-Agent: pherephone (go-fed/activity v1.0.0)
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
``` json
|
||||||
|
|
||||||
|
{
|
||||||
|
"@context": "https://www.w3.org/ns/activitystreams",
|
||||||
|
"actor": "https://floorb.qwazix.com/activityserve_test_actor_1",
|
||||||
|
"id": "https://floorb.qwazix.com/activityserve_test_actor_1/4wJ9DrBab4eIE3Bt",
|
||||||
|
"object": {
|
||||||
|
"actor": "https://cybre.space/users/qwazix",
|
||||||
|
"id": "https://cybre.space/9123da78-21a5-44bc-bce5-4039a4072e4c",
|
||||||
|
"object": "https://floorb.qwazix.com/activityserve_test_actor_1",
|
||||||
|
"type": "Follow"
|
||||||
|
},
|
||||||
|
"to": "https://cybre.space/users/qwazix",
|
||||||
|
"type": "Accept"
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
|
@ -34,8 +34,19 @@ func PrettyPrintJSON(theJSON []byte) {
|
||||||
log.Info(dst)
|
log.Info(dst)
|
||||||
}
|
}
|
||||||
|
|
||||||
func formatJSON(theJSON []byte) string{
|
func FormatJSON(theJSON []byte) string {
|
||||||
dst := new(bytes.Buffer)
|
dst := new(bytes.Buffer)
|
||||||
json.Indent(dst, theJSON, "", "\t")
|
json.Indent(dst, theJSON, "", "\t")
|
||||||
return dst.String()
|
return dst.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FormatHeaders to string for printing
|
||||||
|
func FormatHeaders(header http.Header) string {
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
header.Write(buf)
|
||||||
|
return buf.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func context() [1]string {
|
||||||
|
return [1]string{"https://www.w3.org/ns/activitystreams"}
|
||||||
|
}
|
||||||
|
|
10
main.go
10
main.go
|
@ -1,8 +1,9 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"flag"
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
// "os"
|
// "os"
|
||||||
// "strings"
|
// "strings"
|
||||||
|
|
||||||
|
@ -26,7 +27,6 @@ import (
|
||||||
"./activityserve"
|
"./activityserve"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
@ -51,9 +51,11 @@ func main() {
|
||||||
|
|
||||||
activityserve.Setup("config.ini", *debugFlag)
|
activityserve.Setup("config.ini", *debugFlag)
|
||||||
|
|
||||||
actor, _ := activityserve.MakeActor("activityserve_test_actor_2", "This is an activityserve test actor", "Service")
|
// actor, _ := activityserve.MakeActor("activityserve_test_actor_2", "This is an activityserve test actor", "Service")
|
||||||
// actor, _ := activityserve.LoadActor("activityserve_test_actor_2")
|
// actor, _ := activityserve.LoadActor("activityserve_test_actor_2")
|
||||||
actor.CreateNote("Hello World!")
|
// actor.CreateNote("Hello World!")
|
||||||
|
|
||||||
|
activityserve.LoadActor("activityserve_test_actor_2")
|
||||||
|
|
||||||
activityserve.Serve()
|
activityserve.Serve()
|
||||||
}
|
}
|
Loading…
Reference in New Issue