aboutsummaryrefslogtreecommitdiff
path: root/db/follow.go
diff options
context:
space:
mode:
Diffstat (limited to 'db/follow.go')
-rw-r--r--db/follow.go481
1 files changed, 481 insertions, 0 deletions
diff --git a/db/follow.go b/db/follow.go
new file mode 100644
index 0000000..386de2b
--- /dev/null
+++ b/db/follow.go
@@ -0,0 +1,481 @@
+package db
+
+import (
+ "bytes"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "net/http"
+ "regexp"
+ "strings"
+ "time"
+
+ "github.com/FChannel0/FChannel-Server/activitypub"
+ "github.com/FChannel0/FChannel-Server/config"
+ "github.com/FChannel0/FChannel-Server/util"
+ "github.com/FChannel0/FChannel-Server/webfinger"
+ _ "github.com/lib/pq"
+)
+
+func GetActorFollowing(w http.ResponseWriter, id string) error {
+ var following activitypub.Collection
+ var err error
+
+ following.AtContext.Context = "https://www.w3.org/ns/activitystreams"
+ following.Type = "Collection"
+ following.TotalItems, _, err = GetActorFollowTotal(id)
+ if err != nil {
+ return err
+ }
+
+ following.Items, err = GetActorFollowingDB(id)
+ if err != nil {
+ return err
+ }
+
+ enc, _ := json.MarshalIndent(following, "", "\t")
+ w.Header().Set("Content-Type", config.ActivityStreams)
+ _, err = w.Write(enc)
+
+ return err
+}
+
+func GetActorFollowers(w http.ResponseWriter, id string) error {
+ var following activitypub.Collection
+ var err error
+
+ following.AtContext.Context = "https://www.w3.org/ns/activitystreams"
+ following.Type = "Collection"
+ _, following.TotalItems, err = GetActorFollowTotal(id)
+ if err != nil {
+ return err
+ }
+
+ following.Items, err = GetActorFollowDB(id)
+ if err != nil {
+ return err
+ }
+
+ enc, _ := json.MarshalIndent(following, "", "\t")
+ w.Header().Set("Content-Type", config.ActivityStreams)
+ _, err = w.Write(enc)
+ return err
+}
+
+func GetActorFollowingDB(id string) ([]activitypub.ObjectBase, error) {
+ var followingCollection []activitypub.ObjectBase
+ query := `select following from following where id=$1`
+
+ rows, err := db.Query(query, id)
+ if err != nil {
+ return followingCollection, err
+ }
+ defer rows.Close()
+
+ for rows.Next() {
+ var obj activitypub.ObjectBase
+
+ if err := rows.Scan(&obj.Id); err != nil {
+ return followingCollection, err
+ }
+
+ followingCollection = append(followingCollection, obj)
+ }
+
+ return followingCollection, nil
+}
+
+func GetActorFollowDB(id string) ([]activitypub.ObjectBase, error) {
+ var followerCollection []activitypub.ObjectBase
+
+ query := `select follower from follower where id=$1`
+
+ rows, err := db.Query(query, id)
+ if err != nil {
+ return followerCollection, err
+ }
+ defer rows.Close()
+
+ for rows.Next() {
+ var obj activitypub.ObjectBase
+
+ if err := rows.Scan(&obj.Id); err != nil {
+ return followerCollection, err
+ }
+
+ followerCollection = append(followerCollection, obj)
+ }
+
+ return followerCollection, nil
+}
+
+func GetActorFollowTotal(id string) (int, int, error) {
+ var following int
+ var followers int
+
+ query := `select count(following) from following where id=$1`
+
+ rows, err := db.Query(query, id)
+ if err != nil {
+ return 0, 0, err
+ }
+ defer rows.Close()
+
+ for rows.Next() {
+ if err := rows.Scan(&following); err != nil {
+ return following, 0, err
+ }
+ }
+
+ query = `select count(follower) from follower where id=$1`
+
+ rows, err = db.Query(query, id)
+ if err != nil {
+ return 0, 0, err
+ }
+ defer rows.Close()
+
+ for rows.Next() {
+ if err := rows.Scan(&followers); err != nil {
+ return following, followers, err
+ }
+
+ }
+
+ return following, followers, nil
+}
+
+func AcceptFollow(activity activitypub.Activity) activitypub.Activity {
+ var accept activitypub.Activity
+ accept.AtContext.Context = activity.AtContext.Context
+ accept.Type = "Accept"
+ var nActor activitypub.Actor
+ accept.Actor = &nActor
+ accept.Actor.Id = activity.Object.Actor
+ var nObj activitypub.ObjectBase
+ accept.Object = &nObj
+ accept.Object.Actor = activity.Actor.Id
+ var nNested activitypub.NestedObjectBase
+ accept.Object.Object = &nNested
+ accept.Object.Object.Actor = activity.Object.Actor
+ accept.Object.Object.Type = "Follow"
+ accept.To = append(accept.To, activity.Object.Actor)
+
+ return accept
+}
+
+func RejectActivity(activity activitypub.Activity) activitypub.Activity {
+ var accept activitypub.Activity
+ accept.AtContext.Context = activity.AtContext.Context
+ accept.Type = "Reject"
+ var nObj activitypub.ObjectBase
+ accept.Object = &nObj
+ var nActor activitypub.Actor
+ accept.Actor = &nActor
+ accept.Actor.Id = activity.Object.Actor
+ accept.Object.Actor = activity.Actor.Id
+ var nNested activitypub.NestedObjectBase
+ accept.Object.Object = &nNested
+ accept.Object.Object.Actor = activity.Object.Actor
+ accept.Object.Object.Type = "Follow"
+ accept.To = append(accept.To, activity.Actor.Id)
+
+ return accept
+}
+
+func IsAlreadyFollowing(actor string, follow string) (bool, error) {
+ followers, err := GetActorFollowingDB(actor)
+ if err != nil {
+ return false, err
+ }
+
+ for _, e := range followers {
+ if e.Id == follow {
+ return true, nil
+ }
+ }
+
+ return false, nil
+}
+
+func IsAlreadyFollower(actor string, follow string) (bool, error) {
+ followers, err := GetActorFollowDB(actor)
+ if err != nil {
+ return false, err
+ }
+
+ for _, e := range followers {
+ if e.Id == follow {
+ return true, nil
+ }
+ }
+
+ return false, nil
+}
+
+func SetActorFollowerDB(activity activitypub.Activity) (activitypub.Activity, error) {
+ var query string
+ alreadyFollow, err := IsAlreadyFollower(activity.Actor.Id, activity.Object.Actor)
+ if err != nil {
+ return activity, err
+ }
+
+ activity.Type = "Reject"
+ if activity.Actor.Id == activity.Object.Actor {
+ return activity, nil
+ }
+
+ if alreadyFollow {
+ query = `delete from follower where id=$1 and follower=$2`
+ activity.Summary = activity.Object.Actor + " Unfollow " + activity.Actor.Id
+
+ if _, err := db.Exec(query, activity.Actor.Id, activity.Object.Actor); err != nil {
+ return activity, err
+ }
+
+ activity.Type = "Accept"
+ return activity, err
+ }
+
+ query = `insert into follower (id, follower) values ($1, $2)`
+ activity.Summary = activity.Object.Actor + " Follow " + activity.Actor.Id
+
+ if _, err := db.Exec(query, activity.Actor.Id, activity.Object.Actor); err != nil {
+ return activity, err
+ }
+
+ activity.Type = "Accept"
+ return activity, nil
+}
+
+func SetActorFollowingDB(activity activitypub.Activity) (activitypub.Activity, error) {
+ var query string
+ alreadyFollowing := false
+ alreadyFollower := false
+ following, err := GetActorFollowingDB(activity.Object.Actor)
+ if err != nil {
+ return activity, err
+ }
+
+ actor, err := webfinger.FingerActor(activity.Actor.Id)
+ if err != nil {
+ return activity, err
+ }
+
+ remoteActorFollowerCol := GetCollectionFromReq(actor.Followers)
+
+ for _, e := range following {
+ if e.Id == activity.Actor.Id {
+ alreadyFollowing = true
+ }
+ }
+
+ for _, e := range remoteActorFollowerCol.Items {
+ if e.Id == activity.Object.Actor {
+ alreadyFollower = true
+ }
+ }
+
+ activity.Type = "Reject"
+
+ if activity.Actor.Id == activity.Object.Actor {
+ return activity, nil
+ }
+
+ if alreadyFollowing && alreadyFollower {
+ query = `delete from following where id=$1 and following=$2`
+ activity.Summary = activity.Object.Actor + " Unfollowing " + activity.Actor.Id
+ if res, err := IsActorLocal(activity.Actor.Id); err == nil && !res {
+ go DeleteActorCache(activity.Actor.Id)
+ } else {
+ return activity, err
+ }
+
+ if _, err := db.Exec(query, activity.Object.Actor, activity.Actor.Id); err != nil {
+ return activity, err
+ }
+
+ activity.Type = "Accept"
+ return activity, nil
+ }
+
+ if !alreadyFollowing && !alreadyFollower {
+
+ query = `insert into following (id, following) values ($1, $2)`
+ activity.Summary = activity.Object.Actor + " Following " + activity.Actor.Id
+ if res, err := IsActorLocal(activity.Actor.Id); err == nil && !res {
+ go WriteActorToCache(activity.Actor.Id)
+ }
+ if _, err := db.Exec(query, activity.Object.Actor, activity.Actor.Id); err != nil {
+ return activity, err
+ }
+
+ activity.Type = "Accept"
+ return activity, nil
+ }
+
+ return activity, nil
+}
+
+func AutoFollow(actor string) error {
+ following, err := GetActorFollowingDB(actor)
+ if err != nil {
+ return err
+ }
+
+ follower, err := GetActorFollowDB(actor)
+ if err != nil {
+ return err
+ }
+
+ isFollowing := false
+
+ for _, e := range follower {
+ for _, k := range following {
+ if e.Id == k.Id {
+ isFollowing = true
+ }
+ }
+
+ if !isFollowing && e.Id != config.Domain && e.Id != actor {
+ followActivity, err := MakeFollowActivity(actor, e.Id)
+ if err != nil {
+ return err
+ }
+
+ nActor, err := webfinger.FingerActor(e.Id)
+ if err != nil {
+ return err
+ }
+
+ if nActor.Id != "" {
+ MakeActivityRequestOutbox(followActivity)
+ }
+ }
+ }
+
+ return nil
+}
+
+func MakeFollowActivity(actor string, follow string) (activitypub.Activity, error) {
+ var followActivity activitypub.Activity
+ var err error
+
+ followActivity.AtContext.Context = "https://www.w3.org/ns/activitystreams"
+ followActivity.Type = "Follow"
+
+ var obj activitypub.ObjectBase
+ var nactor activitypub.Actor
+ if actor == config.Domain {
+ nactor, err = GetActorFromDB(actor)
+ } else {
+ nactor, err = webfinger.FingerActor(actor)
+ }
+
+ if err != nil {
+ return followActivity, err
+ }
+
+ followActivity.Actor = &nactor
+ followActivity.Object = &obj
+
+ followActivity.Object.Actor = follow
+ followActivity.To = append(followActivity.To, follow)
+
+ return followActivity, nil
+}
+
+func MakeActivityRequestOutbox(activity activitypub.Activity) error {
+ j, _ := json.Marshal(activity)
+
+ if activity.Actor.Outbox == "" {
+ // TODO: good enough?
+ return errors.New("invalid outbox")
+ }
+
+ req, err := http.NewRequest("POST", activity.Actor.Outbox, bytes.NewBuffer(j))
+ if err != nil {
+ return err
+ }
+
+ re := regexp.MustCompile("https?://(www.)?")
+
+ var instance string
+ if activity.Actor.Id == config.Domain {
+ instance = re.ReplaceAllString(config.Domain, "")
+ } else {
+ _, instance = util.GetActorInstance(activity.Actor.Id)
+ }
+
+ date := time.Now().UTC().Format(time.RFC1123)
+ path := strings.Replace(activity.Actor.Outbox, instance, "", 1)
+
+ path = re.ReplaceAllString(path, "")
+
+ sig := fmt.Sprintf("(request-target): %s %s\nhost: %s\ndate: %s", "post", path, instance, date)
+ encSig, err := ActivitySign(*activity.Actor, sig)
+ if err != nil {
+ return err
+ }
+
+ signature := fmt.Sprintf("keyId=\"%s\",headers=\"(request-target) host date\",signature=\"%s\"", activity.Actor.PublicKey.Id, encSig)
+
+ req.Header.Set("Content-Type", config.ActivityStreams)
+ req.Header.Set("Date", date)
+ req.Header.Set("Signature", signature)
+ req.Host = instance
+
+ _, err = util.RouteProxy(req)
+ return err
+}
+
+func MakeActivityRequest(activity activitypub.Activity) error {
+ j, _ := json.MarshalIndent(activity, "", "\t")
+
+ for _, e := range activity.To {
+ if e != activity.Actor.Id {
+ actor, err := webfinger.FingerActor(e)
+ if err != nil {
+ return err
+ }
+
+ if actor.Id != "" {
+ _, instance := util.GetActorInstance(actor.Id)
+
+ if actor.Inbox != "" {
+ req, err := http.NewRequest("POST", actor.Inbox, bytes.NewBuffer(j))
+ if err != nil {
+ return err
+ }
+
+ date := time.Now().UTC().Format(time.RFC1123)
+ path := strings.Replace(actor.Inbox, instance, "", 1)
+
+ re := regexp.MustCompile("https?://(www.)?")
+ path = re.ReplaceAllString(path, "")
+
+ sig := fmt.Sprintf("(request-target): %s %s\nhost: %s\ndate: %s", "post", path, instance, date)
+ encSig, err := ActivitySign(*activity.Actor, sig)
+ if err != nil {
+ return err
+ }
+
+ signature := fmt.Sprintf("keyId=\"%s\",headers=\"(request-target) host date\",signature=\"%s\"", activity.Actor.PublicKey.Id, encSig)
+
+ req.Header.Set("Content-Type", config.ActivityStreams)
+ req.Header.Set("Date", date)
+ req.Header.Set("Signature", signature)
+ req.Host = instance
+
+ _, err = util.RouteProxy(req)
+ if err != nil {
+ fmt.Println("error with sending activity resp to actor " + instance)
+ return err // TODO: needs further testing
+ }
+ }
+ }
+ }
+ }
+
+ return nil
+}