From 972223c992ca5aa5e5d93cff3b2ee4e30182025b Mon Sep 17 00:00:00 2001 From: KushBlazingJudah <59340248+KushBlazingJudah@users.noreply.github.com> Date: Sun, 7 Nov 2021 00:32:39 -0300 Subject: restructuring part 5 --- Makefile | 17 ++ README.md | 3 +- client.go | 531 ++++++------------------------------------------------ db/actor.go | 51 ++++++ db/database.go | 149 ++++++++++++++- main.go | 392 ++++++++++------------------------------ outboxGet.go | 47 ++--- outboxPost.go | 138 +++++++------- routes/archive.go | 53 ++++++ routes/index.go | 9 +- routes/news.go | 69 ++++++- routes/outbox.go | 83 ++++++++- routes/post.go | 104 +++++++++++ routes/util.go | 75 ++++++++ util/util.go | 133 ++++++++++++++ webfinger/comm.go | 19 ++ 16 files changed, 998 insertions(+), 875 deletions(-) create mode 100644 Makefile create mode 100644 db/actor.go create mode 100644 routes/archive.go create mode 100644 routes/post.go diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..47075ac --- /dev/null +++ b/Makefile @@ -0,0 +1,17 @@ +VERSION=`git describe --tags` +BUILD=`date +%FT%T%z` + +LDFLAGS=-X github.com/FChannel0/FChannel-Server/config.Version=${VERSION} -X github.com/FChannel0/FChannel-Server/config.BuildTime=${BUILD} +FLAGS=-ldflags "-w -s ${LDFLAGS}" +FLAGS_DEBUG=-ldflags "${LDFLAGS}" + +debug: + go build -o fchan ${FLAGS_DEBUG} + +build: + go build -o fchan ${FLAGS} + +clean: + if [ -f "fchan" ]; then rm "fchan"; fi + +.PHONY: clean install diff --git a/README.md b/README.md index fd59ebe..23d02fc 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,8 @@ and to fix errors reported by `go vet`. - `git clone` the software - Copy `config-init` to `config` and change the values appropriately to reflect the instance. - Create the database, username, and password for psql that is used in the `config` file. -- Start the server with `go run .`. +- Build the server with `make` +- Start the server with `./fchan`. ### Server Configuration diff --git a/client.go b/client.go index aa277f2..4bcf2f1 100644 --- a/client.go +++ b/client.go @@ -1,17 +1,19 @@ package main import ( - "database/sql" "fmt" "html/template" "log" "net/http" "regexp" - "strconv" "strings" "time" - "github.com/gofiber/fiber/v2" + "github.com/FChannel0/FChannel-Server/activitypub" + "github.com/FChannel0/FChannel-Server/config" + "github.com/FChannel0/FChannel-Server/db" + "github.com/FChannel0/FChannel-Server/util" + "github.com/FChannel0/FChannel-Server/webfinger" _ "github.com/lib/pq" ) @@ -37,160 +39,10 @@ func timeToUnix(t time.Time) string { return fmt.Sprint(t.Unix()) } -func NewsGet(w http.ResponseWriter, r *http.Request, db *sql.DB, timestamp int) { +func CatalogGet(w http.ResponseWriter, r *http.Request, collection activitypub.Collection) error { t := template.Must(template.New("").Funcs(template.FuncMap{ - "sub": sub, - "unixtoreadable": unixToReadable}).ParseFiles("./static/main.html", "./static/news.html")) - - actor := GetActorFromDB(db, Domain) - - var data PageData - data.PreferredUsername = actor.PreferredUsername - data.Boards = Boards - data.Board.Name = "" - data.Key = *Key - data.Board.Domain = Domain - data.Board.ModCred, _ = GetPasswordFromSession(r) - data.Board.Actor = actor - data.Board.Post.Actor = actor.Id - data.Board.Restricted = actor.Restricted - data.NewsItems = []NewsItem{NewsItem{}} - - var err error - data.NewsItems[0], err = getNewsItemFromDB(db, timestamp) - - if err != nil { - w.WriteHeader(http.StatusForbidden) - w.Write([]byte("404 no path")) - return - } - - data.Title = actor.PreferredUsername + ": " + data.NewsItems[0].Title - - data.Themes = &Themes - if cookie, err := r.Cookie("theme"); err == nil { - data.ThemeCookie = strings.SplitN(cookie.String(), "=", 2)[1] - } - - err = t.ExecuteTemplate(w, "layout", data) - if err != nil { - // TODO: actual error handler - log.Printf("NewsGet: %s\n", err) - } -} - -func AllNewsGet(w http.ResponseWriter, r *http.Request, db *sql.DB) { - t := template.Must(template.New("").Funcs(template.FuncMap{ - "mod": mod, - "sub": sub, - "unixtoreadable": unixToReadable}).ParseFiles("./static/main.html", "./static/anews.html")) - - actor := GetActorFromDB(db, Domain) - - var data PageData - data.PreferredUsername = actor.PreferredUsername - data.Title = actor.PreferredUsername + " News" - data.Boards = Boards - data.Board.Name = "" - data.Key = *Key - data.Board.Domain = Domain - data.Board.ModCred, _ = GetPasswordFromSession(r) - data.Board.Actor = actor - data.Board.Post.Actor = actor.Id - data.Board.Restricted = actor.Restricted - data.NewsItems = getNewsFromDB(db, 0) - - data.Themes = &Themes - if cookie, err := r.Cookie("theme"); err == nil { - data.ThemeCookie = strings.SplitN(cookie.String(), "=", 2)[1] - } - - err := t.ExecuteTemplate(w, "layout", data) - if err != nil { - // TODO: actual error handler - log.Printf("AllNewsGet: %s\n", err) - } -} - -func OutboxGet(c *fiber.Ctx) error { - collection, valid := WantToServePage(DB, c.Params("actor"), 0) - - if !valid { - return c.SendString("404") - } - - actor := collection.Actor - - postNum := c.Query("page") - - page, _ := strconv.Atoi(postNum) - - var returnData PageData - - returnData.Board.Name = actor.Name - returnData.Board.PrefName = actor.PreferredUsername - returnData.Board.Summary = actor.Summary - returnData.Board.InReplyTo = "" - returnData.Board.To = actor.Outbox - returnData.Board.Actor = *actor - returnData.Board.ModCred, _ = GetPasswordFromCtx(c) - returnData.Board.Domain = Domain - returnData.Board.Restricted = actor.Restricted - returnData.CurrentPage = page - returnData.ReturnTo = "feed" - - returnData.Board.Post.Actor = actor.Id - - returnData.Board.Captcha = Domain + "/" + GetRandomCaptcha(DB) - returnData.Board.CaptchaCode = GetCaptchaCode(returnData.Board.Captcha) - - returnData.Title = "/" + actor.Name + "/ - " + actor.PreferredUsername - - returnData.Key = *Key - - returnData.Boards = Boards - returnData.Posts = collection.OrderedItems - - var offset = 15 - var pages []int - pageLimit := (float64(collection.TotalItems) / float64(offset)) - - if pageLimit > 11 { - pageLimit = 11 - } - - for i := 0.0; i < pageLimit; i++ { - pages = append(pages, int(i)) - } - - returnData.Pages = pages - returnData.TotalPage = len(returnData.Pages) - 1 - - returnData.Themes = &Themes - - returnData.ThemeCookie = GetThemeCookie(c) - - return c.Render("nposts", fiber.Map{ - "page": returnData, - }, "layouts/main") -} - -func CatalogGet(w http.ResponseWriter, r *http.Request, db *sql.DB, collection Collection) { - t := template.Must(template.New("").Funcs(template.FuncMap{ - "proxy": func(url string) string { - return MediaProxy(url) - }, - "short": func(actorName string, url string) string { - return shortURL(actorName, url) - }, - "parseAttachment": func(obj ObjectBase, catalog bool) template.HTML { - return ParseAttachment(obj, catalog) - }, - "isOnion": func(url string) bool { - return IsOnion(url) - }, "showArchive": func() bool { - col := GetActorCollectionDBTypeLimit(db, collection.Actor.Id, "Archive", 1) + col := GetActorCollectionDBTypeLimit(collection.Actor.Id, "Archive", 1) if len(col.OrderedItems) > 0 { return true @@ -209,264 +61,44 @@ func CatalogGet(w http.ResponseWriter, r *http.Request, db *sql.DB, collection C returnData.Board.Actor = *actor returnData.Board.Summary = actor.Summary returnData.Board.ModCred, _ = GetPasswordFromSession(r) - returnData.Board.Domain = Domain + returnData.Board.Domain = config.Domain returnData.Board.Restricted = actor.Restricted returnData.Key = *Key returnData.ReturnTo = "catalog" returnData.Board.Post.Actor = actor.Id - returnData.Instance = GetActorFromDB(db, Domain) - - returnData.Board.Captcha = Domain + "/" + GetRandomCaptcha(db) - returnData.Board.CaptchaCode = GetCaptchaCode(returnData.Board.Captcha) - - returnData.Title = "/" + actor.Name + "/ - " + actor.PreferredUsername - - returnData.Boards = Boards - - returnData.Posts = collection.OrderedItems - - returnData.Themes = &Themes - if cookie, err := r.Cookie("theme"); err == nil { - returnData.ThemeCookie = strings.SplitN(cookie.String(), "=", 2)[1] + var err error + returnData.Instance, err = db.GetActorFromDB(config.Domain) + if err != nil { + return err } - err := t.ExecuteTemplate(w, "layout", returnData) + capt, err := db.GetRandomCaptcha() if err != nil { - // TODO: actual error handler - log.Printf("CatalogGet: %s\n", err) + return err } -} - -func ArchiveGet(w http.ResponseWriter, r *http.Request, db *sql.DB, collection Collection) { - t := template.Must(template.New("").Funcs(template.FuncMap{ - "proxy": func(url string) string { - return MediaProxy(url) - }, - "short": func(actorName string, url string) string { - return shortURL(actorName, url) - }, - "shortExcerpt": func(post ObjectBase) template.HTML { - return template.HTML(ShortExcerpt(post)) - }, - "parseAttachment": func(obj ObjectBase, catalog bool) template.HTML { - return ParseAttachment(obj, catalog) - }, - "mod": mod, - "sub": sub}).ParseFiles("./static/main.html", "./static/archive.html", "./static/bottom.html")) - - actor := collection.Actor - - var returnData PageData - returnData.Board.Name = actor.Name - returnData.Board.PrefName = actor.PreferredUsername - returnData.Board.InReplyTo = "" - returnData.Board.To = actor.Outbox - returnData.Board.Actor = *actor - returnData.Board.Summary = actor.Summary - returnData.Board.ModCred, _ = GetPasswordFromSession(r) - returnData.Board.Domain = Domain - returnData.Board.Restricted = actor.Restricted - returnData.Key = *Key - returnData.ReturnTo = "archive" - - returnData.Board.Post.Actor = actor.Id - - returnData.Instance = GetActorFromDB(db, Domain) - - returnData.Board.Captcha = Domain + "/" + GetRandomCaptcha(db) - returnData.Board.CaptchaCode = GetCaptchaCode(returnData.Board.Captcha) + returnData.Board.Captcha = config.Domain + "/" + capt + returnData.Board.CaptchaCode = util.GetCaptchaCode(returnData.Board.Captcha) returnData.Title = "/" + actor.Name + "/ - " + actor.PreferredUsername - returnData.Boards = Boards + returnData.Boards = db.Boards returnData.Posts = collection.OrderedItems returnData.Themes = &Themes - if cookie, err := r.Cookie("theme"); err == nil { - returnData.ThemeCookie = strings.SplitN(cookie.String(), "=", 2)[1] - } + returnData.ThemeCookie = getThemeCookie(ctx) err := t.ExecuteTemplate(w, "layout", returnData) if err != nil { // TODO: actual error handler - log.Printf("ArchiveGet: %s\n", err) - } -} - -func PostGet(c *fiber.Ctx) error { - - actor := GetActorByNameFromDB(DB, c.Params("actor")) - postId := c.Params("post") - - inReplyTo := actor.Id + "/" + postId - - var returnData PageData - returnData.Board.Name = actor.Name - returnData.Board.PrefName = actor.PreferredUsername - returnData.Board.To = actor.Outbox - returnData.Board.Actor = actor - returnData.Board.Summary = actor.Summary - returnData.Board.ModCred, _ = GetPasswordFromCtx(c) - returnData.Board.Domain = Domain - returnData.Board.Restricted = actor.Restricted - returnData.ReturnTo = "feed" - - returnData.Board.Captcha = Domain + "/" + GetRandomCaptcha(DB) - returnData.Board.CaptchaCode = GetCaptchaCode(returnData.Board.Captcha) - - returnData.Instance = GetActorFromDB(DB, Domain) - - returnData.Title = "/" + returnData.Board.Name + "/ - " + returnData.Board.PrefName - - returnData.Key = *Key - - returnData.Boards = Boards - - re := regexp.MustCompile("f(\\w|[!@#$%^&*<>])+-(\\w|[!@#$%^&*<>])+") - - if re.MatchString(postId) { // if non local actor post - name := GetActorFollowNameFromPath(postId) - followActors := GetActorsFollowFromName(actor, name) - followCollection := GetActorsFollowPostFromId(DB, followActors, postId) - - if len(followCollection.OrderedItems) > 0 { - returnData.Board.InReplyTo = followCollection.OrderedItems[0].Id - returnData.Posts = append(returnData.Posts, followCollection.OrderedItems[0]) - var actor Actor - actor = FingerActor(returnData.Board.InReplyTo) - returnData.Board.Post.Actor = actor.Id - } - } else { - collection := GetObjectByIDFromDB(DB, inReplyTo) - if collection.Actor != nil { - returnData.Board.Post.Actor = collection.Actor.Id - returnData.Board.InReplyTo = inReplyTo - - if len(collection.OrderedItems) > 0 { - returnData.Posts = append(returnData.Posts, collection.OrderedItems[0]) - } - } - } - - if len(returnData.Posts) > 0 { - returnData.PostId = shortURL(returnData.Board.To, returnData.Posts[0].Id) - } - - returnData.Themes = &Themes - - returnData.ThemeCookie = GetThemeCookie(c) - - return c.Render("npost", fiber.Map{ - "page": returnData, - }, "layouts/main") -} - -func WantToServePage(db *sql.DB, actorName string, page int) (Collection, bool) { - - var collection Collection - serve := false - - if page > 10 { - return collection, serve - } - - actor := GetActorByNameFromDB(db, actorName) - - if actor.Id != "" { - collection = GetObjectFromDBPage(db, actor.Id, page) - collection.Actor = &actor - return collection, true - } - - return collection, serve -} - -func WantToServeCatalog(db *sql.DB, actorName string) (Collection, bool) { - - var collection Collection - serve := false - - actor := GetActorByNameFromDB(db, actorName) - - if actor.Id != "" { - collection = GetObjectFromDBCatalog(db, actor.Id) - collection.Actor = &actor - return collection, true - } - - return collection, serve -} - -func WantToServeArchive(db *sql.DB, actorName string) (Collection, bool) { - - var collection Collection - serve := false - - actor := GetActorByNameFromDB(db, actorName) - - if actor.Id != "" { - collection = GetActorCollectionDBType(db, actor.Id, "Archive") - collection.Actor = &actor - return collection, true - } - - return collection, serve -} - -func StripTransferProtocol(value string) string { - re := regexp.MustCompile("(http://|https://)?(www.)?") - - value = re.ReplaceAllString(value, "") - - return value -} - -func GetCaptchaCode(captcha string) string { - re := regexp.MustCompile("\\w+\\.\\w+$") - - code := re.FindString(captcha) - - re = regexp.MustCompile("\\w+") - - code = re.FindString(code) - - return code -} - -func GetActorsFollowFromName(actor Actor, name string) []string { - var followingActors []string - follow := GetActorCollection(actor.Following) - - re := regexp.MustCompile("\\w+?$") - - for _, e := range follow.Items { - if re.FindString(e.Id) == name { - followingActors = append(followingActors, e.Id) - } + log.Printf("CatalogGet: %s\n", err) } - - return followingActors -} - -func GetActorsFollowPostFromId(db *sql.DB, actors []string, id string) Collection { - var collection Collection - - for _, e := range actors { - tempCol := GetObjectByIDFromDB(db, e+"/"+id) - if len(tempCol.OrderedItems) > 0 { - collection = tempCol - return collection - } - } - - return collection } func MediaProxy(url string) string { - re := regexp.MustCompile("(.+)?" + Domain + "(.+)?") + re := regexp.MustCompile("(.+)?" + config.Domain + "(.+)?") if re.MatchString(url) { return url @@ -482,8 +114,7 @@ func MediaProxy(url string) string { return "/api/media?hash=" + HashMedia(url) } -func ParseAttachment(obj ObjectBase, catalog bool) template.HTML { - +func ParseAttachment(obj activitypub.ObjectBase, catalog bool) template.HTML { if len(obj.Attachment) < 1 { return "" } @@ -557,20 +188,23 @@ func ParseAttachment(obj ObjectBase, catalog bool) template.HTML { return template.HTML(media) } -func ParseContent(db *sql.DB, board Actor, op string, content string, thread ObjectBase) template.HTML { +func ParseContent(board activitypub.Actor, op string, content string, thread activitypub.ObjectBase) (template.HTML, error) { nContent := strings.ReplaceAll(content, `<`, "<") - nContent = ParseLinkComments(db, board, op, nContent, thread) + nContent, err := ParseLinkComments(board, op, nContent, thread) + if err != nil { + return "", err + } nContent = ParseCommentQuotes(nContent) nContent = strings.ReplaceAll(nContent, `/\<`, ">") - return template.HTML(nContent) + return template.HTML(nContent), nil } -func ParseLinkComments(db *sql.DB, board Actor, op string, content string, thread ObjectBase) string { +func ParseLinkComments(board activitypub.Actor, op string, content string, thread activitypub.ObjectBase) (string, error) { re := regexp.MustCompile(`(>>(https?://[A-Za-z0-9_.:\-~]+\/[A-Za-z0-9_.\-~]+\/)(f[A-Za-z0-9_.\-~]+-)?([A-Za-z0-9_.\-~]+)?#?([A-Za-z0-9_.\-~]+)?)`) match := re.FindAllStringSubmatch(content, -1) @@ -603,7 +237,11 @@ func ParseLinkComments(db *sql.DB, board Actor, op string, content string, threa } if quoteTitle == "" { - obj := GetObjectFromDBFromID(db, parsedLink) + obj, err := db.GetObjectFromDBFromID(parsedLink) + if err != nil { + return "", err + } + if len(obj.OrderedItems) > 0 { quoteTitle = ParseLinkTitle(board.Outbox, op, obj.OrderedItems[0].Content) } else { @@ -613,29 +251,40 @@ func ParseLinkComments(db *sql.DB, board Actor, op string, content string, threa } //replace link with quote format - replyID, isReply := IsReplyToOP(db, op, parsedLink) + replyID, isReply, err := db.IsReplyToOP(op, parsedLink) + if err != nil { + return "", err + } + if isReply { - id := shortURL(board.Outbox, replyID) + id := util.ShortURL(board.Outbox, replyID) - content = strings.Replace(content, match[i][0], ">>"+id+""+isOP+"", -1) + content = strings.Replace(content, match[i][0], ">>"+id+""+isOP+"", -1) } else { - //this is a cross post - parsedOP := GetReplyOP(db, parsedLink) - actor := FingerActor(parsedLink) + + parsedOP, err := db.GetReplyOP(parsedLink) + if err != nil { + return "", err + } + + actor, err := webfinger.FingerActor(parsedLink) + if err != nil { + return "", err + } if parsedOP != "" { - link = parsedOP + "#" + shortURL(parsedOP, parsedLink) + link = parsedOP + "#" + util.ShortURL(parsedOP, parsedLink) } if actor.Id != "" { - content = strings.Replace(content, match[i][0], ">>"+shortURL(board.Outbox, parsedLink)+isOP+" →", -1) + content = strings.Replace(content, match[i][0], ">>"+util.ShortURL(board.Outbox, parsedLink)+isOP+" →", -1) } } } - return content + return content, nil } func ParseLinkTitle(actorName string, op string, content string) string { @@ -653,7 +302,7 @@ func ParseLinkTitle(actorName string, op string, content string) string { } link = ConvertHashLink(domain, link) - content = strings.Replace(content, match[i][0], ">>"+shortURL(actorName, link)+isOP, 1) + content = strings.Replace(content, match[i][0], ">>"+util.ShortURL(actorName, link)+isOP, 1) } content = strings.ReplaceAll(content, "'", "") @@ -693,75 +342,3 @@ func ConvertHashLink(domain string, link string) string { return parsedLink } - -func ShortImg(url string) string { - nURL := url - - re := regexp.MustCompile(`(\.\w+$)`) - - fileName := re.ReplaceAllString(url, "") - - if len(fileName) > 26 { - re := regexp.MustCompile(`(^.{26})`) - - match := re.FindStringSubmatch(fileName) - - if len(match) > 0 { - nURL = match[0] - } - - re = regexp.MustCompile(`(\..+$)`) - - match = re.FindStringSubmatch(url) - - if len(match) > 0 { - nURL = nURL + "(...)" + match[0] - } - } - - return nURL -} - -func ConvertSize(size int64) string { - var rValue string - - convert := float32(size) / 1024.0 - - if convert > 1024 { - convert = convert / 1024.0 - rValue = fmt.Sprintf("%.2f MB", convert) - } else { - rValue = fmt.Sprintf("%.2f KB", convert) - } - - return rValue -} - -func ShortExcerpt(post ObjectBase) string { - var returnString string - - if post.Name != "" { - returnString = post.Name + "| " + post.Content - } else { - returnString = post.Content - } - - re := regexp.MustCompile(`(^(.|\r\n|\n){100})`) - - match := re.FindStringSubmatch(returnString) - - if len(match) > 0 { - returnString = match[0] + "..." - } - - re = regexp.MustCompile(`(^.+\|)`) - - match = re.FindStringSubmatch(returnString) - - if len(match) > 0 { - returnString = strings.Replace(returnString, match[0], ""+match[0]+"", 1) - returnString = strings.Replace(returnString, "|", ":", 1) - } - - return returnString -} diff --git a/db/actor.go b/db/actor.go new file mode 100644 index 0000000..51c8f41 --- /dev/null +++ b/db/actor.go @@ -0,0 +1,51 @@ +package db + +import ( + "fmt" + "regexp" + "strings" + + "github.com/FChannel0/FChannel-Server/activitypub" +) + +func GetActorFromPath(location string, prefix string) (activitypub.Actor, error) { + 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 = "main" + } + + var nActor activitypub.Actor + + nActor, err := GetActorByNameFromDB(actor) + if err != nil { + return nActor, err + } + + if nActor.Id == "" { + nActor = GetActorByName(actor) + } + + return nActor, nil +} + +func GetActorByName(name string) activitypub.Actor { + var actor activitypub.Actor + for _, e := range Boards { + if e.Actor.Name == name { + actor = e.Actor + } + } + + return actor +} diff --git a/db/database.go b/db/database.go index 559188b..bd302aa 100644 --- a/db/database.go +++ b/db/database.go @@ -1975,7 +1975,7 @@ func GetNewsFromDB(limit int) ([]NewsItem, error) { return news, nil } -func getNewsItemFromDB(timestamp int) (NewsItem, error) { +func GetNewsItemFromDB(timestamp int) (NewsItem, error) { var news NewsItem var content string query := `select title, content, time from newsItem where time=$1 limit 1` @@ -2330,3 +2330,150 @@ func IsReplyInThread(inReplyTo string, id string) (bool, error) { return false, nil } + +func GetActorsFollowPostFromId(actors []string, id string) (activitypub.Collection, error) { + var collection activitypub.Collection + + for _, e := range actors { + tempCol, err := GetObjectByIDFromDB(e + "/" + id) + if err != nil { + return collection, err + } + + if len(tempCol.OrderedItems) > 0 { + collection = tempCol + return collection, nil + } + } + + return collection, nil +} + +func IsReplyToOP(op string, link string) (string, bool, error) { + if op == link { + return link, true, nil + } + + re := regexp.MustCompile(`f(\w+)\-`) + match := re.FindStringSubmatch(link) + + if len(match) > 0 { + re := regexp.MustCompile(`(.+)\-`) + link = re.ReplaceAllString(link, "") + link = "%" + match[1] + "/" + link + } + + query := `select id from replies where id like $1 and inreplyto=$2` + + rows, err := db.Query(query, link, op) + if err != nil { + return op, false, err + } + defer rows.Close() + + var id string + rows.Next() + if err := rows.Scan(&id); err != nil { + return id, false, err + } + + if id != "" { + return id, true, nil + } + + return "", false, nil +} + +func GetReplyOP(link string) (string, error) { + query := `select id from replies where id in (select inreplyto from replies where id=$1) and inreplyto=''` + + rows, err := db.Query(query, link) + if err != nil { + return "", err + } + defer rows.Close() + + var id string + + rows.Next() + err = rows.Scan(&id) + return id, err +} + +func StartupArchive() error { + for _, e := range FollowingBoards { + actor, err := GetActorFromDB(e.Id) + if err != nil { + return err + } + + if err := ArchivePosts(actor); err != nil { + return err + } + } + + return nil +} + +func CheckInactive() { + for true { + CheckInactiveInstances() + time.Sleep(24 * time.Hour) + } +} + +func CheckInactiveInstances() (map[string]string, error) { + instances := make(map[string]string) + query := `select following from following` + + rows, err := db.Query(query) + if err != nil { + return instances, err + } + defer rows.Close() + + for rows.Next() { + var instance string + if err := rows.Scan(&instance); err != nil { + return instances, err + } + + instances[instance] = instance + } + + query = `select follower from follower` + rows, err = db.Query(query) + if err != nil { + return instances, err + } + defer rows.Close() + + for rows.Next() { + var instance string + if err := rows.Scan(&instance); err != nil { + return instances, err + } + + instances[instance] = instance + } + + re := regexp.MustCompile(config.Domain + `(.+)?`) + for _, e := range instances { + actor, err := webfinger.GetActor(e) + if err != nil { + return instances, err + } + + if actor.Id == "" && !re.MatchString(e) { + if err := AddInstanceToInactiveDB(e); err != nil { + return instances, err + } + } else { + if err := DeleteInstanceFromInactiveDB(e); err != nil { + return instances, err + } + } + } + + return instances, nil +} diff --git a/main.go b/main.go index 187179d..6d86ec9 100644 --- a/main.go +++ b/main.go @@ -56,7 +56,7 @@ func main() { db.RunDatabaseSchema() - go MakeCaptchas(DB, 100) + go MakeCaptchas(100) config.Key = util.CreateKey(32) @@ -216,7 +216,7 @@ func main() { actorDomain[0] = "/" + actorDomain[0] } - if !IsActorLocal(DB, TP+""+actorDomain[1]+""+actorDomain[0]) { + if !IsActorLocal(TP + "" + actorDomain[1] + "" + actorDomain[0]) { c.Status(fiber.StatusBadRequest) return c.Send([]byte("actor not local")) } @@ -274,34 +274,6 @@ func neuter(next http.Handler) http.Handler { }) } -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 = "main" - } - - var nActor Actor - - nActor = GetActorByNameFromDB(db, actor) - - if nActor.Id == "" { - nActor = GetActorByName(db, actor) - } - - return nActor -} - func GetContentType(location string) string { elements := strings.Split(location, ";") if len(elements) > 0 { @@ -311,8 +283,8 @@ func GetContentType(location string) string { } } -func CreateNewActor(board string, prefName string, summary string, authReq []string, restricted bool) *Actor { - actor := new(Actor) +func CreateNewActor(board string, prefName string, summary string, authReq []string, restricted bool) *activitypub.Actor { + actor := new(activitypub.Actor) var path string if board == "" { @@ -338,14 +310,14 @@ func CreateNewActor(board string, prefName string, summary string, authReq []str } func GetActorInfo(w http.ResponseWriter, db *sql.DB, id string) { - actor := GetActorFromDB(db, id) + actor := GetActorFromDB(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 GetActorPost(w http.ResponseWriter, db *sql.DB, path string) { - collection := GetCollectionFromPath(db, Domain+""+path) + collection := GetCollectionFromPath(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\"") @@ -353,8 +325,8 @@ func GetActorPost(w http.ResponseWriter, db *sql.DB, path string) { } } -func CreateObject(objType string) ObjectBase { - var nObj ObjectBase +func CreateObject(objType string) activitypub.ObjectBase { + var nObj activitypub.ObjectBase nObj.Type = objType nObj.Published = time.Now().UTC() @@ -363,7 +335,7 @@ func CreateObject(objType string) ObjectBase { return nObj } -func AddFollowersToActivity(db *sql.DB, activity Activity) Activity { +func AddFollowersToActivity(activity activitypub.Activity) activitypub.Activity { activity.To = append(activity.To, activity.Actor.Id) @@ -374,7 +346,7 @@ func AddFollowersToActivity(db *sql.DB, activity Activity) Activity { } } - var nActivity Activity + var nActivity activitypub.Activity for _, e := range activity.To { var alreadyTo = false @@ -394,8 +366,8 @@ func AddFollowersToActivity(db *sql.DB, activity Activity) Activity { return activity } -func CreateActivity(activityType string, obj ObjectBase) Activity { - var newActivity Activity +func CreateActivity(activityType string, obj activitypub.ObjectBase) activitypub.Activity { + var newActivity activitypub.Activity actor := FingerActor(obj.Actor) newActivity.AtContext.Context = "https://www.w3.org/ns/activitystreams" @@ -419,12 +391,12 @@ func CreateActivity(activityType string, obj ObjectBase) Activity { return newActivity } -func ProcessActivity(db *sql.DB, activity Activity) { +func ProcessActivity(activity activitypub.Activity) { activityType := activity.Type if activityType == "Create" { for _, e := range activity.To { - if GetActorFromDB(db, e).Id != "" { + if GetActorFromDB(e).Id != "" { fmt.Println("actor is in the database") } else { fmt.Println("actor is NOT in the database") @@ -437,13 +409,13 @@ func ProcessActivity(db *sql.DB, activity Activity) { } } -func CreatePreviewObject(obj ObjectBase) *NestedObjectBase { +func CreatePreviewObject(obj activitypub.ObjectBase) *activitypub.NestedObjectBase { re := regexp.MustCompile(`/.+$`) mimetype := re.ReplaceAllString(obj.MediaType, "") - var nPreview NestedObjectBase + var nPreview activitypub.NestedObjectBase if mimetype != "image" { return &nPreview @@ -471,14 +443,14 @@ func CreatePreviewObject(obj ObjectBase) *NestedObjectBase { err := cmd.Run() if CheckError(err, "error with resize attachment preview") != nil { - var preview NestedObjectBase + var preview activitypub.NestedObjectBase return &preview } return &nPreview } -func CreateAttachmentObject(file multipart.File, header *multipart.FileHeader) ([]ObjectBase, *os.File) { +func CreateAttachmentObject(file multipart.File, header *multipart.FileHeader) ([]activitypub.ObjectBase, *os.File) { contentType, _ := GetFileContentType(file) filename := header.Filename size := header.Size @@ -489,8 +461,8 @@ func CreateAttachmentObject(file multipart.File, header *multipart.FileHeader) ( tempFile, _ := ioutil.TempFile("./public", "*."+fileType) - var nAttachment []ObjectBase - var image ObjectBase + var nAttachment []activitypub.ObjectBase + var image activitypub.ObjectBase image.Type = "Attachment" image.Name = filename @@ -504,7 +476,7 @@ func CreateAttachmentObject(file multipart.File, header *multipart.FileHeader) ( return nAttachment, tempFile } -func ParseCommentForReplies(db *sql.DB, comment string, op string) []ObjectBase { +func ParseCommentForReplies(comment string, op string) []activitypub.ObjectBase { re := regexp.MustCompile(`(>>(https?://[A-Za-z0-9_.:\-~]+\/[A-Za-z0-9_.\-~]+\/)(f[A-Za-z0-9_.\-~]+-)?([A-Za-z0-9_.\-~]+)?#?([A-Za-z0-9_.\-~]+)?)`) match := re.FindAllStringSubmatch(comment, -1) @@ -517,17 +489,17 @@ func ParseCommentForReplies(db *sql.DB, comment string, op string) []ObjectBase str = strings.Replace(str, "http://", "", 1) str = strings.Replace(str, "https://", "", 1) str = TP + "" + str - _, isReply := IsReplyToOP(db, op, str) + _, isReply := IsReplyToOP(op, str) if !IsInStringArray(links, str) && isReply { links = append(links, str) } } - var validLinks []ObjectBase + var validLinks []activitypub.ObjectBase for i := 0; i < len(links); i++ { _, isValid := CheckValidActivity(links[i]) if isValid { - var reply = new(ObjectBase) + var reply = new(activitypub.ObjectBase) reply.Id = links[i] reply.Published = time.Now().UTC() validLinks = append(validLinks, *reply) @@ -537,7 +509,7 @@ func ParseCommentForReplies(db *sql.DB, comment string, op string) []ObjectBase return validLinks } -func IsValidActor(id string) (Actor, bool) { +func IsValidActor(id string) (activitypub.Actor, bool) { actor := FingerActor(id) @@ -548,31 +520,31 @@ func IsValidActor(id string) (Actor, bool) { return actor, false } -func IsActivityLocal(db *sql.DB, activity Activity) bool { +func IsActivityLocal(activity activitypub.Activity) bool { for _, e := range activity.To { - if GetActorFromDB(db, e).Id != "" { + if GetActorFromDB(e).Id != "" { return true } } for _, e := range activity.Cc { - if GetActorFromDB(db, e).Id != "" { + if GetActorFromDB(e).Id != "" { return true } } - if activity.Actor != nil && GetActorFromDB(db, activity.Actor.Id).Id != "" { + if activity.Actor != nil && GetActorFromDB(activity.Actor.Id).Id != "" { return true } return false } -func GetObjectFromActivity(activity Activity) ObjectBase { +func GetObjectFromActivity(activity activitypub.Activity) activitypub.ObjectBase { return *activity.Object } -func MakeCaptchas(db *sql.DB, total int) { +func MakeCaptchas(total int) { difference := total - GetCaptchaTotal(db) for i := 0; i < difference; i++ { @@ -617,26 +589,26 @@ func GetActorReported(w http.ResponseWriter, r *http.Request, db *sql.DB, id str return } - if !HasAuth(db, verification[1], id) { + if !HasAuth(verification[1], id) { w.WriteHeader(http.StatusBadRequest) w.Write([]byte("")) return } - var following Collection + var following activitypub.Collection following.AtContext.Context = "https://www.w3.org/ns/activitystreams" following.Type = "Collection" - following.TotalItems = GetActorReportedTotal(db, id) - following.Items = GetActorReportedDB(db, id) + following.TotalItems = GetActorReportedTotal(id) + following.Items = GetActorReportedDB(id) enc, _ := json.MarshalIndent(following, "", "\t") w.Header().Set("Content-Type", activitystreams) w.Write(enc) } -func GetCollectionFromID(id string) Collection { - var nColl Collection +func GetCollectionFromID(id string) activitypub.Collection { + var nColl activitypub.Collection req, err := http.NewRequest("GET", id, nil) @@ -706,57 +678,57 @@ func GetUniqueFilename(_type string) string { return "" } -func DeleteObjectRequest(db *sql.DB, id string) { - var nObj ObjectBase - var nActor Actor +func DeleteObjectRequest(id string) { + var nObj activitypub.ObjectBase + var nActor activitypub.Actor nObj.Id = id nObj.Actor = nActor.Id activity := CreateActivity("Delete", nObj) - obj := GetObjectFromPath(db, id) + obj := GetObjectFromPath(id) actor := FingerActor(obj.Actor) activity.Actor = &actor - followers := GetActorFollowDB(db, obj.Actor) + followers := GetActorFollowDB(obj.Actor) for _, e := range followers { activity.To = append(activity.To, e.Id) } - following := GetActorFollowingDB(db, obj.Actor) + following := GetActorFollowingDB(obj.Actor) for _, e := range following { activity.To = append(activity.To, e.Id) } - MakeActivityRequest(db, activity) + MakeActivityRequest(activity) } -func DeleteObjectAndRepliesRequest(db *sql.DB, id string) { - var nObj ObjectBase - var nActor Actor +func DeleteObjectAndRepliesRequest(id string) { + var nObj activitypub.ObjectBase + var nActor activitypub.Actor nObj.Id = id nObj.Actor = nActor.Id activity := CreateActivity("Delete", nObj) - obj := GetObjectByIDFromDB(db, id) + obj := GetObjectByIDFromDB(id) activity.Actor.Id = obj.OrderedItems[0].Actor activity.Object = &obj.OrderedItems[0] - followers := GetActorFollowDB(db, obj.OrderedItems[0].Actor) + followers := GetActorFollowDB(obj.OrderedItems[0].Actor) for _, e := range followers { activity.To = append(activity.To, e.Id) } - following := GetActorFollowingDB(db, obj.OrderedItems[0].Actor) + following := GetActorFollowingDB(obj.OrderedItems[0].Actor) for _, e := range following { activity.To = append(activity.To, e.Id) } - MakeActivityRequest(db, activity) + MakeActivityRequest(activity) } func ResizeAttachmentToPreview(db *sql.DB) { @@ -790,13 +762,13 @@ func ResizeAttachmentToPreview(db *sql.DB) { nHref := GetUniqueFilename(file) - var nPreview NestedObjectBase + var nPreview activitypub.NestedObjectBase re = regexp.MustCompile(`/\w+$`) actor := re.ReplaceAllString(id, "") nPreview.Type = "Preview" - nPreview.Id = fmt.Sprintf("%s/%s", actor, CreateUniqueID(db, actor)) + nPreview.Id = fmt.Sprintf("%s/%s", actor, CreateUniqueID(actor)) nPreview.Name = name nPreview.Href = Domain + "" + nHref nPreview.MediaType = mediatype @@ -817,15 +789,15 @@ func ResizeAttachmentToPreview(db *sql.DB) { if err == nil { fmt.Println(objFile + " -> " + nHref) - WritePreviewToDB(db, nPreview) - UpdateObjectWithPreview(db, id, nPreview.Id) + WritePreviewToDB(nPreview) + UpdateObjectWithPreview(id, nPreview.Id) } } } } } -func UpdateObjectWithPreview(db *sql.DB, id string, preview string) { +func UpdateObjectWithPreview(id string, preview string) { query := `update activitystream set preview=$1 where attachment=$2` _, err := db.Exec(query, preview, id) @@ -857,19 +829,8 @@ func ParseCommentForReply(comment string) string { return "" } -func GetActorByName(db *sql.DB, name string) Actor { - var actor Actor - for _, e := range Boards { - if e.Actor.Name == name { - actor = e.Actor - } - } - - return actor -} - -func GetActorCollectionReq(r *http.Request, collection string) Collection { - var nCollection Collection +func GetActorCollectionReq(r *http.Request, collection string) activitypub.Collection { + var nCollection activitypub.Collection req, err := http.NewRequest("GET", collection, nil) @@ -899,77 +860,6 @@ func GetActorCollectionReq(r *http.Request, collection string) Collection { return nCollection } -func shortURL(actorName string, url string) string { - - re := regexp.MustCompile(`.+\/`) - - actor := re.FindString(actorName) - - urlParts := strings.Split(url, "|") - - op := urlParts[0] - - var reply string - - if len(urlParts) > 1 { - reply = urlParts[1] - } - - re = regexp.MustCompile(`\w+$`) - temp := re.ReplaceAllString(op, "") - - if temp == actor { - id := localShort(op) - - re := regexp.MustCompile(`.+\/`) - replyCheck := re.FindString(reply) - - if reply != "" && replyCheck == actor { - id = id + "#" + localShort(reply) - } else if reply != "" { - id = id + "#" + remoteShort(reply) - } - - return id - } else { - id := remoteShort(op) - - re := regexp.MustCompile(`.+\/`) - replyCheck := re.FindString(reply) - - if reply != "" && replyCheck == actor { - id = id + "#" + localShort(reply) - } else if reply != "" { - id = id + "#" + remoteShort(reply) - } - - return id - } -} - -func localShort(url string) string { - re := regexp.MustCompile(`\w+$`) - return re.FindString(StripTransferProtocol(url)) -} - -func remoteShort(url string) string { - re := regexp.MustCompile(`\w+$`) - - id := re.FindString(StripTransferProtocol(url)) - - re = regexp.MustCompile(`.+/.+/`) - - actorurl := re.FindString(StripTransferProtocol(url)) - - re = regexp.MustCompile(`/.+/`) - - actorname := re.FindString(actorurl) - - actorname = strings.Replace(actorname, "/", "", -1) - - return "f" + actorname + "-" + id -} - func CreatedNeededDirectories() { if _, err := os.Stat("./public"); os.IsNotExist(err) { os.Mkdir("./public", 0755) @@ -1007,7 +897,7 @@ func AddInstanceToIndex(actor string) { } } -func AddInstanceToIndexDB(db *sql.DB, actor string) { +func AddInstanceToIndexDB(actor string) { //sleep to be sure the webserver is fully initialized //before making finger request @@ -1093,109 +983,6 @@ func HasValidation(w http.ResponseWriter, r *http.Request, actor activitypub.Act return true } -func IsReplyToOP(db *sql.DB, op string, link string) (string, bool) { - - if op == link { - return link, true - } - - re := regexp.MustCompile(`f(\w+)\-`) - match := re.FindStringSubmatch(link) - - if len(match) > 0 { - re := regexp.MustCompile(`(.+)\-`) - link = re.ReplaceAllString(link, "") - link = "%" + match[1] + "/" + link - } - - query := `select id from replies where id like $1 and inreplyto=$2` - - rows, err := db.Query(query, link, op) - - CheckError(err, "error selecting in reply to op from db") - - var id string - defer rows.Close() - rows.Next() - rows.Scan(&id) - - if id != "" { - - return id, true - } - - return "", false -} - -func GetReplyOP(db *sql.DB, link string) string { - - query := `select id from replies where id in (select inreplyto from replies where id=$1) and inreplyto=''` - - rows, err := db.Query(query, link) - - CheckError(err, "could not get reply OP from db ") - - var id string - - defer rows.Close() - rows.Next() - rows.Scan(&id) - - return id -} - -func StartupArchive(db *sql.DB) { - for _, e := range FollowingBoards { - ArchivePosts(db, GetActorFromDB(db, e.Id)) - } -} - -func CheckInactive(db *sql.DB) { - for true { - CheckInactiveInstances(db) - time.Sleep(24 * time.Hour) - } -} - -func CheckInactiveInstances(db *sql.DB) map[string]string { - instances := make(map[string]string) - query := `select following from following` - rows, err := db.Query(query) - - CheckError(err, "cold not select instances from following") - - defer rows.Close() - for rows.Next() { - var instance string - rows.Scan(&instance) - instances[instance] = instance - } - - query = `select follower from follower` - rows, err = db.Query(query) - - CheckError(err, "cold not select instances from follower") - - defer rows.Close() - for rows.Next() { - var instance string - rows.Scan(&instance) - instances[instance] = instance - } - - re := regexp.MustCompile(Domain + `(.+)?`) - for _, e := range instances { - actor := GetActor(e) - if actor.Id == "" && !re.MatchString(e) { - AddInstanceToInactiveDB(db, e) - } else { - DeleteInstanceFromInactiveDB(db, e) - } - } - - return instances -} - func TemplateFunctions(engine *html.Engine) { engine.AddFunc( "mod", mod, @@ -1209,38 +996,25 @@ func TemplateFunctions(engine *html.Engine) { "unixtoreadable", unixToReadable, ) - engine.AddFunc("proxy", func(url string) string { - return MediaProxy(url) - }) + engine.AddFunc("proxy", MediaProxy) - engine.AddFunc("short", func(actorName string, url string) string { - return shortURL(actorName, url) - }) + // previously short + engine.AddFunc("shortURL", util.ShortURL) - engine.AddFunc("parseAttachment", func(obj ObjectBase, catalog bool) template.HTML { - return ParseAttachment(obj, catalog) - }) + engine.AddFunc("parseAttachment", ParseAttachment) - engine.AddFunc("parseContent", func(board Actor, op string, content string, thread ObjectBase) template.HTML { - return ParseContent(DB, board, op, content, thread) - }) + engine.AddFunc("parseContent", ParseContent) - engine.AddFunc("shortImg", func(url string) string { - return ShortImg(url) - }) + engine.AddFunc("shortImg", util.ShortImg) - engine.AddFunc("convertSize", func(size int64) string { - return ConvertSize(size) - }) + engine.AddFunc("convertSize", util.ConvertSize) - engine.AddFunc("isOnion", func(url string) bool { - return IsOnion(url) - }) + engine.AddFunc("isOnion", util.IsOnion) engine.AddFunc("parseReplyLink", func(actorId string, op string, id string, content string) template.HTML { actor := FingerActor(actorId) title := strings.ReplaceAll(ParseLinkTitle(actor.Id, op, content), `/\<`, ">") - link := ">>" + shortURL(actor.Outbox, id) + "" + link := fmt.Sprintf(">>%s", actor.Name, util.ShortURL(actor.Outbox, op), util.ShortURL(actor.Outbox, id), title, util.ShortURL(actor.Outbox, id)) return template.HTML(link) }) @@ -1259,4 +1033,34 @@ func TemplateFunctions(engine *html.Engine) { engine.AddFunc( "sub", sub, ) + + engine.AddFunc("shortExcerpt", + func(post activitypub.ObjectBase) string { + var returnString string + + if post.Name != "" { + returnString = post.Name + "| " + post.Content + } else { + returnString = post.Content + } + + re := regexp.MustCompile(`(^(.|\r\n|\n){100})`) + + match := re.FindStringSubmatch(returnString) + + if len(match) > 0 { + returnString = match[0] + "..." + } + + re = regexp.MustCompile(`(^.+\|)`) + + match = re.FindStringSubmatch(returnString) + + if len(match) > 0 { + returnString = strings.Replace(returnString, match[0], ""+match[0]+"", 1) + returnString = strings.Replace(returnString, "|", ":", 1) + } + + return returnString + }) } diff --git a/outboxGet.go b/outboxGet.go index 10efff0..cde1551 100644 --- a/outboxGet.go +++ b/outboxGet.go @@ -6,19 +6,21 @@ import ( "encoding/json" + "github.com/FChannel0/FChannel-Server/activitypub" _ "github.com/lib/pq" + "golang.org/x/perf/storage/db" ) func GetActorOutbox(w http.ResponseWriter, r *http.Request, db *sql.DB) { - actor := GetActorFromPath(db, r.URL.Path, "/") - var collection Collection + actor := GetActorFromPath(r.URL.Path, "/") + var collection activitypub.Collection - collection.OrderedItems = GetActorObjectCollectionFromDB(db, actor.Id).OrderedItems + collection.OrderedItems = GetActorObjectCollectionFromDB(actor.Id).OrderedItems collection.AtContext.Context = "https://www.w3.org/ns/activitystreams" collection.Actor = &actor - collection.TotalItems = GetObjectPostsTotalDB(db, actor) - collection.TotalImgs = GetObjectImgsTotalDB(db, actor) + collection.TotalItems = GetObjectPostsTotalDB(actor) + collection.TotalImgs = GetObjectImgsTotalDB(actor) enc, _ := json.Marshal(collection) @@ -26,10 +28,10 @@ func GetActorOutbox(w http.ResponseWriter, r *http.Request, db *sql.DB) { w.Write(enc) } -func GetCollectionFromPath(db *sql.DB, path string) Collection { +func GetCollectionFromPath(path string) activitypub.Collection { - var nColl Collection - var result []ObjectBase + var nColl activitypub.Collection + var result []activitypub.ObjectBase query := `select id, name, content, type, published, attributedto, attachment, preview, actor from activitystream where id=$1 order by published desc` @@ -40,8 +42,8 @@ func GetCollectionFromPath(db *sql.DB, path string) Collection { defer rows.Close() for rows.Next() { - var actor Actor - var post ObjectBase + var actor activitypub.Actor + var post activitypub.ObjectBase var attachID string var previewID string @@ -51,20 +53,20 @@ func GetCollectionFromPath(db *sql.DB, path string) Collection { post.Actor = actor.Id - post.InReplyTo = GetInReplyToDB(db, post) + post.InReplyTo = GetInReplyToDB(post) var postCnt int var imgCnt int - post.Replies, postCnt, imgCnt = GetObjectRepliesDB(db, post) + post.Replies, postCnt, imgCnt = GetObjectRepliesDB(post) - post.Replies.TotalItems, post.Replies.TotalImgs = GetObjectRepliesCount(db, post) + post.Replies.TotalItems, post.Replies.TotalImgs = GetObjectRepliesCount(post) post.Replies.TotalItems = post.Replies.TotalItems + postCnt post.Replies.TotalImgs = post.Replies.TotalImgs + imgCnt - post.Attachment = GetObjectAttachment(db, attachID) + post.Attachment = GetObjectAttachment(attachID) - post.Preview = GetObjectPreview(db, previewID) + post.Preview = GetObjectPreview(previewID) result = append(result, post) } @@ -76,9 +78,8 @@ func GetCollectionFromPath(db *sql.DB, path string) Collection { return nColl } -func GetObjectFromPath(db *sql.DB, path string) ObjectBase { - - var nObj ObjectBase +func GetObjectFromPath(path string) activitypub.ObjectBase { + var nObj activitypub.ObjectBase query := `select id, name, content, type, published, attributedto, attachment, preview, actor from activitystream where id=$1 order by published desc` @@ -91,7 +92,7 @@ func GetObjectFromPath(db *sql.DB, path string) ObjectBase { var attachID string var previewID string - var nActor Actor + var nActor activitypub.Actor nObj.Actor = nActor.Id err = rows.Scan(&nObj.Id, &nObj.Name, &nObj.Content, &nObj.Type, &nObj.Published, &nObj.AttributedTo, &attachID, &previewID, &nObj.Actor) @@ -101,16 +102,16 @@ func GetObjectFromPath(db *sql.DB, path string) ObjectBase { var postCnt int var imgCnt int - nObj.Replies, postCnt, imgCnt = GetObjectRepliesDB(db, nObj) + nObj.Replies, postCnt, imgCnt = GetObjectRepliesDB(nObj) - nObj.Replies.TotalItems, nObj.Replies.TotalImgs = GetObjectRepliesCount(db, nObj) + nObj.Replies.TotalItems, nObj.Replies.TotalImgs = GetObjectRepliesCount(nObj) nObj.Replies.TotalItems = nObj.Replies.TotalItems + postCnt nObj.Replies.TotalImgs = nObj.Replies.TotalImgs + imgCnt - nObj.Attachment = GetObjectAttachment(db, attachID) + nObj.Attachment = GetObjectAttachment(attachID) - nObj.Preview = GetObjectPreview(db, previewID) + nObj.Preview = GetObjectPreview(previewID) return nObj } diff --git a/outboxPost.go b/outboxPost.go index ee1e9e5..7ab6547 100644 --- a/outboxPost.go +++ b/outboxPost.go @@ -12,20 +12,22 @@ import ( "regexp" "strings" + "github.com/FChannel0/FChannel-Server/activitypub" _ "github.com/lib/pq" + "golang.org/x/perf/storage/db" ) func ParseOutboxRequest(w http.ResponseWriter, r *http.Request, db *sql.DB) { var activity Activity - actor := GetActorFromPath(db, r.URL.Path, "/") + actor := GetActorFromPath(r.URL.Path, "/") contentType := GetContentType(r.Header.Get("content-type")) defer r.Body.Close() if contentType == "multipart/form-data" || contentType == "application/x-www-form-urlencoded" { r.ParseMultipartForm(5 << 20) - if BoardHasAuthType(db, actor.Name, "captcha") && CheckCaptcha(db, r.FormValue("captcha")) { + if BoardHasAuthType(actor.Name, "captcha") && CheckCaptcha(db, r.FormValue("captcha")) { f, header, _ := r.FormFile("file") if header != nil { @@ -36,7 +38,7 @@ func ParseOutboxRequest(w http.ResponseWriter, r *http.Request, db *sql.DB) { return } - if IsMediaBanned(db, f) { + if IsMediaBanned(f) { fmt.Println("media banned") http.Redirect(w, r, Domain, http.StatusSeeOther) return @@ -56,15 +58,15 @@ func ParseOutboxRequest(w http.ResponseWriter, r *http.Request, db *sql.DB) { nObj.Actor = Domain + "/" + actor.Name - nObj = WriteObjectToDB(db, nObj) + nObj = WriteObjectToDB(nObj) if len(nObj.To) == 0 { - ArchivePosts(db, actor) + ArchivePosts(actor) } activity := CreateActivity("Create", nObj) - activity = AddFollowersToActivity(db, activity) - go MakeActivityRequest(db, activity) + activity = AddFollowersToActivity(activity) + go MakeActivityRequest(activity) var id string op := len(nObj.InReplyTo) - 1 @@ -85,7 +87,7 @@ func ParseOutboxRequest(w http.ResponseWriter, r *http.Request, db *sql.DB) { w.Write([]byte("captcha could not auth")) } else { activity = GetActivityFromJson(r, db) - if IsActivityLocal(db, activity) { + if IsActivityLocal(activity) { if !VerifyHeaderSignature(r, *activity.Actor) { w.WriteHeader(http.StatusBadRequest) w.Write([]byte("")) @@ -108,11 +110,11 @@ func ParseOutboxRequest(w http.ResponseWriter, r *http.Request, db *sql.DB) { var rActivity Activity if validActor && validLocalActor { rActivity = AcceptFollow(activity) - rActivity = SetActorFollowingDB(db, rActivity) - MakeActivityRequest(db, activity) + rActivity = SetActorFollowingDB(rActivity) + MakeActivityRequest(activity) } - FollowingBoards = GetActorFollowingDB(db, Domain) + FollowingBoards = GetActorFollowingDB(Domain) Boards = GetBoardCollection(db) break @@ -133,7 +135,7 @@ func ParseOutboxRequest(w http.ResponseWriter, r *http.Request, db *sql.DB) { summary := activity.Object.Summary restricted := activity.Object.Sensitive - actor := CreateNewBoardDB(db, *CreateNewActor(name, prefname, summary, authReq, restricted)) + actor := CreateNewBoardDB(*CreateNewActor(name, prefname, summary, authReq, restricted)) if actor.Id != "" { var board []ObjectBase @@ -174,17 +176,17 @@ func ParseOutboxRequest(w http.ResponseWriter, r *http.Request, db *sql.DB) { } } -func ObjectFromJson(r *http.Request, obj ObjectBase) ObjectBase { +func ObjectFromJson(r *http.Request, obj activitypub.ObjectBase) activitypub.ObjectBase { body, _ := ioutil.ReadAll(r.Body) - var respActivity ActivityRaw + var respActivity activitypub.ActivityRaw err := json.Unmarshal(body, &respActivity) CheckError(err, "error with object from json") if HasContextFromJson(respActivity.AtContextRaw.Context) { - var jObj ObjectBase + var jObj activitypub.ObjectBase jObj = GetObjectFromJson(respActivity.ObjectRaw) jObj.To = GetToFromJson(respActivity.ToRaw) jObj.Cc = GetToFromJson(respActivity.CcRaw) @@ -193,19 +195,19 @@ func ObjectFromJson(r *http.Request, obj ObjectBase) ObjectBase { return obj } -func GetObjectFromJson(obj []byte) ObjectBase { +func GetObjectFromJson(obj []byte) activitypub.ObjectBase { var generic interface{} err := json.Unmarshal(obj, &generic) CheckError(err, "error with getting obj from json") - var nObj ObjectBase + var nObj activitypub.ObjectBase if generic != nil { switch generic.(type) { case []interface{}: - var lObj ObjectBase - var arrContext ObjectArray + var lObj activitypub.ObjectBase + var arrContext activitypub.ObjectArray err = json.Unmarshal(obj, &arrContext.Object) CheckError(err, "error with []interface{} oject from json") if len(arrContext.Object) > 0 { @@ -215,15 +217,15 @@ func GetObjectFromJson(obj []byte) ObjectBase { break case map[string]interface{}: - var arrContext Object + var arrContext activitypub.Object err = json.Unmarshal(obj, &arrContext.Object) CheckError(err, "error with object from json") nObj = *arrContext.Object break case string: - var lObj ObjectBase - var arrContext ObjectString + var lObj activitypub.ObjectBase + var arrContext activitypub.ObjectString err = json.Unmarshal(obj, &arrContext.Object) CheckError(err, "error with string object from json") lObj.Id = arrContext.Object @@ -235,9 +237,9 @@ func GetObjectFromJson(obj []byte) ObjectBase { return nObj } -func GetActorFromJson(actor []byte) Actor { +func GetActorFromJson(actor []byte) activitypub.Actor { var generic interface{} - var nActor Actor + var nActor activitypub.Actor err := json.Unmarshal(actor, &generic) if err != nil { @@ -303,7 +305,7 @@ func HasContextFromJson(context []byte) bool { switch generic.(type) { case []interface{}: - var arrContext AtContextArray + var arrContext activitypub.AtContextArray err = json.Unmarshal(context, &arrContext.Context) CheckError(err, "error with []interface{}") if len(arrContext.Context) > 0 { @@ -312,7 +314,7 @@ func HasContextFromJson(context []byte) bool { } } case string: - var arrContext AtContextString + var arrContext activitypub.AtContextString err = json.Unmarshal(context, &arrContext.Context) CheckError(err, "error with string") if arrContext.Context == "https://www.w3.org/ns/activitystreams" { @@ -323,7 +325,7 @@ func HasContextFromJson(context []byte) bool { return hasContext } -func ObjectFromForm(r *http.Request, db *sql.DB, obj ObjectBase) ObjectBase { +func ObjectFromForm(r *http.Request, db *sql.DB, obj activitypub.ObjectBase) activitypub.ObjectBase { file, header, _ := r.FormFile("file") @@ -361,19 +363,19 @@ func ObjectFromForm(r *http.Request, db *sql.DB, obj ObjectBase) ObjectBase { obj = ParseOptions(r, obj) - var originalPost ObjectBase + var originalPost activitypub.ObjectBase originalPost.Id = EscapeString(r.FormValue("inReplyTo")) obj.InReplyTo = append(obj.InReplyTo, originalPost) - var activity Activity + var activity activitypub.Activity if !IsInStringArray(activity.To, originalPost.Id) { activity.To = append(activity.To, originalPost.Id) } if originalPost.Id != "" { - if !IsActivityLocal(db, activity) { + if !IsActivityLocal(activity) { actor := FingerActor(originalPost.Id) if !IsInStringArray(obj.To, actor.Id) { obj.To = append(obj.To, actor.Id) @@ -381,7 +383,7 @@ func ObjectFromForm(r *http.Request, db *sql.DB, obj ObjectBase) ObjectBase { } } - replyingTo := ParseCommentForReplies(db, r.FormValue("comment"), originalPost.Id) + replyingTo := ParseCommentForReplies(r.FormValue("comment"), originalPost.Id) for _, e := range replyingTo { @@ -397,11 +399,11 @@ func ObjectFromForm(r *http.Request, db *sql.DB, obj ObjectBase) ObjectBase { if !has { obj.InReplyTo = append(obj.InReplyTo, e) - var activity Activity + var activity activitypub.Activity activity.To = append(activity.To, e.Id) - if !IsActivityLocal(db, activity) { + if !IsActivityLocal(activity) { actor := FingerActor(e.Id) if !IsInStringArray(obj.To, actor.Id) { obj.To = append(obj.To, actor.Id) @@ -413,7 +415,7 @@ func ObjectFromForm(r *http.Request, db *sql.DB, obj ObjectBase) ObjectBase { return obj } -func ParseOptions(r *http.Request, obj ObjectBase) ObjectBase { +func ParseOptions(r *http.Request, obj activitypub.ObjectBase) activitypub.ObjectBase { options := EscapeString(r.FormValue("options")) if options != "" { option := strings.Split(options, ";") @@ -431,7 +433,7 @@ func ParseOptions(r *http.Request, obj ObjectBase) ObjectBase { obj.Option = append(obj.Option, "email:"+e) } else if wallet.MatchString(e) { obj.Option = append(obj.Option, "wallet") - var wallet CryptoCur + var wallet activitypub.CryptoCur value := strings.Split(e, ":") wallet.Type = value[0] wallet.Address = value[1] @@ -445,12 +447,12 @@ func ParseOptions(r *http.Request, obj ObjectBase) ObjectBase { return obj } -func GetActivityFromJson(r *http.Request, db *sql.DB) Activity { +func GetActivityFromJson(r *http.Request, db *sql.DB) activitypub.Activity { body, _ := ioutil.ReadAll(r.Body) - var respActivity ActivityRaw + var respActivity activitypub.ActivityRaw - var nActivity Activity + var nActivity activitypub.Activity var nType string @@ -459,7 +461,7 @@ func GetActivityFromJson(r *http.Request, db *sql.DB) Activity { CheckError(err, "error with activity from json") if HasContextFromJson(respActivity.AtContextRaw.Context) { - var jObj ObjectBase + var jObj activitypub.ObjectBase if respActivity.Type == "Note" { jObj = GetObjectFromJson(body) @@ -494,7 +496,7 @@ func GetActivityFromJson(r *http.Request, db *sql.DB) Activity { return nActivity } -func CheckCaptcha(db *sql.DB, captcha string) bool { +func CheckCaptcha(captcha string) bool { parts := strings.Split(captcha, ":") if strings.Trim(parts[0], " ") == "" || strings.Trim(parts[1], " ") == "" { @@ -502,10 +504,10 @@ func CheckCaptcha(db *sql.DB, captcha string) bool { } path := "public/" + parts[0] + ".png" - code := GetCaptchaCodeDB(db, path) + code := GetCaptchaCodeDB(path) if code != "" { - DeleteCaptchaCodeDB(db, path) + DeleteCaptchaCodeDB(path) CreateNewCaptcha(db) } @@ -526,7 +528,7 @@ func ParseInboxRequest(w http.ResponseWriter, r *http.Request, db *sql.DB) { if !VerifyHeaderSignature(r, *activity.Actor) { response := RejectActivity(activity) - MakeActivityRequest(db, response) + MakeActivityRequest(response) return } @@ -534,8 +536,8 @@ func ParseInboxRequest(w http.ResponseWriter, r *http.Request, db *sql.DB) { case "Create": for _, e := range activity.To { - if IsActorLocal(db, e) { - if !IsActorLocal(db, activity.Actor.Id) { + if IsActorLocal(e) { + if !IsActorLocal(activity.Actor.Id) { col := GetCollectionFromID(activity.Object.Id) @@ -543,9 +545,9 @@ func ParseInboxRequest(w http.ResponseWriter, r *http.Request, db *sql.DB) { break } - WriteObjectToCache(db, *activity.Object) - ArchivePosts(db, GetActorFromDB(db, e)) - //SendToFollowers(db, e, activity) + WriteObjectToCache(*activity.Object) + ArchivePosts(GetActorFromDB(db, e)) + //SendToFollowers(e, activity) } } } @@ -554,15 +556,15 @@ func ParseInboxRequest(w http.ResponseWriter, r *http.Request, db *sql.DB) { case "Delete": for _, e := range activity.To { - actor := GetActorFromDB(db, e) + actor := GetActorFromDB(e) if actor.Id != "" && actor.Id != Domain { if activity.Object.Replies != nil { for _, k := range activity.Object.Replies.OrderedItems { - TombstoneObject(db, k.Id) + TombstoneObject(k.Id) } } - TombstoneObject(db, activity.Object.Id) - UnArchiveLast(db, actor.Id) + TombstoneObject(activity.Object.Id) + UnArchiveLast(actor.Id) break } } @@ -570,15 +572,15 @@ func ParseInboxRequest(w http.ResponseWriter, r *http.Request, db *sql.DB) { case "Follow": for _, e := range activity.To { - if GetActorFromDB(db, e).Id != "" { + if GetActorFromDB(e).Id != "" { response := AcceptFollow(activity) - response = SetActorFollowerDB(db, response) - MakeActivityRequest(db, response) + response = SetActorFollowerDB(response) + MakeActivityRequest(response) alreadyFollow := false alreadyFollowing := false - autoSub := GetActorAutoSubscribeDB(db, response.Actor.Id) - following := GetActorFollowingDB(db, response.Actor.Id) + autoSub := GetActorAutoSubscribeDB(response.Actor.Id) + following := GetActorFollowingDB(response.Actor.Id) for _, e := range following { if e.Id == response.Object.Id { @@ -596,16 +598,16 @@ func ParseInboxRequest(w http.ResponseWriter, r *http.Request, db *sql.DB) { } if autoSub && !alreadyFollow && alreadyFollowing { - followActivity := MakeFollowActivity(db, response.Actor.Id, response.Object.Actor) + followActivity := MakeFollowActivity(response.Actor.Id, response.Object.Actor) if FingerActor(response.Object.Actor).Id != "" { - MakeActivityRequestOutbox(db, followActivity) + MakeActivityRequestOutbox(followActivity) } } } else { fmt.Println("follow request for rejected") response := RejectActivity(activity) - MakeActivityRequest(db, response) + MakeActivityRequest(response) return } } @@ -614,13 +616,13 @@ func ParseInboxRequest(w http.ResponseWriter, r *http.Request, db *sql.DB) { case "Reject": if activity.Object.Object.Type == "Follow" { fmt.Println("follow rejected") - SetActorFollowingDB(db, activity) + SetActorFollowingDB(activity) } break } } -func MakeActivityFollowingReq(w http.ResponseWriter, r *http.Request, activity Activity) bool { +func MakeActivityFollowingReq(w http.ResponseWriter, r *http.Request, activity activitypub.Activity) bool { actor := GetActor(activity.Object.Id) req, err := http.NewRequest("POST", actor.Inbox, nil) @@ -635,7 +637,7 @@ func MakeActivityFollowingReq(w http.ResponseWriter, r *http.Request, activity A body, _ := ioutil.ReadAll(resp.Body) - var respActivity Activity + var respActivity activitypub.Activity err = json.Unmarshal(body, &respActivity) @@ -646,7 +648,7 @@ func MakeActivityFollowingReq(w http.ResponseWriter, r *http.Request, activity A return false } -func IsMediaBanned(db *sql.DB, f multipart.File) bool { +func IsMediaBanned(f multipart.File) bool { f.Seek(0, 0) fileBytes := make([]byte, 2048) @@ -680,12 +682,12 @@ func IsMediaBanned(db *sql.DB, f multipart.File) bool { return false } -func SendToFollowers(db *sql.DB, actor string, activity Activity) { +func SendToFollowers(actor string, activity activitypub.Activity) { - nActor := GetActorFromDB(db, actor) + nActor := GetActorFromDB(actor) activity.Actor = &nActor - followers := GetActorFollowDB(db, actor) + followers := GetActorFollowDB(actor) var to []string for _, e := range followers { @@ -699,6 +701,6 @@ func SendToFollowers(db *sql.DB, actor string, activity Activity) { activity.To = to if len(activity.Object.InReplyTo) > 0 { - MakeActivityRequest(db, activity) + MakeActivityRequest(activity) } } diff --git a/routes/archive.go b/routes/archive.go new file mode 100644 index 0000000..87f3b8b --- /dev/null +++ b/routes/archive.go @@ -0,0 +1,53 @@ +package routes + +import ( + "github.com/FChannel0/FChannel-Server/activitypub" + "github.com/FChannel0/FChannel-Server/config" + "github.com/FChannel0/FChannel-Server/db" + "github.com/FChannel0/FChannel-Server/util" + "github.com/gofiber/fiber/v2" +) + +func ArchiveGet(ctx *fiber.Ctx) error { + // TODO + collection := ctx.Locals("collection").(activitypub.Collection) + actor := collection.Actor + + var returnData PageData + returnData.Board.Name = actor.Name + returnData.Board.PrefName = actor.PreferredUsername + returnData.Board.InReplyTo = "" + returnData.Board.To = actor.Outbox + returnData.Board.Actor = *actor + returnData.Board.Summary = actor.Summary + returnData.Board.ModCred, _ = getPassword(ctx) + returnData.Board.Domain = config.Domain + returnData.Board.Restricted = actor.Restricted + returnData.Key = config.Key + returnData.ReturnTo = "archive" + + returnData.Board.Post.Actor = actor.Id + + var err error + returnData.Instance, err = db.GetActorFromDB(config.Domain) + + capt, err := db.GetRandomCaptcha() + if err != nil { + return err + } + returnData.Board.Captcha = config.Domain + "/" + capt + returnData.Board.CaptchaCode = util.GetCaptchaCode(returnData.Board.Captcha) + + returnData.Title = "/" + actor.Name + "/ - " + actor.PreferredUsername + + returnData.Boards = db.Boards + + returnData.Posts = collection.OrderedItems + + returnData.Themes = &config.Themes + returnData.ThemeCookie = getThemeCookie(ctx) + + return ctx.Render("archive", fiber.Map{ + "page": returnData, + }, "layouts/main") +} diff --git a/routes/index.go b/routes/index.go index a130796..3599455 100644 --- a/routes/index.go +++ b/routes/index.go @@ -7,7 +7,7 @@ import ( "github.com/gofiber/fiber/v2" ) -func Index(c *fiber.Ctx) error { +func Index(ctx *fiber.Ctx) error { actor, err := db.GetActorFromDB(config.Domain) if err != nil { return err @@ -20,7 +20,7 @@ func Index(c *fiber.Ctx) error { data.Board.Name = "" data.Key = config.Key data.Board.Domain = config.Domain - data.Board.ModCred, _ = getPassword(c) + data.Board.ModCred, _ = getPassword(ctx) data.Board.Actor = actor data.Board.Post.Actor = actor.Id data.Board.Restricted = actor.Restricted @@ -46,10 +46,9 @@ func Index(c *fiber.Ctx) error { } data.Themes = &config.Themes + data.ThemeCookie = getThemeCookie(ctx) - data.ThemeCookie = getThemeCookie(c) - - return c.Render("index", fiber.Map{ + return ctx.Render("index", fiber.Map{ "page": data, }, "layouts/main") } diff --git a/routes/news.go b/routes/news.go index 585614d..98d0019 100644 --- a/routes/news.go +++ b/routes/news.go @@ -1,7 +1,70 @@ package routes -import "github.com/gofiber/fiber/v2" +import ( + "github.com/FChannel0/FChannel-Server/config" + "github.com/FChannel0/FChannel-Server/db" + "github.com/gofiber/fiber/v2" +) -func NewsGet(c *fiber.Ctx) error { - return c.SendString("news get") +func NewsGet(ctx *fiber.Ctx) error { + // TODO + timestamp := 0 + + actor, err := db.GetActorFromDB(config.Domain) + if err != nil { + return err + } + + var data PageData + data.PreferredUsername = actor.PreferredUsername + data.Boards = db.Boards + data.Board.Name = "" + data.Key = config.Key + data.Board.Domain = config.Domain + data.Board.ModCred, _ = getPassword(ctx) + data.Board.Actor = actor + data.Board.Post.Actor = actor.Id + data.Board.Restricted = actor.Restricted + data.NewsItems = make([]db.NewsItem, 1) + + data.NewsItems[0], err = db.GetNewsItemFromDB(timestamp) + if err != nil { + return err + } + + data.Title = actor.PreferredUsername + ": " + data.NewsItems[0].Title + + data.Themes = &config.Themes + data.ThemeCookie = getThemeCookie(ctx) + + return ctx.Render("news", fiber.Map{"page": data}, "layouts/main") +} + +func AllNewsGet(ctx *fiber.Ctx) error { + actor, err := db.GetActorFromDB(config.Domain) + if err != nil { + return err + } + + var data PageData + data.PreferredUsername = actor.PreferredUsername + data.Title = actor.PreferredUsername + " News" + data.Boards = db.Boards + data.Board.Name = "" + data.Key = config.Key + data.Board.Domain = config.Domain + data.Board.ModCred, _ = getPassword(ctx) + data.Board.Actor = actor + data.Board.Post.Actor = actor.Id + data.Board.Restricted = actor.Restricted + + data.NewsItems, err = db.GetNewsFromDB(0) + if err != nil { + return err + } + + data.Themes = &config.Themes + data.ThemeCookie = getThemeCookie(ctx) + + return ctx.Render("anews", fiber.Map{"page": data}, "layouts/main") } diff --git a/routes/outbox.go b/routes/outbox.go index 8945bb1..b00f946 100644 --- a/routes/outbox.go +++ b/routes/outbox.go @@ -1,9 +1,86 @@ package routes -import "github.com/gofiber/fiber/v2" +import ( + "strconv" -func Outbox(c *fiber.Ctx) error { + "github.com/FChannel0/FChannel-Server/config" + "github.com/FChannel0/FChannel-Server/db" + "github.com/FChannel0/FChannel-Server/util" + "github.com/gofiber/fiber/v2" +) + +func Outbox(ctx *fiber.Ctx) error { // STUB - return c.SendString("main outbox") + return ctx.SendString("main outbox") +} + +func OutboxGet(ctx *fiber.Ctx) error { + collection, valid, err := wantToServePage(ctx.Params("actor"), 0) + if err != nil { + return err + } else if !valid { + // TODO: 404 template + return ctx.SendString("404") + } + + actor := collection.Actor + + postNum := ctx.Query("page") + page, err := strconv.Atoi(postNum) + if err != nil { + return err + } + + var returnData PageData + + returnData.Board.Name = actor.Name + returnData.Board.PrefName = actor.PreferredUsername + returnData.Board.Summary = actor.Summary + returnData.Board.InReplyTo = "" + returnData.Board.To = actor.Outbox + returnData.Board.Actor = *actor + returnData.Board.ModCred, _ = getPassword(ctx) + returnData.Board.Domain = config.Domain + returnData.Board.Restricted = actor.Restricted + returnData.CurrentPage = page + returnData.ReturnTo = "feed" + + returnData.Board.Post.Actor = actor.Id + + capt, err := db.GetRandomCaptcha() + if err != nil { + return err + } + returnData.Board.Captcha = config.Domain + "/" + capt + returnData.Board.CaptchaCode = util.GetCaptchaCode(returnData.Board.Captcha) + + returnData.Title = "/" + actor.Name + "/ - " + actor.PreferredUsername + + returnData.Key = config.Key + + returnData.Boards = db.Boards + returnData.Posts = collection.OrderedItems + + var offset = 15 + var pages []int + pageLimit := (float64(collection.TotalItems) / float64(offset)) + + if pageLimit > 11 { + pageLimit = 11 + } + + for i := 0.0; i < pageLimit; i++ { + pages = append(pages, int(i)) + } + + returnData.Pages = pages + returnData.TotalPage = len(returnData.Pages) - 1 + + returnData.Themes = &config.Themes + returnData.ThemeCookie = getThemeCookie(ctx) + + return ctx.Render("nposts", fiber.Map{ + "page": returnData, + }, "layouts/main") } diff --git a/routes/post.go b/routes/post.go new file mode 100644 index 0000000..41706c0 --- /dev/null +++ b/routes/post.go @@ -0,0 +1,104 @@ +package routes + +import ( + "regexp" + + "github.com/FChannel0/FChannel-Server/config" + "github.com/FChannel0/FChannel-Server/db" + "github.com/FChannel0/FChannel-Server/util" + "github.com/FChannel0/FChannel-Server/webfinger" + "github.com/gofiber/fiber/v2" +) + +func PostGet(ctx *fiber.Ctx) error { + actor, err := db.GetActorByNameFromDB(ctx.Params("actor")) + if err != nil { + return err + } + + postId := ctx.Params("post") + + inReplyTo := actor.Id + "/" + postId + + var returnData PageData + returnData.Board.Name = actor.Name + returnData.Board.PrefName = actor.PreferredUsername + returnData.Board.To = actor.Outbox + returnData.Board.Actor = actor + returnData.Board.Summary = actor.Summary + returnData.Board.ModCred, _ = getPassword(ctx) + returnData.Board.Domain = config.Domain + returnData.Board.Restricted = actor.Restricted + returnData.ReturnTo = "feed" + + capt, err := db.GetRandomCaptcha() + if err != nil { + return err + } + returnData.Board.Captcha = config.Domain + "/" + capt + returnData.Board.CaptchaCode = util.GetCaptchaCode(returnData.Board.Captcha) + + returnData.Instance, err = db.GetActorFromDB(config.Domain) + if err != nil { + return err + } + + returnData.Title = "/" + returnData.Board.Name + "/ - " + returnData.Board.PrefName + + returnData.Key = config.Key + + returnData.Boards = db.Boards + + re := regexp.MustCompile("f(\\w|[!@#$%^&*<>])+-(\\w|[!@#$%^&*<>])+") + + if re.MatchString(postId) { // if non local actor post + name := util.GetActorFollowNameFromPath(postId) + + followActors, err := webfinger.GetActorsFollowFromName(actor, name) + if err != nil { + return err + } + + followCollection, err := db.GetActorsFollowPostFromId(followActors, postId) + if err != nil { + return err + } + + if len(followCollection.OrderedItems) > 0 { + returnData.Board.InReplyTo = followCollection.OrderedItems[0].Id + returnData.Posts = append(returnData.Posts, followCollection.OrderedItems[0]) + + actor, err := webfinger.FingerActor(returnData.Board.InReplyTo) + if err != nil { + return err + } + + returnData.Board.Post.Actor = actor.Id + } + } else { + collection, err := db.GetObjectByIDFromDB(inReplyTo) + if err != nil { + return err + } + + if collection.Actor != nil { + returnData.Board.Post.Actor = collection.Actor.Id + returnData.Board.InReplyTo = inReplyTo + + if len(collection.OrderedItems) > 0 { + returnData.Posts = append(returnData.Posts, collection.OrderedItems[0]) + } + } + } + + if len(returnData.Posts) > 0 { + returnData.PostId = util.ShortURL(returnData.Board.To, returnData.Posts[0].Id) + } + + returnData.Themes = &config.Themes + returnData.ThemeCookie = getThemeCookie(ctx) + + return ctx.Render("npost", fiber.Map{ + "page": returnData, + }, "layouts/main") +} diff --git a/routes/util.go b/routes/util.go index a38e969..0a8dc9c 100644 --- a/routes/util.go +++ b/routes/util.go @@ -1,13 +1,17 @@ package routes import ( + "errors" "fmt" "strings" + "github.com/FChannel0/FChannel-Server/activitypub" "github.com/FChannel0/FChannel-Server/db" "github.com/gofiber/fiber/v2" ) +var ErrorPageLimit = errors.New("above page limit") + func getThemeCookie(c *fiber.Ctx) string { cookie := c.Cookies("theme") if cookie != "" { @@ -38,3 +42,74 @@ func getPassword(r *fiber.Ctx) (string, string) { return "", "" } + +func wantToServePage(actorName string, page int) (activitypub.Collection, bool, error) { + var collection activitypub.Collection + serve := false + + // TODO: don't hard code? + if page > 10 { + return collection, serve, ErrorPageLimit + } + + actor, err := db.GetActorByNameFromDB(actorName) + if err != nil { + return collection, false, err + } + + if actor.Id != "" { + collection, err = db.GetObjectFromDBPage(actor.Id, page) + if err != nil { + return collection, false, err + } + + collection.Actor = &actor + return collection, true, nil + } + + return collection, serve, nil +} + +func wantToServeCatalog(actorName string) (activitypub.Collection, bool, error) { + var collection activitypub.Collection + serve := false + + actor, err := db.GetActorByNameFromDB(actorName) + if err != nil { + return collection, false, err + } + + if actor.Id != "" { + collection, err = db.GetObjectFromDBCatalog(actor.Id) + if err != nil { + return collection, false, err + } + + collection.Actor = &actor + return collection, true, nil + } + + return collection, serve, nil +} + +func wantToServeArchive(actorName string) (activitypub.Collection, bool, error) { + var collection activitypub.Collection + serve := false + + actor, err := db.GetActorByNameFromDB(actorName) + if err != nil { + return collection, false, err + } + + if actor.Id != "" { + collection, err = db.GetActorCollectionDBType(actor.Id, "Archive") + if err != nil { + return collection, false, err + } + + collection.Actor = &actor + return collection, true, nil + } + + return collection, serve, nil +} diff --git a/util/util.go b/util/util.go index 5ee548e..8f32363 100644 --- a/util/util.go +++ b/util/util.go @@ -1,6 +1,7 @@ package util import ( + "fmt" "regexp" "strings" ) @@ -63,3 +64,135 @@ func GetActorFollowNameFromPath(path string) string { return actor } + +func StripTransferProtocol(value string) string { + re := regexp.MustCompile("(http://|https://)?(www.)?") + + value = re.ReplaceAllString(value, "") + + return value +} + +func GetCaptchaCode(captcha string) string { + re := regexp.MustCompile("\\w+\\.\\w+$") + code := re.FindString(captcha) + + re = regexp.MustCompile("\\w+") + code = re.FindString(code) + + return code +} + +func ShortURL(actorName string, url string) string { + + re := regexp.MustCompile(`.+\/`) + + actor := re.FindString(actorName) + + urlParts := strings.Split(url, "|") + + op := urlParts[0] + + var reply string + + if len(urlParts) > 1 { + reply = urlParts[1] + } + + re = regexp.MustCompile(`\w+$`) + temp := re.ReplaceAllString(op, "") + + if temp == actor { + id := LocalShort(op) + + re := regexp.MustCompile(`.+\/`) + replyCheck := re.FindString(reply) + + if reply != "" && replyCheck == actor { + id = id + "#" + LocalShort(reply) + } else if reply != "" { + id = id + "#" + RemoteShort(reply) + } + + return id + } else { + id := RemoteShort(op) + + re := regexp.MustCompile(`.+\/`) + replyCheck := re.FindString(reply) + + if reply != "" && replyCheck == actor { + id = id + "#" + LocalShort(reply) + } else if reply != "" { + id = id + "#" + RemoteShort(reply) + } + + return id + } +} + +func LocalShort(url string) string { + re := regexp.MustCompile(`\w+$`) + return re.FindString(StripTransferProtocol(url)) +} + +func RemoteShort(url string) string { + re := regexp.MustCompile(`\w+$`) + + id := re.FindString(StripTransferProtocol(url)) + + re = regexp.MustCompile(`.+/.+/`) + + actorurl := re.FindString(StripTransferProtocol(url)) + + re = regexp.MustCompile(`/.+/`) + + actorname := re.FindString(actorurl) + + actorname = strings.Replace(actorname, "/", "", -1) + + return "f" + actorname + "-" + id +} + +func ShortImg(url string) string { + nURL := url + + re := regexp.MustCompile(`(\.\w+$)`) + + fileName := re.ReplaceAllString(url, "") + + if len(fileName) > 26 { + re := regexp.MustCompile(`(^.{26})`) + + match := re.FindStringSubmatch(fileName) + + if len(match) > 0 { + nURL = match[0] + } + + re = regexp.MustCompile(`(\..+$)`) + + match = re.FindStringSubmatch(url) + + if len(match) > 0 { + nURL = nURL + "(...)" + match[0] + } + } + + return nURL +} + +func ConvertSize(size int64) string { + var rValue string + + convert := float32(size) / 1024.0 + + if convert > 1024 { + convert = convert / 1024.0 + rValue = fmt.Sprintf("%.2f MB", convert) + } else { + rValue = fmt.Sprintf("%.2f KB", convert) + } + + return rValue +} diff --git a/webfinger/comm.go b/webfinger/comm.go index dd25cb4..35ad335 100644 --- a/webfinger/comm.go +++ b/webfinger/comm.go @@ -5,6 +5,7 @@ import ( "errors" "io/ioutil" "net/http" + "regexp" "github.com/FChannel0/FChannel-Server/activitypub" "github.com/FChannel0/FChannel-Server/config" @@ -68,3 +69,21 @@ func GetCollectionFromReq(path string) (activitypub.Collection, error) { err = json.Unmarshal(body, &respCollection) return respCollection, err } + +func GetActorsFollowFromName(actor activitypub.Actor, name string) ([]string, error) { + var followingActors []string + follow, err := GetActorCollection(actor.Following) + if err != nil { + return followingActors, err + } + + re := regexp.MustCompile("\\w+?$") + + for _, e := range follow.Items { + if re.FindString(e.Id) == name { + followingActors = append(followingActors, e.Id) + } + } + + return followingActors, nil +} -- cgit v1.2.3