master
Michael Demetriou 2019-10-08 15:23:25 +03:00
parent 76f9e14b93
commit 5fc3b48e70
4 changed files with 64 additions and 37 deletions

19
TODO
View File

@ -13,7 +13,7 @@
[✔] 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
[ ] Write incoming activities to disk (do we have to?) [x] Write incoming activities to disk (do we have to?)
[✔] Write all the announcements (boosts) to the database to [✔] Write all the announcements (boosts) to the database to
their correct actors their correct actors
[✔] Check if we are already following users [✔] Check if we are already following users
@ -22,12 +22,13 @@
[✔] Make OS-independent (mosty directory separators) [✔] Make OS-independent (mosty directory separators)
[✔] Create outbox.json programmatically [✔] Create outbox.json programmatically
[✔] Make storage configurable (search for "storage" in project) [✔] Make storage configurable (search for "storage" in project)
[ ] Check if we're boosting only stuff from actors we follow, not whatever comes [] Check if we're boosting only stuff from actors we follow, not whatever comes
through in our inbox through in our inbox
[✔] Boost not only articles but other things too [✔] Boost not only articles but other things too
[✔] Sanitize input, never allow slashes or dots [✔] Sanitize input, never allow slashes or dots
[✔] Add summary to actors.json [✔] Add summary to actors.json
[ ] Check local actor names for characters illegal for filenames and ban them [✔] Check local actor names for characters illegal for filenames and ban them
(Done in pherephone, not activityserve)
[✔] Create debug flag [✔] Create debug flag
[✔] Write to following only upon accept [✔] Write to following only upon accept
(waiting to actually get an accept so that I can test this) (waiting to actually get an accept so that I can test this)
@ -41,16 +42,16 @@
[ ] Implement nodeinfo and statistics [ ] Implement nodeinfo and statistics
[✔] Accept even if already follows us [✔] Accept even if already follows us
[✔] Handle paging [✔] Handle paging
[ ] Test paging [] Test paging
[✔] Handle http signatures [✔] Handle http signatures
[ ] Verify http signatures [ ] Verify http signatures
[ ] Refactor, comment and clean up [] Refactor, comment and clean up
[ ] Split to pherephone and activityServe [] Split to pherephone and activityServe
[ ] Decide what's to be done with actors removed from `actors.json`. [ ] Decide what's to be done with actors removed from `actors.json`.
[ ] Remove them? [ ] Remove them?
[ ] Leave them read-only? [ ] Leave them read-only?
[ ] Leave them as is? [] Leave them as is?
[✔] Handle followers and following uri's [✔] Handle followers and following uri's
[ ] Do I care about the inbox? [ ] Do I care about the inbox?
[ ] Expose configuration to apps [] Expose configuration to apps
[ ] Do not boost replies (configurable) [] Do not boost replies (configurable)

View File

