Made onFollow overridable (untested)
parent
29ad8059ba
commit
bf09851483
|
@ -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)
|
||||||
|
|
||||||
|
}
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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")
|
||||||
|
|
6
main.go
6
main.go
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue