2025-01-13 19:21:10 -06:00
|
|
|
package main
|
|
|
|
|
2025-01-13 22:50:12 -06:00
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"embed"
|
|
|
|
"flag"
|
|
|
|
"fmt"
|
|
|
|
"image"
|
|
|
|
"image/color"
|
|
|
|
"image/png"
|
|
|
|
"io"
|
|
|
|
"math"
|
|
|
|
"net/http"
|
|
|
|
"runtime/debug"
|
|
|
|
"text/template"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"golang.org/x/image/draw"
|
|
|
|
|
|
|
|
"git.chandlerswift.com/chandlerswift/bannergen/internal/datafetchers"
|
|
|
|
"git.chandlerswift.com/chandlerswift/bannergen/internal/datafetchers/espn"
|
|
|
|
"git.chandlerswift.com/chandlerswift/bannergen/internal/types"
|
|
|
|
)
|
|
|
|
|
|
|
|
//go:embed static templates
|
|
|
|
var frontend embed.FS
|
|
|
|
|
2025-01-13 19:21:10 -06:00
|
|
|
func main() {
|
2025-01-13 22:50:12 -06:00
|
|
|
port := flag.Int("port", 8000, "Port to listen on")
|
|
|
|
flag.Parse()
|
|
|
|
|
|
|
|
// preload teams data
|
|
|
|
fetchers := []datafetchers.DataFetcher{
|
|
|
|
espn.ESPNDataFetcher{},
|
|
|
|
}
|
|
|
|
var sports []types.Sport
|
|
|
|
for _, fetcher := range fetchers {
|
|
|
|
sports = append(sports, fetcher.Fetch()...)
|
|
|
|
}
|
|
|
|
|
|
|
|
rawBuildInfo, _ := debug.ReadBuildInfo()
|
|
|
|
var revision, time string
|
|
|
|
var dirty bool
|
|
|
|
for _, buildSetting := range rawBuildInfo.Settings {
|
|
|
|
if buildSetting.Key == "vcs.revision" {
|
|
|
|
revision = buildSetting.Value
|
|
|
|
} else if buildSetting.Key == "vcs.time" {
|
|
|
|
time = buildSetting.Value
|
|
|
|
} else if buildSetting.Key == "vcs.modified" {
|
|
|
|
dirty = buildSetting.Value == "true"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
buildInfo := map[string]interface{}{
|
|
|
|
"GitRev": revision,
|
|
|
|
"GitDirty": dirty,
|
|
|
|
"GitTime": time,
|
|
|
|
"GoVersion": rawBuildInfo.GoVersion,
|
|
|
|
"AllBuildInfo": rawBuildInfo.String(),
|
|
|
|
}
|
|
|
|
|
|
|
|
http.Handle("GET /static/", http.FileServer(http.FS(frontend)))
|
|
|
|
tmpls := template.Must(template.ParseFS(frontend, "templates/*"))
|
|
|
|
http.HandleFunc("GET /{$}", func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
err := tmpls.ExecuteTemplate(w, "index.html", map[string]interface{}{})
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
http.HandleFunc("GET /about.html", func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
err := tmpls.ExecuteTemplate(w, "about.html", map[string]interface{}{
|
|
|
|
"buildInfo": buildInfo,
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
http.HandleFunc("/generate/{image_name}", func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
r.ParseForm()
|
|
|
|
|
|
|
|
colorLeftHex := r.Form.Get("left_color")
|
|
|
|
if colorLeftHex == "" {
|
|
|
|
http.Error(w, "no left_color provided", 400)
|
|
|
|
}
|
|
|
|
colorRightHex := r.Form.Get("right_color")
|
|
|
|
if colorRightHex == "" {
|
|
|
|
http.Error(w, "no right_color provided", 400)
|
|
|
|
}
|
|
|
|
logoLeftPath := r.Form.Get("left_logo")
|
|
|
|
if logoLeftPath == "" {
|
|
|
|
http.Error(w, "no left_logo provided", 400)
|
|
|
|
}
|
|
|
|
logoRightPath := r.Form.Get("right_logo")
|
|
|
|
if logoRightPath == "" {
|
|
|
|
http.Error(w, "no right_logo provided", 400)
|
|
|
|
}
|
|
|
|
|
|
|
|
size := 400 // TODO: param
|
|
|
|
|
|
|
|
// Set image dimensions; TODO: param
|
|
|
|
const width, height = 1280, 720
|
|
|
|
|
|
|
|
// Create a blank RGBA image
|
|
|
|
img := image.NewRGBA(image.Rect(0, 0, width, height))
|
|
|
|
|
|
|
|
// Convert hex colors to RGBA
|
|
|
|
colorLeft := hexToRGBA(colorLeftHex)
|
|
|
|
colorRight := hexToRGBA(colorRightHex)
|
|
|
|
|
|
|
|
// Fill the image with colors split by angle
|
|
|
|
centerX, centerY := float64(width)/2, float64(height)/2
|
|
|
|
for x := 0; x < width; x++ {
|
|
|
|
for y := 0; y < height; y++ {
|
|
|
|
dx, dy := float64(x)-centerX, float64(y)-centerY
|
|
|
|
rotatedAngle := math.Atan2(dy, dx) - 16.0*math.Pi/180
|
|
|
|
if math.Cos(rotatedAngle) >= 0 {
|
|
|
|
img.Set(x, y, colorRight)
|
|
|
|
} else {
|
|
|
|
img.Set(x, y, colorLeft)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Load logos
|
|
|
|
logoLeft, err := loadPNG(logoLeftPath)
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
logoRight, err := loadPNG(logoRightPath)
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Resize logos using golang.org/x/image/draw
|
|
|
|
logoLeftResized := resizeImage(logoLeft, size, size)
|
|
|
|
logoRightResized := resizeImage(logoRight, size, size)
|
|
|
|
|
|
|
|
// Place logos
|
|
|
|
logoLeftX, logoLeftY := (width/4)-(size/2), (height/2)-(size/2)
|
|
|
|
logoRightX, logoRightY := (3*width/4)-(size/2), (height/2)-(size/2)
|
|
|
|
draw.Draw(img, image.Rect(logoLeftX, logoLeftY, logoLeftX+size, logoLeftY+size), logoLeftResized, image.Point{}, draw.Over)
|
|
|
|
draw.Draw(img, image.Rect(logoRightX, logoRightY, logoRightX+size, logoRightY+size), logoRightResized, image.Point{}, draw.Over)
|
|
|
|
|
|
|
|
png.Encode(w, img)
|
|
|
|
})
|
|
|
|
|
|
|
|
panic(http.ListenAndServe(fmt.Sprintf(":%v", *port), nil))
|
|
|
|
}
|
|
|
|
|
|
|
|
func resizeImage(src *image.RGBA, newWidth, newHeight int) *image.RGBA {
|
|
|
|
dst := image.NewRGBA(image.Rect(0, 0, newWidth, newHeight))
|
|
|
|
draw.CatmullRom.Scale(dst, dst.Bounds(), src, src.Bounds(), draw.Over, nil)
|
|
|
|
return dst
|
|
|
|
}
|
|
|
|
|
|
|
|
func hexToRGBA(hex string) color.RGBA {
|
|
|
|
var r, g, b int
|
|
|
|
var err error
|
|
|
|
if hex[0] == '#' {
|
|
|
|
_, err = fmt.Sscanf(hex, "#%02x%02x%02x", &r, &g, &b)
|
|
|
|
} else {
|
|
|
|
_, err = fmt.Sscanf(hex, "%02x%02x%02x", &r, &g, &b)
|
|
|
|
}
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
return color.RGBA{uint8(r), uint8(g), uint8(b), 255}
|
|
|
|
}
|
|
|
|
|
|
|
|
func loadPNG(path string) (*image.RGBA, error) {
|
|
|
|
var reader io.ReadCloser
|
|
|
|
var err error
|
|
|
|
|
|
|
|
// TODO: smarter limits here
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
|
|
|
|
defer cancel()
|
|
|
|
req, err := http.NewRequestWithContext(ctx, "GET", path, nil)
|
|
|
|
|
|
|
|
resp, err := http.DefaultClient.Do(req)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
|
|
resp.Body.Close()
|
|
|
|
return nil, fmt.Errorf("failed to fetch image: %s", resp.Status)
|
|
|
|
}
|
|
|
|
reader = resp.Body
|
|
|
|
defer reader.Close()
|
|
|
|
|
|
|
|
img, err := png.Decode(reader)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2025-01-13 19:21:10 -06:00
|
|
|
|
2025-01-13 22:50:12 -06:00
|
|
|
// Convert to RGBA
|
|
|
|
rgbaImg := image.NewRGBA(img.Bounds())
|
|
|
|
draw.Draw(rgbaImg, rgbaImg.Bounds(), img, image.Point{}, draw.Src)
|
|
|
|
return rgbaImg, nil
|
2025-01-13 19:21:10 -06:00
|
|
|
}
|