Made onFollow overridable (untested)

pull/15/head
Michael Demetriou 2019-09-13 20:21:54 +03:00
parent 29ad8059ba
commit bf09851483
4 changed files with 89 additions and 62 deletions

View File

@ -40,6 +40,7 @@ type Actor struct {
publicKeyPem string publicKeyPem string
privateKeyPem string privateKeyPem string
publicKeyID string publicKeyID string
OnFollow func(map[string]interface{})
} }
// ActorToSave is a stripped down actor representation // ActorToSave is a stripped down actor representation
@ -78,6 +79,9 @@ func MakeActor(name, summary, actorType string) (Actor, error) {
publicKeyID: publicKeyID, publicKeyID: publicKeyID,
} }
// set auto accept by default (this could be a configuration value)
actor.OnFollow = func(activity map[string]interface{}) {actor.Accept(activity)}
// create actor's keypair // create actor's keypair
rng := rand.Reader rng := rand.Reader
privateKey, err := rsa.GenerateKey(rng, 2048) privateKey, err := rsa.GenerateKey(rng, 2048)
@ -273,19 +277,19 @@ func (a *Actor) newIDurl() string {
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.newIDurl() hash := a.newIDhash()
create := make(map[string]interface{}) create := make(map[string]interface{})
note := make(map[string]interface{}) note := make(map[string]interface{})
create["@context"] = context() 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 + "/" + hash
create["object"] = note create["object"] = note
note["attributedTo"] = baseURL + a.name note["attributedTo"] = baseURL + a.name
note["cc"] = a.followersIRI note["cc"] = a.followersIRI
note["content"] = content note["content"] = content
// note["inReplyTo"] = "https://cybre.space/@qwazix/102688373602724023" // note["inReplyTo"] = "https://cybre.space/@qwazix/102688373602724023"
note["id"] = baseURL + a.name + "/note/" + id note["id"] = baseURL + a.name + "/note/" + hash
note["published"] = time.Now().Format(time.RFC3339) note["published"] = time.Now().Format(time.RFC3339)
note["url"] = create["id"] note["url"] = create["id"]
note["type"] = "Note" note["type"] = "Note"
@ -293,11 +297,11 @@ func (a *Actor) CreateNote(content string) {
create["published"] = note["published"] create["published"] = note["published"]
create["type"] = "Create" create["type"] = "Create"
go a.sendToFollowers(create) go a.sendToFollowers(create)
err := a.saveItem(id, create) err := a.saveItem(hash, create)
if err != nil { if err != nil {
log.Info("Could not save note to disk") log.Info("Could not save note to disk")
} }
err = a.appendToOutbox(id) err = a.appendToOutbox(baseURL + a.name + "/" + hash)
if err != nil { if err != nil {
log.Info("Could not append Note to outbox.txt") log.Info("Could not append Note to outbox.txt")
} }
@ -589,3 +593,46 @@ func (a *Actor) followersSlice() []string {
} }
return followersSlice return followersSlice
} }
// Accept a follow request
func (a *Actor) Accept(follow map[string]interface{}){
// it's a follow, write it down
newFollower := follow["actor"].(string)
// check we aren't following ourselves
if newFollower == follow["object"] {
log.Info("You can't follow yourself")
return
}
follower, err := NewRemoteActor(follow["actor"].(string))
// check if this user is already following us
if _, ok := a.followers[newFollower]; ok {
log.Info("You're already following us, yay!")
// do nothing, they're already following us
} else {
a.NewFollower(newFollower, follower.inbox)
}
// 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(follow, "@context")
accept := make(map[string]interface{})
accept["@context"] = "https://www.w3.org/ns/activitystreams"
accept["to"] = follow["actor"]
accept["id"] = a.newIDurl()
accept["actor"] = a.iri
accept["object"] = follow
accept["type"] = "Accept"
if err != nil {
log.Info("Couldn't retrieve remote actor info, maybe server is down?")
log.Info(err)
}
go a.signedHTTPPost(accept, follower.inbox)
}

View File

