added information being pulled from anilist
This commit is contained in:
		| @@ -1,89 +1,13 @@ | ||||
| package AniList | ||||
| package main | ||||
|  | ||||
| import ( | ||||
| 	"AniTrack" | ||||
| 	"bytes" | ||||
| 	"context" | ||||
| 	"encoding/json" | ||||
| 	"fmt" | ||||
| 	"github.com/wailsapp/wails/v2/pkg/runtime" | ||||
| 	"io" | ||||
| 	"log" | ||||
| 	"net/http" | ||||
| 	"net/url" | ||||
| 	"os" | ||||
| 	"strings" | ||||
| 	"sync" | ||||
| ) | ||||
|  | ||||
| type AniListJWT struct { | ||||
| 	TokenType    string `json:"token_type"` | ||||
| 	ExpiresIn    int    `json:"expires_in"` | ||||
| 	AccessToken  string `json:"access_token"` | ||||
| 	RefreshToken string `json:"refresh_token"` | ||||
| } | ||||
|  | ||||
| type AniListUser struct { | ||||
| 	Data struct { | ||||
| 		Viewer struct { | ||||
| 			ID   int    `json:"id"` | ||||
| 			Name string `json:"name"` | ||||
| 		} `json:"Viewer"` | ||||
| 	} `json:"data"` | ||||
| } | ||||
|  | ||||
| type AniListCurrentUserWatchList struct { | ||||
| 	Data struct { | ||||
| 		Page struct { | ||||
| 			PageInfo struct { | ||||
| 				Total       int  `json:"total"` | ||||
| 				PerPage     int  `json:"perPage"` | ||||
| 				CurrentPage int  `json:"currentPage"` | ||||
| 				LastPage    int  `json:"lastPage"` | ||||
| 				HasNextPage bool `json:"hasNextPage"` | ||||
| 			} `json:"pageInfo"` | ||||
| 			MediaList []struct { | ||||
| 				ID      int `json:"id"` | ||||
| 				MediaID int `json:"mediaId"` | ||||
| 				Media   struct { | ||||
| 					ID    int `json:"id"` | ||||
| 					IDMal int `json:"idMal"` | ||||
| 					Title struct { | ||||
| 						Romaji  string `json:"romaji"` | ||||
| 						English string `json:"english"` | ||||
| 						Native  string `json:"native"` | ||||
| 					} `json:"title"` | ||||
| 					Description string `json:"description"` | ||||
| 					CoverImage  struct { | ||||
| 						Large string `json:"large"` | ||||
| 					} `json:"coverImage"` | ||||
| 					Season     string `json:"season"` | ||||
| 					SeasonYear int    `json:"seasonYear"` | ||||
| 					Episodes   int    `json:"episodes"` | ||||
| 				} `json:"media"` | ||||
| 				Status   string `json:"status"` | ||||
| 				Notes    string `json:"notes"` | ||||
| 				Progress int    `json:"progress"` | ||||
| 				Score    int    `json:"score"` | ||||
| 				Repeat   int    `json:"repeat"` | ||||
| 				User     struct { | ||||
| 					Statistics struct { | ||||
| 						Anime struct { | ||||
| 							Count    int `json:"count"` | ||||
| 							Statuses []struct { | ||||
| 								Status string `json:"status"` | ||||
| 								Count  int    `json:"count"` | ||||
| 							} `json:"statuses"` | ||||
| 						} `json:"anime"` | ||||
| 					} `json:"statistics"` | ||||
| 				} `json:"user"` | ||||
| 			} `json:"mediaList"` | ||||
| 		} `json:"Page"` | ||||
| 	} `json:"data"` | ||||
| } | ||||
|  | ||||
| var jwt AniListJWT | ||||
|  | ||||
| func AniListQuery(body interface{}, login bool) (json.RawMessage, string) { | ||||
| 	reader, _ := json.Marshal(body) | ||||
| 	response, err := http.NewRequest("POST", "https://graphql.anilist.co", bytes.NewBuffer(reader)) | ||||
| @@ -93,7 +17,7 @@ func AniListQuery(body interface{}, login bool) (json.RawMessage, string) { | ||||
| 	if login && (AniListJWT{}) != jwt { | ||||
| 		response.Header.Add("Authorization", "Bearer "+jwt.AccessToken) | ||||
| 	} else if login { | ||||
| 		return nil, "Please login to AniList to make this request" | ||||
| 		return nil, "Please login to anilist to make this request" | ||||
| 	} | ||||
| 	response.Header.Add("Content-Type", "application/json") | ||||
| 	response.Header.Add("Accept", "application/json") | ||||
| @@ -108,17 +32,10 @@ func AniListQuery(body interface{}, login bool) (json.RawMessage, string) { | ||||
|  | ||||
| 	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 | ||||
| 	return returnedBody, "" | ||||
| } | ||||
|  | ||||
| func (a *main.App) GetAniListItem(aniId int) any { | ||||
| func (a *App) GetAniListItem(aniId int) any { | ||||
| 	type Variables struct { | ||||
| 		ID       int    `json:"id"` | ||||
| 		ListType string `json:"listType"` | ||||
| @@ -173,7 +90,7 @@ func (a *main.App) GetAniListItem(aniId int) any { | ||||
| 	return post | ||||
| } | ||||
|  | ||||
| func (a *main.App) AniListSearch(query string) any { | ||||
| func (a *App) AniListSearch(query string) any { | ||||
| 	type Variables struct { | ||||
| 		Search   string `json:"search"` | ||||
| 		ListType string `json:"listType"` | ||||
| @@ -223,127 +140,120 @@ func (a *main.App) AniListSearch(query string) any { | ||||
| 	return post | ||||
| } | ||||
|  | ||||
| var ctxShutdown, cancel = context.WithCancel(context.Background()) | ||||
|  | ||||
| func (a *main.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) AniListJWT { | ||||
| 	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 AniListJWT | ||||
| 	err = json.Unmarshal(returnedBody, &post) | ||||
| 	if err != nil { | ||||
| 		log.Printf("Failed at unmarshal, %s\n", err) | ||||
| 	} | ||||
|  | ||||
| 	return post | ||||
| } | ||||
|  | ||||
| func (a *main.App) GetAniListLoggedInUserId() AniListUser { | ||||
| 	if (AniListJWT{}) == jwt { | ||||
| 		a.AniListLogin() | ||||
| func (a *App) GetAniListUserWatchingList(page int, perPage int, sort string) AniListCurrentUserWatchList { | ||||
| 	var user = a.GetAniListLoggedInUserId() | ||||
| 	type Variables struct { | ||||
| 		Page     int    `json:"page"` | ||||
| 		PerPage  int    `json:"perPage"` | ||||
| 		UserId   int    `json:"userId"` | ||||
| 		ListType string `json:"listType"` | ||||
| 		Status   string `json:"status"` | ||||
| 		Sort     string `json:"sort"` | ||||
| 	} | ||||
| 	body := struct { | ||||
| 		Query string `json:"query"` | ||||
| 		Query     string    `json:"query"` | ||||
| 		Variables Variables `json:"variables"` | ||||
| 	}{ | ||||
| 		Query: ` | ||||
| 		query { | ||||
| 		  Viewer { | ||||
| 			id | ||||
| 			name | ||||
| 		  } | ||||
| 		} | ||||
| 				query( | ||||
| 				  $page: Int | ||||
| 				  $perPage: Int | ||||
| 				  $userId: Int | ||||
| 				  $listType: MediaType | ||||
| 				  $status: MediaListStatus | ||||
| 				  $sort:[MediaListSort] | ||||
| 				) { | ||||
| 				  Page(page: $page, perPage: $perPage) { | ||||
| 					pageInfo { | ||||
| 					  total | ||||
| 					  perPage | ||||
| 					  currentPage | ||||
| 					  lastPage | ||||
| 					  hasNextPage | ||||
| 					} | ||||
| 					mediaList(userId: $userId, type: $listType, status: $status, sort: $sort) { | ||||
| 					  id | ||||
| 					  mediaId | ||||
| 					  userId | ||||
| 					  media { | ||||
| 						id | ||||
| 						idMal | ||||
| 						title { | ||||
| 						  romaji | ||||
| 						  english | ||||
| 						  native | ||||
| 						} | ||||
| 						description | ||||
| 						coverImage { | ||||
| 						  large | ||||
| 						} | ||||
| 						season | ||||
| 						seasonYear | ||||
| 						status | ||||
| 						episodes | ||||
| 						nextAiringEpisode{ | ||||
| 						  airingAt | ||||
| 						  timeUntilAiring | ||||
| 						  episode | ||||
| 						} | ||||
| 					  } | ||||
| 					  status | ||||
| 					  notes | ||||
| 					  progress | ||||
| 					  score | ||||
| 					  repeat | ||||
| 					  user { | ||||
| 						id | ||||
| 						name | ||||
| 						avatar{ | ||||
| 						  large | ||||
| 						  medium | ||||
| 						} | ||||
| 						statistics { | ||||
| 						  anime { | ||||
| 							count | ||||
| 							statuses { | ||||
| 							  status | ||||
| 							  count | ||||
| 							} | ||||
| 						  } | ||||
| 						} | ||||
| 					  } | ||||
| 					} | ||||
| 				  } | ||||
| 				} | ||||
| 	`, | ||||
| 		Variables: Variables{ | ||||
| 			Page:     page, | ||||
| 			PerPage:  perPage, | ||||
| 			UserId:   user.Data.Viewer.ID, | ||||
| 			ListType: "ANIME", | ||||
| 			Status:   "CURRENT", | ||||
| 			Sort:     sort, | ||||
| 		}, | ||||
| 	} | ||||
|  | ||||
| 	user, _ := AniListQuery(body, true) | ||||
| 	returnedBody, _ := AniListQuery(body, true) | ||||
|  | ||||
| 	var post AniListUser | ||||
| 	err := json.Unmarshal(user, &post) | ||||
| 	var post AniListCurrentUserWatchList | ||||
| 	err := json.Unmarshal(returnedBody, &post) | ||||
| 	if err != nil { | ||||
| 		log.Printf("Failed at unmarshal, %s\n", err) | ||||
| 	} | ||||
|  | ||||
| 	fmt.Println("UserInfo: ", post) | ||||
| 	// Getting the real total, finding the real last page and storing that in the Page info | ||||
| 	statuses := post.Data.Page.MediaList[0].User.Statistics.Anime.Statuses | ||||
| 	var total int | ||||
| 	for _, status := range statuses { | ||||
| 		if status.Status == "CURRENT" { | ||||
| 			total = status.Count | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	lastPage := total / perPage | ||||
|  | ||||
| 	post.Data.Page.PageInfo.Total = total | ||||
| 	post.Data.Page.PageInfo.LastPage = lastPage | ||||
|  | ||||
| 	return post | ||||
|  | ||||
| } | ||||
|  | ||||
| func (a *main.App) GetAniListUserWatchingList() { | ||||
|  | ||||
| } | ||||
|   | ||||
							
								
								
									
										143
									
								
								AniListTypes.go
									
									
									
									
									
								
							
							
						
						
									
										143
									
								
								AniListTypes.go
									
									
									
									
									
								
							| @@ -1 +1,144 @@ | ||||
| package main | ||||
|  | ||||
| type AniListJWT struct { | ||||
| 	TokenType    string `json:"token_type"` | ||||
| 	ExpiresIn    int    `json:"expires_in"` | ||||
| 	AccessToken  string `json:"access_token"` | ||||
| 	RefreshToken string `json:"refresh_token"` | ||||
| } | ||||
|  | ||||
| type AniListUser struct { | ||||
| 	Data struct { | ||||
| 		Viewer struct { | ||||
| 			ID   int    `json:"id"` | ||||
| 			Name string `json:"name"` | ||||
| 		} `json:"Viewer"` | ||||
| 	} `json:"data"` | ||||
| } | ||||
|  | ||||
| type AniListCurrentUserWatchList struct { | ||||
| 	Data struct { | ||||
| 		Page struct { | ||||
| 			PageInfo struct { | ||||
| 				Total       int  `json:"total"` | ||||
| 				PerPage     int  `json:"perPage"` | ||||
| 				CurrentPage int  `json:"currentPage"` | ||||
| 				LastPage    int  `json:"lastPage"` | ||||
| 				HasNextPage bool `json:"hasNextPage"` | ||||
| 			} `json:"pageInfo"` | ||||
| 			MediaList []struct { | ||||
| 				ID      int `json:"id"` | ||||
| 				MediaID int `json:"mediaId"` | ||||
| 				UserID  int `json:"userId"` | ||||
| 				Media   struct { | ||||
| 					ID    int `json:"id"` | ||||
| 					IDMal int `json:"idMal"` | ||||
| 					Title struct { | ||||
| 						Romaji  string `json:"romaji"` | ||||
| 						English string `json:"english"` | ||||
| 						Native  string `json:"native"` | ||||
| 					} `json:"title"` | ||||
| 					Description string `json:"description"` | ||||
| 					CoverImage  struct { | ||||
| 						Large string `json:"large"` | ||||
| 					} `json:"coverImage"` | ||||
| 					Season            string `json:"season"` | ||||
| 					SeasonYear        int    `json:"seasonYear"` | ||||
| 					Status            string `json:"status"` | ||||
| 					Episodes          int    `json:"episodes"` | ||||
| 					NextAiringEpisode struct { | ||||
| 						AiringAt        int `json:"airingAt"` | ||||
| 						TimeUntilAiring int `json:"timeUntilAiring"` | ||||
| 						Episode         int `json:"episode"` | ||||
| 					} `json:"nextAiringEpisode"` | ||||
| 				} `json:"media"` | ||||
| 				Status   string `json:"status"` | ||||
| 				Notes    string `json:"notes"` | ||||
| 				Progress int    `json:"progress"` | ||||
| 				Score    int    `json:"score"` | ||||
| 				Repeat   int    `json:"repeat"` | ||||
| 				User     struct { | ||||
| 					ID     int    `json:"id"` | ||||
| 					Name   string `json:"name"` | ||||
| 					Avatar struct { | ||||
| 						Large  string `json:"large"` | ||||
| 						Medium string `json:"medium"` | ||||
| 					} `json:"avatar"` | ||||
| 					Statistics struct { | ||||
| 						Anime struct { | ||||
| 							Count    int `json:"count"` | ||||
| 							Statuses []struct { | ||||
| 								Status string `json:"status"` | ||||
| 								Count  int    `json:"count"` | ||||
| 							} `json:"statuses"` | ||||
| 						} `json:"anime"` | ||||
| 					} `json:"statistics"` | ||||
| 				} `json:"user"` | ||||
| 			} `json:"mediaList"` | ||||
| 		} `json:"Page"` | ||||
| 	} `json:"data"` | ||||
| } | ||||
|  | ||||
| var MediaListSort = struct { | ||||
| 	MediaId               string | ||||
| 	MediaIdDesc           string | ||||
| 	Score                 string | ||||
| 	ScoreDesc             string | ||||
| 	Status                string | ||||
| 	StatusDesc            string | ||||
| 	Progress              string | ||||
| 	ProgressDesc          string | ||||
| 	ProgressVolumes       string | ||||
| 	ProgressVolumesDesc   string | ||||
| 	Repeat                string | ||||
| 	RepeatDesc            string | ||||
| 	Priority              string | ||||
| 	PriorityDesc          string | ||||
| 	StartedOn             string | ||||
| 	StartedOnDesc         string | ||||
| 	FinishedOn            string | ||||
| 	FinishedOnDesc        string | ||||
| 	AddedTime             string | ||||
| 	AddedTimeDesc         string | ||||
| 	UpdatedTime           string | ||||
| 	UpdatedTimeDesc       string | ||||
| 	MediaTitleRomaji      string | ||||
| 	MediaTitleRomajiDesc  string | ||||
| 	MediaTitleEnglish     string | ||||
| 	MediaTitleEnglishDesc string | ||||
| 	MediaTitleNative      string | ||||
| 	MediaTitleNativeDesc  string | ||||
| 	MediaPopularity       string | ||||
| 	MediaPopularityDesc   string | ||||
| }{ | ||||
| 	MediaId:               "MEDIA_ID", | ||||
| 	MediaIdDesc:           "MEDIA_ID_DESC", | ||||
| 	Score:                 "SCORE", | ||||
| 	ScoreDesc:             "SCORE_DESC", | ||||
| 	Status:                "STATUS", | ||||
| 	StatusDesc:            "STATUS_DESC", | ||||
| 	Progress:              "PROGRESS", | ||||
| 	ProgressDesc:          "PROGRESS_DESC", | ||||
| 	ProgressVolumes:       "PROGRESS_VOLUMES", | ||||
| 	ProgressVolumesDesc:   "PROGRESS_VOLUMES_DESC", | ||||
| 	Repeat:                "REPEAT", | ||||
| 	RepeatDesc:            "REPEAT_DESC", | ||||
| 	Priority:              "PRIORITY", | ||||
| 	PriorityDesc:          "PRIORITY_DESC", | ||||
| 	StartedOn:             "STARTED_ON", | ||||
| 	StartedOnDesc:         "STARTED_ON_DESC", | ||||
| 	FinishedOn:            "FINISHED_ON", | ||||
| 	FinishedOnDesc:        "FINISHED_ON_DESC", | ||||
| 	AddedTime:             "ADDED_TIME", | ||||
| 	AddedTimeDesc:         "ADDED_TIME_DESC", | ||||
| 	UpdatedTime:           "UPDATED_TIME", | ||||
| 	UpdatedTimeDesc:       "UPDATED_TIME_DESC", | ||||
| 	MediaTitleRomaji:      "MEDIA_TITLE_ROMAJI", | ||||
| 	MediaTitleRomajiDesc:  "MEDIA_TITLE_ROMAJI_DESC", | ||||
| 	MediaTitleEnglish:     "MEDIA_TITLE_ENGLISH", | ||||
| 	MediaTitleEnglishDesc: "MEDIA_TITLE_ENGLISH_DESC", | ||||
| 	MediaTitleNative:      "MEDIA_TITLE_NATIVE", | ||||
| 	MediaTitleNativeDesc:  "MEDIA_TITLE_NATIVE_DESC", | ||||
| 	MediaPopularity:       "MEDIA_POPULARITY", | ||||
| 	MediaPopularityDesc:   "MEDIA_POPULARITY_DESC", | ||||
| } | ||||
|   | ||||
| @@ -1 +1,170 @@ | ||||
| package main | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"encoding/json" | ||||
| 	"fmt" | ||||
| 	"github.com/99designs/keyring" | ||||
| 	"github.com/wailsapp/wails/v2/pkg/runtime" | ||||
| 	"io" | ||||
| 	"log" | ||||
| 	"net/http" | ||||
| 	"net/url" | ||||
| 	"os" | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
| 	"sync" | ||||
| ) | ||||
|  | ||||
| var jwt AniListJWT | ||||
|  | ||||
| var ring, _ = keyring.Open(keyring.Config{ | ||||
| 	ServiceName: "AniTrack", | ||||
| }) | ||||
|  | ||||
| var ctxShutdown, cancel = context.WithCancel(context.Background()) | ||||
|  | ||||
| func (a *App) AniListLogin() { | ||||
| 	if (AniListJWT{}) == jwt { | ||||
| 		tokenType, err := ring.Get("anilistTokenType") | ||||
| 		expiresIn, err := ring.Get("anilistTokenExpiresIn") | ||||
| 		accessToken, err := ring.Get("anilistAccessToken") | ||||
| 		refreshToken, err := ring.Get("anilistRefreshToken") | ||||
| 		if err != nil { | ||||
| 			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() | ||||
| 		} else { | ||||
| 			jwt.TokenType = string(tokenType.Data) | ||||
| 			jwt.AccessToken = string(accessToken.Data) | ||||
| 			jwt.RefreshToken = string(refreshToken.Data) | ||||
| 			expiresInString := string(expiresIn.Data) | ||||
| 			jwt.ExpiresIn, _ = strconv.Atoi(expiresInString) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| 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) | ||||
| 			_ = ring.Set(keyring.Item{ | ||||
| 				Key:  "anilistTokenType", | ||||
| 				Data: []byte(jwt.TokenType), | ||||
| 			}) | ||||
| 			_ = ring.Set(keyring.Item{ | ||||
| 				Key:  "anilistTokenExpiresIn", | ||||
| 				Data: []byte(string(jwt.ExpiresIn)), | ||||
| 			}) | ||||
| 			_ = ring.Set(keyring.Item{ | ||||
| 				Key:  "anilistAccessToken", | ||||
| 				Data: []byte(jwt.AccessToken), | ||||
| 			}) | ||||
| 			_ = ring.Set(keyring.Item{ | ||||
| 				Key:  "anilistRefreshToken", | ||||
| 				Data: []byte(jwt.RefreshToken), | ||||
| 			}) | ||||
| 			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) AniListJWT { | ||||
| 	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 AniListJWT | ||||
| 	err = json.Unmarshal(returnedBody, &post) | ||||
| 	if err != nil { | ||||
| 		log.Printf("Failed at unmarshal, %s\n", err) | ||||
| 	} | ||||
|  | ||||
| 	return post | ||||
| } | ||||
|  | ||||
| func (a *App) GetAniListLoggedInUserId() AniListUser { | ||||
| 	a.AniListLogin() | ||||
| 	body := struct { | ||||
| 		Query string `json:"query"` | ||||
| 	}{ | ||||
| 		Query: ` | ||||
| 		query { | ||||
| 		  Viewer { | ||||
| 			id | ||||
| 			name | ||||
| 		  } | ||||
| 		} | ||||
| 	`, | ||||
| 	} | ||||
|  | ||||
| 	user, _ := AniListQuery(body, true) | ||||
|  | ||||
| 	var post AniListUser | ||||
| 	err := json.Unmarshal(user, &post) | ||||
| 	if err != nil { | ||||
| 		log.Printf("Failed at unmarshal, %s\n", err) | ||||
| 	} | ||||
|  | ||||
| 	return post | ||||
|  | ||||
| } | ||||
|   | ||||
							
								
								
									
										5
									
								
								frontend/wailsjs/go/main/App.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										5
									
								
								frontend/wailsjs/go/main/App.d.ts
									
									
									
									
										vendored
									
									
								
							| @@ -1,5 +1,6 @@ | ||||
| // Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL | ||||
| // This file is automatically generated. DO NOT EDIT | ||||
| import {main} from '../models'; | ||||
|  | ||||
| export function AniListLogin():Promise<void>; | ||||
|  | ||||
| @@ -7,4 +8,8 @@ export function AniListSearch(arg1:string):Promise<any>; | ||||
|  | ||||
| export function GetAniListItem(arg1:number):Promise<any>; | ||||
|  | ||||
| export function GetAniListLoggedInUserId():Promise<main.AniListUser>; | ||||
|  | ||||
| export function GetAniListUserWatchingList(arg1:number,arg2:number,arg3:string):Promise<main.AniListCurrentUserWatchList>; | ||||
|  | ||||
| export function Greet(arg1:string):Promise<string>; | ||||
|   | ||||
| @@ -14,6 +14,14 @@ export function GetAniListItem(arg1) { | ||||
|   return window['go']['main']['App']['GetAniListItem'](arg1); | ||||
| } | ||||
|  | ||||
| export function GetAniListLoggedInUserId() { | ||||
|   return window['go']['main']['App']['GetAniListLoggedInUserId'](); | ||||
| } | ||||
|  | ||||
| export function GetAniListUserWatchingList(arg1, arg2, arg3) { | ||||
|   return window['go']['main']['App']['GetAniListUserWatchingList'](arg1, arg2, arg3); | ||||
| } | ||||
|  | ||||
| export function Greet(arg1) { | ||||
|   return window['go']['main']['App']['Greet'](arg1); | ||||
| } | ||||
|   | ||||
							
								
								
									
										67
									
								
								frontend/wailsjs/go/models.ts
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										67
									
								
								frontend/wailsjs/go/models.ts
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,67 @@ | ||||
| export namespace main { | ||||
| 	 | ||||
| 	export class AniListCurrentUserWatchList { | ||||
| 	    // Go type: struct { Page struct { PageInfo struct { Total int "json:\"total\""; PerPage int "json:\"perPage\""; CurrentPage int "json:\"currentPage\""; LastPage int "json:\"lastPage\""; HasNextPage bool "json:\"hasNextPage\"" } "json:\"pageInfo\""; MediaList []struct { ID int "json:\"id\""; MediaID int "json:\"mediaId\""; UserID int "json:\"userId\""; Media struct { ID int "json:\"id\""; IDMal int "json:\"idMal\""; Title struct { Romaji string "json:\"romaji\""; English string "json:\"english\""; Native string "json:\"native\"" } "json:\"title\""; Description string "json:\"description\""; CoverImage struct { Large string "json:\"large\"" } "json:\"coverImage\""; Season string "json:\"season\""; SeasonYear int "json:\"seasonYear\""; Status string "json:\"status\""; Episodes int "json:\"episodes\""; NextAiringEpisode struct { AiringAt int "json:\"airingAt\""; TimeUntilAiring int "json:\"timeUntilAiring\""; Episode int "json:\"episode\"" } "json:\"nextAiringEpisode\"" } "json:\"media\""; Status string "json:\"status\""; Notes string "json:\"notes\""; Progress int "json:\"progress\""; Score int "json:\"score\""; Repeat int "json:\"repeat\""; User struct { ID int "json:\"id\""; Name string "json:\"name\""; Avatar struct { Large string "json:\"large\""; Medium string "json:\"medium\"" } "json:\"avatar\""; Statistics struct { Anime struct { Count int "json:\"count\""; Statuses []struct { Status string "json:\"status\""; Count int "json:\"count\"" } "json:\"statuses\"" } "json:\"anime\"" } "json:\"statistics\"" } "json:\"user\"" } "json:\"mediaList\"" } "json:\"Page\"" } | ||||
| 	    data: any; | ||||
| 	 | ||||
| 	    static createFrom(source: any = {}) { | ||||
| 	        return new AniListCurrentUserWatchList(source); | ||||
| 	    } | ||||
| 	 | ||||
| 	    constructor(source: any = {}) { | ||||
| 	        if ('string' === typeof source) source = JSON.parse(source); | ||||
| 	        this.data = this.convertValues(source["data"], Object); | ||||
| 	    } | ||||
| 	 | ||||
| 		convertValues(a: any, classs: any, asMap: boolean = false): any { | ||||
| 		    if (!a) { | ||||
| 		        return a; | ||||
| 		    } | ||||
| 		    if (a.slice && a.map) { | ||||
| 		        return (a as any[]).map(elem => this.convertValues(elem, classs)); | ||||
| 		    } else if ("object" === typeof a) { | ||||
| 		        if (asMap) { | ||||
| 		            for (const key of Object.keys(a)) { | ||||
| 		                a[key] = new classs(a[key]); | ||||
| 		            } | ||||
| 		            return a; | ||||
| 		        } | ||||
| 		        return new classs(a); | ||||
| 		    } | ||||
| 		    return a; | ||||
| 		} | ||||
| 	} | ||||
| 	export class AniListUser { | ||||
| 	    // Go type: struct { Viewer struct { ID int "json:\"id\""; Name string "json:\"name\"" } "json:\"Viewer\"" } | ||||
| 	    data: any; | ||||
| 	 | ||||
| 	    static createFrom(source: any = {}) { | ||||
| 	        return new AniListUser(source); | ||||
| 	    } | ||||
| 	 | ||||
| 	    constructor(source: any = {}) { | ||||
| 	        if ('string' === typeof source) source = JSON.parse(source); | ||||
| 	        this.data = this.convertValues(source["data"], Object); | ||||
| 	    } | ||||
| 	 | ||||
| 		convertValues(a: any, classs: any, asMap: boolean = false): any { | ||||
| 		    if (!a) { | ||||
| 		        return a; | ||||
| 		    } | ||||
| 		    if (a.slice && a.map) { | ||||
| 		        return (a as any[]).map(elem => this.convertValues(elem, classs)); | ||||
| 		    } else if ("object" === typeof a) { | ||||
| 		        if (asMap) { | ||||
| 		            for (const key of Object.keys(a)) { | ||||
| 		                a[key] = new classs(a[key]); | ||||
| 		            } | ||||
| 		            return a; | ||||
| 		        } | ||||
| 		        return new classs(a); | ||||
| 		    } | ||||
| 		    return a; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| } | ||||
|  | ||||
		Reference in New Issue
	
	Block a user