SimklFunctions.go: - Update SimklHelper to return (json.RawMessage, error) - Add status code validation (200-299 range) - Update SimklGetUserWatchlist to return (SimklWatchListType, error) - Update SimklSyncEpisodes to return (SimklAnime, error) - Update SimklSyncRating to return (SimklAnime, error) - Update SimklSyncStatus to return (SimklAnime, error) - Update SimklSearch to return (SimklAnime, error) - Update SimklSyncRemove to return (bool, error) - Add proper error wrapping for all failure scenarios SimklUserFunctions.go: - Replace log.Fatalf with log.Printf in server error handling (line 119) - Replace log.Fatal with log.Printf in JSON marshaling (line 141) All Simkl API calls now properly propagate errors to frontend.
233 lines
5.9 KiB
Go
233 lines
5.9 KiB
Go
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"log"
|
|
"net/http"
|
|
"sync"
|
|
|
|
"github.com/99designs/keyring"
|
|
"github.com/wailsapp/wails/v2/pkg/runtime"
|
|
)
|
|
|
|
var simklJwt SimklJWT
|
|
|
|
var simklRing, _ = keyring.Open(keyring.Config{
|
|
ServiceName: "AniTrack",
|
|
KeychainName: "AniTrack",
|
|
KeychainSynchronizable: false,
|
|
KeychainTrustApplication: true,
|
|
KeychainAccessibleWhenUnlocked: true,
|
|
})
|
|
|
|
var simklCtxShutdown, simklCancel = context.WithCancel(context.Background())
|
|
|
|
func (a *App) CheckIfSimklLoggedIn() bool {
|
|
if (SimklJWT{} == simklJwt) {
|
|
tokenType, tokenTypeErr := simklRing.Get("SimklTokenType")
|
|
accessToken, accessTokenErr := simklRing.Get("SimklAccessToken")
|
|
scope, scopeErr := simklRing.Get("SimklScope")
|
|
if (tokenTypeErr != nil || accessTokenErr != nil || scopeErr != nil) || len(accessToken.Data) == 0 {
|
|
return false
|
|
} else {
|
|
simklJwt.TokenType = string(tokenType.Data)
|
|
simklJwt.AccessToken = string(accessToken.Data)
|
|
simklJwt.Scope = string(scope.Data)
|
|
return true
|
|
}
|
|
} else {
|
|
return true
|
|
}
|
|
}
|
|
|
|
func (a *App) SimklLogin() {
|
|
if !a.CheckIfSimklLoggedIn() {
|
|
tokenType, tokenTypeErr := simklRing.Get("SimklTokenType")
|
|
accessToken, accessTokenErr := simklRing.Get("SimklAccessToken")
|
|
scope, scopeErr := simklRing.Get("SimklScope")
|
|
if (tokenTypeErr != nil || accessTokenErr != nil || scopeErr != nil) || len(accessToken.Data) == 0 {
|
|
getSimklCodeUrl := "https://simkl.com/oauth/authorize?response_type=code&client_id=" + Environment.SIMKL_CLIENT_ID + "&redirect_uri=" + Environment.SIMKL_CALLBACK_URI
|
|
runtime.BrowserOpenURL(*wailsContext, getSimklCodeUrl)
|
|
|
|
serverDone := &sync.WaitGroup{}
|
|
serverDone.Add(1)
|
|
a.handleSimklCallback(serverDone)
|
|
serverDone.Wait()
|
|
} else {
|
|
simklJwt.TokenType = string(tokenType.Data)
|
|
simklJwt.AccessToken = string(accessToken.Data)
|
|
simklJwt.Scope = string(scope.Data)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (a *App) handleSimklCallback(wg *sync.WaitGroup) {
|
|
mux := http.NewServeMux()
|
|
srv := &http.Server{Addr: ":6734", Handler: mux}
|
|
mux.HandleFunc("/callback", func(w http.ResponseWriter, r *http.Request) {
|
|
select {
|
|
case <-simklCtxShutdown.Done():
|
|
fmt.Println("Shutting down...")
|
|
return
|
|
default:
|
|
}
|
|
content := r.FormValue("code")
|
|
|
|
if content != "" {
|
|
simklJwt = getSimklAuthorizationToken(content)
|
|
_ = simklRing.Set(keyring.Item{
|
|
Key: "SimklTokenType",
|
|
Data: []byte(simklJwt.TokenType),
|
|
})
|
|
_ = simklRing.Set(keyring.Item{
|
|
Key: "SimklAccessToken",
|
|
Data: []byte(simklJwt.AccessToken),
|
|
})
|
|
_ = simklRing.Set(keyring.Item{
|
|
Key: "SimklScope",
|
|
Data: []byte(simklJwt.Scope),
|
|
})
|
|
_, err := runtime.MessageDialog(*wailsContext, runtime.MessageDialogOptions{
|
|
Title: "Simkl Authorization",
|
|
Message: "It is now safe to close your browser tab",
|
|
})
|
|
if err != nil {
|
|
log.Println(err)
|
|
}
|
|
fmt.Println("Shutting down...")
|
|
simklCancel()
|
|
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 && !errors.Is(err, http.ErrServerClosed) {
|
|
log.Printf("Server error: %s\n", err)
|
|
}
|
|
fmt.Println("Shutting down...")
|
|
}()
|
|
}
|
|
|
|
func getSimklAuthorizationToken(content string) SimklJWT {
|
|
data := 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"`
|
|
}{
|
|
GrantType: "authorization_code",
|
|
ClientID: Environment.SIMKL_CLIENT_ID,
|
|
ClientSecret: Environment.SIMKL_CLIENT_SECRET,
|
|
RedirectURI: Environment.SIMKL_CALLBACK_URI,
|
|
Code: content,
|
|
}
|
|
jsonData, err := json.Marshal(data)
|
|
if err != nil {
|
|
log.Printf("Failed to marshal data: %s\n", err)
|
|
return SimklJWT{}
|
|
}
|
|
|
|
response, err := http.NewRequest("POST", "https://api.simkl.com/oauth/token", bytes.NewBuffer(jsonData))
|
|
if err != nil {
|
|
log.Printf("Failed at response, %s\n", err)
|
|
}
|
|
response.Header.Add("Content-Type", "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)
|
|
if err != nil {
|
|
log.Printf("Could not read returned body, %s\n.", err)
|
|
}
|
|
|
|
var post SimklJWT
|
|
err = json.Unmarshal(returnedBody, &post)
|
|
if err != nil {
|
|
log.Printf("Failed at unmarshal, %s\n", err)
|
|
}
|
|
|
|
return post
|
|
}
|
|
|
|
func (a *App) GetSimklLoggedInUser() SimklUser {
|
|
a.SimklLogin()
|
|
|
|
client := &http.Client{}
|
|
|
|
req, _ := http.NewRequest("POST", "https://api.simkl.com/users/settings", nil)
|
|
|
|
req.Header.Add("Content-Type", "application/json")
|
|
req.Header.Add("Authorization", "Bearer "+simklJwt.AccessToken)
|
|
req.Header.Add("simkl-api-key", Environment.SIMKL_CLIENT_ID)
|
|
|
|
response, err := client.Do(req)
|
|
if err != nil {
|
|
log.Printf("Failed at request, %s\n", err)
|
|
return SimklUser{}
|
|
}
|
|
|
|
defer response.Body.Close()
|
|
|
|
respBody, _ := io.ReadAll(response.Body)
|
|
|
|
var errCheck struct {
|
|
Error string `json:"error"`
|
|
Message string `json:"message"`
|
|
}
|
|
|
|
err = json.Unmarshal(respBody, &errCheck)
|
|
if err != nil {
|
|
log.Printf("Failed at unmarshal, %s\n", err)
|
|
}
|
|
|
|
if errCheck.Error != "" {
|
|
a.LogoutSimkl()
|
|
return SimklUser{}
|
|
}
|
|
|
|
var user SimklUser
|
|
|
|
err = json.Unmarshal(respBody, &user)
|
|
if err != nil {
|
|
log.Printf("Failed at unmarshal, %s\n", err)
|
|
}
|
|
|
|
return user
|
|
}
|
|
|
|
func (a *App) LogoutSimkl() string {
|
|
if (SimklJWT{} != simklJwt) {
|
|
tokenTypeErr := simklRing.Remove("SimklTokenType")
|
|
accessTokenErr := simklRing.Remove("SimklAccessToken")
|
|
scopeErr := simklRing.Remove("SimklScope")
|
|
|
|
if tokenTypeErr != nil || accessTokenErr != nil || scopeErr != nil {
|
|
fmt.Println("Simkl Logout Failed")
|
|
}
|
|
simklJwt = SimklJWT{}
|
|
}
|
|
|
|
return "Simkl Logged Out Successfully"
|
|
}
|