diff options
-rw-r--r-- | cmd/search.go | 18 | ||||
-rw-r--r-- | cmd/status.go | 43 | ||||
-rw-r--r-- | mal/episodes.go | 22 | ||||
-rw-r--r-- | mal/mal.go | 6 | ||||
-rw-r--r-- | mal/search.go | 32 | ||||
-rw-r--r-- | mal/status.go | 16 | ||||
-rw-r--r-- | ui/actions.go | 83 | ||||
-rw-r--r-- | ui/episodes.go | 37 | ||||
-rw-r--r-- | ui/input.go | 37 | ||||
-rw-r--r-- | ui/search.go | 49 | ||||
-rw-r--r-- | ui/status.go | 65 |
11 files changed, 355 insertions, 53 deletions
diff --git a/cmd/search.go b/cmd/search.go index 28be82c..453f13d 100644 --- a/cmd/search.go +++ b/cmd/search.go @@ -19,10 +19,10 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. package cmd import ( + "strings" "github.com/spf13/cobra" "github.com/MikunoNaka/macli/ui" - "strings" - "fmt" + "github.com/MikunoNaka/macli/mal" ) var searchCmd = &cobra.Command { @@ -32,6 +32,7 @@ var searchCmd = &cobra.Command { -- help/description to be added later `, Run: func(cmd *cobra.Command, args []string) { + mal.Init() // needs to be manually called else it won't let you login // read searchInput from command searchInput := strings.Join(args, " ") mangaMode, _ := cmd.Flags().GetBool("manga") @@ -45,22 +46,27 @@ var searchCmd = &cobra.Command { } func searchManga(searchInput string) { + mangaIsAdded := false if searchInput == "" { - searchInput = ui.TextInput("Search Manga:", "Search can't be blank.") + searchInput = ui.TextInput("Search Manga: ", "Search can't be blank.") + } + manga := ui.MangaSearch("Select Manga:", searchInput) + if manga.MyListStatus.Status != "" { + mangaIsAdded = true } - fmt.Printf("You typed in \"%s\" but macli doesn't search manga yet.\n", searchInput) + ui.MangaActionMenu(mangaIsAdded)(manga) } func searchAnime(searchInput string) { animeIsAdded := false if searchInput == "" { - searchInput = ui.TextInput("Search Anime", "Search can't be blank.") + searchInput = ui.TextInput("Search Anime: ", "Search can't be blank.") } anime := ui.AnimeSearch("Select Anime:", searchInput) if anime.MyListStatus.Status != "" { animeIsAdded = true } - ui.ActionMenu(animeIsAdded)(anime) + ui.AnimeActionMenu(animeIsAdded)(anime) } func init() { diff --git a/cmd/status.go b/cmd/status.go index d1b45c2..048bd70 100644 --- a/cmd/status.go +++ b/cmd/status.go @@ -1,7 +1,21 @@ /* -Copyright © 2022 NAME HERE <EMAIL ADDRESS> +macli - Unofficial CLI-Based MyAnimeList Client +Copyright © 2022 Vidhu Kant Sharma <vidhukant@vidhukant.xyz> +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see <http://www.gnu.org/licenses/>. */ + package cmd import ( @@ -21,6 +35,7 @@ var statusCmd = &cobra.Command{ -- help/description to be added later `, Run: func(cmd *cobra.Command, args []string) { + mal.Init() // needs to be manually called else it won't let you login searchInput := strings.Join(args, " ") statusInput, err := cmd.Flags().GetString("status") @@ -34,8 +49,7 @@ var statusCmd = &cobra.Command{ } if mangaMode { - // setMangaStatus(statusInput, searchInput) - fmt.Println("Manga mode coming soon") + setMangaStatus(statusInput, searchInput) } else { setAnimeStatus(statusInput, searchInput) } @@ -45,16 +59,31 @@ var statusCmd = &cobra.Command{ func setAnimeStatus(statusInput, searchInput string) { if searchInput == "" { - searchInput = ui.TextInput("Search Anime To Update", "Search can't be blank.") + searchInput = ui.TextInput("Search Anime To Update: ", "Search can't be blank.") } anime := ui.AnimeSearch("Select Anime:", searchInput) if statusInput == "" { - ui.StatusMenu(anime) + ui.AnimeStatusMenu(anime) + } else { + mal.SetAnimeStatus(anime.Id, statusInput) + fmt.Printf("Successfully set \"%s\" to \"%s\"\n", anime.Title, statusInput) + } +} + +func setMangaStatus(statusInput, searchInput string) { + if searchInput == "" { + searchInput = ui.TextInput("Search Manga To Update: ", "Search can't be blank.") + } + + manga := ui.MangaSearch("Select Manga:", searchInput) + + if statusInput == "" { + ui.MangaStatusMenu(manga) } else { - mal.SetStatus(anime.Id, statusInput) - fmt.Printf("Successfully set \"%s\" to \"%s\"", anime.Title, statusInput) + mal.SetAnimeStatus(manga.Id, statusInput) + fmt.Printf("Successfully set \"%s\" to \"%s\"\n", manga.Title, statusInput) } } diff --git a/mal/episodes.go b/mal/episodes.go index 30fc5d2..255f8b0 100644 --- a/mal/episodes.go +++ b/mal/episodes.go @@ -19,30 +19,40 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. package mal import ( - "log" + "fmt" + "os" "strconv" ) func SetEpisodes(animeId int, ep string) { epValue, err := strconv.Atoi(ep) if err != nil { - log.Fatal("Error while parsing episode input", err) + fmt.Println("Error while parsing episode input", err) + os.Exit(1) } sign := ep[0:1] if sign == "+" || sign == "-" { - log.Printf("Cannot increment/decrement watched episodes by %d\n. Currently that doesn't wokr", epValue) - return + fmt.Printf("Cannot increment/decrement watched episodes by %d\n. Currently that doesn't wokr", epValue) + os.Exit(2) } userAnimeClient.SetWatchedEpisodes(animeId, epValue) } + func SetChapters(mangaId int, ch string) { chValue, err := strconv.Atoi(ch) if err != nil { - log.Fatal("Error while parsing chapter input", err) + fmt.Println("Error while parsing chapter input", err) + os.Exit(1) + } + + sign := ch[0:1] + if sign == "+" || sign == "-" { + fmt.Printf("Cannot increment/decrement read chapters by %d\n. Currently that doesn't wokr", chValue) + os.Exit(2) } - log.Printf("peeepee%s%d", ch[0:1], chValue) + userMangaClient.SetChaptersRead(mangaId, chValue) } @@ -23,11 +23,15 @@ import ( "os" a "github.com/MikunoNaka/MAL2Go/anime" + m "github.com/MikunoNaka/MAL2Go/manga" ua "github.com/MikunoNaka/MAL2Go/user/anime" + um "github.com/MikunoNaka/MAL2Go/user/manga" ) var animeClient a.Client +var mangaClient m.Client var userAnimeClient ua.Client +var userMangaClient um.Client func init() { // TODO: don't load access token from .env @@ -35,5 +39,7 @@ func init() { // initialise MAL2Go Client(s) animeClient.AuthToken = "Bearer " + accessToken + mangaClient.AuthToken = "Bearer " + accessToken userAnimeClient.AuthToken = "Bearer " + accessToken + userMangaClient.AuthToken = "Bearer " + accessToken } diff --git a/mal/search.go b/mal/search.go index 2968611..703cdb4 100644 --- a/mal/search.go +++ b/mal/search.go @@ -19,24 +19,36 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. package mal import ( - "log" + "fmt" + "os" a "github.com/MikunoNaka/MAL2Go/anime" + m "github.com/MikunoNaka/MAL2Go/manga" ) -func SearchAnime(searchString string, extraFields []string) []a.Anime { - // TODO: load limit, offset and (maybe) fields from config +func SearchAnime(searchString string) []a.Anime { + // TODO: read limit, offset from flags limit, offset := 10, 0 - - fields := []string{"title", "id"} - for _, i := range extraFields { - fields = append(fields, i) - } + fields := []string{"title", "id", "my_list_status"} res, err := animeClient.SearchAnime(searchString, limit, offset, fields) if err != nil { - log.Println(err) - return []a.Anime{} + fmt.Println("MyAnimeList reported error while searching:", err.Error()) + os.Exit(1) } return res.Animes } + +func SearchManga(searchString string) []m.Manga { + // TODO: read limit, offset from flags + limit, offset := 10, 0 + fields := []string{"title", "id", "my_list_status"} + + res, err := mangaClient.SearchManga(searchString, limit, offset, fields) + if err != nil { + fmt.Println("MyAnimeList reported error while searching:", err.Error()) + os.Exit(1) + } + + return res.Mangas +} diff --git a/mal/status.go b/mal/status.go index 422a838..1e11106 100644 --- a/mal/status.go +++ b/mal/status.go @@ -23,14 +23,26 @@ import ( "os" ) -func SetStatus(animeId int, status string) { +func SetAnimeStatus(animeId int, status string) { resp, err := userAnimeClient.SetStatus(animeId, status) if err != nil { fmt.Println("Error while parsing status:", err.Error()) os.Exit(1) } if resp.Error != "" { - fmt.Println("MyAnimeList reported error on SetStatus", resp.Error, resp.Message) + fmt.Println("MyAnimeList reported error on setting anime status", resp.Error, resp.Message) + os.Exit(1) + } +} + +func SetMangaStatus(mangaId int, status string) { + resp, err := userMangaClient.SetStatus(mangaId, status) + if err != nil { + fmt.Println("Error while parsing status:", err.Error()) + os.Exit(1) + } + if resp.Error != "" { + fmt.Println("MyAnimeList reported error on setting manga status", resp.Error, resp.Message) os.Exit(1) } } diff --git a/ui/actions.go b/ui/actions.go index 7db5a96..cc9e391 100644 --- a/ui/actions.go +++ b/ui/actions.go @@ -24,32 +24,97 @@ import ( "os" p "github.com/manifoldco/promptui" a "github.com/MikunoNaka/MAL2Go/anime" + m "github.com/MikunoNaka/MAL2Go/manga" ) -type Action struct { +type AnimeAction struct { Label string Description string Method func(a.Anime) } -// only search animes probably only now -func ActionMenu(animeIsAdded bool) func(a.Anime) { +type MangaAction struct { + Label string + Description string + Method func(m.Manga) +} + +func AnimeActionMenu(animeIsAdded bool) func(a.Anime) { // TODO: load promptLength from config promptLength := 5 - options := []Action { - {"Set Status", "Set status for an anime (watching, dropped, etc)", StatusMenu}, + options := []AnimeAction { + {"Set Status", "Set status for an anime (watching, dropped, etc)", AnimeStatusMenu}, {"Set Episodes", "Set number of episodes watched", EpisodeInput}, - {"Set Score", "Set score", StatusMenu}, - {"Set Rewatching", "Set if rewatching", StatusMenu}, - {"Set Times Rewatched", "Set number of times rewatched", StatusMenu}, + // these only temporarily run AnimeStatusMenu + // because that functionality doesnt exist yet + {"Set Score", "Set score", AnimeStatusMenu}, + {"Set Re-watching", "Set if re-watching", AnimeStatusMenu}, + {"Set Times Re-watched", "Set number of times re-watched", AnimeStatusMenu}, } // if anime not in list if animeIsAdded { options = append( options, - Action{"Delete Anime", "Delete Anime From Your MyAnimeList List.", StatusMenu}, + AnimeAction{"Delete Anime", "Delete Anime From Your MyAnimeList List.", AnimeStatusMenu}, + ) + } + + template := &p.SelectTemplates { + Label: "{{ .Label }}", + Active: "{{ .Label | magenta }} {{ .Description | faint }}", + Inactive: "{{ .Label }}", + Selected: "{{ .Label | magenta }}", + Details: ` +------------------- +{{ .Description }} +`, + } + + // returns true if input == anime title + searcher := func(input string, index int) bool { + action := strings.Replace(strings.ToLower(options[index].Label), " ", "", -1) + input = strings.Replace(strings.ToLower(input), " ", "", -1) + return strings.Contains(action, input) + } + + prompt := p.Select { + Label: "Select Action: ", + Items: options, + Templates: template, + Searcher: searcher, + Size: promptLength, + } + + res, _, err := prompt.Run() + if err != nil { + fmt.Println("Error running actions menu.", err.Error()) + os.Exit(1) + } + + return options[res].Method +} + +func MangaActionMenu(mangaIsAdded bool) func(m.Manga) { + // TODO: load promptLength from config + promptLength := 5 + + options := []MangaAction { + {"Set Status", "Set status for a manga (reading, dropped, etc)", MangaStatusMenu}, + {"Set Chapters", "Set number of chapters read", ChapterInput}, + // these only temporarily run MangaStatusMenu + // because that functionality doesnt exist yet + {"Set Score", "Set score", MangaStatusMenu}, + {"Set Re-reading", "Set if re-reading", MangaStatusMenu}, + {"Set Times Re-read", "Set number of times re-read", MangaStatusMenu}, + } + + // if manga not in list + if mangaIsAdded { + options = append( + options, + MangaAction{"Delete Manga", "Delete Manga From Your MyAnimeList List.", MangaStatusMenu}, ) } diff --git a/ui/episodes.go b/ui/episodes.go index 53c1cb9..d4c3791 100644 --- a/ui/episodes.go +++ b/ui/episodes.go @@ -25,7 +25,7 @@ import ( "errors" "github.com/MikunoNaka/macli/mal" a "github.com/MikunoNaka/MAL2Go/anime" - // m "github.com/MikunoNaka/MAL2Go/manga" + m "github.com/MikunoNaka/MAL2Go/manga" p "github.com/manifoldco/promptui" ) @@ -63,3 +63,38 @@ func EpisodeInput(anime a.Anime) { mal.SetEpisodes(anime.Id, res) } + +func ChapterInput(manga m.Manga) { + validate := func(input string) error { + if _, err := strconv.ParseFloat(input, 64); err != nil { + return errors.New("Input must be a number.") + } + return nil + } + + template := &p.PromptTemplates { + Valid: "\x1b[0m{{ . | magenta }}", + Invalid: "\x1b[0m{{ . | magenta }}\x1b[31m ", + Success: "{{ . | cyan }}", + } + + prompt := p.Prompt { + Label: "Set Chapter Number: ", + Templates: template, + Validate: validate, + } + + // print current chapter number if any + chNum := manga.MyListStatus.ChaptersRead + if chNum != 0 { + fmt.Printf("\x1b[33mYou currently have read %d chapters.\n\x1b[0m", chNum) + } + + res, err := prompt.Run() + if err != nil { + fmt.Println("Error Running chapter input Prompt.", err.Error()) + os.Exit(1) + } + + mal.SetChapters(manga.Id, res) +} diff --git a/ui/input.go b/ui/input.go index 0aa2f06..a334943 100644 --- a/ui/input.go +++ b/ui/input.go @@ -33,14 +33,49 @@ func TextInput(label, errMessage string) string { return nil } + template := &p.PromptTemplates { + Valid: "\x1b[0m{{ . | magenta }}", + Invalid: "\x1b[0m{{ . | magenta }}\x1b[31m", + Success: "{{ . | cyan }}", + } + + prompt := p.Prompt { + Label: label, + Validate: validate, + Templates: template,} + res, err := prompt.Run() + if err != nil { + fmt.Println("Failed to run input prompt.", err.Error()) + os.Exit(1) + } + + return res +} + +func PasswordInput(label, errMessage string) string { + validate := func(input string) error { + if input == "" { + return errors.New(errMessage) + } + return nil + } + + template := &p.PromptTemplates { + Valid: "{{ . | cyan }}", + Invalid: "{{ . | cyan }}", + Success: "{{ . | blue }}", + } + prompt := p.Prompt { Label: label, + Templates: template, Validate: validate, + Mask: '*', } res, err := prompt.Run() if err != nil { - fmt.Println("Failed to run TextInput Prompt.", err.Error()) + fmt.Println("Failed to run input prompt.", err.Error()) os.Exit(1) } diff --git a/ui/search.go b/ui/search.go index c02ae22..911add5 100644 --- a/ui/search.go +++ b/ui/search.go @@ -25,15 +25,14 @@ import ( p "github.com/manifoldco/promptui" mal "github.com/MikunoNaka/macli/mal" a "github.com/MikunoNaka/MAL2Go/anime" + m "github.com/MikunoNaka/MAL2Go/manga" ) // only search animes probably only now func AnimeSearch(label, searchString string) a.Anime { // TODO: load promptLength from config promptLength := 5 - - extraFields := []string{"my_list_status"} - animes := mal.SearchAnime(searchString, extraFields) + animes := mal.SearchAnime(searchString) template := &p.SelectTemplates { Label: "{{ . }}", @@ -61,13 +60,51 @@ More Details To Be Added Later Size: promptLength, } - var anime a.Anime animeIndex, _, err := prompt.Run() if err != nil { fmt.Println("Error running search menu.", err.Error()) os.Exit(1) } - anime = animes[animeIndex] - return anime + return animes[animeIndex] +} + +func MangaSearch(label, searchString string) m.Manga { + // TODO: load promptLength from config + promptLength := 5 + mangas := mal.SearchManga(searchString) + + template := &p.SelectTemplates { + Label: "{{ . }}", + Active: "{{ .Title | magenta }}", + Inactive: "{{ .Title }}", + Selected: "{{ .Title | blue }}", + Details: ` +--------- {{ .Title }} ---------- +More Details To Be Added Later +`, + } + + // returns true if input == anime title + searcher := func(input string, index int) bool { + title := strings.Replace(strings.ToLower(mangas[index].Title), " ", "", -1) + input = strings.Replace(strings.ToLower(input), " ", "", -1) + return strings.Contains(title, input) + } + + prompt := p.Select { + Label: label, + Items: mangas, + Templates: template, + Searcher: searcher, + Size: promptLength, + } + + mangaIndex, _, err := prompt.Run() + if err != nil { + fmt.Println("Error running search menu.", err.Error()) + os.Exit(1) + } + + return mangas[mangaIndex] } diff --git a/ui/status.go b/ui/status.go index 661abe1..11fe5d8 100644 --- a/ui/status.go +++ b/ui/status.go @@ -24,6 +24,7 @@ import ( "os" "github.com/MikunoNaka/macli/mal" a "github.com/MikunoNaka/MAL2Go/anime" + m "github.com/MikunoNaka/MAL2Go/manga" p "github.com/manifoldco/promptui" ) @@ -32,8 +33,7 @@ type StatusOption struct { Status string } -// only search animes probably only now -func StatusMenu(anime a.Anime) { +func AnimeStatusMenu(anime a.Anime) { options := []StatusOption { {"Watching", "watching"}, {"Completed", "completed"}, @@ -67,8 +67,63 @@ func StatusMenu(anime a.Anime) { } promptLabel := "Set Status: " - if anime.MyListStatus.Status != "" { - promptLabel = promptLabel + "(current - " + anime.MyListStatus.Status + ")" + if animeStatus != "" { + promptLabel = promptLabel + "(current - " + animeStatus + ")" + } + + prompt := p.Select { + Label: promptLabel, + Items: options, + Templates: template, + Searcher: searcher, + Size: 5, + } + + res, _, err := prompt.Run() + if err != nil { + fmt.Println("Error running status prompt.", err.Error()) + os.Exit(1) + } + + mal.SetAnimeStatus(anime.Id, options[res].Status) +} + +func MangaStatusMenu(manga m.Manga) { + options := []StatusOption { + {"Reading", "reading"}, + {"Completed", "completed"}, + {"On Hold", "on_hold"}, + {"Dropped", "dropped"}, + {"Plan to Read", "plan_to_read"}, + } + + // highlight current status (if any) + mangaStatus := manga.MyListStatus.Status + if mangaStatus != "" { + for i := range options { + if options[i].Status == mangaStatus { + options[i].Label = options[i].Label + " \x1b[35m\U00002714\x1b[0m" + } + } + } + + template := &p.SelectTemplates { + Label: "{{ .Label }}", + Active: "{{ .Label | magenta }}", + Inactive: "{{ .Label }}", + Selected: "{{ .Label | cyan }}", + } + + // returns true if input == anime title + searcher := func(input string, index int) bool { + status := strings.Replace(strings.ToLower(options[index].Label), " ", "", -1) + input = strings.Replace(strings.ToLower(input), " ", "", -1) + return strings.Contains(status, input) + } + + promptLabel := "Set Status: " + if mangaStatus != "" { + promptLabel = promptLabel + "(current - " + mangaStatus + ")" } prompt := p.Select { @@ -85,5 +140,5 @@ func StatusMenu(anime a.Anime) { os.Exit(1) } - mal.SetStatus(anime.Id, options[res].Status) + mal.SetMangaStatus(manga.Id, options[res].Status) } |