@ -91,14 +91,22 @@ func Serve() {
w.WriteHeader(http.StatusNotFound) w.WriteHeader(http.StatusNotFound)
return return
} }
postsPerPage := 100
var response []byte var response []byte
filename := storage + slash + "actors" + slash + actor.name + slash + "outbox.txt"
totalLines, err := lineCounter(filename)
if err != nil {
log.Info("Can't read outbox.txt")
log.Info(err)
return
}
if pageStr == "" { if pageStr == "" {
//TODO fix total items //TODO fix total items
response = []byte(`{ response = []byte(`{
"@context" : "https://www.w3.org/ns/activitystreams", "@context" : "https://www.w3.org/ns/activitystreams",
"first" : "` + baseURL + actor.name + `/outbox?page=true", "first" : "` + baseURL + actor.name + `/outbox?page=1",
"id" : "` + baseURL + actor.name + `/outbox", "id" : "` + baseURL + actor.name + `/outbox",
"last" : "` + baseURL + actor.name + `/outbox?min_id=0&page=1", "last" : "` + baseURL + actor.name + `/outbox?page=` + strconv.Itoa(totalLines/postsPerPage+1) + `",
"totalItems" : 10, "totalItems" : 10,
"type" : "OrderedCollection" "type" : "OrderedCollection"
}`) }`)
@ -108,8 +116,6 @@ func Serve() {
log.Info("Page number not a number, assuming 1") log.Info("Page number not a number, assuming 1")
page = 1 page = 1
} }
postsPerPage := 100
filename := storage + slash + "actors" + slash + actor.name + slash + "outbox.txt"
lines, err := ReadLines(filename, (page-1)*postsPerPage, page*(postsPerPage+1)-1) lines, err := ReadLines(filename, (page-1)*postsPerPage, page*(postsPerPage+1)-1)
if err != nil { if err != nil {
log.Info("Can't read outbox file") log.Info("Can't read outbox file")
@ -119,12 +125,7 @@ func Serve() {
responseMap := make(map[string]interface{}) responseMap := make(map[string]interface{})
responseMap["@context"] = context() responseMap["@context"] = context()
responseMap["id"] = baseURL + actor.name + "/outbox?page=" + pageStr responseMap["id"] = baseURL + actor.name + "/outbox?page=" + pageStr
totalLines, err := lineCounter(filename)
if err != nil {
log.Info("The file was deleted? It was okay a few lines above. Wtf?")
log.Info(err)
return
}
if page*postsPerPage < totalLines { if page*postsPerPage < totalLines {
responseMap["next"] = baseURL + actor.name + "/outbox?page=" + strconv.Itoa(page+1) responseMap["next"] = baseURL + actor.name + "/outbox?page=" + strconv.Itoa(page+1)
} }
@ -134,15 +135,28 @@ func Serve() {
responseMap["partOf"] = baseURL + actor.name + "/outbox" responseMap["partOf"] = baseURL + actor.name + "/outbox"
responseMap["type"] = "OrderedCollectionPage" responseMap["type"] = "OrderedCollectionPage"
orderedItems := make(map[string]interface{}) orderedItems := make([]interface{}, 0, postsPerPage)
for item := range lines { for _, item := range lines {
// read the line // split the line
// parse the hash parts := strings.Split(item, "/")
// keep the hash
hash := parts[len(parts)-1]
// build the filename // build the filename
filename := storage + slash + "actors" + slash + actor.name + slash + "items" + slash + hash + ".json"
// open the file // open the file
// unmarshal activityJSON, err := ioutil.ReadFile(filename)
if err != nil {
log.Error("can't read activity")
log.Info(filename)
return
}
var temp map[string]interface{}
// put it into a map
json.Unmarshal(activityJSON, &temp)
// append to orderedItems // append to orderedItems
orderedItems = append(orderedItems, temp)
} }
responseMap["orderedItems"] = orderedItems responseMap["orderedItems"] = orderedItems
@ -173,51 +187,13 @@ func Serve() {
// check if case is going to be an issue // check if case is going to be an issue
switch activity["type"] { switch activity["type"] {
case "Follow": 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 // load the object as actor
actor, err := LoadActor(mux.Vars(r)["actor"]) // load the actor from disk actor, err := LoadActor(mux.Vars(r)["actor"]) // load the actor from disk
if err != nil { if err != nil {
log.Error("No such actor") log.Error("No such actor")
return return
} }
actor.OnFollow(activity)
follower, err := NewRemoteActor(activity["actor"].(string))
// 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, follower.inbox)
}
// 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"
if err != nil {
log.Info("Couldn't retrieve remote actor info, maybe server is down?")
log.Info(err)
}
go actor.signedHTTPPost(accept, follower.inbox)
case "Accept": case "Accept":
acceptor := activity["actor"].(string) acceptor := activity["actor"].(string)
actor, err := LoadActor(mux.Vars(r)["actor"]) // load the actor from disk actor, err := LoadActor(mux.Vars(r)["actor"]) // load the actor from disk
@ -226,6 +202,8 @@ func Serve() {
return return
} }
// From here down this could be moved to Actor (TBD)
follow := activity["object"].(map[string]interface{}) follow := activity["object"].(map[string]interface{})
id := follow["id"].(string) id := follow["id"].(string)

View File

@ -58,7 +58,7 @@ func context() [1]string {
// ReadLines reads specific lines from a file and returns them as // ReadLines reads specific lines from a file and returns them as
// an array of strings // an array of strings
func ReadLines(filename string, from, to int) (lines []string, err error) { func ReadLines(filename string, from, to int) (lines []string, err error) {
lines = make([]string, to-from) lines = make([]string, 0, to-from)
reader, err := os.Open(filename) reader, err := os.Open(filename)
if err != nil { if err != nil {
log.Info("could not read file") log.Info("could not read file")

View File

@ -47,6 +47,7 @@ func main() {
if *debugFlag == true { if *debugFlag == true {
log.EnableLevel("info") log.EnableLevel("info")
log.EnableLevel("error")
} }
activityserve.Setup("config.ini", *debugFlag) activityserve.Setup("config.ini", *debugFlag)
@ -56,8 +57,9 @@ func main() {
// actor.Follow("https://cybre.space/users/tzo") // actor.Follow("https://cybre.space/users/tzo")
// actor.CreateNote("Hello World!") // actor.CreateNote("Hello World!")
actor, _ := activityserve.LoadActor("activityserve_test_actor_2") actor, _ :=
actor.CreateNote("Hello World, again!") activityserve.LoadActor("activityserve_test_actor_2")
actor.CreateNote("I'm building #ActivityPub stuff")
activityserve.Serve() activityserve.Serve()
} }