added ability to get Anilist Item, Search and JWT token
This commit is contained in:
		
							
								
								
									
										237
									
								
								AniListFunctions.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										237
									
								
								AniListFunctions.go
									
									
									
									
									
										Normal file
									
								
							@@ -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
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,22 +1,68 @@
 | 
				
			|||||||
<script lang="ts">
 | 
					<script lang="ts">
 | 
				
			||||||
  import logo from './assets/images/logo-universal.png'
 | 
					  import logo from './assets/images/logo-universal.png'
 | 
				
			||||||
  import {Greet} from '../wailsjs/go/main/App.js'
 | 
					  import {Greet} from '../wailsjs/go/main/App.js'
 | 
				
			||||||
 | 
					  import {AniListLogin, AniListSearch, GetAniListItem} from "../wailsjs/go/main/App";
 | 
				
			||||||
 | 
					  import type {AniListItem, AniSearchList} from "./AniListTypes";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  let resultText: string = "Please enter your name below 👇"
 | 
					  let resultText: string = "Please enter your name below 👇"
 | 
				
			||||||
  let name: string
 | 
					  let name: string
 | 
				
			||||||
 | 
					  let aniId = "157371"
 | 
				
			||||||
 | 
					  let aniSearch = ""
 | 
				
			||||||
 | 
					  let aniListItem: AniListItem
 | 
				
			||||||
 | 
					  let aniListSearch: AniSearchList
 | 
				
			||||||
 | 
					  let aniListLoggedIn = false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  function greet(): void {
 | 
					  function greet(): void {
 | 
				
			||||||
    Greet(name).then(result => resultText = result)
 | 
					    Greet(name).then(result => resultText = result)
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  function getAniListitem(): void {
 | 
				
			||||||
 | 
					    GetAniListItem(Number(aniId)).then(result => aniListItem = result)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  function runAniListSearch(): void {
 | 
				
			||||||
 | 
					    AniListSearch(aniSearch).then(result => aniListSearch = result)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  function loginToAniList(): void {
 | 
				
			||||||
 | 
					    AniListLogin().then(() => aniListLoggedIn = true)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<main>
 | 
					<main>
 | 
				
			||||||
  <img alt="Wails logo" id="logo" src="{logo}">
 | 
					  <img alt="Wails logo" id="logo" src="{aniListItem === undefined ? logo : aniListItem.data.Media.coverImage.extraLarge}">
 | 
				
			||||||
  <div class="result" id="result">{resultText}</div>
 | 
					<!--  <div class="result" id="result">{resultText}</div>-->
 | 
				
			||||||
  <div class="input-box" id="input">
 | 
					<!--  <div class="input-box" id="input">-->
 | 
				
			||||||
    <input autocomplete="off" bind:value={name} class="input" id="name" type="text"/>
 | 
					<!--    <input autocomplete="off" bind:value={name} class="input" id="name" type="text"/>-->
 | 
				
			||||||
    <button class="btn" on:click={greet}>Greet</button>
 | 
					<!--    <button class="btn" on:click={greet}>Greet</button>-->
 | 
				
			||||||
 | 
					<!--  </div>-->
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  <div class="result" id="AniTest">{aniListItem !== undefined ? aniListItem.data.Media.title.english : ""}</div>
 | 
				
			||||||
 | 
					  <div class="input-box" id="aniButton">
 | 
				
			||||||
 | 
					    <input autocomplete="off" bind:value={aniId} class="input" id="aniId" type="text"/>
 | 
				
			||||||
 | 
					    <button class="btn" on:click={getAniListitem}>Get AniList Item</button>
 | 
				
			||||||
  </div>
 | 
					  </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  <button class="btn" on:click={loginToAniList}>Login to AniList</button>
 | 
				
			||||||
 | 
					  {#if aniListLoggedIn}
 | 
				
			||||||
 | 
					    <div>You are logged in!</div>
 | 
				
			||||||
 | 
					  {/if}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  <div class="input-box" id="aniSearch">
 | 
				
			||||||
 | 
					    <input autocomplete="off" bind:value={aniSearch} class="input" id="aniSearchInput" type="text"/>
 | 
				
			||||||
 | 
					    <button class="btn" on:click={runAniListSearch}>Search AniList</button>
 | 
				
			||||||
 | 
					  </div>
 | 
				
			||||||
 | 
					  {#if aniListSearch !== undefined}
 | 
				
			||||||
 | 
					    <ul>
 | 
				
			||||||
 | 
					        {#each aniListSearch.data.Page.media as media, index (media.id)}
 | 
				
			||||||
 | 
					          <li>
 | 
				
			||||||
 | 
					            <div>{media.title.english !== null ? media.title.english : media.title.romaji}</div>
 | 
				
			||||||
 | 
					          </li>
 | 
				
			||||||
 | 
					        {:else}
 | 
				
			||||||
 | 
					          <div>No Results Yet...</div>
 | 
				
			||||||
 | 
					        {/each}
 | 
				
			||||||
 | 
					    </ul>
 | 
				
			||||||
 | 
					  {/if}
 | 
				
			||||||
</main>
 | 
					</main>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<style>
 | 
					<style>
 | 
				
			||||||
@@ -40,7 +86,7 @@
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  .input-box .btn {
 | 
					  .input-box .btn {
 | 
				
			||||||
    width: 60px;
 | 
					    /*width: 60px;*/
 | 
				
			||||||
    height: 30px;
 | 
					    height: 30px;
 | 
				
			||||||
    line-height: 30px;
 | 
					    line-height: 30px;
 | 
				
			||||||
    border-radius: 3px;
 | 
					    border-radius: 3px;
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user