gemini-homepage

Gemini homepage for knutsen.co
git clone git://git.knutsen.co/gemini-homepage
Log | Files | Refs | LICENSE

main.go (3926B)


package main

import ("os"; "io"; "fmt"; "log")
import ("os/exec"; "time"; "bufio")
import "strings"
import "path/filepath"
import "text/template"
import "net/http"

import "github.com/pitr/gig"

var GEMINI_CERT = os.Getenv("GEMINI_CERT")
var GEMINI_KEY = os.Getenv("GEMINI_KEY")

type DefaultTemplate struct {
	Date time.Time
	Fortune string
	Copyright string
	Footer string
}

func NewDefaultTemplate() (t DefaultTemplate) {
	t = DefaultTemplate{}
	t.Date = time.Now()
	t.Copyright = "\U0001F12F Copyleft, all wrongs reserved."
	t.Fortune = "Failed to run `fortune`."

	fortune := exec.Command("fortune", "-n", "short")
	stdout, _ := fortune.StdoutPipe()

	err := fortune.Start()
	if err == nil {
		t.Fortune = ""
		buf := bufio.NewReader(stdout)
		for {
			line, _, err := buf.ReadLine()
			if err != nil {
				if err == io.EOF { break }
				log.Println("Error reading line.", err)
			}
			l := string(line)
			l = strings.ReplaceAll(l, "--", "\u2014")
			t.Fortune += "\n> " + l
		}
	}

	t.Footer = fmt.Sprintf("\n" +
		"## \u2E3B\n" +
		"%s\n" +
		"```The date.\n" +
		"~%04d.%02d.%02d\n" +
		"```\n\n" +
		"%s",
			t.Fortune,
			t.Date.Year(), t.Date.Month(), t.Date.Day(),
			t.Copyright)
	return
}

func walkTemplates(roots ...string) map[string] *template.Template {
	templates := make(map[string] *template.Template)
	for _, root := range roots {
		err := filepath.Walk(root,
			func(path string, info os.FileInfo, err error) error {
				if path[len(path) - 4:] == ".gmi" {
					templates[path] = template.Must(template.ParseFiles(path))
					if err != nil {
						log.Println(err)
					}
				}
				return err
			})
		if err != nil {
			panic(err)
		}
	}

	for path, t := range templates {
		log.Printf("<Template name=%s> %s\n", t.Name(), path)
	}
	return templates
}

func Template(c gig.Context, templates map[string] *template.Template,
              path string, data interface{}) (err error) {
	res := c.Response()

	t, ok := templates[path]
	if !ok { return gig.ErrNotFound }
	err = res.WriteHeader(gig.StatusSuccess, gig.MIMETextGemini);
	if err != nil { return }

	return t.ExecuteTemplate(res, t.Name(), data)
}

func main() {
	fmt.Println("[~] CERT:", GEMINI_CERT)
	fmt.Println("[~]  KEY:", GEMINI_KEY)

	s := gig.Default()

	templates := walkTemplates("./views", "./users")

	defaultView := func (file string) (func(gig.Context) error) {
		return func(c gig.Context) error {
			defaultTemplate := NewDefaultTemplate()
			return Template(c, templates, "views/" + file + ".gmi",
				defaultTemplate);
		}
	}

	userView := func (name string, file string) (func(gig.Context) error) {
		return func(c gig.Context) error {
			defaultTemplate := NewDefaultTemplate()
			return Template(c, templates, "users/" + name + "/" + file + ".gmi",
				defaultTemplate);
		}
	}

	s.Static("/static", "static")
	s.Handle("/static", func(c gig.Context) error {
		return c.NoContent(gig.StatusRedirectPermanent, "/static/")
	})
	s.Handle("/", defaultView("index"))

	s.Handle("weather", func(c gig.Context) error {
		query, err := c.QueryString()
		if err != nil { return err }

		if query == "" {
			return c.NoContent(gig.StatusInput, "Enter your location.")
		}

		loc := strings.Join(strings.Split(query, " "), "+")
		res, err := http.Get("https://wttr.in/" + loc + "?AF0")
		if err != nil || res.StatusCode != http.StatusOK {
			return gig.ErrProxyError
		}
		defer res.Body.Close()

		return c.Stream("text/html", res.Body)
	})

	user := s.Group("/~:name")
	{
		fileView := func(file string) (func(gig.Context) error) {
			return func(c gig.Context) error {
				return userView(c.Param("name"), file)(c)
			}
		}

		user.Handle("",  fileView("index"))
		user.Handle("/", fileView("index"))
		user.Handle("/:path", func(c gig.Context) error {
			return fileView(c.Param("path"))(c)
		})
	}

	s.Handle("/:path", func(c gig.Context) error {
		return defaultView(c.Param("path"))(c)
	})

	s.Run(GEMINI_CERT, GEMINI_KEY)
}