From cbef98f293d36d30b603f4f6d8f52d8a67097755 Mon Sep 17 00:00:00 2001 From: John O'Keefe Date: Sun, 21 Jul 2024 10:25:21 -0400 Subject: [PATCH] added ability to get Anilist Item, Search and JWT token --- AniListFunctions.go | 237 ++++++++++++++++++++++++++++++++++++++++ frontend/src/App.svelte | 58 +++++++++- 2 files changed, 289 insertions(+), 6 deletions(-) create mode 100644 AniListFunctions.go diff --git a/AniListFunctions.go b/AniListFunctions.go new file mode 100644 index 0000000..fbf374c --- /dev/null +++ b/AniListFunctions.go @@ -0,0 +1,237 @@ +package main + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "github.com/wailsapp/wails/v2/pkg/runtime" + "io" + "log" + "net/http" + "net/url" + "os" + "strings" + "sync" +) + +type JWT struct { + TokenType string `json:"token_type"` + ExpiresIn int `json:"expires_in"` + AccessToken string `json:"access_token"` + RefreshToken string `json:"refresh_token"` +} + +var jwt JWT + +func AniListQuery(body interface{}, login bool) interface{} { + reader, _ := json.Marshal(body) + response, err := http.NewRequest("POST", "https://graphql.anilist.co", bytes.NewBuffer(reader)) + if err != nil { + log.Printf("Failed at response, %s\n", err) + } + if login && (JWT{}) != jwt { + response.Header.Add("Authorization", "Bearer "+jwt.AccessToken) + } else if login { + return "Please login to AniList to make this request" + } + response.Header.Add("Content-Type", "application/json") + response.Header.Add("Accept", "application/json") + + client := &http.Client{} + res, reserr := client.Do(response) + if reserr != nil { + log.Printf("Failed at res, %s\n", err) + } + + defer res.Body.Close() + + returnedBody, err := io.ReadAll(res.Body) + + var post interface{} + err = json.Unmarshal(returnedBody, &post) + if err != nil { + log.Printf("Failed at unmarshal, %s\n", err) + } + + return post +} + +func (a *App) GetAniListItem(aniId int) any { + type Variables struct { + ID int `json:"id"` + ListType string `json:"listType"` + } + body := struct { + Query string `json:"query"` + Variables Variables `json:"variables"` + }{ + Query: ` + query ($id: Int!, $listType: MediaType) { + Media (id: $id, type: $listType) { + id + idMal + title { + romaji + english + } + description + coverImage { + medium + large + extraLarge + color + } + tags { + id + name + description + category + rank + isGeneralSpoiler + isMediaSpoiler + isAdult + } + } + } + `, + Variables: Variables{ + ID: aniId, + ListType: "ANIME", + }, + } + + return AniListQuery(body, false) +} + +func (a *App) AniListSearch(query string) any { + type Variables struct { + Search string `json:"search"` + ListType string `json:"listType"` + } + body := struct { + Query string `json:"query"` + Variables Variables `json:"variables"` + }{ + Query: ` + query ($search: String!, $listType: MediaType) { + Page (page: 1, perPage: 100) { + pageInfo { + total + currentPage + lastPage + hasNextPage + perPage + } + media (search: $search, type: $listType) { + id + idMal + title { + romaji + english + } + coverImage { + extraLarge + color + } + } + } + } + `, + Variables: Variables{ + Search: query, + ListType: "ANIME", + }, + } + return AniListQuery(body, false) +} + +var ctxShutdown, cancel = context.WithCancel(context.Background()) + +func (a *App) AniListLogin() { + getAniListCodeUrl := "https://anilist.co/api/v2/oauth/authorize?client_id=" + os.Getenv("ANILIST_APP_ID") + "&redirect_uri=" + os.Getenv("ANILIST_CALLBACK_URI") + "&response_type=code" + runtime.BrowserOpenURL(a.ctx, getAniListCodeUrl) + + serverDone := &sync.WaitGroup{} + serverDone.Add(1) + handleAniListCallback(serverDone) + serverDone.Wait() +} + +func handleAniListCallback(wg *sync.WaitGroup) { + srv := &http.Server{Addr: ":6734"} + http.HandleFunc("/callback", func(w http.ResponseWriter, r *http.Request) { + select { + case <-ctxShutdown.Done(): + fmt.Println("Shutting down...") + return + default: + } + content := r.FormValue("code") + + if content != "" { + jwt = getAniListAuthorizationToken(content) + fmt.Println("Shutting down...") + cancel() + err := srv.Shutdown(context.Background()) + if err != nil { + log.Println("server.Shutdown:", err) + } + } else { + _, err := fmt.Fprintf(w, "Getting code failed.") + if err != nil { + return + } + } + + }) + + go func() { + defer wg.Done() + if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { + log.Fatalf("listen: %s\n", err) + } + fmt.Println("Shutting down...") + }() +} + +func getAniListAuthorizationToken(content string) JWT { + apiUrl := "https://anilist.co/api/v2/oauth/token" + resource := "/api/v2/oauth/token" + data := url.Values{} + data.Set("grant_type", "authorization_code") + data.Set("client_id", os.Getenv("ANILIST_APP_ID")) + data.Set("client_secret", os.Getenv("ANILIST_SECRET_TOKEN")) + data.Set("redirect_uri", os.Getenv("ANILIST_CALLBACK_URI")) + data.Set("code", content) + + u, _ := url.ParseRequestURI(apiUrl) + u.Path = resource + urlStr := u.String() + + response, err := http.NewRequest("POST", urlStr, strings.NewReader(data.Encode())) + if err != nil { + log.Printf("Failed at response, %s\n", err) + } + response.Header.Add("content-type", "application/x-www-form-urlencoded") + response.Header.Add("Content-Type", "application/json") + response.Header.Add("Accept", "application/json") + + client := &http.Client{} + res, reserr := client.Do(response) + if reserr != nil { + log.Printf("Failed at res, %s\n", err) + } + + defer res.Body.Close() + + returnedBody, err := io.ReadAll(res.Body) + + var post JWT + err = json.Unmarshal(returnedBody, &post) + if err != nil { + log.Printf("Failed at unmarshal, %s\n", err) + } + + return post +} diff --git a/frontend/src/App.svelte b/frontend/src/App.svelte index 1987eb0..d688775 100644 --- a/frontend/src/App.svelte +++ b/frontend/src/App.svelte @@ -1,22 +1,68 @@
- -
{resultText}
-
- - + + + + + + + +
{aniListItem !== undefined ? aniListItem.data.Media.title.english : ""}
+
+ +
+ + + {#if aniListLoggedIn} +
You are logged in!
+ {/if} + +
+ + +
+ {#if aniListSearch !== undefined} +
    + {#each aniListSearch.data.Page.media as media, index (media.id)} +
  • +
    {media.title.english !== null ? media.title.english : media.title.romaji}
    +
  • + {:else} +
    No Results Yet...
    + {/each} +
+ {/if}