@ -54,8 +54,8 @@ type ActorToSave struct {
Followers, Following, Rejected, Requested map[string]interface{} Followers, Following, Rejected, Requested map[string]interface{}
} }
// MakeActor returns a new local actor we can act // MakeActor creates and returns a new local actor we can act
// on behalf of // on behalf of. It also creates its files on disk
func MakeActor(name, summary, actorType string) (Actor, error) { func MakeActor(name, summary, actorType string) (Actor, error) {
followers := make(map[string]interface{}) followers := make(map[string]interface{})
following := make(map[string]interface{}) following := make(map[string]interface{})
@ -125,7 +125,7 @@ func MakeActor(name, summary, actorType string) (Actor, error) {
return actor, nil return actor, nil
} }
// GetOutboxIRI returns the outbox iri in net/url // GetOutboxIRI returns the outbox iri in net/url format
func (a *Actor) GetOutboxIRI() *url.URL { func (a *Actor) GetOutboxIRI() *url.URL {
iri := a.iri + "/outbox" iri := a.iri + "/outbox"
nuiri, _ := url.Parse(iri) nuiri, _ := url.Parse(iri)
@ -133,7 +133,8 @@ func (a *Actor) GetOutboxIRI() *url.URL {
} }
// LoadActor searches the filesystem and creates an Actor // LoadActor searches the filesystem and creates an Actor
// from the data in name.json // from the data in <name>.json
// This does not preserve events so use with caution
func LoadActor(name string) (Actor, error) { func LoadActor(name string) (Actor, error) {
// make sure our users can't read our hard drive // make sure our users can't read our hard drive
if strings.ContainsAny(name, "./ ") { if strings.ContainsAny(name, "./ ") {
@ -161,9 +162,6 @@ func LoadActor(name string) (Actor, error) {
return Actor{}, err return Actor{}, err
} }
// publicKeyNewLines := strings.ReplaceAll(jsonData["PublicKey"].(string), "\\n", "\n")
// privateKeyNewLines := strings.ReplaceAll(jsonData["PrivateKey"].(string), "\\n", "\n")
publicKeyDecoded, rest := pem.Decode([]byte(jsonData["PublicKey"].(string))) publicKeyDecoded, rest := pem.Decode([]byte(jsonData["PublicKey"].(string)))
if publicKeyDecoded == nil { if publicKeyDecoded == nil {
log.Info(rest) log.Info(rest)
@ -245,7 +243,7 @@ func GetActor(name, summary, actorType string) (Actor, error) {
} }
// func LoadActorFromIRI(iri string) a Actor{ // func LoadActorFromIRI(iri string) a Actor{
// TODO, this should parse the iri and load the right actor
// } // }
// save the actor to file // save the actor to file
@ -445,6 +443,9 @@ func (a *Actor) GetFollowing(page int) (response []byte, err error) {
return a.getPeers(page, "following") return a.getPeers(page, "following")
} }
// signedHTTPPost performs an HTTP post on behalf of Actor with the
// request-target, date, host and digest headers signed
// with the actor's private key.
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 {
@ -498,7 +499,7 @@ func (a *Actor) signedHTTPPost(content map[string]interface{}, to string) (err e
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), FormatHeaders(req.Header)) log.Errorf("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
} }
@ -556,6 +557,8 @@ func (a *Actor) NewFollower(iri string, inbox string) error {
return a.save() return a.save()
} }
// appendToOutbox adds a new line with the id of the activity
// to outbox.txt
func (a *Actor) appendToOutbox(iri string) (err error) { func (a *Actor) appendToOutbox(iri string) (err error) {
// create outbox file if it doesn't exist // create outbox file if it doesn't exist
var outbox *os.File var outbox *os.File
@ -574,6 +577,7 @@ func (a *Actor) appendToOutbox(iri string) (err error) {
return nil return nil
} }
// batchSend sends a batch of http posts to a list of recipients
func (a *Actor) batchSend(activity map[string]interface{}, recipients []string) (err error) { func (a *Actor) batchSend(activity map[string]interface{}, recipients []string) (err error) {
for _, v := range recipients { for _, v := range recipients {
err := a.signedHTTPPost(activity, v) err := a.signedHTTPPost(activity, v)
@ -584,6 +588,7 @@ func (a *Actor) batchSend(activity map[string]interface{}, recipients []string)
return return
} }
// send to followers sends a batch of http posts to each one of the followers
func (a *Actor) sendToFollowers(activity map[string]interface{}) (err error) { func (a *Actor) sendToFollowers(activity map[string]interface{}) (err error) {
recipients := make([]string, len(a.followers)) recipients := make([]string, len(a.followers))
@ -645,15 +650,34 @@ func (a *Actor) Follow(user string) (err error) {
// was accepted when initially following that user // was accepted when initially following that user
// (this is read from the `actor.following` map // (this is read from the `actor.following` map
func (a *Actor) Unfollow(user string) { func (a *Actor) Unfollow(user string) {
// if we have a request to follow this user cancel it
cancelRequest := false
if _, ok := a.requested[user]; ok {
log.Info("Cancelling follow request")
cancelRequest = true
// then continue to send the unfollow to the receipient
// to inform them that the request is cancelled.
} else if _, ok := a.following[user]; !ok {
log.Info("We are not following this user, ignoring...")
return
}
log.Info("Unfollowing " + user) log.Info("Unfollowing " + user)
var hash string
// find the id of the original follow
if cancelRequest {
hash = a.requested[user].(string)
} else {
hash = a.following[user].(string)
}
// create an undo activiy // create an undo activiy
undo := make(map[string]interface{}) undo := make(map[string]interface{})
undo["@context"] = context() undo["@context"] = context()
undo["actor"] = a.iri undo["actor"] = a.iri
undo["id"] = baseURL + "/item/" + hash + "/undo"
// find the id of the original follow undo["type"] = "Undo"
hash := a.following[user].(string)
follow := make(map[string]interface{}) follow := make(map[string]interface{})
@ -673,22 +697,23 @@ func (a *Actor) Unfollow(user string) {
return return
} }
// only if we're already following them PrettyPrint(undo)
if _, ok := a.following[user]; ok { go func() {
PrettyPrint(undo) err := a.signedHTTPPost(undo, remoteUser.inbox)
go func() { if err != nil {
err := a.signedHTTPPost(undo, remoteUser.inbox) log.Info("Couldn't unfollow " + user)
if err != nil { log.Info(err)
log.Info("Couldn't unfollow " + user) return
log.Info(err) }
return // if there was no error then delete the follow
} // from the list
// if there was no error then delete the follow if cancelRequest {
// from the list delete(a.requested, user)
} else {
delete(a.following, user) delete(a.following, user)
a.save() }
}() a.save()
} }()
} }
// Announce this activity to our followers // Announce this activity to our followers

View File

@ -251,6 +251,7 @@ func Serve(actors map[string]Actor) {
// return // return
// } // }
actor.following[acceptor] = hash actor.following[acceptor] = hash
PrettyPrint(activity)
delete(actor.requested, acceptor) delete(actor.requested, acceptor)
actor.save() actor.save()
case "Reject": case "Reject":

View File

@ -2,7 +2,7 @@
## A very light ActivityPub library in go ## A very light ActivityPub library in go
This library was built to support the very little functions that [pherephone](https://github.com/writeas/pherephone) requires. It might never be feature-complete but it's a very good point to start your activityPub journey. Take a look at [activityserve-example] for a simple main file that uses **activityserve** to post a "Hello, world" message. This library was built to support the very little functions that [pherephone](https://github.com/writeas/pherephone) requires. It might never be feature-complete but it's a very good point to start your activityPub journey. Take a look at [activityserve-example](https://github.com/writeas/activityserve-example) for a simple main file that uses **activityserve** to post a "Hello, world" message.
For now it supports following and unfollowing users, accepting follows, announcing (boosting) other posts and this is pretty much it. For now it supports following and unfollowing users, accepting follows, announcing (boosting) other posts and this is pretty much it.