Skip to content

Commit

Permalink
Add "--this" flag
Browse files Browse the repository at this point in the history
  • Loading branch information
arimatakao committed Jun 19, 2024
1 parent 3ae374d commit dbf14c9
Show file tree
Hide file tree
Showing 7 changed files with 200 additions and 47 deletions.
8 changes: 6 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ mdx is a simple CLI application for downloading manga from the [MangaDex website
![GitHub Downloads (all assets, all releases)](https://img.shields.io/github/downloads/arimatakao/mdx/total)
![GitHub Repo stars](https://img.shields.io/github/stars/arimatakao/mdx)

![demo v1.4.0](./.github/assets/demo.gif)
![demo](./.github/assets/demo.gif)

</div>

Expand Down Expand Up @@ -58,6 +58,10 @@ mdx dl -e pdf mangadex.org/title/319df2e2-e6a6-4e3a-a31c-68539c140a84

# download a specific chapter
mdx dl -c 123 mangadex.org/title/319df2e2-e6a6-4e3a-a31c-68539c140a84/slam-dunk
# or set direct link to the chapter
mdx dl --this mangadex.org/chapter/b5461c55-6bb7-4d53-9534-9caabf8c069f
# or
mdx dl mangadex.org/chapter/b5461c55-6bb7-4d53-9534-9caabf8c069f

# download a range of chapters
mdx dl -c 12-34 mangadex.org/title/319df2e2-e6a6-4e3a-a31c-68539c140a84
Expand Down Expand Up @@ -128,7 +132,7 @@ mdx ping
- [ ] Add flag `random` in `info` subcommand to get information about random manga.
- [ ] Add flag to `download`:
- [ ] `last` - download latest chapter.
- [ ] `this` - download specific chapter using link from user. Make download chapter get the chapter link instead of the manga link.
- [X] `this` - download a specific chapter using a link provided by the user.
- [ ] `volume` - download all chapters of specified volume.
- [ ] `volume-range` - download all chapters of specified volume range.
- [ ] `oneshot` - download all oneshots of manga (if available).
Expand Down
2 changes: 1 addition & 1 deletion cmd/consts.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package cmd

const (
MDX_APP_VERSION = "v1.7.0"
MDX_APP_VERSION = "v1.8.0"
MANGADEX_API_VERSION = "v5.10.2"

MDX_USER_AGENT = "mdx-cli " + MDX_APP_VERSION
Expand Down
110 changes: 79 additions & 31 deletions cmd/download.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,16 +36,18 @@ var (
func init() {
rootCmd.AddCommand(downloadCmd)

downloadCmd.Flags().StringVarP(&mangaUrl,
"url", "u", "", "specify the URL for the manga")
downloadCmd.Flags().StringVarP(&mangaChapterUrl,
"this", "s", "", "specify the direct URL to a specific chapter")
downloadCmd.Flags().StringVarP(&outputExt,
"ext", "e", "cbz", "choose output file format: cbz pdf epub")
downloadCmd.Flags().StringVarP(&mangaurl,
"url", "u", "", "specify the URL for the manga")
downloadCmd.Flags().StringVarP(&outputDir,
"output", "o", ".", "specify output directory for file")
downloadCmd.Flags().StringVarP(&language,
"language", "l", "en", "specify language")
downloadCmd.Flags().StringVarP(&translateGroup,
"translated-by", "t", "", "specify part of name translation group")
"translated-by", "t", "", "specify a part of the translation group's name")
downloadCmd.Flags().StringVarP(&chaptersRange,
"chapter", "c", "1", "specify chapters")
downloadCmd.Flags().BoolVarP(&isJpgFileFormat,
Expand All @@ -59,22 +61,43 @@ func init() {
}

func checkDownloadArgs(cmd *cobra.Command, args []string) {
if len(args) == 0 && mangaurl == "" {
if len(args) == 0 && mangaUrl == "" && mangaChapterUrl == "" {
cmd.Help()
os.Exit(0)
}

if mangaurl == "" {
mangaId = mangadexapi.GetMangaIdFromArg(args)
if mangaUrl == "" {
mangaId = mangadexapi.GetMangaIdFromArgs(args)
} else {
mangaId = mangadexapi.GetMangaIdFromUrl(mangaUrl)
}

if mangaChapterUrl == "" {
mangaChapterId = mangadexapi.GetChapterIdFromArgs(args)
} else {
mangaId = mangadexapi.GetMangaIdFromUrl(mangaurl)
mangaChapterId = mangadexapi.GetChapterIdFromUrl(mangaChapterUrl)
}

if mangaId == "" {
if mangaId == "" && mangaChapterId == "" {
e.Println("Malformated URL")
os.Exit(0)
}

if isJpgFileFormat {
imgExt = "jpg"
}

if outputExt != filekit.CBZ_EXT &&
outputExt != filekit.PDF_EXT &&
outputExt != filekit.EPUB_EXT {
e.Printfln("%s format of file is not supported", outputExt)
os.Exit(0)
}

if mangaChapterId != "" {
return
}

singleChapter, err := strconv.Atoi(chaptersRange)
if err == nil {
if singleChapter < 0 {
Expand Down Expand Up @@ -110,26 +133,21 @@ func checkDownloadArgs(cmd *cobra.Command, args []string) {
os.Exit(0)
}

if isJpgFileFormat {
imgExt = "jpg"
}

if outputExt != filekit.CBZ_EXT &&
outputExt != filekit.PDF_EXT &&
outputExt != filekit.EPUB_EXT {
e.Printf("%s format of file is not supported\n", outputExt)
os.Exit(0)
}
}

func downloadManga(cmd *cobra.Command, args []string) {
if mangaChapterId != "" {
downloadSingleChapter()
return
}

c := mangadexapi.NewClient(MDX_USER_AGENT)

spinnerMangaInfo, _ := pterm.DefaultSpinner.Start("Fetching manga info...")
resp, err := c.GetMangaInfo(mangaId)
if err != nil {
spinnerMangaInfo.Fail("Failed to get manga info")
e.Println("While getting manga info, maybe you get malformated link")
e.Println("While getting manga info, maybe you set malformated link")
os.Exit(1)
}
mangaInfo := resp.MangaInfo()
Expand Down Expand Up @@ -162,6 +180,36 @@ func downloadManga(cmd *cobra.Command, args []string) {
}
}

func downloadSingleChapter() {
c := mangadexapi.NewClient(MDX_USER_AGENT)
spinnerChapInfo, _ := pterm.DefaultSpinner.Start("Fetching chapter info...")
resp, err := c.GetChapterInfo(mangaChapterId)
if err != nil {
spinnerChapInfo.Fail("Failed to get chapter info")
os.Exit(1)
}
chapterInfo := resp.GetChapterInfo()
chapterFullInfo, err := c.GetChapterImagesInFullInfo(chapterInfo)
if err != nil {
spinnerChapInfo.Fail("Failed to get chapter info")
os.Exit(1)
}
spinnerChapInfo.Success("Fetched chapter info")

mangaId := chapterInfo.GetMangaId()
spinnerMangaInfo, _ := pterm.DefaultSpinner.Start("Fetching manga info...")
respManga, err := c.GetMangaInfo(mangaId)
if err != nil {
spinnerMangaInfo.Fail("Failed to get manga info")
os.Exit(1)
}
mangaInfo := respManga.MangaInfo()
spinnerMangaInfo.Success("Fetched manga info")
chapArr := []mangadexapi.ChapterFullInfo{chapterFullInfo}
printShortMangaInfo(mangaInfo)
downloadChapters(c, mangaInfo, chapArr, outputExt, MDX_USER_AGENT, isJpgFileFormat)
}

func printShortMangaInfo(i mangadexapi.MangaInfo) {
optionPrint.Print("Manga title: ")
dp.Println(i.Title("en"))
Expand All @@ -172,6 +220,18 @@ func printShortMangaInfo(i mangadexapi.MangaInfo) {
dp.Println("==============")
}

func printChapterInfo(c mangadexapi.ChapterFullInfo) {
tableData := pterm.TableData{
{optionPrint.Sprint("Chapter"), dp.Sprint(c.Number())},
{optionPrint.Sprint("Chapter title"), dp.Sprint(c.Title())},
{optionPrint.Sprint("Volume"), dp.Sprint(c.Volume())},
{optionPrint.Sprint("Language"), dp.Sprint(c.Language())},
{optionPrint.Sprint("Translated by"), dp.Sprint(c.Translator())},
{optionPrint.Sprint("Uploaded by"), dp.Sprint(c.UploadedBy())},
}
pterm.DefaultTable.WithData(tableData).Render()
}

func downloadMergeChapters(client mangadexapi.Clientapi,
mangaInfo mangadexapi.MangaInfo,
chapters []mangadexapi.ChapterFullInfo,
Expand Down Expand Up @@ -236,18 +296,6 @@ func downloadChapters(client mangadexapi.Clientapi,
}
}

func printChapterInfo(c mangadexapi.ChapterFullInfo) {
tableData := pterm.TableData{
{optionPrint.Sprint("Chapter"), dp.Sprint(c.Number())},
{optionPrint.Sprint("Chapter title"), dp.Sprint(c.Title())},
{optionPrint.Sprint("Volume"), dp.Sprint(c.Volume())},
{optionPrint.Sprint("Language"), dp.Sprint(c.Language())},
{optionPrint.Sprint("Translated by"), dp.Sprint(c.Translator())},
{optionPrint.Sprint("Uploaded by"), dp.Sprint(c.UploadedBy())},
}
pterm.DefaultTable.WithData(tableData).Render()
}

func downloadProcess(
client mangadexapi.Clientapi,
chapter mangadexapi.ChapterFullInfo,
Expand Down
10 changes: 5 additions & 5 deletions cmd/info.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,19 +20,19 @@ var (
func init() {
rootCmd.AddCommand(infoCmd)

infoCmd.Flags().StringVarP(&mangaurl, "url", "u", "", "specify the URL for the manga")
infoCmd.Flags().StringVarP(&mangaUrl, "url", "u", "", "specify the URL for the manga")
}

func checkInfoArgs(cmd *cobra.Command, args []string) {
if len(args) == 0 && mangaurl == "" {
if len(args) == 0 && mangaUrl == "" {
cmd.Help()
os.Exit(0)
}

if mangaurl == "" {
mangaId = mangadexapi.GetMangaIdFromArg(args)
if mangaUrl == "" {
mangaId = mangadexapi.GetMangaIdFromArgs(args)
} else {
mangaId = mangadexapi.GetMangaIdFromUrl(mangaurl)
mangaId = mangadexapi.GetMangaIdFromUrl(mangaUrl)
}

if mangaId == "" {
Expand Down
6 changes: 4 additions & 2 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@ import (

var (
// general flags
mangaurl string
mangaId string
mangaUrl string
mangaId string
mangaChapterUrl string
mangaChapterId string
)

var (
Expand Down
92 changes: 86 additions & 6 deletions mangadexapi/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ const (
manga_path = "/manga"
specific_manga_path = "/manga/{id}"
manga_feed_path = "/manga/{id}/feed"
chapter_info_path = "/chapter/{id}"
chapter_images_path = "/at-home/server/{id}"
download_high_quility_path = "/data/{chapterHash}/{imageFilename}"
download_low_quility_path = "/data-saver/{chapterHash}/{imageFilename}"
Expand All @@ -31,29 +32,35 @@ var (
ErrUnexpectedHeader = errors.New("unexpected response header value")
)

func GetMangaIdFromUrl(link string) string {

func getMangaDexPaths(link string) []string {
if !strings.HasPrefix(link, "https://") && !strings.HasPrefix(link, "http://") {
link = "https://" + link
}

parsedUrl, err := url.Parse(link)
if err != nil {
return ""
return []string{}
}

if parsedUrl.Host != "mangadex.org" {
return ""
return []string{}
}

paths := strings.Split(parsedUrl.Path, "/")
return strings.Split(parsedUrl.Path, "/")
}

func GetMangaIdFromUrl(link string) string {
paths := getMangaDexPaths(link)
if len(paths) < 3 {
return ""
}
if paths[1] != "title" {
return ""
}
return paths[2]
}

func GetMangaIdFromArg(args []string) string {
func GetMangaIdFromArgs(args []string) string {
for _, arg := range args {
if u := GetMangaIdFromUrl(arg); u != "" {
return u
Expand All @@ -62,6 +69,26 @@ func GetMangaIdFromArg(args []string) string {
return ""
}

func GetChapterIdFromUrl(link string) string {
paths := getMangaDexPaths(link)
if len(paths) < 3 {
return ""
}
if paths[1] != "chapter" {
return ""
}
return paths[2]
}

func GetChapterIdFromArgs(args []string) string {
for _, arg := range args {
if u := GetChapterIdFromUrl(arg); u != "" {
return u
}
}
return ""
}

type Clientapi struct {
c *resty.Client
}
Expand Down Expand Up @@ -159,6 +186,31 @@ func (a Clientapi) GetMangaInfo(mangaId string) (MangaInfoResponse, error) {
return info, nil
}

func (a Clientapi) GetChapterInfo(chapterId string) (ResponseChapter, error) {
if chapterId == "" {
return ResponseChapter{}, ErrBadInput
}

chapterInfo := ResponseChapter{}
respErr := ErrorResponse{}

resp, err := a.c.R().
SetError(&respErr).
SetResult(&chapterInfo).
SetPathParam("id", chapterId).
SetQueryString("includes[]=scanlation_group&includes[]=user").
Get(chapter_info_path)
if err != nil {
return ResponseChapter{}, ErrConnection
}

if resp.IsError() {
return ResponseChapter{}, &respErr
}

return chapterInfo, nil
}

func (a Clientapi) GetChaptersList(limit, offset int, mangaId, language string) (ResponseChapterList, error) {

if mangaId == "" {
Expand Down Expand Up @@ -251,6 +303,34 @@ func (a Clientapi) DownloadImage(baseUrl, chapterHash, imageFilename string,
return resp.Body(), nil
}

func (a Clientapi) GetChapterImagesInFullInfo(chap Chapter) (ChapterFullInfo, error) {
chapImages := ResponseChapterImages{}
respErr := ErrorResponse{}

resp, err := a.c.R().
SetError(&respErr).
SetResult(&chapImages).
SetPathParam("id", chap.ID).
Get(chapter_images_path)
if err != nil {
return ChapterFullInfo{}, ErrConnection
}

if resp.IsError() {
return ChapterFullInfo{}, &respErr
}

fullInfo := ChapterFullInfo{
info: chap,
DownloadBaseURL: chapImages.BaseURL,
HashId: chapImages.ChapterMetaInfo.Hash,
PngFiles: chapImages.ChapterMetaInfo.Data,
JpgFiles: chapImages.ChapterMetaInfo.DataSaver,
}

return fullInfo, nil
}

func (a Clientapi) GetFullChaptersInfo(mangaId, language, translationGroup string,
lowestChapter, highestChapter int) ([]ChapterFullInfo, error) {

Expand Down
Loading

0 comments on commit dbf14c9

Please sign in to comment.