package main import ( "fmt" "io" "log" "net/http" "os" "regexp" "strings" xmltpl "text/template" ) type Item struct { ISBN string FileExtension string Author string Title string Filename string Date string } func (i Item) ImageURL(extension string) string { return "https://medien.umbreitkatalog.de/bildzentrale_original/" + i.ISBN[0:3] + "/" + i.ISBN[3:6] + "/" + i.ISBN[6:9] + "/" + i.ISBN[9:13] + "." + extension } func (i Item) targetFilename(extension string) string { return "covers/" + i.ISBN + "." + extension } func (i Item) downloadCover() error { extension := "jpg" fmt.Printf("Downloading %v ...\n", i.ImageURL(extension)) resp, err := http.Get(i.ImageURL(extension)) if err != nil { return err } // TODO: refactor later ... if resp.StatusCode == 404 { extension = "png" fmt.Printf("Downloading %v ...\n", i.ImageURL(extension)) resp, err = http.Get(i.ImageURL(extension)) if err != nil { return err } } defer func(Body io.ReadCloser) { err := Body.Close() if err != nil { log.Fatal("Can not close the response body") } }(resp.Body) if resp.StatusCode != 200 { return fmt.Errorf("HTTP status code is %d", resp.StatusCode) } out, err := os.Create(i.targetFilename(extension)) if err != nil { return err } defer func(out *os.File) { err := out.Close() if err != nil { log.Fatal("Can not close the file") } }(out) _, err = io.Copy(out, resp.Body) return err } func getItems(filename string) []Item { var items []Item // Get all book URLS url := "https://git.okoyono.de/mezzo/buch_des_monats/raw/branch/master/" + filename resp, err := http.Get(url) if err != nil { log.Fatal(filename + " is missing") } defer func(Body io.ReadCloser) { err := Body.Close() if err != nil { log.Fatal("Can not close the response body") } }(resp.Body) content, err := io.ReadAll(resp.Body) if err != nil { log.Fatal("Can not download the file. Network problem?") } currentYear := "" currentMonth := 0 re := regexp.MustCompile(`^[^[]+ \[(?P[^"]+)"(?P[^"]+)"]\(.+buchhandel\.de\/buch\/(?P<isbn>[0-9]+).*$`) yearRe := regexp.MustCompile(`^## (?P<year>20[0-9]{2})$`) var yearBucket []Item for _, line := range strings.Split(string(content), "\n") { // Do we find a year? yearMatches := yearRe.FindStringSubmatch(line) if len(yearMatches) > 0 { currentYear = yearMatches[1] currentMonth = 0 // Add the bucket in reverse order for i := len(yearBucket) - 1; i >= 0; i-- { items = append(items, yearBucket[i]) } yearBucket = nil } matches := re.FindStringSubmatch(line) if len(matches) == 4 { currentMonth++ extension, err := getFileExtension(matches[3]) if err != nil { log.Printf("%v", err) } yearBucket = append(yearBucket, Item{ ISBN: matches[3], FileExtension: extension, Author: strings.Trim(matches[1], " "), Title: strings.Trim(matches[2], " "), Filename: filename, Date: fmt.Sprintf("01-%02d-%s", currentMonth, currentYear), }) } } log.Printf("Output all items:") for _, i := range items { log.Printf("%v", i) } return items } func getFileExtension(isbn string) (string, error) { // List all files in covers directory // TODO: Cache this line files, err := os.ReadDir("covers/") if err != nil { log.Fatal("Can not read the covers directory") return "", err } for _, file := range files { if strings.HasPrefix(file.Name(), isbn) { return strings.Split(file.Name(), ".")[1], nil } } return "", fmt.Errorf("File not found for ISBN: %v", isbn) } func getTemplate(sourceFile string, templateFilename string, w http.ResponseWriter) { // Get all items from the git repo items := getItems(sourceFile) // Generate the resulting HTML t, err := xmltpl.ParseFiles("templates/" + templateFilename) if err != nil { panic(err) } err = t.Execute(w, map[string]interface{}{ "Items": items, }) if err != nil { panic(err) } } func main() { // All static files (CSS, JS) fileServer := http.FileServer(http.Dir("./static")) http.Handle("/static/", http.StripPrefix("/static", fileServer)) // Cover images imageServer := http.FileServer(http.Dir("./covers/")) http.Handle("/covers/", http.StripPrefix("/covers", imageServer)) // Update "Hook" /update?filename=COMIC.mkd http.HandleFunc("/update", func(w http.ResponseWriter, r *http.Request) { filename := r.URL.Query().Get("filename") log.Printf("Update hook triggered for %v", filename) // Get all items from the git repo items := getItems(filename) for _, item := range items { err := item.downloadCover() if err != nil { fmt.Printf("%v", err) fmt.Printf("ERROR: File %s not found\n", item.ImageURL("jpg")) } } }) http.HandleFunc("/book", func(w http.ResponseWriter, r *http.Request) { log.Print("/book") getTemplate("BOOK.mkd", "book.html", w) }) http.HandleFunc("/comic", func(w http.ResponseWriter, r *http.Request) { log.Print("/comic") getTemplate("COMIC.mkd", "comic.html", w) }) http.HandleFunc("/book.xml", func(w http.ResponseWriter, r *http.Request) { log.Print("/book.xml") w.Header().Add("Content-Type", "Application/rss+xml") getTemplate("BOOK.mkd", "book.xml", w) }) http.HandleFunc("/comic.xml", func(w http.ResponseWriter, r *http.Request) { log.Print("/comic.xml") w.Header().Add("Content-Type", "Application/rss+xml") getTemplate("COMIC.mkd", "comic.xml", w) }) // Spawn the webserver (blocking) log.Print("Spawn webserver on port :9783 and waiting for requests ... ...") err := http.ListenAndServe(":9783", nil) if err != nil { panic(err) } }