added MAL Login
This commit is contained in:
parent
43a054ac92
commit
fa3304db92
@ -31,14 +31,13 @@ func (a *App) CheckIfAniListLoggedIn() bool {
|
|||||||
expiresIn, err := aniRing.Get("anilistTokenExpiresIn")
|
expiresIn, err := aniRing.Get("anilistTokenExpiresIn")
|
||||||
accessToken, err := aniRing.Get("anilistAccessToken")
|
accessToken, err := aniRing.Get("anilistAccessToken")
|
||||||
refreshToken, err := aniRing.Get("anilistRefreshToken")
|
refreshToken, err := aniRing.Get("anilistRefreshToken")
|
||||||
if err != nil {
|
if err != nil || len(accessToken.Data) == 0 {
|
||||||
return false
|
return false
|
||||||
} else {
|
} else {
|
||||||
aniListJwt.TokenType = string(tokenType.Data)
|
aniListJwt.TokenType = string(tokenType.Data)
|
||||||
aniListJwt.AccessToken = string(accessToken.Data)
|
aniListJwt.AccessToken = string(accessToken.Data)
|
||||||
aniListJwt.RefreshToken = string(refreshToken.Data)
|
aniListJwt.RefreshToken = string(refreshToken.Data)
|
||||||
expiresInString := string(expiresIn.Data)
|
aniListJwt.ExpiresIn, _ = strconv.Atoi(string(expiresIn.Data))
|
||||||
aniListJwt.ExpiresIn, _ = strconv.Atoi(expiresInString)
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -52,7 +51,7 @@ func (a *App) AniListLogin() {
|
|||||||
expiresIn, err := aniRing.Get("anilistTokenExpiresIn")
|
expiresIn, err := aniRing.Get("anilistTokenExpiresIn")
|
||||||
accessToken, err := aniRing.Get("anilistAccessToken")
|
accessToken, err := aniRing.Get("anilistAccessToken")
|
||||||
refreshToken, err := aniRing.Get("anilistRefreshToken")
|
refreshToken, err := aniRing.Get("anilistRefreshToken")
|
||||||
if err != nil {
|
if err != nil || len(accessToken.Data) == 0 {
|
||||||
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"
|
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)
|
runtime.BrowserOpenURL(a.ctx, getAniListCodeUrl)
|
||||||
|
|
||||||
@ -64,8 +63,7 @@ func (a *App) AniListLogin() {
|
|||||||
aniListJwt.TokenType = string(tokenType.Data)
|
aniListJwt.TokenType = string(tokenType.Data)
|
||||||
aniListJwt.AccessToken = string(accessToken.Data)
|
aniListJwt.AccessToken = string(accessToken.Data)
|
||||||
aniListJwt.RefreshToken = string(refreshToken.Data)
|
aniListJwt.RefreshToken = string(refreshToken.Data)
|
||||||
expiresInString := string(expiresIn.Data)
|
aniListJwt.ExpiresIn, _ = strconv.Atoi(string(expiresIn.Data))
|
||||||
aniListJwt.ExpiresIn, _ = strconv.Atoi(expiresInString)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -89,7 +87,7 @@ func handleAniListCallback(wg *sync.WaitGroup) {
|
|||||||
})
|
})
|
||||||
_ = aniRing.Set(keyring.Item{
|
_ = aniRing.Set(keyring.Item{
|
||||||
Key: "anilistTokenExpiresIn",
|
Key: "anilistTokenExpiresIn",
|
||||||
Data: []byte(string(aniListJwt.ExpiresIn)),
|
Data: []byte(strconv.Itoa(aniListJwt.ExpiresIn)),
|
||||||
})
|
})
|
||||||
_ = aniRing.Set(keyring.Item{
|
_ = aniRing.Set(keyring.Item{
|
||||||
Key: "anilistAccessToken",
|
Key: "anilistAccessToken",
|
||||||
|
39
MALTypes.go
Normal file
39
MALTypes.go
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
type MyAnimeListJWT struct {
|
||||||
|
TokenType string `json:"token_type"`
|
||||||
|
ExpiresIn int `json:"expires_in"`
|
||||||
|
AccessToken string `json:"access_token"`
|
||||||
|
RefreshToken string `json:"refresh_token"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type MyAnimeListUser struct {
|
||||||
|
Id int32 `json:"id" ts_type:"id"`
|
||||||
|
Name string `json:"name" ts_type:"name"`
|
||||||
|
Picture string `json:"picture" ts_type:"picture"`
|
||||||
|
Gender string `json:"gender" ts_type:"gender"`
|
||||||
|
Birthday string `json:"birthday" ts_type:"birthday"`
|
||||||
|
Location string `json:"location" ts_type:"location"`
|
||||||
|
JoinedAt string `json:"joined_at" ts_type:"joinedAt"`
|
||||||
|
AnimeStatistics `json:"anime_statistics" ts_type:"AnimeStatistics"`
|
||||||
|
TimeZone string `json:"time_zone" ts_type:"timeZone"`
|
||||||
|
IsSupporter bool `json:"is_supporter" ts_type:"isSupporter"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type AnimeStatistics struct {
|
||||||
|
NumItemsWatching int `json:"num_items_watching" ts_type:"numItemsWatching"`
|
||||||
|
NumItemsCompleted int `json:"num_items_completed" ts_type:"numItemsCompleted"`
|
||||||
|
NumItemsOnHold int `json:"num_items_on_hold" ts_type:"numItemsOnHold"`
|
||||||
|
NumItemsDropped int `json:"num_items_dropped" ts_type:"numItemsDropped"`
|
||||||
|
NumItemsPlanToWatch int `json:"num_items_plan_to_watch" ts_type:"numItemsPlanToWatch"`
|
||||||
|
NumItems int `json:"num_items" ts_type:"numItems"`
|
||||||
|
NumDaysWatched float64 `json:"num_days_watched" ts_type:"numDaysWatched"`
|
||||||
|
NumDaysWatching float64 `json:"num_days_watching" ts_type:"numDaysWatching"`
|
||||||
|
NumDaysCompleted float64 `json:"num_days_completed" ts_type:"numDaysCompleted"`
|
||||||
|
NumDaysOnHold float64 `json:"num_days_on_hold" ts_type:"numDaysOnHold"`
|
||||||
|
NumDaysDropped float64 `json:"num_days_dropped" ts_type:"numDaysDropped"`
|
||||||
|
NumDays float64 `json:"num_days" ts_type:"numDays"`
|
||||||
|
NumEpisodes int `json:"num_episodes" ts_type:"numEpisodes"`
|
||||||
|
NumTimesRewatched int `json:"num_times_rewatched" ts_type:"numTimesRewatched"`
|
||||||
|
MeanScore float64 `json:"mean_score" ts_type:"meanScore"`
|
||||||
|
}
|
250
MALUserFunctions.go
Normal file
250
MALUserFunctions.go
Normal file
@ -0,0 +1,250 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"math/rand"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/99designs/keyring"
|
||||||
|
"github.com/wailsapp/wails/v2/pkg/runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
var myAnimeListJwt MyAnimeListJWT
|
||||||
|
|
||||||
|
var myAnimeListRing, _ = keyring.Open(keyring.Config{
|
||||||
|
ServiceName: "AniTrack",
|
||||||
|
})
|
||||||
|
|
||||||
|
var myAnimeListCtxShutdown, myAnimeListCancel = context.WithCancel(context.Background())
|
||||||
|
|
||||||
|
type CodeVerifier struct {
|
||||||
|
Value string
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
length = 32
|
||||||
|
)
|
||||||
|
|
||||||
|
func base64URLEncode(str []byte) string {
|
||||||
|
encoded := base64.StdEncoding.EncodeToString(str)
|
||||||
|
encoded = strings.Replace(encoded, "+", "-", -1)
|
||||||
|
encoded = strings.Replace(encoded, "/", "_", -1)
|
||||||
|
encoded = strings.Replace(encoded, "=", "", -1)
|
||||||
|
return encoded
|
||||||
|
}
|
||||||
|
|
||||||
|
func verifier() (*CodeVerifier, error) {
|
||||||
|
r := rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||||
|
b := make([]byte, length, length)
|
||||||
|
for i := 0; i < length; i++ {
|
||||||
|
b[i] = byte(r.Intn(255))
|
||||||
|
}
|
||||||
|
return CreateCodeVerifierFromBytes(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateCodeVerifierFromBytes(b []byte) (*CodeVerifier, error) {
|
||||||
|
return &CodeVerifier{
|
||||||
|
Value: base64URLEncode(b),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *CodeVerifier) CodeChallengeS256() string {
|
||||||
|
h := sha256.New()
|
||||||
|
h.Write([]byte(v.Value))
|
||||||
|
return base64URLEncode(h.Sum(nil))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) CheckIfMyAnimeListLoggedIn() bool {
|
||||||
|
if (MyAnimeListJWT{} == myAnimeListJwt) {
|
||||||
|
tokenType, err := myAnimeListRing.Get("MyAnimeListTokenType")
|
||||||
|
expiresIn, err := myAnimeListRing.Get("MyAnimeListExpiresIn")
|
||||||
|
accessToken, err := myAnimeListRing.Get("MyAnimeListAccessToken")
|
||||||
|
refreshToken, err := myAnimeListRing.Get("MyAnimeListRefreshToken")
|
||||||
|
if err != nil || len(accessToken.Data) == 0 {
|
||||||
|
return false
|
||||||
|
} else {
|
||||||
|
myAnimeListJwt.TokenType = string(tokenType.Data)
|
||||||
|
myAnimeListJwt.ExpiresIn, err = strconv.Atoi(string(expiresIn.Data))
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("unable to convert string to int")
|
||||||
|
}
|
||||||
|
myAnimeListJwt.AccessToken = string(accessToken.Data)
|
||||||
|
myAnimeListJwt.RefreshToken = string(refreshToken.Data)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) MyAnimeListLogin() {
|
||||||
|
if a.CheckIfMyAnimeListLoggedIn() == false {
|
||||||
|
tokenType, err := myAnimeListRing.Get("MyAnimeListTokenType")
|
||||||
|
expiresIn, err := myAnimeListRing.Get("MyAnimeListExpiresIn")
|
||||||
|
accessToken, err := myAnimeListRing.Get("MyAnimeListAccessToken")
|
||||||
|
refreshToken, err := myAnimeListRing.Get("MyAnimeListRefreshToken")
|
||||||
|
if err != nil || len(accessToken.Data) == 0 {
|
||||||
|
verifier, _ := verifier()
|
||||||
|
getMyAnimeListCodeUrl := "https://myanimelist.net/v1/oauth2/authorize?response_type=code&client_id=" + os.Getenv("MAL_CLIENT_ID") + "&redirect_uri=" + os.Getenv("MAL_CALLBACK_URI") + "&code_challenge=" + verifier.Value + "&code_challenge_method=plain"
|
||||||
|
runtime.BrowserOpenURL(a.ctx, getMyAnimeListCodeUrl)
|
||||||
|
serverDone := &sync.WaitGroup{}
|
||||||
|
serverDone.Add(1)
|
||||||
|
handleMyAnimeListCallback(serverDone, verifier)
|
||||||
|
serverDone.Wait()
|
||||||
|
} else {
|
||||||
|
myAnimeListJwt.TokenType = string(tokenType.Data)
|
||||||
|
myAnimeListJwt.ExpiresIn, err = strconv.Atoi(string(expiresIn.Data))
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("unable to convert string to int in Login function")
|
||||||
|
}
|
||||||
|
myAnimeListJwt.AccessToken = string(accessToken.Data)
|
||||||
|
myAnimeListJwt.RefreshToken = string(refreshToken.Data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleMyAnimeListCallback(wg *sync.WaitGroup, verifier *CodeVerifier) {
|
||||||
|
srv := &http.Server{Addr: ":6734"}
|
||||||
|
http.HandleFunc("/callback", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
select {
|
||||||
|
case <-myAnimeListCtxShutdown.Done():
|
||||||
|
fmt.Println("Shutting down...")
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
content := r.FormValue("code")
|
||||||
|
|
||||||
|
if content != "" {
|
||||||
|
myAnimeListJwt = getMyAnimeListAuthorizationToken(content, verifier)
|
||||||
|
_ = myAnimeListRing.Set(keyring.Item{
|
||||||
|
Key: "MyAnimeListTokenType",
|
||||||
|
Data: []byte(myAnimeListJwt.TokenType),
|
||||||
|
})
|
||||||
|
_ = myAnimeListRing.Set(keyring.Item{
|
||||||
|
Key: "MyAnimeListExpiresIn",
|
||||||
|
Data: []byte(strconv.Itoa(myAnimeListJwt.ExpiresIn)),
|
||||||
|
})
|
||||||
|
_ = myAnimeListRing.Set(keyring.Item{
|
||||||
|
Key: "MyAnimeListAccessToken",
|
||||||
|
Data: []byte(myAnimeListJwt.AccessToken),
|
||||||
|
})
|
||||||
|
_ = myAnimeListRing.Set(keyring.Item{
|
||||||
|
Key: "MyAnimeListRefreshToken",
|
||||||
|
Data: []byte(myAnimeListJwt.RefreshToken),
|
||||||
|
})
|
||||||
|
fmt.Println("Shutting down...")
|
||||||
|
myAnimeListCancel()
|
||||||
|
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 getMyAnimeListAuthorizationToken(content string, verifier *CodeVerifier) MyAnimeListJWT {
|
||||||
|
dataForURLs := struct {
|
||||||
|
GrantType string `json:"grant_type"`
|
||||||
|
ClientID string `json:"client_id"`
|
||||||
|
ClientSecret string `json:"client_secret"`
|
||||||
|
RedirectURI string `json:"redirect_uri"`
|
||||||
|
Code string `json:"code"`
|
||||||
|
CodeVerifier *CodeVerifier `json:"code_verifier"`
|
||||||
|
}{
|
||||||
|
GrantType: "authorization_code",
|
||||||
|
ClientID: os.Getenv("MAL_CLIENT_ID"),
|
||||||
|
ClientSecret: os.Getenv("MAL_CLIENT_SECRET"),
|
||||||
|
RedirectURI: os.Getenv("MAL_CALLBACK_URI"),
|
||||||
|
Code: content,
|
||||||
|
CodeVerifier: verifier,
|
||||||
|
}
|
||||||
|
|
||||||
|
data := url.Values{}
|
||||||
|
data.Set("grant_type", dataForURLs.GrantType)
|
||||||
|
data.Set("client_id", dataForURLs.ClientID)
|
||||||
|
data.Set("client_secret", dataForURLs.ClientSecret)
|
||||||
|
data.Set("redirect_uri", dataForURLs.RedirectURI)
|
||||||
|
data.Set("code", dataForURLs.Code)
|
||||||
|
data.Set("code_verifier", dataForURLs.CodeVerifier.Value)
|
||||||
|
|
||||||
|
response, err := http.NewRequest("POST", "https://myanimelist.net/v1/oauth2/token", 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")
|
||||||
|
|
||||||
|
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 MyAnimeListJWT
|
||||||
|
err = json.Unmarshal(returnedBody, &post)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Failed at unmarshal, %s\n", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return post
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) GetMyAnimeListLoggedInUser() MyAnimeListUser {
|
||||||
|
a.MyAnimeListLogin()
|
||||||
|
|
||||||
|
client := &http.Client{}
|
||||||
|
|
||||||
|
req, _ := http.NewRequest("GET", "https://api.myanimelist.net/v2/users/@me?fields=anime_statistics", nil)
|
||||||
|
|
||||||
|
req.Header.Add("Content-Type", "application/json")
|
||||||
|
req.Header.Add("Authorization", "Bearer "+myAnimeListJwt.AccessToken)
|
||||||
|
req.Header.Add("myAnimeList-api-key", os.Getenv("MAL_CLIENT_ID"))
|
||||||
|
|
||||||
|
response, err := client.Do(req)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Failed at request, %s\n", err)
|
||||||
|
return MyAnimeListUser{}
|
||||||
|
}
|
||||||
|
|
||||||
|
defer response.Body.Close()
|
||||||
|
|
||||||
|
var user MyAnimeListUser
|
||||||
|
|
||||||
|
respBody, _ := io.ReadAll(response.Body)
|
||||||
|
|
||||||
|
err = json.Unmarshal(respBody, &user)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Failed at unmarshal, %s\n", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return user
|
||||||
|
}
|
@ -28,7 +28,7 @@ func (a *App) CheckIfSimklLoggedIn() bool {
|
|||||||
tokenType, err := simklRing.Get("SimklTokenType")
|
tokenType, err := simklRing.Get("SimklTokenType")
|
||||||
accessToken, err := simklRing.Get("SimklAccessToken")
|
accessToken, err := simklRing.Get("SimklAccessToken")
|
||||||
scope, err := simklRing.Get("SimklScope")
|
scope, err := simklRing.Get("SimklScope")
|
||||||
if err != nil {
|
if err != nil || len(accessToken.Data) == 0 {
|
||||||
return false
|
return false
|
||||||
} else {
|
} else {
|
||||||
simklJwt.TokenType = string(tokenType.Data)
|
simklJwt.TokenType = string(tokenType.Data)
|
||||||
@ -46,7 +46,7 @@ func (a *App) SimklLogin() {
|
|||||||
tokenType, err := simklRing.Get("SimklTokenType")
|
tokenType, err := simklRing.Get("SimklTokenType")
|
||||||
accessToken, err := simklRing.Get("SimklAccessToken")
|
accessToken, err := simklRing.Get("SimklAccessToken")
|
||||||
scope, err := simklRing.Get("SimklScope")
|
scope, err := simklRing.Get("SimklScope")
|
||||||
if err != nil {
|
if err != nil || len(accessToken.Data) == 0 {
|
||||||
getSimklCodeUrl := "https://simkl.com/oauth/authorize?response_type=code&client_id=" + os.Getenv("SIMKL_CLIENT_ID") + "&redirect_uri=" + os.Getenv("SIMKL_CALLBACK_URI")
|
getSimklCodeUrl := "https://simkl.com/oauth/authorize?response_type=code&client_id=" + os.Getenv("SIMKL_CLIENT_ID") + "&redirect_uri=" + os.Getenv("SIMKL_CALLBACK_URI")
|
||||||
runtime.BrowserOpenURL(a.ctx, getSimklCodeUrl)
|
runtime.BrowserOpenURL(a.ctx, getSimklCodeUrl)
|
||||||
|
|
||||||
|
3
app.go
3
app.go
@ -2,7 +2,6 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"github.com/wailsapp/wails/v2/pkg/runtime"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// App struct
|
// App struct
|
||||||
@ -19,5 +18,5 @@ func NewApp() *App {
|
|||||||
// so we can call the runtime methods
|
// so we can call the runtime methods
|
||||||
func (a *App) startup(ctx context.Context) {
|
func (a *App) startup(ctx context.Context) {
|
||||||
a.ctx = ctx
|
a.ctx = ctx
|
||||||
runtime.WindowMaximise(ctx)
|
//runtime.WindowMaximise(ctx)
|
||||||
}
|
}
|
||||||
|
@ -3,9 +3,14 @@ vars {
|
|||||||
ANILIST_SECRET_TOKEN: {{process.env.ANILIST_SECRET_TOKEN}}
|
ANILIST_SECRET_TOKEN: {{process.env.ANILIST_SECRET_TOKEN}}
|
||||||
SIMKL_CLIENT_ID: {{process.env.SIMKL_CLIENT_ID}}
|
SIMKL_CLIENT_ID: {{process.env.SIMKL_CLIENT_ID}}
|
||||||
SIMKL_CLIENT_SECRET: {{process.env.SIMKL_CLIENT_SECRET}}
|
SIMKL_CLIENT_SECRET: {{process.env.SIMKL_CLIENT_SECRET}}
|
||||||
|
MAL_CLIENT_ID: {{process.env.MAL_CLIENT_ID}}
|
||||||
|
MAL_CLIENT_SECRET: {{process.env.MAL_CLIENT_SECRET}}
|
||||||
|
MAL_CALLBACK_URI: {{process.env.MAL_CALLBACK_URI}}
|
||||||
}
|
}
|
||||||
vars:secret [
|
vars:secret [
|
||||||
code,
|
code,
|
||||||
SIMKL_AUTH_TOKEN,
|
SIMKL_AUTH_TOKEN,
|
||||||
ANILIST_ACCESS_TOKEN
|
ANILIST_ACCESS_TOKEN,
|
||||||
|
MAL_CODE,
|
||||||
|
MAL_VERIFIER
|
||||||
]
|
]
|
||||||
|
@ -4,6 +4,8 @@
|
|||||||
anilistModal,
|
anilistModal,
|
||||||
aniListPrimary,
|
aniListPrimary,
|
||||||
aniListUser,
|
aniListUser,
|
||||||
|
malUser,
|
||||||
|
malLoggedIn,
|
||||||
aniListWatchlist,
|
aniListWatchlist,
|
||||||
GetAniListSingleItemAndOpenModal,
|
GetAniListSingleItemAndOpenModal,
|
||||||
simklLoggedIn,
|
simklLoggedIn,
|
||||||
@ -14,10 +16,12 @@
|
|||||||
import {
|
import {
|
||||||
CheckIfAniListLoggedIn,
|
CheckIfAniListLoggedIn,
|
||||||
CheckIfSimklLoggedIn,
|
CheckIfSimklLoggedIn,
|
||||||
|
CheckIfMyAnimeListLoggedIn,
|
||||||
GetAniListLoggedInUser,
|
GetAniListLoggedInUser,
|
||||||
GetAniListUserWatchingList,
|
GetAniListUserWatchingList,
|
||||||
GetSimklLoggedInUser,
|
GetSimklLoggedInUser,
|
||||||
SimklGetUserWatchlist,
|
SimklGetUserWatchlist,
|
||||||
|
GetMyAnimeListLoggedInUser,
|
||||||
} from "../wailsjs/go/main/App";
|
} from "../wailsjs/go/main/App";
|
||||||
import {MediaListSort} from "./anilist/types/AniListTypes";
|
import {MediaListSort} from "./anilist/types/AniListTypes";
|
||||||
import type {AniListCurrentUserWatchList} from "./anilist/types/AniListCurrentUserWatchListType"
|
import type {AniListCurrentUserWatchList} from "./anilist/types/AniListCurrentUserWatchListType"
|
||||||
@ -58,6 +62,15 @@
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
await CheckIfMyAnimeListLoggedIn().then(result => {
|
||||||
|
if (result) {
|
||||||
|
GetMyAnimeListLoggedInUser().then(result => {
|
||||||
|
malUser.set(result)
|
||||||
|
malLoggedIn.set(result)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
await CheckIfSimklLoggedIn().then(result => {
|
await CheckIfSimklLoggedIn().then(result => {
|
||||||
if (result) {
|
if (result) {
|
||||||
GetSimklLoggedInUser().then(result => {
|
GetSimklLoggedInUser().then(result => {
|
||||||
@ -85,7 +98,7 @@
|
|||||||
<main>
|
<main>
|
||||||
{#if isAniListLoggedIn}
|
{#if isAniListLoggedIn}
|
||||||
<div class="mx-auto max-w-2xl p-4 sm:p-6 lg:max-w-7xl lg:px-8">
|
<div class="mx-auto max-w-2xl p-4 sm:p-6 lg:max-w-7xl lg:px-8">
|
||||||
<h1 class="text-left text-xl font-bold mb-4">Your WatchList</h1>
|
<h1 class="text-left text-xl font-bold mb-4">Your AniList WatchList</h1>
|
||||||
|
|
||||||
<div class="mb-8">
|
<div class="mb-8">
|
||||||
<nav aria-label="Page navigation example">
|
<nav aria-label="Page navigation example">
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<script lang="ts" context="module">
|
<script lang="ts" context="module">
|
||||||
import {
|
import {
|
||||||
GetAniListItem,
|
GetAniListItem,
|
||||||
GetAniListLoggedInUser,
|
GetAniListLoggedInUser, GetMyAnimeListLoggedInUser,
|
||||||
GetSimklLoggedInUser
|
GetSimklLoggedInUser
|
||||||
} from "../wailsjs/go/main/App";
|
} from "../wailsjs/go/main/App";
|
||||||
import type {
|
import type {
|
||||||
@ -11,16 +11,19 @@
|
|||||||
import {writable} from 'svelte/store'
|
import {writable} from 'svelte/store'
|
||||||
import type {SimklUser, SimklWatchList} from "./simkl/types/simklTypes";
|
import type {SimklUser, SimklWatchList} from "./simkl/types/simklTypes";
|
||||||
import {type AniListUser} from "./anilist/types/AniListTypes";
|
import {type AniListUser} from "./anilist/types/AniListTypes";
|
||||||
|
import type {MyAnimeListUser} from "./mal/types/MALTypes";
|
||||||
|
|
||||||
export let aniListAnime: AniListGetSingleAnime
|
export let aniListAnime: AniListGetSingleAnime
|
||||||
export let title = writable("")
|
export let title = writable("")
|
||||||
export let anilistModal = writable(false);
|
export let anilistModal = writable(false);
|
||||||
export let aniListLoggedIn = writable(false)
|
export let aniListLoggedIn = writable(false)
|
||||||
export let simklLoggedIn = writable(false)
|
export let simklLoggedIn = writable(false)
|
||||||
|
export let malLoggedIn = writable(false)
|
||||||
export let simklWatchList = writable({} as SimklWatchList)
|
export let simklWatchList = writable({} as SimklWatchList)
|
||||||
export let aniListPrimary = writable(true)
|
export let aniListPrimary = writable(true)
|
||||||
export let simklUser = writable({} as SimklUser)
|
export let simklUser = writable({} as SimklUser)
|
||||||
export let aniListUser = writable({} as AniListUser)
|
export let aniListUser = writable({} as AniListUser)
|
||||||
|
export let malUser = writable({} as MyAnimeListUser)
|
||||||
export let aniListWatchlist = writable({} as AniListCurrentUserWatchList)
|
export let aniListWatchlist = writable({} as AniListCurrentUserWatchList)
|
||||||
|
|
||||||
export function GetAniListSingleItemAndOpenModal(aniId: number, login: boolean): void {
|
export function GetAniListSingleItemAndOpenModal(aniId: number, login: boolean): void {
|
||||||
@ -35,15 +38,22 @@
|
|||||||
|
|
||||||
export function loginToSimkl(): void {
|
export function loginToSimkl(): void {
|
||||||
GetSimklLoggedInUser().then(result => {
|
GetSimklLoggedInUser().then(result => {
|
||||||
simklUser = result
|
simklUser.set(result)
|
||||||
simklLoggedIn.set(true)
|
simklLoggedIn.set(true)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export function loginToAniList(): void {
|
export function loginToAniList(): void {
|
||||||
GetAniListLoggedInUser().then(result => {
|
GetAniListLoggedInUser().then(result => {
|
||||||
aniListUser = result
|
aniListUser.set(result)
|
||||||
aniListLoggedIn.set(true)
|
aniListLoggedIn.set(true)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function loginToMAL(): void {
|
||||||
|
GetMyAnimeListLoggedInUser().then(result => {
|
||||||
|
malUser.set(result)
|
||||||
|
malLoggedIn.set(true)
|
||||||
|
})
|
||||||
|
}
|
||||||
</script>
|
</script>
|
@ -5,23 +5,32 @@
|
|||||||
import {
|
import {
|
||||||
aniListLoggedIn,
|
aniListLoggedIn,
|
||||||
simklLoggedIn,
|
simklLoggedIn,
|
||||||
|
malLoggedIn,
|
||||||
loginToSimkl,
|
loginToSimkl,
|
||||||
loginToAniList,
|
loginToAniList,
|
||||||
|
loginToMAL,
|
||||||
aniListUser,
|
aniListUser,
|
||||||
simklUser
|
simklUser,
|
||||||
|
malUser
|
||||||
} from "./GlobalVariablesAndHelperFunctions.svelte"
|
} from "./GlobalVariablesAndHelperFunctions.svelte"
|
||||||
import type {AniListUser} from "./anilist/types/AniListTypes";
|
import type {AniListUser} from "./anilist/types/AniListTypes";
|
||||||
import type {SimklUser} from "./simkl/types/simklTypes";
|
import type {SimklUser} from "./simkl/types/simklTypes";
|
||||||
|
import type {MyAnimeListUser} from "./mal/types/MALTypes";
|
||||||
|
|
||||||
let isAniListLoggedIn: boolean
|
let isAniListLoggedIn: boolean
|
||||||
let isSimklLoggedIn: boolean
|
let isSimklLoggedIn: boolean
|
||||||
|
let isMALLoggedIn: boolean
|
||||||
let currentAniListUser: AniListUser
|
let currentAniListUser: AniListUser
|
||||||
let currentSimklUser: SimklUser
|
let currentSimklUser: SimklUser
|
||||||
|
let currentMALUser: MyAnimeListUser
|
||||||
|
|
||||||
aniListLoggedIn.subscribe((value) => isAniListLoggedIn = value)
|
aniListLoggedIn.subscribe((value) => isAniListLoggedIn = value)
|
||||||
simklLoggedIn.subscribe((value) => isSimklLoggedIn = value)
|
simklLoggedIn.subscribe((value) => isSimklLoggedIn = value)
|
||||||
|
malLoggedIn.subscribe((value) => isMALLoggedIn = value)
|
||||||
aniListUser.subscribe((value) => currentAniListUser = value)
|
aniListUser.subscribe((value) => currentAniListUser = value)
|
||||||
simklUser.subscribe((value) => currentSimklUser = value)
|
simklUser.subscribe((value) => currentSimklUser = value)
|
||||||
|
malUser.subscribe((value) => currentMALUser = value)
|
||||||
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@ -35,14 +44,21 @@
|
|||||||
<div class="flex space-x-2 items-center">
|
<div class="flex space-x-2 items-center">
|
||||||
<div>
|
<div>
|
||||||
{#if isAniListLoggedIn}
|
{#if isAniListLoggedIn}
|
||||||
<span class="bg-green-100 text-green-800 text-sm font-medium me-2 px-2.5 py-0.5 rounded dark:bg-green-900 dark:text-green-300 cursor-default">AniList: {currentAniListUser.data.Viewer.name}</span>
|
<span class="bg-green-100 text-green-800 text-sm font-medium me-2 px-2.5 py-0.5 rounded dark:bg-green-800 dark:text-green-200 cursor-default">AniList: {currentAniListUser.data.Viewer.name}</span>
|
||||||
{:else}
|
{:else}
|
||||||
<button on:click={loginToAniList} class="bg-blue-100 hover:bg-blue-200 text-blue-800 text-sm font-medium me-2 px-2.5 py-0.5 rounded dark:bg-gray-700 dark:text-blue-400 border border-blue-400 inline-flex items-center justify-center">AniList</button>
|
<button on:click={loginToAniList} class="bg-blue-100 hover:bg-blue-200 text-blue-800 text-sm font-medium me-2 px-2.5 py-0.5 rounded dark:bg-gray-700 dark:text-blue-400 border border-blue-400 inline-flex items-center justify-center">AniList</button>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
<div>
|
||||||
|
{#if isMALLoggedIn}
|
||||||
|
<span class="bg-blue-100 text-blue-800 text-sm font-medium me-2 px-2.5 py-0.5 rounded dark:bg-blue-800 dark:text-blue-200 cursor-default">MAL: {currentMALUser.name}</span>
|
||||||
|
{:else}
|
||||||
|
<button on:click={loginToMAL} class="bg-blue-100 hover:bg-blue-200 text-blue-800 text-sm font-medium me-2 px-2.5 py-0.5 rounded dark:bg-gray-700 dark:text-blue-400 border border-blue-400 inline-flex items-center justify-center">MAL</button>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
<div>
|
<div>
|
||||||
{#if isSimklLoggedIn}
|
{#if isSimklLoggedIn}
|
||||||
<span class="bg-indigo-100 text-indigo-800 text-sm font-medium me-2 px-2.5 py-0.5 rounded dark:bg-indigo-900 dark:text-indigo-300 cursor-default">Simkl: {currentSimklUser.user.name}</span>
|
<span class="bg-indigo-100 text-indigo-800 text-sm font-medium me-2 px-2.5 py-0.5 rounded dark:bg-indigo-800 dark:text-indigo-200 cursor-default">Simkl: {currentSimklUser.user.name}</span>
|
||||||
{:else}
|
{:else}
|
||||||
<button on:click={loginToSimkl} class="bg-blue-100 hover:bg-blue-200 text-blue-800 text-sm font-medium me-2 px-2.5 py-0.5 rounded dark:bg-gray-700 dark:text-blue-400 border border-blue-400 inline-flex items-center justify-center">Simkl</button>
|
<button on:click={loginToSimkl} class="bg-blue-100 hover:bg-blue-200 text-blue-800 text-sm font-medium me-2 px-2.5 py-0.5 rounded dark:bg-gray-700 dark:text-blue-400 border border-blue-400 inline-flex items-center justify-center">Simkl</button>
|
||||||
{/if}
|
{/if}
|
||||||
|
31
frontend/src/mal/types/MALTypes.ts
Normal file
31
frontend/src/mal/types/MALTypes.ts
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
export interface MyAnimeListUser {
|
||||||
|
id: number
|
||||||
|
name: string
|
||||||
|
picture: string
|
||||||
|
gender: string
|
||||||
|
birthday: string
|
||||||
|
location: string
|
||||||
|
joinedAt: string
|
||||||
|
AnimeStatistics: AnimeStatistics
|
||||||
|
timeZone: string
|
||||||
|
isSupporter: boolean
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AnimeStatistics {
|
||||||
|
numItemsWatching: number
|
||||||
|
numItemsCompleted: number
|
||||||
|
numItemsOnHold: number
|
||||||
|
numItemsDropped: number
|
||||||
|
numItemsPlanToWatch: number
|
||||||
|
numItems: number
|
||||||
|
numDaysWatched: number
|
||||||
|
numDaysWatching: number
|
||||||
|
numDaysCompleted: number
|
||||||
|
numDaysOnHold: number
|
||||||
|
numDaysDropped: number
|
||||||
|
numDays: number
|
||||||
|
numEpisodes: number
|
||||||
|
numTimesRewatched: number
|
||||||
|
meanScore: number
|
||||||
|
}
|
6
frontend/wailsjs/go/main/App.d.ts
vendored
6
frontend/wailsjs/go/main/App.d.ts
vendored
@ -10,6 +10,8 @@ export function AniListUpdateEntry(arg1:number,arg2:number,arg3:string,arg4:numb
|
|||||||
|
|
||||||
export function CheckIfAniListLoggedIn():Promise<boolean>;
|
export function CheckIfAniListLoggedIn():Promise<boolean>;
|
||||||
|
|
||||||
|
export function CheckIfMyAnimeListLoggedIn():Promise<boolean>;
|
||||||
|
|
||||||
export function CheckIfSimklLoggedIn():Promise<boolean>;
|
export function CheckIfSimklLoggedIn():Promise<boolean>;
|
||||||
|
|
||||||
export function GetAniListItem(arg1:number,arg2:boolean):Promise<main.AniListGetSingleAnime>;
|
export function GetAniListItem(arg1:number,arg2:boolean):Promise<main.AniListGetSingleAnime>;
|
||||||
@ -18,8 +20,12 @@ export function GetAniListLoggedInUser():Promise<main.AniListUser>;
|
|||||||
|
|
||||||
export function GetAniListUserWatchingList(arg1:number,arg2:number,arg3:string):Promise<main.AniListCurrentUserWatchList>;
|
export function GetAniListUserWatchingList(arg1:number,arg2:number,arg3:string):Promise<main.AniListCurrentUserWatchList>;
|
||||||
|
|
||||||
|
export function GetMyAnimeListLoggedInUser():Promise<main.MyAnimeListUser>;
|
||||||
|
|
||||||
export function GetSimklLoggedInUser():Promise<main.SimklUser>;
|
export function GetSimklLoggedInUser():Promise<main.SimklUser>;
|
||||||
|
|
||||||
|
export function MyAnimeListLogin():Promise<void>;
|
||||||
|
|
||||||
export function SimklGetUserWatchlist():Promise<main.SimklWatchList>;
|
export function SimklGetUserWatchlist():Promise<main.SimklWatchList>;
|
||||||
|
|
||||||
export function SimklLogin():Promise<void>;
|
export function SimklLogin():Promise<void>;
|
||||||
|
@ -18,6 +18,10 @@ export function CheckIfAniListLoggedIn() {
|
|||||||
return window['go']['main']['App']['CheckIfAniListLoggedIn']();
|
return window['go']['main']['App']['CheckIfAniListLoggedIn']();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function CheckIfMyAnimeListLoggedIn() {
|
||||||
|
return window['go']['main']['App']['CheckIfMyAnimeListLoggedIn']();
|
||||||
|
}
|
||||||
|
|
||||||
export function CheckIfSimklLoggedIn() {
|
export function CheckIfSimklLoggedIn() {
|
||||||
return window['go']['main']['App']['CheckIfSimklLoggedIn']();
|
return window['go']['main']['App']['CheckIfSimklLoggedIn']();
|
||||||
}
|
}
|
||||||
@ -34,10 +38,18 @@ export function GetAniListUserWatchingList(arg1, arg2, arg3) {
|
|||||||
return window['go']['main']['App']['GetAniListUserWatchingList'](arg1, arg2, arg3);
|
return window['go']['main']['App']['GetAniListUserWatchingList'](arg1, arg2, arg3);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function GetMyAnimeListLoggedInUser() {
|
||||||
|
return window['go']['main']['App']['GetMyAnimeListLoggedInUser']();
|
||||||
|
}
|
||||||
|
|
||||||
export function GetSimklLoggedInUser() {
|
export function GetSimklLoggedInUser() {
|
||||||
return window['go']['main']['App']['GetSimklLoggedInUser']();
|
return window['go']['main']['App']['GetSimklLoggedInUser']();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function MyAnimeListLogin() {
|
||||||
|
return window['go']['main']['App']['MyAnimeListLogin']();
|
||||||
|
}
|
||||||
|
|
||||||
export function SimklGetUserWatchlist() {
|
export function SimklGetUserWatchlist() {
|
||||||
return window['go']['main']['App']['SimklGetUserWatchlist']();
|
return window['go']['main']['App']['SimklGetUserWatchlist']();
|
||||||
}
|
}
|
||||||
|
@ -177,6 +177,64 @@ export namespace main {
|
|||||||
return a;
|
return a;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
export class MyAnimeListUser {
|
||||||
|
id: id;
|
||||||
|
name: name;
|
||||||
|
picture: picture;
|
||||||
|
gender: gender;
|
||||||
|
birthday: birthday;
|
||||||
|
location: location;
|
||||||
|
joined_at: joinedAt;
|
||||||
|
num_items_watching: numItemsWatching;
|
||||||
|
num_items_completed: numItemsCompleted;
|
||||||
|
num_items_on_hold: numItemsOnHold;
|
||||||
|
num_items_dropped: numItemsDropped;
|
||||||
|
num_items_plan_to_watch: numItemsPlanToWatch;
|
||||||
|
num_items: numItems;
|
||||||
|
num_days_watched: numDaysWatched;
|
||||||
|
num_days_watching: numDaysWatching;
|
||||||
|
num_days_completed: numDaysCompleted;
|
||||||
|
num_days_on_hold: numDaysOnHold;
|
||||||
|
num_days_dropped: numDaysDropped;
|
||||||
|
num_days: numDays;
|
||||||
|
num_episodes: numEpisodes;
|
||||||
|
num_times_rewatched: numTimesRewatched;
|
||||||
|
mean_score: meanScore;
|
||||||
|
time_zone: timeZone;
|
||||||
|
is_supporter: isSupporter;
|
||||||
|
|
||||||
|
static createFrom(source: any = {}) {
|
||||||
|
return new MyAnimeListUser(source);
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(source: any = {}) {
|
||||||
|
if ('string' === typeof source) source = JSON.parse(source);
|
||||||
|
this.id = source["id"];
|
||||||
|
this.name = source["name"];
|
||||||
|
this.picture = source["picture"];
|
||||||
|
this.gender = source["gender"];
|
||||||
|
this.birthday = source["birthday"];
|
||||||
|
this.location = source["location"];
|
||||||
|
this.joined_at = source["joined_at"];
|
||||||
|
this.num_items_watching = source["num_items_watching"];
|
||||||
|
this.num_items_completed = source["num_items_completed"];
|
||||||
|
this.num_items_on_hold = source["num_items_on_hold"];
|
||||||
|
this.num_items_dropped = source["num_items_dropped"];
|
||||||
|
this.num_items_plan_to_watch = source["num_items_plan_to_watch"];
|
||||||
|
this.num_items = source["num_items"];
|
||||||
|
this.num_days_watched = source["num_days_watched"];
|
||||||
|
this.num_days_watching = source["num_days_watching"];
|
||||||
|
this.num_days_completed = source["num_days_completed"];
|
||||||
|
this.num_days_on_hold = source["num_days_on_hold"];
|
||||||
|
this.num_days_dropped = source["num_days_dropped"];
|
||||||
|
this.num_days = source["num_days"];
|
||||||
|
this.num_episodes = source["num_episodes"];
|
||||||
|
this.num_times_rewatched = source["num_times_rewatched"];
|
||||||
|
this.mean_score = source["mean_score"];
|
||||||
|
this.time_zone = source["time_zone"];
|
||||||
|
this.is_supporter = source["is_supporter"];
|
||||||
|
}
|
||||||
|
}
|
||||||
export class SimklUser {
|
export class SimklUser {
|
||||||
user: user;
|
user: user;
|
||||||
account: account;
|
account: account;
|
||||||
|
6
main.go
6
main.go
@ -25,9 +25,9 @@ func main() {
|
|||||||
|
|
||||||
// Create application with options
|
// Create application with options
|
||||||
err := wails.Run(&options.App{
|
err := wails.Run(&options.App{
|
||||||
Title: "AniTrack",
|
Title: "AniTrack",
|
||||||
//Width: 1600,
|
Width: 1024,
|
||||||
//Height: 900,
|
Height: 768,
|
||||||
AssetServer: &assetserver.Options{
|
AssetServer: &assetserver.Options{
|
||||||
Assets: assets,
|
Assets: assets,
|
||||||
},
|
},
|
||||||
|
Loading…
Reference in New Issue
Block a user