Made onFollow overridable (untested)
parent
29ad8059ba
commit
bf09851483
|
@ -40,6 +40,7 @@ type Actor struct {
|
|||
publicKeyPem string
|
||||
privateKeyPem string
|
||||
publicKeyID string
|
||||
OnFollow func(map[string]interface{})
|
||||
}
|
||||
|
||||
// ActorToSave is a stripped down actor representation
|
||||
|
@ -78,6 +79,9 @@ func MakeActor(name, summary, actorType string) (Actor, error) {
|
|||
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
|
||||
rng := rand.Reader
|
||||
privateKey, err := rsa.GenerateKey(rng, 2048)
|
||||
|
@ -273,19 +277,19 @@ func (a *Actor) newIDurl() string {
|
|||
func (a *Actor) CreateNote(content string) {
|
||||
// for now I will just write this to the outbox
|
||||
|
||||
id := a.newIDurl()
|
||||
hash := a.newIDhash()
|
||||
create := make(map[string]interface{})
|
||||
note := make(map[string]interface{})
|
||||
create["@context"] = context()
|
||||
create["actor"] = baseURL + a.name
|
||||
create["cc"] = a.followersIRI
|
||||
create["id"] = baseURL + a.name + "/" + id
|
||||
create["id"] = baseURL + a.name + "/" + hash
|
||||
create["object"] = note
|
||||
note["attributedTo"] = baseURL + a.name
|
||||
note["cc"] = a.followersIRI
|
||||
note["content"] = content
|
||||
// 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["url"] = create["id"]
|
||||
note["type"] = "Note"
|
||||
|
@ -293,11 +297,11 @@ func (a *Actor) CreateNote(content string) {
|
|||
create["published"] = note["published"]
|
||||
create["type"] = "Create"
|
||||
go a.sendToFollowers(create)
|
||||
err := a.saveItem(id, create)
|
||||
err := a.saveItem(hash, create)
|
||||
if err != nil {
|
||||
log.Info("Could not save note to disk")
|
||||
}
|
||||
err = a.appendToOutbox(id)
|
||||
err = a.appendToOutbox(baseURL + a.name + "/" + hash)
|
||||
if err != nil {
|
||||
log.Info("Could not append Note to outbox.txt")
|
||||
}
|
||||
|
@ -589,3 +593,46 @@ func (a *Actor) followersSlice() []string {
|
|||
}
|
||||
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)
|
||||
return
|
||||
}
|
||||
postsPerPage := 100
|
||||
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 == "" {
|
||||
//TODO fix total items
|
||||
response = []byte(`{
|
||||
"@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",
|
||||
"last" : "` + baseURL + actor.name + `/outbox?min_id=0&page=1",
|
||||
"last" : "` + baseURL + actor.name + `/outbox?page=` + strconv.Itoa(totalLines/postsPerPage+1) + `",
|
||||
"totalItems" : 10,
|
||||
"type" : "OrderedCollection"
|
||||
}`)
|
||||
|
@ -108,8 +116,6 @@ func Serve() {
|
|||
log.Info("Page number not a number, assuming 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)
|
||||
if err != nil {
|
||||
log.Info("Can't read outbox file")
|
||||
|
@ -119,12 +125,7 @@ func Serve() {
|
|||
responseMap := make(map[string]interface{})
|
||||
responseMap["@context"] = context()
|
||||
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 {
|
||||
responseMap["next"] = baseURL + actor.name + "/outbox?page=" + strconv.Itoa(page+1)
|
||||
}
|
||||
|
@ -134,15 +135,28 @@ func Serve() {
|
|||
responseMap["partOf"] = baseURL + actor.name + "/outbox"
|
||||
responseMap["type"] = "OrderedCollectionPage"
|
||||
|
||||
orderedItems := make(map[string]interface{})
|
||||
orderedItems := make([]interface{}, 0, postsPerPage)
|
||||
|
||||
for item := range lines {
|
||||
// read the line
|
||||
// parse the hash
|
||||
for _, item := range lines {
|
||||
// split the line
|
||||
parts := strings.Split(item, "/")
|
||||
|
||||
// keep the hash
|
||||
hash := parts[len(parts)-1]
|
||||
// build the filename
|
||||
filename := storage + slash + "actors" + slash + actor.name + slash + "items" + slash + hash + ".json"
|
||||
// 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
|
||||
orderedItems = append(orderedItems, temp)
|
||||
}
|
||||
|
||||
responseMap["orderedItems"] = orderedItems
|
||||
|
@ -173,51 +187,13 @@ func Serve() {
|
|||
// 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
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
actor.OnFollow(activity)
|
||||
case "Accept":
|
||||
acceptor := activity["actor"].(string)
|
||||
actor, err := LoadActor(mux.Vars(r)["actor"]) // load the actor from disk
|
||||
|
@ -226,6 +202,8 @@ func Serve() {
|
|||
return
|
||||
}
|
||||
|
||||
// From here down this could be moved to Actor (TBD)
|
||||
|
||||
follow := activity["object"].(map[string]interface{})
|
||||
id := follow["id"].(string)
|
||||
|
||||
|
|
|
@ -58,7 +58,7 @@ func context() [1]string {
|
|||
// ReadLines reads specific lines from a file and returns them as
|
||||
// an array of strings
|
||||
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)
|
||||
if err != nil {
|
||||
log.Info("could not read file")
|
||||
|
|
6
main.go
6
main.go
|
@ -47,6 +47,7 @@ func main() {
|
|||
|
||||
if *debugFlag == true {
|
||||
log.EnableLevel("info")
|
||||
log.EnableLevel("error")
|
||||
}
|
||||
|
||||
activityserve.Setup("config.ini", *debugFlag)
|
||||
|
@ -56,8 +57,9 @@ func main() {
|
|||
// actor.Follow("https://cybre.space/users/tzo")
|
||||
// actor.CreateNote("Hello World!")
|
||||
|
||||
actor, _ := activityserve.LoadActor("activityserve_test_actor_2")
|
||||
actor.CreateNote("Hello World, again!")
|
||||
actor, _ :=
|
||||
activityserve.LoadActor("activityserve_test_actor_2")
|
||||
actor.CreateNote("I'm building #ActivityPub stuff")
|
||||
|
||||
activityserve.Serve()
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue