diff --git a/anilist/anilist.go b/anilist/anilist.go index 2c712db..7e9cc9b 100644 --- a/anilist/anilist.go +++ b/anilist/anilist.go @@ -17,7 +17,7 @@ const ALDomain = "https://anilist.co" var InvalidToken = errors.New("Invalid token") -//TODO downloading only given list like watching/completed +// TODO downloading only given list like watching/completed func QueryUserLists(userId int, token oauth2.OAuthToken) ([]MediaListGroup, error) { vars := make(map[string]interface{}) vars["userID"] = userId @@ -51,6 +51,19 @@ func SaveMediaListEntry(entry *MediaListEntry, token oauth2.OAuthToken) error { return gqlErrorsHandler(graphQLRequestParsed(saveMediaListEntry, vars, token, entryData)) } +func AddMediaListEntry(id int, status MediaListStatus, token oauth2.OAuthToken) ( + MediaListEntry, error, +) { + vars := make(map[string]interface{}) + vars["mediaId"] = id + vars["status"] = string(status) + entryData := &struct { + MediaListEntry `json:"SaveMediaListEntry"` + }{} + err := gqlErrorsHandler(graphQLRequestParsed(addMediaListEntry, vars, token, entryData)) + return entryData.MediaListEntry, err +} + func QueryAiringSchedule(mediaId, episode int, token oauth2.OAuthToken) (AiringSchedule, error) { vars := make(map[string]interface{}) vars["mediaId"] = mediaId @@ -170,8 +183,8 @@ func graphQLRequestParsed(query string, vars map[string]interface{}, t oauth2.OA return nil, err } if len(respData.Errors) > 0 { - //TODO include all error fields - //TODO better error handling -> maybe typedef error array as QueryErrors? + // TODO include all error fields + // TODO better error handling -> maybe typedef error array as QueryErrors? return respData.Errors, nil } return nil, nil diff --git a/anilist/gql_queries.go b/anilist/gql_queries.go index cb09b71..4f7886b 100644 --- a/anilist/gql_queries.go +++ b/anilist/gql_queries.go @@ -97,6 +97,36 @@ mutation ($listId: Int, $mediaId: Int, $status: MediaListStatus, $progress: Int, } ` +var addMediaListEntry = ` +mutation ($mediaId: Int, $status: MediaListStatus) { + SaveMediaListEntry (mediaId: $mediaId, status: $status) { + id + status + score(format: POINT_10) + progress + repeat + updatedAt + media { + id + idMal + title { + romaji + english + native + userPreferred + } + type + format + status + season + episodes + duration + synonyms + } + } +} +` + var queryAiringSchedule = ` query ($mediaId: Int, $episode: Int) { AiringSchedule(mediaId: $mediaId, episode: $episode) { diff --git a/anilist_app.go b/anilist_app.go index 8f6fc59..8946081 100644 --- a/anilist_app.go +++ b/anilist_app.go @@ -142,11 +142,12 @@ func AniListApp(app *cli.App) *cli.App { }, }, cli.Command{ - Name: "search", - Category: "Action", - Usage: "Search online database", - UsageText: "mal search [search query]", - Action: alSearch, + Name: "search", + Aliases: []string{"add", "browse"}, + Category: "Action", + Usage: "Browse online database and new entries", + UsageText: "mal search [search query]", + Action: alSearch, SkipFlagParsing: true, }, cli.Command{ diff --git a/search_cui.go b/search_cui.go index 05b7f3b..723278b 100644 --- a/search_cui.go +++ b/search_cui.go @@ -6,6 +6,7 @@ import ( "strings" "github.com/aqatl/mal/anilist" + "github.com/aqatl/mal/dialog" "github.com/fatih/color" "github.com/jroimartin/gocui" "github.com/urfave/cli" @@ -84,18 +85,22 @@ var searchResultHighlight = color.New(color.FgBlack, color.BgYellow) var yellowC = color.New(color.FgYellow) var cyanC = color.New(color.FgCyan) +func (sc *searchCui) setGuiKeyBindings(gui *gocui.Gui) { + gui.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quitGocui) +} + func (sc *searchCui) Layout(gui *gocui.Gui) error { switch sc.Mode { case scListView: - return sc.ListLayout() + return sc.listLayout() case scFullDetailsView: - return sc.FullDetailsLayout() + return sc.fullDetailsLayout() default: return fmt.Errorf("invalid mode: %d", sc.Mode) } } -func (sc *searchCui) ListLayout() error { +func (sc *searchCui) listLayout() error { w, h := sc.Gui.Size() if err := sc.filtersView(); err != nil { @@ -143,7 +148,7 @@ func (sc *searchCui) ListLayout() error { return nil } -func (sc *searchCui) FullDetailsLayout() error { +func (sc *searchCui) fullDetailsLayout() error { w, h := sc.Gui.Size() if err := sc.filtersView(); err != nil { @@ -156,6 +161,10 @@ func (sc *searchCui) FullDetailsLayout() error { } v.Wrap = true + v.Editor = sc + v.Editable = true + + sc.Gui.SetCurrentView(v.Name()) fmt.Fprintln(v, sc.Results[sc.SelIdx].Description) } @@ -170,6 +179,8 @@ func (sc *searchCui) filtersView() error { return err } + v.Editor = sc + fmt.Fprintln(v, "Search:", sc.SearchQuery) fmt.Fprintln(v, "Results:", len(sc.Results)) } @@ -202,6 +213,11 @@ func (sc *searchCui) Edit(v *gocui.View, key gocui.Key, ch rune, mod gocui.Modif case key == gocui.KeyEnter: sc.Mode = scListView sc.Gui.DeleteView(strconv.Itoa(sc.Results[sc.SelIdx].Id)) + case ch == 'a': + if len(sc.Results) == 0 { + return + } + sc.addEntry() } } } @@ -228,6 +244,22 @@ func (sc *searchCui) previousResult() { } } -func (sc *searchCui) setGuiKeyBindings(gui *gocui.Gui) { - gui.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quitGocui) +func (sc *searchCui) addEntry() { + if entry := sc.Al.GetMediaListById(sc.Results[sc.SelIdx].Id); entry != nil { + dialog.JustShowOkDialog(sc.Gui, "Add entry", + "Entry already added (on list "+entry.Status.String()+")") + return + } + + entry, err := anilist.AddMediaListEntry(sc.Results[sc.SelIdx].Id, anilist.Planning, sc.Al.Token) + if err != nil { + dialog.JustShowOkDialog(sc.Gui, "Error", err.Error()) + return + } + dialog.JustShowOkDialog(sc.Gui, "Success", entry.Title.UserPreferred+" added") + + sc.Al.List = append(sc.Al.List, entry) + sc.Gui.Update(func(gui *gocui.Gui) error { + return saveAniListAnimeLists(sc.Al) + }) }