diff --git a/CHANGELOG.md b/CHANGELOG.md index f28f461..1165210 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,11 +1,16 @@ ### ✨ What's New -- You can now find the docs on https://chibi-cli.pages.dev/ -- You can now install chibi in windows via winget. To do so, type in +- The `ls` command now shows Rewatching/Rereading Media along with Current Media. The Rewatching/Rereading media will be marked by **"(R)"** before the media title. +- You can now pass incremental or decremental progress to `update` command. For example, + To increment progress of a media by 2, ```shell - winget install CosmicPredator.Chibi + $ chibi update 8861 -p +2 + ``` + + To decrement progress of a media by 5, + ```shell + $ chibi update 8861 -p -5 ``` -- Added install/uninstall scripts for linux and macOS. ### 🐛 Bug Fixes -- Undo gitignoring `go.sum` for nix flake builds. \ No newline at end of file +- None for now 👽️ \ No newline at end of file diff --git a/cmd/cmd_add.go b/cmd/cmd_add.go index fec3148..6b76d50 100644 --- a/cmd/cmd_add.go +++ b/cmd/cmd_add.go @@ -35,12 +35,12 @@ func handleMediaAdd(mediaId int) { mediaUpdate := internal.NewMediaUpdate() if mediaAddStatus == "CURRENT" { currDate := fmt.Sprintf("%d/%d/%d", time.Now().Day(), time.Now().Month(), time.Now().Year()) - err := mediaUpdate.Get(true, mediaId, 0, mediaAddStatus, currDate) + err := mediaUpdate.Get(true, mediaId, "0", mediaAddStatus, currDate) if err != nil { ErrorMessage(err.Error()) } } else { - err := mediaUpdate.Get(true, mediaId, 0, mediaAddStatus, "") + err := mediaUpdate.Get(true, mediaId, "0", mediaAddStatus, "") if err != nil { ErrorMessage(err.Error()) } diff --git a/cmd/cmd_list.go b/cmd/cmd_list.go index d7f4787..536de0c 100644 --- a/cmd/cmd_list.go +++ b/cmd/cmd_list.go @@ -41,19 +41,26 @@ func handleLs() { } rows := [][]string{} - for _, i := range mediaList.Data.MediaListCollection.Lists[0].Entries { - var progress string - if mediaType == "ANIME" { - progress = fmt.Sprintf("%d/%d", i.Progress, i.Media.Episodes) - } else { - progress = fmt.Sprintf("%d/%d", i.Progress, i.Media.Chapters) - } - rows = append(rows, []string{ - strconv.Itoa(i.Media.Id), - i.Media.Title.UserPreferred, - progress, - }) + for _, lists := range mediaList.Data.MediaListCollection.Lists { + for _, entry := range lists.Entries { + var progress string + if mediaType == "ANIME" { + progress = fmt.Sprintf("%d/%d", entry.Progress, entry.Media.Episodes) + } else { + progress = fmt.Sprintf("%d/%d", entry.Progress, entry.Media.Chapters) + } + + if lists.Status == "REPEATING" { + entry.Media.Title.UserPreferred = "(R) " + entry.Media.Title.UserPreferred + } + + rows = append(rows, []string{ + strconv.Itoa(entry.Media.Id), + entry.Media.Title.UserPreferred, + progress, + }) + } } // get size of terminal @@ -85,9 +92,9 @@ func handleLs() { } var mediaListCmd = &cobra.Command{ - Use: "list", - Short: "List your current anime/manga list", - Aliases: []string{ "ls" }, + Use: "list", + Short: "List your current anime/manga list", + Aliases: []string{"ls"}, Run: func(cmd *cobra.Command, args []string) { handleLs() }, @@ -98,6 +105,6 @@ func init() { &listMediaType, "type", "t", "anime", "Type of media. for anime, pass 'anime' or 'a', for manga, use 'manga' or 'm'", ) mediaListCmd.Flags().StringVarP( - &listStatus, "status", "s", "watching", "Status of the media. Can be 'watching/w or reading/r', 'planning/p', 'completed/c', 'dropped/d', 'paused/ps', 'repeating/rp'", + &listStatus, "status", "s", "watching", "Status of the media. Can be 'watching/w or reading/r', 'planning/p', 'completed/c', 'dropped/d', 'paused/ps'", ) } diff --git a/cmd/cmd_update.go b/cmd/cmd_update.go index 8d2d0c5..f6f941e 100644 --- a/cmd/cmd_update.go +++ b/cmd/cmd_update.go @@ -9,18 +9,22 @@ import ( ) // TODO: Update progress relatively. For example "+2", "-10" etc., -var progress int +var progress string func handleUpdate(mediaId int) { CheckIfTokenExists() - if progress == 0 { - fmt.Println( - ERROR_MESSAGE_TEMPLATE.Render("The flag 'progress' should be greater than 0."), - ) + + progressInt, err := strconv.Atoi(progress) + if err == nil { + if progressInt == 0 { + fmt.Println( + ERROR_MESSAGE_TEMPLATE.Render("The flag 'progress' should be greater than 0."), + ) + } } mediaUpdate := internal.NewMediaUpdate() - err := mediaUpdate.Get(false, mediaId, progress, "", "") + err = mediaUpdate.Get(false, mediaId, progress, "", "") if err != nil { ErrorMessage(err.Error()) @@ -35,8 +39,11 @@ func handleUpdate(mediaId int) { var mediaUpdateCmd = &cobra.Command{ Use: "update [id]", Short: "Update a list entry", - Args: cobra.ExactArgs(1), + Args: cobra.MinimumNArgs(1), Run: func(cmd *cobra.Command, args []string) { + if len(args) == 2 { + progress = args[1] + } id, err := strconv.Atoi(args[0]) if err != nil { fmt.Println( @@ -48,11 +55,11 @@ var mediaUpdateCmd = &cobra.Command{ } func init() { - mediaUpdateCmd.Flags().IntVarP( + mediaUpdateCmd.Flags().StringVarP( &progress, "progress", "p", - 0, + "0", "The number of episodes/chapter to update", ) } diff --git a/internal/media_list.go b/internal/media_list.go index fdd3dd9..791938b 100644 --- a/internal/media_list.go +++ b/internal/media_list.go @@ -2,25 +2,28 @@ package internal import "github.com/CosmicPredator/chibi/types" +type ListCollection struct { + Lists []struct { + Status string `json:"status"` + Entries []struct { + Progress int `json:"progress"` + ProgressVolumes int `json:"progressVolumes"` + Media struct { + Id int `json:"id"` + Title struct { + UserPreferred string `json:"userPreferred"` + } `json:"title"` + Chapters int `json:"chapters"` + Volumes int `json:"volumes"` + Episodes int `json:"episodes"` + } `json:"media"` + } `json:"entries"` + } `json:"lists"` +} + type MediaList struct { Data struct { - MediaListCollection struct { - Lists []struct { - Entries []struct { - Progress int `json:"progress"` - ProgressVolumes int `json:"progressVolumes"` - Media struct { - Id int `json:"id"` - Title struct { - UserPreferred string `json:"userPreferred"` - } `json:"title"` - Chapters int `json:"chapters"` - Volumes int `json:"volumes"` - Episodes int `json:"episodes"` - } `json:"media"` - } `json:"entries"` - } `json:"lists"` - } `json:"MediaListCollection"` + MediaListCollection ListCollection `json:"MediaListCollection"` } `json:"data"` } @@ -36,8 +39,6 @@ func parseMediaStatus(status string) string { return "DROPPED" case "paused", "ps": return "PAUSED" - case "repeating", "rp": - return "REPEATING" default: return "CURRENT" } @@ -53,9 +54,10 @@ func (ml *MediaList) Get(mediaType string, status string) error { } query := - `query($userId: Int, $type: MediaType, $status: MediaListStatus) { - MediaListCollection(userId: $userId, type: $type, status: $status) { + `query($userId: Int, $type: MediaType, $status: [MediaListStatus]) { + MediaListCollection(userId: $userId, type: $type, status_in: $status) { lists { + status entries { progress progressVolumes @@ -73,12 +75,22 @@ func (ml *MediaList) Get(mediaType string, status string) error { } }` + parsedStatus := parseMediaStatus(status) + + var parsedStatusSlice []string = make([]string, 0) + + if parsedStatus == "CURRENT" { + parsedStatusSlice = append(parsedStatusSlice, parsedStatus, "REPEATING") + } else { + parsedStatusSlice = append(parsedStatusSlice, parsedStatus) + } + err = anilistClient.ExecuteGraqhQL( query, map[string]interface{}{ "type": mediaType, "userId": tokenConfig.UserId, - "status": parseMediaStatus(status), + "status": parsedStatusSlice, }, &ml, ) diff --git a/internal/media_update.go b/internal/media_update.go index d9119e3..191b522 100644 --- a/internal/media_update.go +++ b/internal/media_update.go @@ -1,53 +1,100 @@ package internal import ( + "errors" "fmt" "os" "strconv" "strings" "time" + "github.com/CosmicPredator/chibi/types" "github.com/charmbracelet/huh" "github.com/charmbracelet/lipgloss" ) -func getTotalEps(mediaId int) (int, error) { +type MediaListUpdate struct { + Data struct { + AnimeListCollection ListCollection `json:"AnimeListCollection"` + MangaListCollection ListCollection `json:"MangaListCollection"` + } `json:"data"` +} + +func getTotalCurrent(mediaId int) (int, int, error) { query := `query ($id: Int) { - Media(id: $id) { - episodes - chapters - type - } - }` + AnimeListCollection: MediaListCollection(userId: $id, type: ANIME, status_in:[CURRENT, REPEATING]){ + lists { + status + entries { + progress + media { + id + title { + userPreferred + } + episodes + chapters + } + } + } + } + MangaListCollection: MediaListCollection(userId: $id, type: MANGA, status_in:[CURRENT, REPEATING]){ + lists { + status + entries { + progress + media { + id + title { + userPreferred + } + episodes + chapters + } + } + } + } + }` - var reponseData struct { - Data struct { - Media struct { - Episodes int `json:"episodes"` - Chapters int `json:"chapters"` - Type string `json:"type"` - } `json:"media"` - } `json:"data"` + tokenConfig := types.NewTokenConfig() + err := tokenConfig.ReadFromJsonFile() + + if err != nil { + return 0, 0, err } + var responseObj MediaListUpdate + anilistClient := NewAnilistClient() - err := anilistClient.ExecuteGraqhQL( + err = anilistClient.ExecuteGraqhQL( query, map[string]interface{}{ - "id": mediaId, + "id": tokenConfig.UserId, }, - &reponseData, + &responseObj, ) if err != nil { - return 0, err + return 0, 0, err } - if reponseData.Data.Media.Type == "ANIME" { - return reponseData.Data.Media.Episodes, nil - } else { - return reponseData.Data.Media.Chapters, nil + for _, list := range responseObj.Data.AnimeListCollection.Lists { + for _, entry := range list.Entries { + if entry.Media.Id == mediaId { + return entry.Progress, entry.Media.Episodes, nil + } + } } + + for _, list := range responseObj.Data.MangaListCollection.Lists { + for _, entry := range list.Entries { + if entry.Media.Id == mediaId { + return entry.Progress, entry.Media.Chapters, nil + } + } + } + + return 0, 0, errors.New("list empty") } type updateMediaFields struct { @@ -118,17 +165,38 @@ type MediaUpdate struct { } `json:"data"` } -func (mu *MediaUpdate) Get(isMediaAdd bool, mediaId int, progress int, status string, startDate string) error { +func (mu *MediaUpdate) Get(isMediaAdd bool, mediaId int, progress string, status string, startDate string) error { if status == "" { status = "COMPLETED" } + var accumulatedProgress int + current, total, err := getTotalCurrent(mediaId) + + if strings.Contains(progress, "+") || strings.Contains(progress, "-") { + if progress[:1] == "+" { + prgInt, _ := strconv.Atoi(progress[1:]) + accumulatedProgress = current + prgInt + } else { + if current == 0 { + accumulatedProgress = 0 + } else { + prgInt, _ := strconv.Atoi(progress[1:]) + accumulatedProgress = current - prgInt + } + } + } else { + pgrInt, err := strconv.Atoi(progress) + if err != nil { + return err + } + accumulatedProgress = pgrInt + } - total, err := getTotalEps(mediaId) if err != nil { return err } - if progress > total { + if total != 0 && accumulatedProgress > total { fmt.Println(lipgloss.NewStyle().Foreground(lipgloss.Color("#FF0000")).Render( fmt.Sprintf("Entered value is greater than total episodes / chapters, which is %d", total), )) @@ -199,7 +267,7 @@ func (mu *MediaUpdate) Get(isMediaAdd bool, mediaId int, progress int, status st var canEditList bool = false - if progress == total { + if accumulatedProgress == total { huh.NewConfirm(). Title("Seems like you completed the anime/manga. Do you want to mark this as completed?"). Affirmative("Yes!"). @@ -218,7 +286,7 @@ func (mu *MediaUpdate) Get(isMediaAdd bool, mediaId int, progress int, status st mutation, map[string]interface{}{ "id": mediaId, - "progress": progress, + "progress": accumulatedProgress, "score": mediaFields.Score, "notes": mediaFields.Notes, "cDate": mediaFields.CompletedAtDate, @@ -234,7 +302,7 @@ func (mu *MediaUpdate) Get(isMediaAdd bool, mediaId int, progress int, status st mutation, map[string]interface{}{ "id": mediaId, - "progress": progress, + "progress": accumulatedProgress, }, &mu, )