opengraph-image-gen/main.go

113 lines
2.8 KiB
Go

package main
import (
"embed"
"encoding/base64"
"encoding/json"
"fmt"
"os"
"strings"
"text/template"
"time"
"net/url"
"github.com/canhlinh/svg2png"
"github.com/patrickmn/go-cache"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/middleware/compress"
"github.com/gofiber/fiber/v2/middleware/cors"
"github.com/gofiber/fiber/v2/middleware/logger"
)
//go:embed index.html svg.tmpl
var content embed.FS
var chrome *svg2png.Chrome
func main() {
chrome = svg2png.NewChrome().SetHeight(600).SetWith(1200).SetTimeout(10 * time.Second)
ch := cache.New(24*time.Hour, 48*time.Hour)
app := fiber.New()
app.Use(compress.New())
app.Use(cors.New())
app.Use(logger.New())
app.Get("/", func(c *fiber.Ctx) error {
c.Type("html", "UTF8")
body, _ := content.ReadFile("index.html")
return c.Send(body)
})
app.Get("/opengraph", func(c *fiber.Ctx) error {
vars := map[string]string{
"siteTitle": ensureDecoded(c.Query("siteTitle", "")),
"title": ensureDecoded(c.Query("title", "")),
"tags": ensureDecoded(c.Query("tags", "")),
"image": ensureDecoded(c.Query("image", "")),
"twitter": ensureDecoded(c.Query("twitter", "")),
"github": ensureDecoded(c.Query("github", "")),
"website": ensureDecoded(c.Query("website", "")),
"bgColor": ensureDecoded(c.Query("bgColor", c.Query("bgColour", "#fff"))),
"fgColor": ensureDecoded(c.Query("fgColor", c.Query("fgColour", "#2B414D"))),
}
key := generateKey(vars)
png, found := ch.Get(key)
if !found || len(png.([]byte)) == 0 {
var err error
png, err = generateImage(vars)
if err != nil {
fmt.Println(err)
return c.SendStatus(500)
}
ch.Set(key, png, cache.DefaultExpiration)
}
c.Type("png")
return c.Send(png.([]byte))
})
app.Listen(":3000")
}
func generateKey(vars map[string]string) string {
varsByte, _ := json.Marshal(vars)
return base64.StdEncoding.EncodeToString(varsByte)
}
func generateImage(vars map[string]string) ([]byte, error) {
file, err := os.CreateTemp(os.TempDir(), "img-*.html")
if err != nil {
return nil, err
}
defer os.Remove(file.Name())
t := template.Must(template.New("svg.tmpl").Funcs(template.FuncMap{
"split": func(input string) []string {
return strings.Split(input, ",")
},
}).ParseFS(content, "svg.tmpl"))
t.Execute(file, vars)
imageFile, err := os.CreateTemp(os.TempDir(), "img-*.png")
if err := chrome.Screenshoot(fmt.Sprintf("file://%s", file.Name()), imageFile.Name()); err != nil {
return nil, err
}
defer os.Remove(imageFile.Name())
return os.ReadFile(imageFile.Name())
}
// Some sites (LinkedIn) encode the already encoded URL so we need to double-decode to be sure
func ensureDecoded(str string) string {
decoded, err := url.QueryUnescape(str)
if err != nil {
return str
}
return decoded
}