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(media)
}
if regexp.MustCompile(`audio\/`).MatchString(obj.Attachment[0].MediaType) {
media = ""
return template.HTML(media)
}
if regexp.MustCompile(`video\/`).MatchString(obj.Attachment[0].MediaType) {
media = ""
return template.HTML(media)
}
return template.HTML(media)
}
func ParseContent(db *sql.DB, board Actor, op string, content string, thread ObjectBase) template.HTML {
nContent := strings.ReplaceAll(content, `<`, "<")
nContent = ParseLinkComments(db, board, op, nContent, thread)
nContent = ParseCommentQuotes(nContent)
nContent = strings.ReplaceAll(nContent, `/\<`, ">")
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
}