diff --git a/cmd/fetch-header-image/main.go b/cmd/fetch-header-image/main.go index 77861e9..6ac676d 100644 --- a/cmd/fetch-header-image/main.go +++ b/cmd/fetch-header-image/main.go @@ -2,6 +2,7 @@ package main import ( "bytes" + "encoding/json" "flag" "io" "log" @@ -13,12 +14,53 @@ import ( ) func main() { + headerImages := flag.String("i", "data/pending-header-images.json", "header images json file") + currentPendingImage := flag.String("ip", "", "current pending image") targetFile := flag.String("o", "out.jpg", "target file") + odFile := flag.String("od", "out-data.json", "target file data") flag.Parse() - var bfr bytes.Buffer + pis, err := LoadPendingImages(*headerImages) + if err != nil { + log.Fatal(err) + } - if err := fetchHeaderImage(&bfr, "https://lmika.org/uploads/2025/pxl-20251120-083552448.jpg"); err != nil { + pi, ok := pis.FindPendingImage() + if !ok { + log.Println("no pending image found") + return + } + log.Printf("found pending image: %s\n", pi.URL) + + if *currentPendingImage != "" { + if cpi, err := LoadPendingImageFromURL(*currentPendingImage); err == nil { + if pi.URL == cpi.URL { + log.Println("current pending image is already the latest") + return + } + } else { + log.Printf("warn: failed to load current pending image: %s\n", err) + } + } + + if err := fetchHeaderImageFromURL(*targetFile, pi); err != nil { + log.Fatal(err) + } + + // Write out the current pending image to the data file + var dBfr bytes.Buffer + if err := json.NewEncoder(&dBfr).Encode(pi); err != nil { + log.Fatal(err) + } + + if err := os.WriteFile(*odFile, dBfr.Bytes(), 0644); err != nil { + log.Fatal(err) + } +} + +func fetchHeaderImageFromURL(outFile string, pi PendingImage) error { + var bfr bytes.Buffer + if err := fetchHeaderImage(&bfr, pi.URL); err != nil { log.Fatal(err) } @@ -29,16 +71,19 @@ func main() { rImg := imaging.Resize(img, 1280, 0, imaging.Lanczos) - // TEMP: crop bottom half - rImg = imaging.CropAnchor(rImg, rImg.Bounds().Dx(), rImg.Bounds().Dy()/2, imaging.Bottom) + if pi.Crop == CropBottom { + rImg = imaging.CropAnchor(rImg, rImg.Bounds().Dx(), rImg.Bounds().Dy()/2, imaging.Bottom) + } - if err := os.MkdirAll(filepath.Dir(*targetFile), 0755); err != nil { + if err := os.MkdirAll(filepath.Dir(outFile), 0755); err != nil { log.Fatal(err) } - if err := imaging.Save(rImg, *targetFile, imaging.JPEGQuality(70)); err != nil { + if err := imaging.Save(rImg, outFile, imaging.JPEGQuality(70)); err != nil { log.Fatal(err) } + + return nil } func fetchHeaderImage(w io.Writer, url string) error { diff --git a/cmd/fetch-header-image/models.go b/cmd/fetch-header-image/models.go new file mode 100644 index 0000000..f00fd97 --- /dev/null +++ b/cmd/fetch-header-image/models.go @@ -0,0 +1,72 @@ +package main + +import ( + "encoding/json" + "errors" + "net/http" + "os" + "sort" + "time" +) + +const CropBottom = "bottom" + +var ErrNon200Error = errors.New("non 200 status code") + +type PendingImage struct { + Date time.Time `json:"date"` + URL string `json:"url"` + Crop string `json:"crop"` +} + +func LoadPendingImageFromURL(url string) (pi PendingImage, err error) { + resp, err := http.Get(url) + if err != nil { + return PendingImage{}, err + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return PendingImage{}, ErrNon200Error + } + + if err := json.NewDecoder(resp.Body).Decode(&pi); err != nil { + return PendingImage{}, err + } + return pi, nil +} + +type PendingImages struct { + Images []PendingImage `json:"images"` +} + +func (pi PendingImages) FindPendingImage() (bestImg PendingImage, ok bool) { + timeNow := time.Now().UTC() + + if len(pi.Images) == 0 { + return PendingImage{}, false + } + + for _, img := range pi.Images { + if timeNow.Before(img.Date) { + continue + } + bestImg = img + } + return bestImg, bestImg.URL != "" +} + +func LoadPendingImages(jsonFile string) (pi PendingImages, _ error) { + f, err := os.Open(jsonFile) + if err != nil { + return PendingImages{}, err + } + defer f.Close() + + if err := json.NewDecoder(f).Decode(&pi); err != nil { + return PendingImages{}, err + } + + sort.Slice(pi.Images, func(i, j int) bool { return pi.Images[i].Date.Before(pi.Images[j].Date) }) + return pi, nil +} diff --git a/data/pending-header-images.json b/data/pending-header-images.json new file mode 100644 index 0000000..1e9d334 --- /dev/null +++ b/data/pending-header-images.json @@ -0,0 +1,13 @@ +{ + "images": [ + { + "date": "2025-11-22T13:00:00Z", + "url": "https://lmika.org/uploads/2025/pxl-20251120-083552448.jpg", + "crop": "bottom" + }, + { + "date": "2025-11-30T13:00:00Z", + "url": "https://lmika.org/uploads/2025/pxl-20251120-090249613.jpg" + } + ] +} \ No newline at end of file