package main import ( "database/sql" "fmt" "html/template" "log" "net/http" "regexp" "strconv" "strings" "time" "github.com/gofiber/fiber/v2" _ "github.com/lib/pq" ) var Key *string = new(string) func mod(i, j int) bool { return i%j == 0 } func sub(i, j int) int { return i - j } func unixToReadable(u int) string { return time.Unix(int64(u), 0).Format("Jan 02, 2006") } func timeToReadableLong(t time.Time) string { return t.Format("01/02/06(Mon)15:04:05") } func timeToUnix(t time.Time) string { return fmt.Sprint(t.Unix()) } func NewsGet(w http.ResponseWriter, r *http.Request, db *sql.DB, timestamp int) { 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) if len(col.OrderedItems) > 0 { return true } return false }, "sub": sub}).ParseFiles("./static/main.html", "./static/ncatalog.html", "./static/top.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 = "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] } err := t.ExecuteTemplate(w, "layout", returnData) if err != nil { // TODO: actual error handler log.Printf("CatalogGet: %s\n", 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.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] } 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) } } 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 + "(.+)?") if re.MatchString(url) { return url } re = regexp.MustCompile("(.+)?\\.onion(.+)?") if re.MatchString(url) { return url } MediaHashs[HashMedia(url)] = url return "/api/media?hash=" + HashMedia(url) } func ParseAttachment(obj ObjectBase, catalog bool) template.HTML { if len(obj.Attachment) < 1 { return "" } var media string if regexp.MustCompile(`image\/`).MatchString(obj.Attachment[0].MediaType) { media = "") return template.HTML(nContent) } func ParseLinkComments(db *sql.DB, board Actor, op string, content string, thread ObjectBase) string { 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) //add url to each matched reply for i, _ := range match { link := strings.Replace(match[i][0], ">>", "", 1) isOP := "" domain := match[i][2] if link == op { isOP = " (OP)" } parsedLink := ConvertHashLink(domain, link) //formate the hover title text var quoteTitle string // if the quoted content is local get it // else get it from the database if thread.Id == link { quoteTitle = ParseLinkTitle(board.Outbox, op, thread.Content) } else { for _, e := range thread.Replies.OrderedItems { if e.Id == parsedLink { quoteTitle = ParseLinkTitle(board.Outbox, op, e.Content) break } } if quoteTitle == "" { obj := GetObjectFromDBFromID(db, parsedLink) if len(obj.OrderedItems) > 0 { quoteTitle = ParseLinkTitle(board.Outbox, op, obj.OrderedItems[0].Content) } else { quoteTitle = ParseLinkTitle(board.Outbox, op, parsedLink) } } } //replace link with quote format replyID, isReply := IsReplyToOP(db, op, parsedLink) if isReply { id := shortURL(board.Outbox, replyID) content = strings.Replace(content, match[i][0], ">>"+id+""+isOP+"", -1) } else { //this is a cross post parsedOP := GetReplyOP(db, parsedLink) actor := FingerActor(parsedLink) if parsedOP != "" { link = parsedOP + "#" + shortURL(parsedOP, parsedLink) } if actor.Id != "" { content = strings.Replace(content, match[i][0], ">>"+shortURL(board.Outbox, parsedLink)+isOP+" →", -1) } } } return content } func ParseLinkTitle(actorName string, op string, content string) string { re := regexp.MustCompile(`(>>(https?://[A-Za-z0-9_.:\-~]+\/[A-Za-z0-9_.\-~]+\/)\w+(#.+)?)`) match := re.FindAllStringSubmatch(content, -1) for i, _ := range match { link := strings.Replace(match[i][0], ">>", "", 1) isOP := "" domain := match[i][2] if link == op { isOP = " (OP)" } link = ConvertHashLink(domain, link) content = strings.Replace(content, match[i][0], ">>"+shortURL(actorName, link)+isOP, 1) } content = strings.ReplaceAll(content, "'", "") content = strings.ReplaceAll(content, "\"", "") content = strings.ReplaceAll(content, ">", `/\<`) return content } func ParseCommentQuotes(content string) string { // replace quotes re := regexp.MustCompile(`((\r\n|\r|\n|^)>(.+)?[^\r\n])`) match := re.FindAllStringSubmatch(content, -1) for i, _ := range match { quote := strings.Replace(match[i][0], ">", ">", 1) line := re.ReplaceAllString(match[i][0], ""+quote+"") content = strings.Replace(content, match[i][0], line, 1) } //replace isolated greater than symboles re = regexp.MustCompile(`(\r\n|\n|\r)>`) return re.ReplaceAllString(content, "\r\n>") } func ConvertHashLink(domain string, link string) string { re := regexp.MustCompile(`(#.+)`) parsedLink := re.FindString(link) if parsedLink != "" { parsedLink = domain + "" + strings.Replace(parsedLink, "#", "", 1) parsedLink = strings.Replace(parsedLink, "\r", "", -1) } else { parsedLink = link } 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 }