diff --git a/README.md b/README.md index 6a96ad4..91e29b4 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ mdx is a simple CLI application for downloading manga from the [MangaDex website - Works on ***Windows, MacOS, Linux***. - Downloads ***multiple chapters***. -- Saving manga in ***CBZ and PDF formats***. +- Saving manga in ***CBZ, PDF, EPUB formats***. - Saving multiple chapters in ***one file***. - Automatically generates metadata for downloaded files, ***adapted for e-readers***. - Searches manga. @@ -127,9 +127,9 @@ mdx ping - [ ] `all` - download all chapters. - [X] `merge` - download chapter in one file. - [ ] `volume-bundle` - download all chapters of volume into one file. - - [ ] `extension` (or `format`) - sets the extension of the outpud file. Add file support formats: + - [X] `extension` - sets the extension of the outpud file. Add file support formats: - [X] pdf (include metadata). - - [ ] epub (include metadata). + - [X] epub (include metadata). - [ ] Add interactive mode for `find` subcommand. - [ ] Add interactive mode for `download` subcommand. @@ -152,3 +152,4 @@ This project uses the following third-party libraries: - Resty (https://github.com/go-resty/resty) - Licensed under the MIT - PTerm (https://github.com/pterm/pterm) - Licensed under the MIT - gopdf (https://github.com/signintech/gopdf) - Licensed under the MIT +- go-epub (https://github.com/go-shiori/go-epub) - Licensed under the MIT diff --git a/cmd/consts.go b/cmd/consts.go index 0eb8cbd..a8fd2f0 100644 --- a/cmd/consts.go +++ b/cmd/consts.go @@ -1,7 +1,7 @@ package cmd const ( - MDX_APP_VERSION = "v1.4.0" + MDX_APP_VERSION = "v1.5.0" MANGADEX_API_VERSION = "v5.10.2" MDX_USER_AGENT = "mdx-cli " + MDX_APP_VERSION diff --git a/cmd/download.go b/cmd/download.go index 81159fb..ab56b99 100644 --- a/cmd/download.go +++ b/cmd/download.go @@ -111,7 +111,9 @@ func checkDownloadArgs(cmd *cobra.Command, args []string) { imgExt = "jpg" } - if outputExt != filekit.CBZ_EXT && outputExt != filekit.PDF_EXT { + if outputExt != filekit.CBZ_EXT && + outputExt != filekit.PDF_EXT && + outputExt != filekit.EPUB_EXT { fmt.Printf("error: %s format of file is not supported\n", outputExt) os.Exit(0) } @@ -168,6 +170,7 @@ func downloadMergeChapters(client mangadexapi.Clientapi, containerFile, err := filekit.NewContainer(outputExtension) if err != nil { fmt.Printf("error while creating output file: %v\n", err) + os.Exit(1) } for _, chapter := range chapters { @@ -189,6 +192,7 @@ func downloadMergeChapters(client mangadexapi.Clientapi, err = containerFile.WriteOnDiskAndClose(outputDir, filename, metaInfo) if err != nil { fmt.Printf("error while saving %s on disk: %v\n", filename, err) + os.Exit(1) } } @@ -204,11 +208,13 @@ func downloadChapters(client mangadexapi.Clientapi, containerFile, err := filekit.NewContainer(outputExtension) if err != nil { fmt.Printf("error while creating output file: %v\n", err) + os.Exit(1) } err = downloadProcess(client, mangaInfo, chapter, containerFile, isJpg) if err != nil { fmt.Printf("error while downloading chapter: %v\n", err) + os.Exit(1) } filename := fmt.Sprintf("[%s] %s vol%s ch%s", @@ -217,6 +223,7 @@ func downloadChapters(client mangadexapi.Clientapi, err = containerFile.WriteOnDiskAndClose(outputDir, filename, metaInfo) if err != nil { fmt.Printf("error while saving %s on disk: %v\n", filename, err) + os.Exit(1) } } } diff --git a/filekit/container.go b/filekit/container.go index 1c3af85..9e2cf4d 100644 --- a/filekit/container.go +++ b/filekit/container.go @@ -7,8 +7,9 @@ import ( ) const ( - CBZ_EXT = "cbz" - PDF_EXT = "pdf" + CBZ_EXT = "cbz" + PDF_EXT = "pdf" + EPUB_EXT = "epub" ) var ErrExtensionNotSupport = errors.New("extension container is not supported") @@ -25,6 +26,8 @@ func NewContainer(extension string) (Container, error) { return newCBZArchive() case PDF_EXT: return newPdfFile() + case EPUB_EXT: + return newEpubArchive() } return nil, ErrExtensionNotSupport diff --git a/filekit/epub.go b/filekit/epub.go new file mode 100644 index 0000000..34e931e --- /dev/null +++ b/filekit/epub.go @@ -0,0 +1,85 @@ +package filekit + +import ( + "fmt" + "os" + "path/filepath" + + "github.com/arimatakao/mdx/filekit/metadata" + "github.com/go-shiori/go-epub" +) + +const imageSectionTemplate = `%s` + +type epubArchive struct { + b *epub.Epub + tempDir string + filesPaths []string +} + +func newEpubArchive() (*epubArchive, error) { + book, err := epub.NewEpub("") + if err != nil { + return &epubArchive{}, err + } + + dir, err := os.MkdirTemp("", "mdxepubfiles") + if err != nil { + return &epubArchive{}, err + } + + return &epubArchive{ + b: book, + tempDir: dir, + filesPaths: []string{}, + }, nil +} + +func (e epubArchive) WriteOnDiskAndClose(outputDir string, outputFileName string, + m metadata.Metadata) error { + + for i, filePath := range e.filesPaths { + indexStr := fmt.Sprint(i + 1) + imageEpubPath, err := e.b.AddImage(filePath, indexStr) + if err != nil { + return err + } + sectionStr := fmt.Sprintf(imageSectionTemplate, imageEpubPath, indexStr) + _, err = e.b.AddSection(sectionStr, indexStr, "", "") + if err != nil { + return err + } + } + + bookTitle := fmt.Sprintf("%s vol%s ch%s", m.CI.Title, m.CI.Volume, m.CI.Number) + e.b.SetTitle(bookTitle) + + authors := m.P.Authors + " | " + m.P.Artists + e.b.SetAuthor(authors) + + e.b.SetLang(m.CI.LanguageISO) + + err := os.MkdirAll(outputDir, os.ModePerm) + if err != nil { + return err + } + + err = e.b.Write(filepath.Join(outputDir, outputFileName)) + if err != nil { + return err + } + + return os.RemoveAll(e.tempDir) +} + +func (e *epubArchive) AddFile(fileName string, imageBytes []byte) error { + filePath := filepath.Join(e.tempDir, fileName) + err := os.WriteFile(filePath, imageBytes, os.ModePerm) + if err != nil { + return err + } + + e.filesPaths = append(e.filesPaths, filePath) + + return nil +} diff --git a/go.mod b/go.mod index a9da089..da70728 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.22.4 require ( github.com/go-resty/resty/v2 v2.13.1 + github.com/go-shiori/go-epub v1.2.1 github.com/pterm/pterm v0.12.79 github.com/signintech/gopdf v0.26.0 github.com/spf13/cobra v1.8.0 @@ -14,6 +15,8 @@ require ( atomicgo.dev/keyboard v0.2.9 // indirect atomicgo.dev/schedule v0.1.0 // indirect github.com/containerd/console v1.0.3 // indirect + github.com/gabriel-vasile/mimetype v1.4.3 // indirect + github.com/gofrs/uuid/v5 v5.0.0 // indirect github.com/gookit/color v1.5.4 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/lithammer/fuzzysearch v1.1.8 // indirect @@ -22,6 +25,7 @@ require ( github.com/pkg/errors v0.8.1 // indirect github.com/rivo/uniseg v0.4.4 // indirect github.com/spf13/pflag v1.0.5 // indirect + github.com/vincent-petithory/dataurl v1.0.0 // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect golang.org/x/net v0.25.0 // indirect golang.org/x/sys v0.20.0 // indirect diff --git a/go.sum b/go.sum index 0701e8f..8e04caa 100644 --- a/go.sum +++ b/go.sum @@ -22,8 +22,14 @@ github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46t github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= +github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= github.com/go-resty/resty/v2 v2.13.1 h1:x+LHXBI2nMB1vqndymf26quycC4aggYJ7DECYbiz03g= github.com/go-resty/resty/v2 v2.13.1/go.mod h1:GznXlLxkq6Nh4sU59rPmUw3VtgpO3aS96ORAI6Q7d+0= +github.com/go-shiori/go-epub v1.2.1 h1:+K/WxrvmfFQY69cpryiObrT6X7WhkwpqhHY65AHs2Rg= +github.com/go-shiori/go-epub v1.2.1/go.mod h1:3rCTODnigEgy2j3ksndClrGT9h/dcz3js9q4yPX7hf8= +github.com/gofrs/uuid/v5 v5.0.0 h1:p544++a97kEL+svbcFbCQVM9KFu0Yo25UoISXGNNH9M= +github.com/gofrs/uuid/v5 v5.0.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8= github.com/gookit/color v1.4.2/go.mod h1:fqRyamkC1W8uxl+lxCQxOT09l/vYfZ+QeiX3rKQHCoQ= github.com/gookit/color v1.5.0/go.mod h1:43aQb+Zerm/BWh2GnrgOQm7ffz7tvQXEKV6BFMl7wAo= github.com/gookit/color v1.5.4 h1:FZmqs7XOyGgCAxmWyPslpiok1k05wmY3SJTytgvYFs0= @@ -76,6 +82,8 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/vincent-petithory/dataurl v1.0.0 h1:cXw+kPto8NLuJtlMsI152irrVw9fRDX8AbShPRpg2CI= +github.com/vincent-petithory/dataurl v1.0.0/go.mod h1:FHafX5vmDzyP+1CQATJn7WFKc9CvnvxyvZy6I1MrG/U= github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778/go.mod h1:2MuV+tbUrU1zIOPMxZ5EncGwgmMJsa+9ucAQZXxsObs= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=