aboutsummaryrefslogtreecommitdiff
path: root/main.go
diff options
context:
space:
mode:
Diffstat (limited to 'main.go')
-rw-r--r--main.go937
1 files changed, 937 insertions, 0 deletions
diff --git a/main.go b/main.go
new file mode 100644
index 0000000..7ee8a84
--- /dev/null
+++ b/main.go
@@ -0,0 +1,937 @@
+package main
+
+import "fmt"
+import "strings"
+import "net/http"
+import "net/url"
+import "database/sql"
+import _ "github.com/lib/pq"
+import "math/rand"
+import "time"
+import "regexp"
+import "os/exec"
+import "bytes"
+import "encoding/json"
+import "io/ioutil"
+import "mime/multipart"
+import "os"
+
+const (
+ host = "localhost"
+ port = 5432
+ user = "postgres"
+ password = "password"
+ dbname = "fchan_server"
+)
+
+var Port = ":3000"
+var TP = "https://"
+var Domain = TP + "server.fchan.xyz"
+
+var authReq = []string{"captcha","email","passphrase"}
+
+var supportedFiles = []string{"image/gif","image/jpeg","image/png","image/svg+xml","image/webp","image/avif","image/apng","video/mp4","video/ogg","video/webm","audio/mpeg","audio/ogg","audio/wav"}
+
+
+var SiteEmail string //contact@fchan.xyz
+var SiteEmailPassword string
+var SiteEmailServer string //mail.fchan.xyz
+var SiteEmailPort string //587
+
+type BoardAccess struct {
+ boards []string
+}
+
+func main() {
+
+ if _, err := os.Stat("./public"); os.IsNotExist(err) {
+ os.Mkdir("./public", os.ModeDir)
+ }
+
+ db := ConnectDB();
+
+ defer db.Close()
+
+ go MakeCaptchas(db, 100)
+
+ // root actor is used to follow remote feeds that are not local
+ //name, prefname, summary, auth requirements, restricted
+ CreateNewBoardDB(db, *CreateNewActor("", "FChan", "FChan is a federated image board instance.", authReq, false))
+
+ // Allow access to public media folder
+ fileServer := http.FileServer(http.Dir("./public"))
+ http.Handle("/public/", http.StripPrefix("/public", neuter(fileServer)))
+
+ // main routing
+ http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request){
+ path := r.URL.Path
+
+ // remove trailing slash
+ if path != "/" {
+ re := regexp.MustCompile(`/$`)
+ path = re.ReplaceAllString(path, "")
+ }
+
+ method := r.Method
+
+ actor := GetActorFromPath(db, path, "/")
+
+ var mainActor bool
+ var mainInbox bool
+ var mainOutbox bool
+ var mainFollowing bool
+ var mainFollowers bool
+
+ var actorMain bool
+ var actorInbox bool
+ var actorOutbox bool
+ var actorFollowing bool
+ var actorFollowers bool
+ var actorReported bool
+ var actorVerification bool
+
+
+ if(actor.Id != ""){
+ if actor.Name == "main" {
+ mainActor = (path == "/")
+ mainInbox = (path == "/inbox")
+ mainOutbox = (path == "/outbox")
+ mainFollowing = (path == "/following")
+ mainFollowers = (path == "/followers")
+ } else {
+ actorMain = (path == "/" + actor.Name)
+ actorInbox = (path == "/" + actor.Name + "/inbox")
+ actorOutbox = (path == "/" + actor.Name + "/outbox")
+ actorFollowing = (path == "/" + actor.Name + "/following")
+ actorFollowers = (path == "/" + actor.Name + "/followers")
+ actorReported = (path == "/" + actor.Name + "/reported")
+ actorVerification = (path == "/" + actor.Name + "/verification")
+ }
+ }
+
+
+ if mainActor {
+ GetActorInfo(w, db, Domain)
+ } else if mainInbox {
+ if method == "POST" {
+
+ } else {
+ w.WriteHeader(http.StatusForbidden)
+ w.Write([]byte("404 no path"))
+ }
+ } else if mainOutbox {
+ if method == "GET" {
+ GetActorOutbox(w, r, db)
+ } else if method == "POST" {
+ ParseOutboxRequest(w, r, db)
+ } else {
+ w.WriteHeader(http.StatusForbidden)
+ w.Write([]byte("404 no path"))
+ }
+ } else if mainFollowing {
+ GetActorFollowing(w, db, Domain)
+ } else if mainFollowers {
+ GetActorFollowers(w, db, Domain)
+ } else if actorMain {
+ GetActorInfo(w, db, actor.Id)
+ } else if actorInbox {
+ if method == "POST" {
+ ParseInboxRequest(w, r, db)
+ } else {
+ w.WriteHeader(http.StatusForbidden)
+ w.Write([]byte("404 no path"))
+ }
+ } else if actorOutbox {
+ if method == "GET" {
+ GetActorOutbox(w, r, db)
+ } else if method == "POST" {
+ ParseOutboxRequest(w, r, db)
+ } else {
+ w.WriteHeader(http.StatusForbidden)
+ w.Write([]byte("404 no path"))
+ }
+ } else if actorFollowing {
+ GetActorFollowing(w, db, actor.Id)
+ } else if actorFollowers {
+ GetActorFollowers(w, db, actor.Id)
+ } else if actorReported {
+ GetActorReported(w, r, db, actor.Id)
+ } else if actorVerification {
+ if method == "POST" {
+ p, _ := url.ParseQuery(r.URL.RawQuery)
+ if len(p["email"]) > 0 {
+ email := p["email"][0]
+ verify := GetVerificationByEmail(db, email)
+ if verify.Identifier != "" || !IsEmailSetup() {
+ w.WriteHeader(http.StatusForbidden)
+ w.Write([]byte("400 no path"))
+ } else {
+ var nVerify Verify
+ nVerify.Type = "email"
+ nVerify.Identifier = email
+ nVerify.Code = CreateKey(32)
+ nVerify.Board = actor.Id
+ CreateVerification(db, nVerify)
+ SendVerification(nVerify)
+ w.WriteHeader(http.StatusCreated)
+ w.Write([]byte("Verification added"))
+ }
+
+ } else {
+ w.WriteHeader(http.StatusForbidden)
+ w.Write([]byte("400 no path"))
+ }
+ } else {
+ w.WriteHeader(http.StatusForbidden)
+ w.Write([]byte("400 no path"))
+ }
+ } else {
+ collection := GetCollectionFromPath(db, Domain + "" + path)
+ if len(collection.OrderedItems) > 0 {
+ enc, _ := json.MarshalIndent(collection, "", "\t")
+ w.Header().Set("Content-Type", "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"")
+ w.Write(enc)
+ } else {
+ w.WriteHeader(http.StatusForbidden)
+ w.Write([]byte("404 no path"))
+ }
+ }
+ })
+
+ http.HandleFunc("/getcaptcha", func(w http.ResponseWriter, r *http.Request){
+ w.Write([]byte(GetRandomCaptcha(db)))
+ })
+
+ http.HandleFunc("/delete", func(w http.ResponseWriter, r *http.Request){
+ values := r.URL.Query().Get("id")
+
+ if len(values) < 1 || !IsIDLocal(db, values) {
+ w.WriteHeader(http.StatusBadRequest)
+ w.Write([]byte(""))
+ return
+ }
+
+ id := values
+ DeleteObject(db, id)
+ w.Write([]byte(""))
+
+ })
+
+ http.HandleFunc("/deleteattach", func(w http.ResponseWriter, r *http.Request){
+
+ values := r.URL.Query().Get("id")
+
+ header := r.Header.Get("Authorization")
+
+ auth := strings.Split(header, " ")
+
+ if len(values) < 1 || len(auth) < 2 {
+ w.WriteHeader(http.StatusBadRequest)
+ w.Write([]byte(""))
+ return
+ }
+
+ id := values
+
+ if !IsIDLocal(db, id) {
+ w.WriteHeader(http.StatusBadRequest)
+ w.Write([]byte(""))
+ return
+ }
+
+ actor := GetActorFromPath(db, id, "/")
+
+ if HasAuth(db, auth[1], actor.Id) {
+ DeleteAttachmentFromFile(db, id)
+ DeleteAttachmentFromDB(db, id)
+ w.Write([]byte(""))
+ return
+ }
+
+ w.WriteHeader(http.StatusBadRequest)
+ w.Write([]byte(""))
+ })
+
+ http.HandleFunc("/report", func(w http.ResponseWriter, r *http.Request){
+
+ id := r.URL.Query().Get("id")
+ close := r.URL.Query().Get("close")
+
+ if close == "1" {
+ if !IsIDLocal(db, id) {
+ w.WriteHeader(http.StatusBadRequest)
+ w.Write([]byte(""))
+ return
+ }
+
+ reported := DeleteReportActivity(db, id)
+ if reported {
+ w.Write([]byte(""))
+ return
+ }
+ }
+
+ reported := ReportActivity(db, id)
+
+ if reported {
+ w.Write([]byte(""))
+ return
+ }
+
+ w.WriteHeader(http.StatusBadRequest)
+ w.Write([]byte(""))
+
+ })
+
+ http.HandleFunc("/verify", func(w http.ResponseWriter, r *http.Request){
+ var verify Verify
+ defer r.Body.Close()
+
+ body, _ := ioutil.ReadAll(r.Body)
+
+ err := json.Unmarshal(body, &verify)
+
+ CheckError(err, "error get verify from json")
+
+ v := GetVerificationByCode(db, verify.Code)
+
+ if v.Identifier == verify.Identifier {
+ w.Write([]byte(v.Board))
+ return
+ }
+
+ w.WriteHeader(http.StatusBadRequest)
+ w.Write([]byte(""))
+ })
+
+ http.ListenAndServe(Port, nil)
+}
+
+func CheckError(e error, m string) error{
+ if e != nil {
+ fmt.Println(m)
+ panic(e)
+ }
+
+ return e
+}
+
+func ConnectDB() *sql.DB {
+ psqlInfo := fmt.Sprintf("host=%s port=%d user=%s password=%s " +
+ "dbname=%s sslmode=disable", host, port, user, password, dbname)
+
+ db, err := sql.Open("postgres", psqlInfo)
+ CheckError(err, "error with db connection")
+
+ err = db.Ping()
+
+ CheckError(err, "error with db ping")
+
+ fmt.Println("Successfully connected DB")
+ return db
+}
+
+func CreateKey(len int) string {
+ var key string
+ str := (CreateTripCode(RandomID(len)))
+ for i := 0; i < len; i++ {
+ key += fmt.Sprintf("%c", str[i])
+ }
+ return key
+}
+
+func neuter(next http.Handler) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ if strings.HasSuffix(r.URL.Path, "/") {
+ http.NotFound(w, r)
+ return
+ }
+
+ next.ServeHTTP(w, r)
+ })
+}
+
+func CreateTripCode(input string) string {
+ cmd := exec.Command("sha512sum")
+ cmd.Stdin = strings.NewReader(input)
+ var out bytes.Buffer
+ cmd.Stdout = &out
+ err := cmd.Run()
+
+ CheckError(err, "error with create trip code")
+
+ return out.String()
+}
+
+func GetActorFromPath(db *sql.DB, location string, prefix string) Actor {
+ pattern := fmt.Sprintf("%s([^/\n]+)(/.+)?", prefix)
+ re := regexp.MustCompile(pattern)
+ match := re.FindStringSubmatch(location)
+
+ var actor string
+
+ if(len(match) < 1 ) {
+ actor = "/"
+ } else {
+ actor = strings.Replace(match[1], "/", "", -1)
+ }
+
+ if actor == "/" || actor == "outbox" || actor == "inbox" || actor == "following" || actor == "followers" {
+ actor = Domain
+ } else {
+ actor = Domain + "/" + actor
+ }
+
+ var nActor Actor
+
+ nActor = GetActorFromDB(db, actor)
+
+ return nActor
+}
+
+func GetContentType(location string) string {
+ elements := strings.Split(location, ";")
+ if len(elements) > 0 {
+ return elements[0]
+ } else {
+ return location
+ }
+}
+
+func RandomID(size int) string {
+ rand.Seed(time.Now().UnixNano())
+ domain := "0123456789ABCDEF"
+ rng := size
+ newID := ""
+ for i := 0; i < rng; i++ {
+ newID += string(domain[rand.Intn(len(domain))])
+ }
+
+ return newID
+}
+
+func CreateUniqueID(db *sql.DB, actor string) string {
+ var newID string
+ isUnique := false
+ for !isUnique {
+ newID = RandomID(8)
+
+ query := fmt.Sprintf("select id from activitystream where id='%s/%s/%s'", Domain, actor, newID)
+
+ rows, err := db.Query(query)
+
+ CheckError(err, "error with unique id query")
+
+ defer rows.Close()
+
+ var count int = 0
+ for rows.Next(){
+ count += 1
+ }
+
+ if count < 1 {
+ isUnique = true
+ }
+ }
+
+ return newID
+}
+
+func CreateNewActor(board string, prefName string, summary string, authReq []string, restricted bool) *Actor{
+ actor := new(Actor)
+
+ var path string
+ if board == "" {
+ path = Domain
+ actor.Name = "main"
+ } else {
+ path = Domain + "/" + board
+ actor.Name = board
+ }
+
+ actor.Type = "Service"
+ actor.Id = fmt.Sprintf("%s", path)
+ actor.Following = fmt.Sprintf("%s/following", actor.Id)
+ actor.Followers = fmt.Sprintf("%s/followers", actor.Id)
+ actor.Inbox = fmt.Sprintf("%s/inbox", actor.Id)
+ actor.Outbox = fmt.Sprintf("%s/outbox", actor.Id)
+ actor.PreferredUsername = prefName
+ actor.Restricted = restricted
+ actor.Summary = summary
+ actor.AuthRequirement = authReq
+
+ return actor
+}
+
+func GetActorInfo(w http.ResponseWriter, db *sql.DB, id string) {
+ actor := GetActorFromDB(db, id)
+ enc, _ := json.MarshalIndent(actor, "", "\t")
+ w.Header().Set("Content-Type", "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"")
+ w.Write(enc)
+}
+
+func CreateObject(objType string) ObjectBase {
+ var nObj ObjectBase
+
+ nObj.Type = objType
+ nObj.Published = time.Now().Format(time.RFC3339)
+ nObj.Updated = time.Now().Format(time.RFC3339)
+
+ return nObj
+}
+
+func CreateActivity(activityType string, obj ObjectBase) Activity {
+ var newActivity Activity
+
+ newActivity.AtContext.Context = "https://www.w3.org/ns/activitystreams"
+ newActivity.Type = activityType
+ newActivity.Published = obj.Published
+ newActivity.Actor = obj.Actor
+ newActivity.Object = &obj
+
+ for _, e := range obj.To {
+ newActivity.To = append(newActivity.To, e)
+ }
+
+ for _, e := range obj.Cc {
+ newActivity.Cc = append(newActivity.Cc, e)
+ }
+
+ return newActivity
+}
+
+func ProcessActivity(db *sql.DB, activity Activity) {
+ activityType := activity.Type
+
+ if activityType == "Create" {
+ for _, e := range activity.To {
+ if GetActorFromDB(db, e).Id != "" {
+ fmt.Println("actor is in the database")
+ } else {
+ fmt.Println("actor is NOT in the database")
+ }
+ }
+ } else if activityType == "Follow" {
+
+ } else if activityType == "Delete" {
+
+ }
+}
+
+func CreateAttachmentObject(file multipart.File, header *multipart.FileHeader) ([]ObjectBase, *os.File) {
+ contentType, _ := GetFileContentType(file)
+ filename := header.Filename
+ size := header.Size
+
+ re := regexp.MustCompile(`.+/`)
+
+ fileType := re.ReplaceAllString(contentType, "")
+
+ tempFile, _ := ioutil.TempFile("./public", "*." + fileType)
+
+ var nAttachment []ObjectBase
+ var image ObjectBase
+
+ image.Type = "Attachment"
+ image.Name = filename
+ image.Href = Domain + "/" + tempFile.Name()
+ image.MediaType = contentType
+ image.Size = size
+ image.Published = time.Now().Format(time.RFC3339)
+
+ nAttachment = append(nAttachment, image)
+
+ return nAttachment, tempFile
+}
+
+func ParseCommentForReplies(comment string) []ObjectBase {
+
+ re := regexp.MustCompile("(>>)(https://|http://)?(www\\.)?.+\\/\\w+")
+ match := re.FindAllStringSubmatch(comment, -1)
+
+ var links []string
+
+ for i:= 0; i < len(match); i++ {
+ str := strings.Replace(match[i][0], ">>", "", 1)
+ str = strings.Replace(str, "www.", "", 1)
+ str = strings.Replace(str, "http://", "", 1)
+ str = TP + "://" + str
+ links = append(links, str)
+ }
+
+ var validLinks []ObjectBase
+ for i:= 0; i < len(links); i++ {
+ _, isValid := CheckValidActivity(links[i])
+ if(isValid) {
+ var reply = new(ObjectBase)
+ reply.Id = links[i]
+ reply.Published = time.Now().Format(time.RFC3339)
+ validLinks = append(validLinks, *reply)
+ }
+ }
+
+ return validLinks
+}
+
+
+func CheckValidActivity(id string) (Collection, bool) {
+
+ req, err := http.NewRequest("GET", id, nil)
+
+ if err != nil {
+ fmt.Println("error with request")
+ panic(err)
+ }
+
+ req.Header.Set("Accept", "json/application/activity+json")
+
+ resp, err := http.DefaultClient.Do(req)
+
+ if err != nil {
+ fmt.Println("error with response")
+ panic(err)
+ }
+
+ defer resp.Body.Close()
+
+ body, _ := ioutil.ReadAll(resp.Body)
+
+ var respCollection Collection
+
+ err = json.Unmarshal(body, &respCollection)
+
+ if err != nil {
+ panic(err)
+ }
+
+ if respCollection.AtContext.Context == "https://www.w3.org/ns/activitystreams" && respCollection.OrderedItems[0].Id != "" {
+ return respCollection, true;
+ }
+
+ return respCollection, false;
+}
+
+func GetActor(id string) Actor {
+
+ var respActor Actor
+
+ req, err := http.NewRequest("GET", id, nil)
+
+ CheckError(err, "error with getting actor req")
+
+ resp, err := http.DefaultClient.Do(req)
+
+ CheckError(err, "error with getting actor resp")
+
+ defer resp.Body.Close()
+
+ body, _ := ioutil.ReadAll(resp.Body)
+
+ err = json.Unmarshal(body, &respActor)
+
+ CheckError(err, "error getting actor from body")
+
+ return respActor
+}
+
+func GetActorCollection(collection string) Collection {
+ var nCollection Collection
+
+ req, err := http.NewRequest("GET", collection, nil)
+
+ CheckError(err, "error with getting actor collection req " + collection)
+
+ resp, err := http.DefaultClient.Do(req)
+
+ CheckError(err, "error with getting actor collection resp " + collection)
+
+ if resp.StatusCode == 200 {
+
+ defer resp.Body.Close()
+
+ body, _ := ioutil.ReadAll(resp.Body)
+
+ err = json.Unmarshal(body, &nCollection)
+
+ CheckError(err, "error getting actor collection from body " + collection)
+ }
+
+ return nCollection
+}
+
+
+func IsValidActor(id string) (Actor, bool) {
+ var respCollection Actor
+ req, err := http.NewRequest("GET", id, nil)
+
+ CheckError(err, "error with valid actor request")
+
+ req.Header.Set("Accept", "json/application/activity+json")
+
+ resp, err := http.DefaultClient.Do(req)
+
+ CheckError(err, "error with valid actor response")
+
+ if resp.StatusCode == 403 {
+ return respCollection, false;
+ }
+
+ defer resp.Body.Close()
+
+ body, _ := ioutil.ReadAll(resp.Body)
+
+ err = json.Unmarshal(body, &respCollection)
+
+ if err != nil {
+ panic(err)
+ }
+
+ if respCollection.AtContext.Context == "https://www.w3.org/ns/activitystreams" && respCollection.Id != "" && respCollection.Inbox != "" && respCollection.Outbox != "" {
+ return respCollection, true;
+ }
+
+ return respCollection, false;
+}
+
+
+
+func IsActivityLocal(db *sql.DB, activity Activity) bool {
+ for _, e := range activity.To {
+ if GetActorFromDB(db, e).Id != "" {
+ return true
+ }
+ }
+
+ for _, e := range activity.Cc {
+ if GetActorFromDB(db, e).Id != "" {
+ return true
+ }
+ }
+
+ if activity.Actor != nil && GetActorFromDB(db, activity.Actor.Id).Id != "" {
+ return true
+ }
+
+ return false
+}
+
+func IsIDLocal(db *sql.DB, id string) bool {
+
+ if GetActivityFromDB(db, id).OrderedItems != nil {
+ return true
+ }
+
+ return false
+}
+
+func IsObjectLocal(db *sql.DB, id string) bool {
+
+ query := fmt.Sprintf("select id from activitystream where id='%s'", id)
+
+ rows, err := db.Query(query)
+
+ defer rows.Close()
+
+ if err != nil {
+ return false
+ }
+
+ return true
+}
+
+func GetObjectFromActivity(activity Activity) ObjectBase {
+ return *activity.Object
+}
+
+func MakeCaptchas(db *sql.DB, total int) {
+ difference := total - GetCaptchaTotal(db)
+
+ for i := 0; i < difference; i++ {
+ CreateNewCaptcha(db)
+ }
+}
+
+func GetFileContentType(out multipart.File) (string, error) {
+
+ buffer := make([]byte, 512)
+
+ _, err := out.Read(buffer)
+ if err != nil {
+ return "", err
+ }
+
+ out.Seek(0, 0)
+
+ contentType := http.DetectContentType(buffer)
+
+ return contentType, nil
+}
+
+func IsReplyInThread(db *sql.DB, inReplyTo string, id string) bool {
+ obj, _ := CheckValidActivity(inReplyTo)
+
+ for _, e := range obj.OrderedItems[0].Replies.OrderedItems {
+ if e.Id == id {
+ return true
+ }
+ }
+ return false
+}
+
+func SupportedMIMEType(mime string) bool {
+ for _, e := range supportedFiles {
+ if e == mime {
+ return true
+ }
+ }
+
+ return false
+}
+
+func DeleteReportActivity(db *sql.DB, id string) bool {
+
+ query := fmt.Sprintf("delete from reported where id='%s'", id)
+
+ _, err := db.Exec(query)
+
+ if err != nil {
+ CheckError(err, "error closing reported activity")
+ return false
+ }
+
+ return true
+}
+
+func ReportActivity(db *sql.DB, id string) bool {
+
+ if !IsIDLocal(db, id) {
+ return false
+ }
+
+ actor := GetActivityFromDB(db, id)
+
+ query := fmt.Sprintf("select count from reported where id='%s'", id)
+
+ rows, err := db.Query(query)
+
+ CheckError(err, "could not select count from reported")
+
+ defer rows.Close()
+ var count int
+ for rows.Next() {
+ rows.Scan(&count)
+ }
+
+ if count < 1 {
+ query = fmt.Sprintf("insert into reported (id, count, board) values ('%s', %d, '%s')", id, 1, actor.Actor)
+
+ _, err := db.Exec(query)
+
+ if err != nil {
+ CheckError(err, "error inserting new reported activity")
+ return false
+ }
+
+ } else {
+ count = count + 1
+ query = fmt.Sprintf("update reported set count=%d where id='%s'", count, id)
+
+ _, err := db.Exec(query)
+
+ if err != nil {
+ CheckError(err, "error updating reported activity")
+ return false
+ }
+ }
+
+ return true
+}
+
+func GetActorReported(w http.ResponseWriter, r *http.Request, db *sql.DB, id string) {
+
+ auth := r.Header.Get("Authorization")
+ verification := strings.Split(auth, " ")
+
+ if len(verification) < 2 {
+ w.WriteHeader(http.StatusBadRequest)
+ w.Write([]byte(""))
+ return
+ }
+
+ if !HasAuth(db, verification[1], id) {
+ w.WriteHeader(http.StatusBadRequest)
+ w.Write([]byte(""))
+ return
+ }
+
+ var following Collection
+
+ following.AtContext.Context = "https://www.w3.org/ns/activitystreams"
+ following.Type = "Collection"
+ following.TotalItems = GetActorReportedTotal(db, id)
+ following.Items = GetActorReportedDB(db, id)
+
+ enc, _ := json.MarshalIndent(following, "", "\t")
+ w.Header().Set("Content-Type", "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"")
+ w.Write(enc)
+}
+
+func MakeActivityRequest(activity Activity) {
+
+ j, _ := json.MarshalIndent(activity, "", "\t")
+ for _, e := range activity.To {
+ actor := GetActor(e)
+
+ req, err := http.NewRequest("POST", actor.Inbox, bytes.NewBuffer(j))
+
+ CheckError(err, "error with sending activity req to")
+
+ _, err = http.DefaultClient.Do(req)
+
+ CheckError(err, "error with sending activity resp to")
+ }
+}
+
+func GetCollectionFromID(id string) Collection {
+ req, err := http.NewRequest("GET", id, nil)
+
+ CheckError(err, "could not get collection from id req")
+
+ resp, err := http.DefaultClient.Do(req)
+
+ CheckError(err, "could not get collection from id resp")
+
+ defer resp.Body.Close()
+
+ body, _ := ioutil.ReadAll(resp.Body)
+
+ var nColl Collection
+
+ err = json.Unmarshal(body, &nColl)
+
+ CheckError(err, "error getting collection resp from json body")
+
+ return nColl
+}
+
+func GetActorFromID(id string) Actor {
+ req, err := http.NewRequest("GET", id, nil)
+
+ CheckError(err, "error getting actor from id req")
+
+ resp, err := http.DefaultClient.Do(req)
+
+ CheckError(err, "error getting actor from id resp")
+
+ defer resp.Body.Close()
+
+ body, _ := ioutil.ReadAll(resp.Body)
+
+ var respCollection Collection
+
+ err = json.Unmarshal(body, &respCollection)
+
+ CheckError(err, "error getting actor resp from json body")
+
+ return *respCollection.OrderedItems[0].Actor
+}