package main import ( "encoding/json" "fmt" "image" "image/jpeg" "io" "net/http" "os" "os/signal" "regexp" "strings" "time" epub "github.com/go-shiori/go-epub" "github.com/gomarkdown/markdown" "github.com/gomarkdown/markdown/html" "github.com/gomarkdown/markdown/parser" "github.com/google/uuid" "github.com/nfnt/resize" ) type Parameters struct { KitchenOwlURL string KitchenOwlKey string KitchenOwlHousehold string RecipeAuthors string RecipeChapters string RecipeOutput string } type Household struct { Name string `json:"name"` Photo string `json:"photo"` } type Item struct { Name string `json:"name"` Description string `json:"description"` Icon string `json:"icon"` Slug string `json:"-"` } type Tag struct { Name string `json:"name"` } type Recipe struct { Name string `json:"name"` ID int `json:"id"` Tags []Tag `json:"tags"` Description string `json:"description"` Items []Item `json:"items"` } // Converts a string to a slug suitable for referencing func Slugify(s string) string { // Convert to lowercase result := strings.ToLower(s) // Replace spaces with hyphens result = strings.ReplaceAll(result, " ", "_") // Return the slug return result } // Converts Markdown text to HTML func MarkdownToHTML(md string) string { // Create markdown parser with extensions extensions := parser.CommonExtensions | parser.AutoHeadingIDs | parser.NoEmptyLineBeforeBlock p := parser.NewWithExtensions(extensions) doc := p.Parse([]byte(md)) // Create HTML renderer with extensions htmlFlags := html.UseXHTML | html.HrefTargetBlank opts := html.RendererOptions{Flags: htmlFlags} renderer := html.NewRenderer(opts) return string(markdown.Render(doc, renderer)) } // Retrieves the household information from the KitchenOwl server func FetchHousehold(param Parameters) (*Household, error) { client := &http.Client{Timeout: 10 * time.Second} req, err := http.NewRequest(http.MethodGet, param.KitchenOwlURL+"/api/household/"+param.KitchenOwlHousehold, nil) if err != nil { return nil, err } req.Header.Set("Authorization", "Bearer "+param.KitchenOwlKey) req.Header.Set("Accept", "application/json") res, err := client.Do(req) if err != nil { return nil, err } defer res.Body.Close() if res.StatusCode != 200 { return nil, fmt.Errorf("status code %d", res.StatusCode) } body, err := io.ReadAll(res.Body) if err != nil { return nil, err } var household Household if err := json.Unmarshal(body, &household); err != nil { return nil, err } return &household, nil } // Retrieves an image from the KitchenOwl server, resize it and saves it to a temporary file func FetchImage(param Parameters, imageName string) (*string, error) { client := &http.Client{Timeout: 10 * time.Second} req, err := http.NewRequest(http.MethodGet, param.KitchenOwlURL+"/api/upload/"+imageName, nil) if err != nil { return nil, err } req.Header.Set("Authorization", "Bearer "+param.KitchenOwlKey) req.Header.Set("Accept", "application/json") res, err := client.Do(req) if err != nil { return nil, err } defer res.Body.Close() if res.StatusCode != 200 { return nil, fmt.Errorf("status code %d", res.StatusCode) } // Open a file for writing filename := "/tmp/" + imageName file, err := os.Create(filename) if err != nil { return nil, err } defer file.Close() // Decode the image img, _, err := image.Decode(res.Body) if err != nil { return nil, err } // Resize the image, encode it to JPEG and write to file err = jpeg.Encode(file, resize.Resize(1000, 0, img, resize.Lanczos3), &jpeg.Options{Quality: 80}) if err != nil { return nil, err } return &filename, nil } // Retrieves the recipes from the KitchenOwl server func FetchRecipes(param Parameters) ([]Recipe, error) { client := &http.Client{Timeout: 10 * time.Second} req, err := http.NewRequest(http.MethodGet, param.KitchenOwlURL+"/api/household/"+param.KitchenOwlHousehold+"/recipe", nil) if err != nil { return nil, err } req.Header.Set("Authorization", "Bearer "+param.KitchenOwlKey) req.Header.Set("Accept", "application/json") res, err := client.Do(req) if err != nil { return nil, err } defer res.Body.Close() if res.StatusCode != 200 { return nil, fmt.Errorf("status code %d", res.StatusCode) } body, err := io.ReadAll(res.Body) if err != nil { return nil, err } var recipes []Recipe if err := json.Unmarshal(body, &recipes); err != nil { return nil, err } for recipe := range recipes { for item := range recipes[recipe].Items { recipes[recipe].Items[item].Slug = Slugify(recipes[recipe].Items[item].Name) } } return recipes, nil } // Create a new EBook with chapters and recipes func CreateEbook(param Parameters, household Household, recipes []Recipe) (*epub.Epub, error) { book, err := epub.NewEpub("KitchenOwl Recipes") if err != nil { fmt.Println("Error creating epub: ", err) } // Set author if param.RecipeAuthors != "" { book.SetAuthor(param.RecipeAuthors) } // Set description book.SetDescription("Recipes from KitchenOwl server " + param.KitchenOwlURL) book.SetLang("fr") book.SetIdentifier("urn:uuid:" + uuid.NewMD5(uuid.NameSpaceDNS, []byte(param.KitchenOwlURL+"/api/household/"+param.KitchenOwlHousehold)).String()) // Add cover image filename, err := FetchImage(param, household.Photo) if err != nil { fmt.Println("Error fetching cover image: ", err) } else { coverImagePath, err := book.AddImage(*filename, "cover.png") if err != nil { fmt.Println("Error setting cover: ", err) } else { err = book.SetCover(coverImagePath, "") if err != nil { fmt.Println("Error setting cover: ", err) } } } // Add an empty section at the beginning content := "