324 lines
6.7 KiB
Go
324 lines
6.7 KiB
Go
|
package main
|
||
|
|
||
|
import (
|
||
|
"bufio"
|
||
|
"bytes"
|
||
|
"encoding/json"
|
||
|
"fmt"
|
||
|
"html/template"
|
||
|
"io"
|
||
|
"io/ioutil"
|
||
|
"net/http"
|
||
|
"os"
|
||
|
"strings"
|
||
|
"time"
|
||
|
|
||
|
"github.com/gin-gonic/contrib/sessions"
|
||
|
"github.com/gin-gonic/gin"
|
||
|
"github.com/pariz/gountries"
|
||
|
"github.com/rjeczalik/notify"
|
||
|
"github.com/thehowl/conf"
|
||
|
"zxq.co/ripple/rippleapi/common"
|
||
|
)
|
||
|
|
||
|
var templates = make(map[string]*template.Template)
|
||
|
var baseTemplates = [...]string{
|
||
|
"templates/base.html",
|
||
|
"templates/navbar.html",
|
||
|
"templates/simplepag.html",
|
||
|
}
|
||
|
var simplePages []templateConfig
|
||
|
|
||
|
var gdb = gountries.New()
|
||
|
|
||
|
func countryReadable(s string) string {
|
||
|
if s == "XX" || s == "" {
|
||
|
return ""
|
||
|
}
|
||
|
reg, err := gdb.FindCountryByAlpha(s)
|
||
|
if err != nil {
|
||
|
return ""
|
||
|
}
|
||
|
return reg.Name.Common
|
||
|
}
|
||
|
|
||
|
func loadTemplates(subdir string) {
|
||
|
ts, err := ioutil.ReadDir("templates" + subdir)
|
||
|
if err != nil {
|
||
|
panic(err)
|
||
|
}
|
||
|
|
||
|
for _, i := range ts {
|
||
|
// if it's a directory, load recursively
|
||
|
if i.IsDir() && i.Name() != ".." && i.Name() != "." {
|
||
|
loadTemplates(subdir + "/" + i.Name())
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
// ignore non-html files
|
||
|
if strings.HasPrefix(i.Name(), ".html") {
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
fullName := "templates" + subdir + "/" + i.Name()
|
||
|
_c := parseConfig(fullName)
|
||
|
var c templateConfig
|
||
|
if _c != nil {
|
||
|
c = *_c
|
||
|
}
|
||
|
if c.NoCompile {
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
var files = c.inc("templates" + subdir + "/")
|
||
|
files = append(files, fullName)
|
||
|
|
||
|
// do not compile base templates on their own
|
||
|
var comp bool
|
||
|
for _, j := range baseTemplates {
|
||
|
if fullName == j {
|
||
|
comp = true
|
||
|
break
|
||
|
}
|
||
|
}
|
||
|
if comp {
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
var inName string
|
||
|
if subdir != "" && subdir[0] == '/' {
|
||
|
inName = subdir[1:] + "/"
|
||
|
}
|
||
|
|
||
|
// add new template to template slice
|
||
|
templates[inName+i.Name()] = template.Must(template.New(i.Name()).Funcs(funcMap).ParseFiles(
|
||
|
append(files, baseTemplates[:]...)...,
|
||
|
))
|
||
|
|
||
|
if _c != nil {
|
||
|
simplePages = append(simplePages, *_c)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func resp(c *gin.Context, statusCode int, tpl string, data interface{}) {
|
||
|
if c == nil {
|
||
|
return
|
||
|
}
|
||
|
t := templates[tpl]
|
||
|
if t == nil {
|
||
|
c.String(500, "Template not found! Please tell this to a dev!")
|
||
|
return
|
||
|
}
|
||
|
sess := getSession(c)
|
||
|
if corrected, ok := data.(page); ok {
|
||
|
corrected.SetMessages(getMessages(c))
|
||
|
corrected.SetPath(c.Request.URL.Path)
|
||
|
corrected.SetContext(getContext(c))
|
||
|
corrected.SetGinContext(c)
|
||
|
corrected.SetSession(sess)
|
||
|
}
|
||
|
sess.Save()
|
||
|
buf := &bytes.Buffer{}
|
||
|
err := t.ExecuteTemplate(buf, "base", data)
|
||
|
if err != nil {
|
||
|
c.String(
|
||
|
200,
|
||
|
"An error occurred while trying to render the page, and we have now been notified about it.",
|
||
|
)
|
||
|
c.Error(err)
|
||
|
return
|
||
|
}
|
||
|
c.Header("Content-Type", "text/html; charset=utf-8")
|
||
|
c.Status(statusCode)
|
||
|
_, err = io.Copy(c.Writer, buf)
|
||
|
if err != nil {
|
||
|
c.Writer.WriteString("We don't know what's happening now.")
|
||
|
c.Error(err)
|
||
|
return
|
||
|
}
|
||
|
}
|
||
|
|
||
|
type baseTemplateData struct {
|
||
|
TitleBar string // required
|
||
|
HeadingTitle string
|
||
|
HeadingOnRight bool
|
||
|
Scripts []string
|
||
|
KyutGrill string
|
||
|
KyutGrillAbsolute bool
|
||
|
SolidColour string
|
||
|
DisableHH bool // HH = Huge Heading
|
||
|
Messages []message
|
||
|
RequestInfo map[string]interface{}
|
||
|
|
||
|
// ignore, they're set by resp()
|
||
|
Context context
|
||
|
Path string
|
||
|
FormData map[string]string
|
||
|
Gin *gin.Context
|
||
|
Session sessions.Session
|
||
|
}
|
||
|
|
||
|
func (b *baseTemplateData) SetMessages(m []message) {
|
||
|
b.Messages = append(b.Messages, m...)
|
||
|
}
|
||
|
func (b *baseTemplateData) SetPath(path string) {
|
||
|
b.Path = path
|
||
|
}
|
||
|
func (b *baseTemplateData) SetContext(c context) {
|
||
|
b.Context = c
|
||
|
}
|
||
|
func (b *baseTemplateData) SetGinContext(c *gin.Context) {
|
||
|
b.Gin = c
|
||
|
}
|
||
|
func (b *baseTemplateData) SetSession(sess sessions.Session) {
|
||
|
b.Session = sess
|
||
|
}
|
||
|
func (b baseTemplateData) Get(s string, params ...interface{}) map[string]interface{} {
|
||
|
s = fmt.Sprintf(s, params...)
|
||
|
req, err := http.NewRequest("GET", config.API+s, nil)
|
||
|
if err != nil {
|
||
|
b.Gin.Error(err)
|
||
|
return nil
|
||
|
}
|
||
|
req.Header.Set("User-Agent", "hanayo")
|
||
|
req.Header.Set("H-Key", config.APISecret)
|
||
|
req.Header.Set("X-Ripple-Token", b.Context.Token)
|
||
|
resp, err := http.DefaultClient.Do(req)
|
||
|
if err != nil {
|
||
|
b.Gin.Error(err)
|
||
|
return nil
|
||
|
}
|
||
|
data, err := ioutil.ReadAll(resp.Body)
|
||
|
defer resp.Body.Close()
|
||
|
if err != nil {
|
||
|
b.Gin.Error(err)
|
||
|
return nil
|
||
|
}
|
||
|
x := make(map[string]interface{})
|
||
|
err = json.Unmarshal(data, &x)
|
||
|
if err != nil {
|
||
|
b.Gin.Error(err)
|
||
|
return nil
|
||
|
}
|
||
|
return x
|
||
|
}
|
||
|
func (b baseTemplateData) Has(privs uint64) bool {
|
||
|
return uint64(b.Context.User.Privileges)&privs == privs
|
||
|
}
|
||
|
func (b baseTemplateData) Conf() interface{} {
|
||
|
return config
|
||
|
}
|
||
|
|
||
|
// list of client flags
|
||
|
const (
|
||
|
CFDarkSite = 1 << iota
|
||
|
)
|
||
|
|
||
|
func (b baseTemplateData) ClientFlags() int {
|
||
|
s, _ := b.Gin.Cookie("cflags")
|
||
|
return common.Int(s)
|
||
|
}
|
||
|
|
||
|
type page interface {
|
||
|
SetMessages([]message)
|
||
|
SetPath(string)
|
||
|
SetContext(context)
|
||
|
SetGinContext(*gin.Context)
|
||
|
SetSession(sessions.Session)
|
||
|
}
|
||
|
|
||
|
func reloader() error {
|
||
|
c := make(chan notify.EventInfo, 1)
|
||
|
if err := notify.Watch("./templates/...", c, notify.All); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
go func() {
|
||
|
var last time.Time
|
||
|
for ev := range c {
|
||
|
if !strings.HasSuffix(ev.Path(), ".html") || time.Since(last) < time.Second*3 {
|
||
|
continue
|
||
|
}
|
||
|
fmt.Println("Change detected! Refreshing templates")
|
||
|
simplePages = []templateConfig{}
|
||
|
loadTemplates("")
|
||
|
l.Close()
|
||
|
last = time.Now()
|
||
|
}
|
||
|
defer notify.Stop(c)
|
||
|
}()
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
type templateConfig struct {
|
||
|
NoCompile bool
|
||
|
Include string
|
||
|
Template string
|
||
|
|
||
|
// Stuff that used to be in simpleTemplate
|
||
|
Handler string
|
||
|
TitleBar string
|
||
|
KyutGrill string
|
||
|
MinPrivileges uint64
|
||
|
HugeHeadingRight bool
|
||
|
AdditionalJS string
|
||
|
}
|
||
|
|
||
|
func (t templateConfig) inc(prefix string) []string {
|
||
|
if t.Include == "" {
|
||
|
return nil
|
||
|
}
|
||
|
a := strings.Split(t.Include, ",")
|
||
|
for i, s := range a {
|
||
|
a[i] = prefix + s
|
||
|
}
|
||
|
return a
|
||
|
}
|
||
|
|
||
|
func (t templateConfig) mp() common.UserPrivileges {
|
||
|
return common.UserPrivileges(t.MinPrivileges)
|
||
|
}
|
||
|
|
||
|
func (t templateConfig) additionalJS() []string {
|
||
|
parts := strings.Split(t.AdditionalJS, ",")
|
||
|
if len(parts) > 0 && parts[len(parts)-1] == "" {
|
||
|
parts = parts[:len(parts)-1]
|
||
|
}
|
||
|
return parts
|
||
|
}
|
||
|
|
||
|
func parseConfig(s string) *templateConfig {
|
||
|
f, err := os.Open(s)
|
||
|
defer f.Close()
|
||
|
if err != nil {
|
||
|
return nil
|
||
|
}
|
||
|
i := bufio.NewScanner(f)
|
||
|
var inConfig bool
|
||
|
var buff string
|
||
|
var t templateConfig
|
||
|
for i.Scan() {
|
||
|
u := i.Text()
|
||
|
switch u {
|
||
|
case "{{/*###":
|
||
|
inConfig = true
|
||
|
case "*/}}":
|
||
|
if !inConfig {
|
||
|
continue
|
||
|
}
|
||
|
conf.LoadRaw(&t, []byte(buff))
|
||
|
t.Template = strings.TrimPrefix(s, "templates/")
|
||
|
return &t
|
||
|
}
|
||
|
if !inConfig {
|
||
|
continue
|
||
|
}
|
||
|
buff += u + "\n"
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func respEmpty(c *gin.Context, title string, messages ...message) {
|
||
|
resp(c, 200, "empty.html", &baseTemplateData{TitleBar: title, Messages: messages})
|
||
|
}
|