added MAL Login

This commit is contained in:
John O'Keefe 2024-08-13 18:54:27 -04:00
parent 43a054ac92
commit fa3304db92
14 changed files with 459 additions and 22 deletions

View File

@ -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
View 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
View 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
}

View File

@ -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
View File

@ -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)
} }

View File

@ -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
] ]

View File

@ -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">

View File

@ -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>

View File

@ -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}

View 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
}

View File

@ -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>;

View File

@ -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']();
} }

View File

@ -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;

View File

@ -26,8 +26,8 @@ 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,
}, },