package core import ( "archive/zip" "fmt" "io" "log" "net/http" "os" fp "path/filepath" "regexp" "strconv" "strings" "github.com/go-shiori/shiori/internal/model" "github.com/pkg/errors" ) func GenerateEbook(req ProcessRequest) (book model.Bookmark, err error) { // variable for store generated html code var html string book = req.Bookmark // Make sure bookmark ID is defined if book.ID == 0 { return book, errors.New("bookmark ID is not valid") } // cheak archive and thumb strID := strconv.Itoa(book.ID) imagePath := fp.Join(req.DataDir, "thumb", fmt.Sprintf("%d", book.ID)) archivePath := fp.Join(req.DataDir, "archive", fmt.Sprintf("%d", book.ID)) if _, err := os.Stat(imagePath); err == nil { book.ImageURL = fp.Join("/", "bookmark", strID, "thumb") } if _, err := os.Stat(archivePath); err == nil { book.HasArchive = true } ebookfile := fp.Join(req.DataDir, "ebook", fmt.Sprintf("%d.epub", book.ID)) // if epub exist finish prosess else continue if _, err := os.Stat(ebookfile); err == nil { book.HasEbook = true return book, nil } contentType := req.ContentType if strings.Contains(contentType, "application/pdf") { return book, errors.New("can't create ebook for pdf") } ebookDir := fp.Join(req.DataDir, "ebook") // check if directory not exsist create that if _, err := os.Stat(ebookDir); os.IsNotExist(err) { err := os.MkdirAll(ebookDir, model.DataDirPerm) if err != nil { return book, errors.Wrap(err, "can't create ebook directory") } } // create epub file epubFile, err := os.Create(ebookfile) if err != nil { return book, errors.Wrap(err, "can't create ebook") } defer epubFile.Close() // Create zip archive epubWriter := zip.NewWriter(epubFile) defer epubWriter.Close() // Create the mimetype file mimetypeWriter, err := epubWriter.Create("mimetype") if err != nil { return book, errors.Wrap(err, "can't create mimetype") } _, err = mimetypeWriter.Write([]byte("application/epub+zip")) if err != nil { return book, errors.Wrap(err, "can't write into mimetype file") } // Create the container.xml file containerWriter, err := epubWriter.Create("META-INF/container.xml") if err != nil { return book, errors.Wrap(err, "can't create container.xml") } _, err = containerWriter.Write([]byte(` `)) if err != nil { return book, errors.Wrap(err, "can't write into container.xml file") } contentOpfWriter, err := epubWriter.Create("OEBPS/content.opf") if err != nil { return book, errors.Wrap(err, "can't create content.opf") } _, err = contentOpfWriter.Write([]byte(` ` + book.Title + ` `)) if err != nil { return book, errors.Wrap(err, "can't write into container.opf file") } // Create the style.css file styleWriter, err := epubWriter.Create("style.css") if err != nil { return book, errors.Wrap(err, "can't create content.xml") } _, err = styleWriter.Write([]byte(`content { display: block; font-size: 1em; line-height: 1.2; padding-left: 0; padding-right: 0; text-align: justify; margin: 0 5pt } img { margin: auto; display: block; }`)) if err != nil { return book, errors.Wrap(err, "can't write into style.css file") } // Create the toc.ncx file tocNcxWriter, err := epubWriter.Create("OEBPS/toc.ncx") if err != nil { return book, errors.Wrap(err, "can't create toc.ncx") } _, err = tocNcxWriter.Write([]byte(` ` + book.Title + ` ` + book.Title + ` `)) if err != nil { return book, errors.Wrap(err, "can't write into toc.ncx file") } // get list of images tag in html imageList, _ := GetImages(book.HTML) imgRegex := regexp.MustCompile(``) // Create a set to store unique image URLs imageSet := make(map[string]bool) // Download image in html file and generate new html html = book.HTML for _, match := range imgRegex.FindAllStringSubmatch(book.HTML, -1) { imageURL := match[1] if _, ok := imageList[imageURL]; ok && !imageSet[imageURL] { // Add the image URL to the set imageSet[imageURL] = true // Download the image resp, err := http.Get(imageURL) if err != nil { log.Fatal(err) } defer resp.Body.Close() // Get the image data imageData, err := io.ReadAll(resp.Body) if err != nil { return book, errors.Wrap(err, "can't get image from the internet") } fileName := fp.Base(imageURL) filePath := "images/" + fileName imageWriter, err := epubWriter.Create(filePath) if err != nil { log.Fatal(err) } // Write the image to the file _, err = imageWriter.Write(imageData) if err != nil { return book, errors.Wrap(err, "can't create image file") } // Replace the image tag with the new downloaded image html = strings.ReplaceAll(html, match[0], fmt.Sprintf(``, filePath)) } } // Create the content.html file contentHtmlWriter, err := epubWriter.Create("OEBPS/content.html") if err != nil { return book, errors.Wrap(err, "can't create content.xml") } _, err = contentHtmlWriter.Write([]byte("\n\n\n\t" + book.Title + "\n\t\n\n\n\t

" + book.Title + "

" + "\n\n" + html + "\n" + "\n")) if err != nil { return book, errors.Wrap(err, "can't write into content.html") } book.HasEbook = true return book, nil } // function get html and return list of image url inside html file func GetImages(html string) (map[string]string, error) { // Regular expression to match image tags and their URLs imageTagRegex := regexp.MustCompile(``) // Find all matches in the HTML string imageTagMatches := imageTagRegex.FindAllStringSubmatch(html, -1) // Create a dictionary to store the image URLs images := make(map[string]string) // Check if there are any matches if len(imageTagMatches) == 0 { return nil, nil } // Loop through all the matches and add them to the dictionary for _, match := range imageTagMatches { imageURL := match[1] images[imageURL] = match[0] } return images, nil }