2020-04-29 17:44:29 -07:00
|
|
|
package gctemplates
|
2013-02-02 15:01:01 -08:00
|
|
|
|
2013-04-20 01:21:40 -07:00
|
|
|
import (
|
2022-01-27 21:02:37 -08:00
|
|
|
"errors"
|
2013-04-20 01:21:40 -07:00
|
|
|
"fmt"
|
2017-10-29 00:59:42 -07:00
|
|
|
"html"
|
2020-04-29 17:44:29 -07:00
|
|
|
"html/template"
|
2023-07-14 11:04:46 -07:00
|
|
|
"path"
|
2014-06-15 02:15:51 -07:00
|
|
|
"strconv"
|
2013-06-07 07:15:42 -07:00
|
|
|
"strings"
|
2020-04-29 17:44:29 -07:00
|
|
|
"time"
|
2019-02-16 19:18:00 -08:00
|
|
|
|
2020-04-29 17:44:29 -07:00
|
|
|
"github.com/gochan-org/gochan/pkg/config"
|
|
|
|
"github.com/gochan-org/gochan/pkg/gcsql"
|
|
|
|
"github.com/gochan-org/gochan/pkg/gcutil"
|
2023-07-14 11:04:46 -07:00
|
|
|
"github.com/gochan-org/gochan/pkg/posting/uploads"
|
2013-04-20 01:21:40 -07:00
|
|
|
)
|
|
|
|
|
2022-01-27 21:02:37 -08:00
|
|
|
var (
|
|
|
|
ErrInvalidKey = errors.New("template map expects string keys")
|
|
|
|
ErrInvalidMap = errors.New("invalid template map call")
|
2022-01-27 21:35:28 -08:00
|
|
|
maxFilename = 10
|
2022-01-27 21:02:37 -08:00
|
|
|
)
|
|
|
|
|
2013-04-20 01:21:40 -07:00
|
|
|
var funcMap = template.FuncMap{
|
2018-10-07 12:20:10 -07:00
|
|
|
// Arithmetic functions
|
2015-12-24 23:26:13 -08:00
|
|
|
"add": func(a, b int) int {
|
2013-06-05 14:01:27 -07:00
|
|
|
return a + b
|
|
|
|
},
|
2015-12-24 23:26:13 -08:00
|
|
|
"subtract": func(a, b int) int {
|
2013-06-05 14:01:27 -07:00
|
|
|
return a - b
|
|
|
|
},
|
2018-10-07 12:20:10 -07:00
|
|
|
|
|
|
|
"isNil": func(i interface{}) bool {
|
|
|
|
return i == nil
|
|
|
|
},
|
|
|
|
|
|
|
|
// Array functions
|
2019-11-29 15:08:18 -08:00
|
|
|
"getSlice": func(arr []interface{}, start, length int) []interface{} {
|
|
|
|
if start < 0 {
|
|
|
|
start = 0
|
|
|
|
}
|
|
|
|
if length > len(arr) {
|
|
|
|
length = len(arr)
|
|
|
|
}
|
|
|
|
return arr[start:length]
|
2013-06-05 14:01:27 -07:00
|
|
|
},
|
2018-10-07 12:20:10 -07:00
|
|
|
|
|
|
|
// String functions
|
2020-07-09 15:54:31 -07:00
|
|
|
"intToString": strconv.Itoa,
|
|
|
|
"escapeString": html.EscapeString,
|
2019-12-06 20:03:37 -08:00
|
|
|
"formatFilesize": func(sizeInt int) string {
|
|
|
|
size := float32(sizeInt)
|
2018-10-07 12:20:10 -07:00
|
|
|
if size < 1000 {
|
2019-12-06 20:03:37 -08:00
|
|
|
return fmt.Sprintf("%d B", sizeInt)
|
2018-10-07 12:20:10 -07:00
|
|
|
} else if size <= 100000 {
|
|
|
|
return fmt.Sprintf("%0.1f KB", size/1024)
|
|
|
|
} else if size <= 100000000 {
|
|
|
|
return fmt.Sprintf("%0.2f MB", size/1024/1024)
|
2018-04-06 01:03:57 -07:00
|
|
|
}
|
2018-10-07 12:20:10 -07:00
|
|
|
return fmt.Sprintf("%0.2f GB", size/1024/1024/1024)
|
2015-12-24 23:26:13 -08:00
|
|
|
},
|
2020-04-29 17:44:29 -07:00
|
|
|
"formatTimestamp": func(t time.Time) string {
|
2021-07-11 11:51:29 -07:00
|
|
|
return t.Format(config.GetBoardConfig("").DateTimeFormat)
|
2020-04-29 17:44:29 -07:00
|
|
|
},
|
2018-06-09 23:40:20 -07:00
|
|
|
"stringAppend": func(strings ...string) string {
|
|
|
|
var appended string
|
|
|
|
for _, str := range strings {
|
|
|
|
appended += str
|
|
|
|
}
|
|
|
|
return appended
|
2013-05-29 13:43:12 -07:00
|
|
|
},
|
2022-01-27 21:35:28 -08:00
|
|
|
"truncateFilename": func(filename string) string {
|
|
|
|
if len(filename) <= maxFilename {
|
|
|
|
return filename
|
|
|
|
}
|
|
|
|
arr := strings.Split(filename, ".")
|
|
|
|
if len(arr) == 1 {
|
|
|
|
return arr[0][:maxFilename]
|
|
|
|
}
|
|
|
|
base := strings.Join(arr[:len(arr)-1], ".")
|
|
|
|
if len(base) >= maxFilename {
|
|
|
|
base = base[:maxFilename]
|
|
|
|
}
|
|
|
|
ext := arr[len(arr)-1:][0]
|
|
|
|
return base + "." + ext
|
|
|
|
},
|
2019-12-06 20:03:37 -08:00
|
|
|
"truncateMessage": func(msg string, limit int, maxLines int) string {
|
2014-06-20 02:18:00 -07:00
|
|
|
var truncated bool
|
2020-07-09 15:54:31 -07:00
|
|
|
split := strings.Split(msg, "<br />")
|
2014-06-20 02:18:00 -07:00
|
|
|
|
2019-12-06 20:03:37 -08:00
|
|
|
if len(split) > maxLines {
|
|
|
|
split = split[:maxLines]
|
2015-12-24 23:26:13 -08:00
|
|
|
msg = strings.Join(split, "<br />")
|
2014-06-20 02:18:00 -07:00
|
|
|
truncated = true
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(msg) < limit {
|
|
|
|
if truncated {
|
|
|
|
msg = msg + "..."
|
|
|
|
}
|
|
|
|
return msg
|
|
|
|
}
|
2020-04-29 17:44:29 -07:00
|
|
|
msg = msg[:limit]
|
|
|
|
truncated = true
|
2014-06-20 02:18:00 -07:00
|
|
|
|
|
|
|
if truncated {
|
|
|
|
msg = msg + "..."
|
2014-06-15 02:15:51 -07:00
|
|
|
}
|
2014-06-20 02:18:00 -07:00
|
|
|
return msg
|
2014-06-15 02:15:51 -07:00
|
|
|
},
|
2020-05-24 21:59:39 +02:00
|
|
|
"truncateHTMLMessage": truncateHTML,
|
2020-05-24 18:56:24 +02:00
|
|
|
"stripHTML": func(htmlStr template.HTML) string {
|
2022-01-29 23:47:13 -08:00
|
|
|
return gcutil.StripHTML(string(htmlStr))
|
2019-02-16 19:18:00 -08:00
|
|
|
},
|
2017-10-29 00:59:42 -07:00
|
|
|
"truncateString": func(msg string, limit int, ellipsis bool) string {
|
|
|
|
if len(msg) > limit {
|
|
|
|
if ellipsis {
|
|
|
|
return msg[:limit] + "..."
|
|
|
|
}
|
2018-06-09 23:40:20 -07:00
|
|
|
return msg[:limit]
|
2017-10-29 00:59:42 -07:00
|
|
|
}
|
2018-06-09 23:40:20 -07:00
|
|
|
return msg
|
2017-10-29 00:59:42 -07:00
|
|
|
},
|
2022-01-27 21:02:37 -08:00
|
|
|
"map": func(values ...interface{}) (map[string]interface{}, error) {
|
|
|
|
dict := make(map[string]interface{})
|
|
|
|
if len(values)%2 != 0 {
|
|
|
|
return nil, ErrInvalidMap
|
|
|
|
}
|
|
|
|
for k := 0; k < len(values); k += 2 {
|
|
|
|
key, ok := values[k].(string)
|
|
|
|
if !ok {
|
|
|
|
return nil, ErrInvalidKey
|
|
|
|
}
|
|
|
|
dict[key] = values[k+1]
|
|
|
|
}
|
|
|
|
return dict, nil
|
|
|
|
},
|
2022-11-17 16:32:28 -08:00
|
|
|
"until": func(t time.Time) string {
|
2023-05-23 08:47:27 -07:00
|
|
|
return time.Until(t).String()
|
2022-11-17 16:32:28 -08:00
|
|
|
},
|
|
|
|
"dereference": func(a *int) int {
|
|
|
|
if a == nil {
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
return *a
|
|
|
|
},
|
2023-05-24 08:55:11 -07:00
|
|
|
|
2018-10-07 12:20:10 -07:00
|
|
|
// Imageboard functions
|
2022-11-09 10:56:38 -08:00
|
|
|
"bannedForever": func(ban *gcsql.IPBan) bool {
|
|
|
|
return ban.IsActive && ban.Permanent && !ban.CanAppeal
|
|
|
|
},
|
|
|
|
"isBanned": func(ban *gcsql.IPBan, board string) bool {
|
|
|
|
return ban.IsActive && ban.BoardID != nil
|
|
|
|
},
|
2023-12-31 18:02:42 -08:00
|
|
|
"banMask": func(ban gcsql.IPBan) string {
|
|
|
|
if ban.ID < 1 {
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
ipn, err := gcutil.GetIPRangeSubnet(ban.RangeStart, ban.RangeEnd)
|
|
|
|
if err != nil {
|
|
|
|
return "?"
|
|
|
|
}
|
|
|
|
return ipn.String()
|
|
|
|
},
|
2022-11-16 15:11:55 -08:00
|
|
|
"getBoardDirFromID": func(id int) string {
|
|
|
|
dir, _ := gcsql.GetBoardDir(id)
|
|
|
|
return dir
|
|
|
|
},
|
2022-11-18 16:47:25 -08:00
|
|
|
"intPtrToBoardDir": func(id *int, ifNil string, ifErr string) string {
|
|
|
|
if id == nil {
|
|
|
|
return ifNil
|
|
|
|
}
|
|
|
|
dir, err := gcsql.GetBoardDir(*id)
|
|
|
|
if err != nil {
|
|
|
|
return ifErr
|
|
|
|
}
|
|
|
|
return dir
|
|
|
|
},
|
2022-11-16 15:11:55 -08:00
|
|
|
"getStaffNameFromID": func(id int) string {
|
|
|
|
username, err := gcsql.GetStaffUsernameFromID(id)
|
|
|
|
if err != nil {
|
|
|
|
return "?"
|
|
|
|
}
|
|
|
|
return username
|
|
|
|
},
|
2022-11-30 10:20:31 -08:00
|
|
|
"getAppealBanIP": func(appealID int) string {
|
|
|
|
ban, err := gcsql.GetIPBanByID(appealID)
|
|
|
|
if err != nil || ban == nil {
|
|
|
|
return "?"
|
|
|
|
}
|
2023-12-28 23:06:44 -08:00
|
|
|
if ban.RangeStart == ban.RangeEnd {
|
|
|
|
return ban.RangeStart
|
|
|
|
}
|
|
|
|
ipn, err := gcutil.GetIPRangeSubnet(ban.RangeStart, ban.RangeEnd)
|
|
|
|
if err != nil {
|
|
|
|
return "?"
|
|
|
|
}
|
|
|
|
return ipn.String()
|
2022-11-30 10:20:31 -08:00
|
|
|
},
|
2018-10-22 22:02:06 -07:00
|
|
|
"getCatalogThumbnail": func(img string) string {
|
2023-07-14 11:04:46 -07:00
|
|
|
_, catalogThumb := uploads.GetThumbnailFilenames(img)
|
|
|
|
return catalogThumb
|
2018-10-22 22:02:06 -07:00
|
|
|
},
|
2022-11-10 12:18:13 -08:00
|
|
|
"getTopPostID": func(post *gcsql.Post) int {
|
|
|
|
id, _ := post.TopPostID()
|
|
|
|
return id
|
|
|
|
},
|
2018-10-22 22:02:06 -07:00
|
|
|
"getThreadThumbnail": func(img string) string {
|
2023-07-14 11:04:46 -07:00
|
|
|
thumb, _ := uploads.GetThumbnailFilenames(img)
|
|
|
|
return thumb
|
2013-07-04 02:39:23 -07:00
|
|
|
},
|
2018-01-28 15:01:59 -08:00
|
|
|
"getUploadType": func(name string) string {
|
2023-07-14 11:04:46 -07:00
|
|
|
return uploads.GetThumbnailExtension(path.Ext(name))
|
2014-06-08 08:26:28 -07:00
|
|
|
},
|
2022-10-11 14:26:31 -07:00
|
|
|
"numReplies": func(boardid, opID int) int {
|
|
|
|
num, err := gcsql.GetThreadReplyCountFromOP(opID)
|
2020-04-29 17:44:29 -07:00
|
|
|
if err != nil {
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
return num
|
|
|
|
},
|
2019-01-03 11:51:59 -08:00
|
|
|
"getBoardDir": func(id int) string {
|
2022-10-11 14:26:31 -07:00
|
|
|
dir, err := gcsql.GetBoardDir(id)
|
|
|
|
if err != nil {
|
2019-01-03 11:51:59 -08:00
|
|
|
return ""
|
|
|
|
}
|
2022-10-11 14:26:31 -07:00
|
|
|
return dir
|
2019-01-03 11:51:59 -08:00
|
|
|
},
|
2023-12-08 15:29:04 -08:00
|
|
|
"getBoardDefaultStyle": func(dir string) string {
|
|
|
|
boardCfg := config.GetBoardConfig(dir)
|
|
|
|
if !boardCfg.IsGlobal() {
|
|
|
|
// /<board>/board.json exists, overriding the default them and theme set in SQL
|
|
|
|
return boardCfg.DefaultStyle
|
|
|
|
}
|
|
|
|
var defaultStyle string
|
|
|
|
err := gcsql.QueryRowSQL(`SELECT default_style FROM DBPREFIXboards WHERE dir = ?`,
|
|
|
|
[]any{dir}, []any{&defaultStyle})
|
|
|
|
if err != nil || defaultStyle == "" {
|
|
|
|
gcutil.LogError(err).Caller().
|
|
|
|
Str("board", dir).
|
|
|
|
Msg("Unable to get default style attribute of board")
|
|
|
|
return boardCfg.DefaultStyle
|
|
|
|
}
|
|
|
|
return defaultStyle
|
|
|
|
},
|
2022-12-24 12:25:23 -08:00
|
|
|
"boardPagePath": func(board *gcsql.Board, page int) string {
|
|
|
|
return config.WebPath(board.Dir, strconv.Itoa(page)+".html")
|
|
|
|
},
|
2022-07-18 12:34:43 -07:00
|
|
|
"webPath": func(part ...string) string {
|
|
|
|
return config.WebPath(part...)
|
|
|
|
},
|
2023-12-07 12:11:58 -08:00
|
|
|
"webPathDir": func(part ...string) string {
|
|
|
|
dir := config.WebPath(part...)
|
|
|
|
if len(dir) > 0 && dir[len(dir)-1] != '/' {
|
|
|
|
dir += "/"
|
|
|
|
}
|
|
|
|
return dir
|
|
|
|
},
|
2023-05-24 08:55:11 -07:00
|
|
|
"sectionBoards": func(sectionID int) []gcsql.Board {
|
|
|
|
var boards []gcsql.Board
|
|
|
|
for _, board := range gcsql.AllBoards {
|
|
|
|
if board.SectionID == sectionID && !board.IsHidden(false) {
|
|
|
|
boards = append(boards, board)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return boards
|
|
|
|
},
|
2018-10-07 12:20:10 -07:00
|
|
|
// Template convenience functions
|
|
|
|
"makeLoop": func(n int, offset int) []int {
|
|
|
|
loopArr := make([]int, n)
|
|
|
|
for i := range loopArr {
|
|
|
|
loopArr[i] = i + offset
|
|
|
|
}
|
|
|
|
return loopArr
|
|
|
|
},
|
|
|
|
"isStyleDefault": func(style string) bool {
|
2021-07-11 11:51:29 -07:00
|
|
|
return style == config.GetBoardConfig("").DefaultStyle
|
2018-10-05 15:21:36 -07:00
|
|
|
},
|
2018-11-26 15:29:26 -08:00
|
|
|
"version": func() string {
|
2021-07-11 11:51:29 -07:00
|
|
|
return config.GetVersion().String()
|
2018-11-22 23:47:51 -08:00
|
|
|
},
|
2013-04-20 01:21:40 -07:00
|
|
|
}
|