package ingestion import ( "log/slog" "net/http" "strconv" "strings" "time" ) var imageClient = &http.Client{ Timeout: 10 * time.Second, CheckRedirect: func(req *http.Request, via []*http.Request) error { if len(via) >= 3 { return http.ErrUseLastResponse } return nil }, } // ValidateImageURL checks that an image URL returns a valid image response. // Returns false (with no error) on any failure — story posts without image. func ValidateImageURL(url string) bool { if url == "" { return false } req, err := http.NewRequest("HEAD", url, nil) if err != nil { slog.Debug("image validation: bad url", "url", url, "err", err) return false } req.Header.Set("User-Agent", userAgent) resp, err := imageClient.Do(req) if err != nil { slog.Warn("image validation: request failed", "url", url, "err", err) return false } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { slog.Warn("image validation: bad status", "url", url, "status", resp.StatusCode) return false } contentType := resp.Header.Get("Content-Type") if !strings.HasPrefix(contentType, "image/") { slog.Warn("image validation: not an image", "url", url, "content_type", contentType) return false } // Filter tracking pixels: Content-Length must be > 5KB if present if cl := resp.Header.Get("Content-Length"); cl != "" { size, err := strconv.ParseInt(cl, 10, 64) if err == nil && size <= 5120 { slog.Warn("image validation: too small (likely tracking pixel)", "url", url, "size", size) return false } } return true }