small pixel drawing of a pufferfish j3s.sh

main.go

package main

import (
	"bytes"
	"fmt"
	"time"
	"html/template"
	"io"
	"log"
	"math/rand"
	"net/http"
	"os"
	"path/filepath"
	"strings"

	"filippo.io/age"
	"filippo.io/age/armor"
	"git.j3s.sh/j3s.sh/atom"
	"git.j3s.sh/j3s.sh/feed"
	"github.com/SlyMarbo/rss"
)

// templateData is a mega-struct that gets
// passed to every single template - put whatever
// you want in it tbh.
//
// "data" is a global object that contains arbitrary
// data for use in templates. it's useful for it to be
// global since it may need to be available in arbitrary
// contexts. maybe that sucks. but idfk!
type templateData struct {
	Feeds []rss.Feed
	TitleWord   string
}

var data templateData

// the populate function populates the global "data" variable
// with ... data. with which to pass into templates.
func (t *templateData) populate() {
	t.Feeds = feed.GetAllFeeds()
	// refresh all rss feeds periodically
	for range time.Tick(time.Minute * 60) {
		log.Println("updating feeds tbh")
		for i := range t.Feeds {
			err := t.Feeds[i].Update()
			if err != nil {
				log.Printf("%s: %s\n", t.Feeds[i].Title, err)
			}
		}
	}
}

func main() {
	go data.populate()

	fs := http.FileServer(http.Dir("./static"))
	http.Handle("/static/", http.StripPrefix("/static/", fs))

	http.HandleFunc("/feed", feedHandler)
	http.HandleFunc("/feed.atom", atom.Handler)
	http.HandleFunc("/age.html", serveAge)
	http.HandleFunc("/ip", ipHandler)
	http.HandleFunc("/review/", babyHandler)
	http.HandleFunc("/thought/", babyHandler)
	http.HandleFunc("/", serveTemplate)

	log.Println("listening on :4666 tbh")
	err := http.ListenAndServe(":4666", nil)
	if err != nil {
		log.Fatal(err)
	}
}

func serveTemplate(w http.ResponseWriter, r *http.Request) {
	lp := filepath.Join("templates", "layout.html")
	if r.URL.Path == "/" {
		r.URL.Path = "index.html"
	}
	fp := filepath.Join("templates", filepath.Clean(r.URL.Path))

	info, err := os.Stat(fp)
	if err != nil {
		if os.IsNotExist(err) {
			http.NotFound(w, r)
			return
		}
	}

	if info.IsDir() {
		http.NotFound(w, r)
		return
	}

	tmpl, err := template.ParseFiles(lp, fp)
	if err != nil {
		log.Println(err.Error())
		http.Error(w, http.StatusText(500), 500)
		return
	}

	var wordList []string
	wordList = append(wordList, "shit hell", "loves shell", "is manic", "hugs you", "*fucking dies*", "logs off", "closes the world", "jes jes jes jes jes", "is a mess", "stands awkwardly", "walks to waterfall", "eats rice pudding", "olson", "plays dota2", "inspires ideas", "hates jump king", "<3", "pets every cat", "is a 0.5 on the binary", "winks at your miata", "has social anxiety", "tilts under pressure", "den", "is a skeletor")
	wordIndex := rand.Intn(len(wordList))
	data.TitleWord = wordList[wordIndex]

	err = tmpl.ExecuteTemplate(w, "layout", data)
	if err != nil {
		log.Println(err.Error())
		http.Error(w, http.StatusText(500), 500)
	}
}

func babyHandler(w http.ResponseWriter, r *http.Request) {
	lp := filepath.Join("templates", "simple-layout.html")
	fp := filepath.Join(strings.TrimPrefix(filepath.Clean(r.URL.Path), "/"))

	info, err := os.Stat(fp)
	if err != nil {
		if os.IsNotExist(err) {
			http.NotFound(w, r)
			return
		}
	}

	if info.IsDir() {
		http.NotFound(w, r)
		return
	}

	content, err := os.ReadFile(fp)
	if err != nil {
		log.Println(err.Error())
		http.Error(w, http.StatusText(500), 500)
		return
	}

	tmpl, err := template.ParseFiles(lp)
	if err != nil {
		log.Println(err.Error())
		http.Error(w, http.StatusText(500), 500)
		return
	}

	err = tmpl.ExecuteTemplate(w, "simple-layout", string(content))
	if err != nil {
		log.Println(err.Error())
		http.Error(w, http.StatusText(500), 500)
	}
}

type AgeForm struct {
	Success   bool
	PublicKey string
	Message   string
}

func serveAge(w http.ResponseWriter, r *http.Request) {
	lp := filepath.Join("templates", "layout.html")
	fp := filepath.Join("templates", "age.html")

	tmpl, err := template.ParseFiles(lp, fp)
	if err != nil {
		log.Println(err.Error())
		http.Error(w, http.StatusText(500), 500)
		return
	}

	if r.Method != http.MethodPost {
		tmpl.ExecuteTemplate(w, "layout", nil)
		return
	}

	formdeets := AgeForm{
		PublicKey: r.FormValue("pubkey"),
		Message:   r.FormValue("message"),
	}

	recipient, err := age.ParseX25519Recipient(formdeets.PublicKey)
	if err != nil {
		log.Println(err.Error())
		http.Error(w, http.StatusText(500), 500)
		return
	}

	buf := &bytes.Buffer{}
	armorWriter := armor.NewWriter(buf)

	write, err := age.Encrypt(armorWriter, recipient)
	if err != nil {
		log.Println(err.Error())
		http.Error(w, http.StatusText(500), 500)
		return
	}
	if _, err := io.WriteString(write, formdeets.Message); err != nil {
		log.Println(err.Error())
		http.Error(w, http.StatusText(500), 500)
		return
	}
	if err := write.Close(); err != nil {
		log.Println(err.Error())
		http.Error(w, http.StatusText(500), 500)
		return
	}
	if err := armorWriter.Close(); err != nil {
		log.Fatalf("Failed to close armor: %v", err)
	}

	log.Printf("%+v", buf)

	tmpl.ExecuteTemplate(w, "layout", buf.String())
}

func ipHandler(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "text/plain")
	// this header is always set by tlstunnel (MAYBE)
	// so we can PROBABLY expect it to be here IDFK RLY
	// IT'S LATE AND IM TIRED
	ip := r.Header.Get("X-Forwarded-For")
	if ip != "" {
		fmt.Fprintf(w, r.Header.Get("X-Forwarded-For")+"\n")
	} else {
		fmt.Fprintf(w, "not found\n")
	}
}

func feedHandler(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, `<!doctype html>
<html lang=en>
<head>
<meta charset=utf-8>
<title>j3s's rss feeds</title>
</head>
<body>`)
	// todo: finish this
	// todo: sort them
	for _, f := range data.Feeds {
		for _, i := range f.Items {
			fmt.Fprintf(w, "%s\n", i.Link)
		}
	}
}