Compare commits
35 Commits
1f796189b4
...
0.7.0
| Author | SHA1 | Date | |
|---|---|---|---|
| a3b4857175 | |||
| 6e4b9c041a | |||
| ae54fd20dd | |||
| 2c7e2d0eff | |||
| 9a9f055c38 | |||
| bc497521e7 | |||
| 48a6005725 | |||
| 93a94d0797 | |||
| a2576b044c | |||
| 2ee2d85e9e | |||
| 1090f112f3 | |||
| 58c9f449e0 | |||
| f016c90353 | |||
| 6bbe0f0f48 | |||
| d841fee1e7 | |||
| f29d8f378e | |||
| 35e93c0ca9 | |||
| 8c169d549a | |||
| b2a8a504f3 | |||
| c85a53a278 | |||
| 2cf3844e76 | |||
| 6ed5fe8b71 | |||
| 8a8baf7f8f | |||
| ca8c8beaf3 | |||
| 3e7f7d1c95 | |||
| b0ca864dfe | |||
| 5ed6dedeab | |||
| 3271af445a | |||
| e7e9e5b826 | |||
| 5337758dee | |||
| 426793e56a | |||
| a794b77654 | |||
| c510c2a138 | |||
| 8cbf5cb20c | |||
| 54a8924384 |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -33,3 +33,6 @@ environment.go
|
||||
|
||||
# REST (http files)
|
||||
http-client.private.env.json
|
||||
|
||||
# Build artifacts
|
||||
build/*.tar.gz
|
||||
|
||||
@@ -3,6 +3,7 @@ package main
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
@@ -93,6 +94,7 @@ func (a *App) GetAniListItem(aniId int, login bool) AniListGetSingleAnime {
|
||||
timeUntilAiring
|
||||
episode
|
||||
}
|
||||
genres
|
||||
tags{
|
||||
id
|
||||
name
|
||||
@@ -182,7 +184,7 @@ func (a *App) GetAniListItem(aniId int, login bool) AniListGetSingleAnime {
|
||||
return post
|
||||
}
|
||||
|
||||
func (a *App) AniListSearch(query string) any {
|
||||
func (a *App) AniListSearch(query string) (interface{}, error) {
|
||||
type Variables struct {
|
||||
Search string `json:"search"`
|
||||
ListType string `json:"listType"`
|
||||
@@ -222,6 +224,7 @@ func (a *App) AniListSearch(query string) any {
|
||||
timeUntilAiring
|
||||
episode
|
||||
}
|
||||
genres
|
||||
tags{
|
||||
id
|
||||
name
|
||||
@@ -240,18 +243,20 @@ func (a *App) AniListSearch(query string) any {
|
||||
ListType: "ANIME",
|
||||
},
|
||||
}
|
||||
returnedBody, _ := AniListQuery(body, false)
|
||||
|
||||
returnedBody, status := AniListQuery(body, false)
|
||||
if status != "200 OK" {
|
||||
return nil, fmt.Errorf("API search failed with status: %s", status)
|
||||
}
|
||||
var post interface{}
|
||||
err := json.Unmarshal(returnedBody, &post)
|
||||
if err != nil {
|
||||
log.Printf("Failed at unmarshal, %s\n", err)
|
||||
return nil, fmt.Errorf("Failed to parse search results")
|
||||
}
|
||||
return post, nil
|
||||
}
|
||||
|
||||
return post
|
||||
}
|
||||
|
||||
func (a *App) GetAniListUserWatchingList(page int, perPage int, sort string) AniListCurrentUserWatchList {
|
||||
func (a *App) GetAniListUserWatchingList(page int, perPage int, sort string) (AniListCurrentUserWatchList, error) {
|
||||
user := a.GetAniListLoggedInUser()
|
||||
type Variables struct {
|
||||
Page int `json:"page"`
|
||||
@@ -307,6 +312,7 @@ func (a *App) GetAniListUserWatchingList(page int, perPage int, sort string) Ani
|
||||
timeUntilAiring
|
||||
episode
|
||||
}
|
||||
genres
|
||||
tags{
|
||||
id
|
||||
name
|
||||
@@ -401,11 +407,15 @@ func (a *App) GetAniListUserWatchingList(page int, perPage int, sort string) Ani
|
||||
err := json.Unmarshal(returnedBody, &badPost)
|
||||
if err != nil {
|
||||
log.Printf("Failed at unmarshal, %s\n", err)
|
||||
return post, fmt.Errorf("API authentication error")
|
||||
}
|
||||
log.Fatal(badPost.Errors[0].Message)
|
||||
return post, fmt.Errorf("AniList API error: %s", badPost.Errors[0].Message)
|
||||
}
|
||||
if status != "200 OK" {
|
||||
return post, fmt.Errorf("API request failed with status: %s", status)
|
||||
}
|
||||
|
||||
return post
|
||||
return post, nil
|
||||
}
|
||||
|
||||
func (a *App) AniListUpdateEntry(updateBody AniListUpdateVariables) AniListGetSingleAnime {
|
||||
|
||||
@@ -74,6 +74,7 @@ type MediaList struct {
|
||||
TimeUntilAiring int `json:"timeUntilAiring"`
|
||||
Episode int `json:"episode"`
|
||||
} `json:"nextAiringEpisode"`
|
||||
Genres []string `json:"genres"`
|
||||
Tags []struct {
|
||||
Id int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
|
||||
@@ -11,10 +11,19 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
func MALHelper(method string, malUrl string, body url.Values) (json.RawMessage, string) {
|
||||
func MALHelper(method string, malUrl string, body url.Values) (json.RawMessage, string, error) {
|
||||
client := &http.Client{}
|
||||
|
||||
req, _ := http.NewRequest(method, malUrl, strings.NewReader(body.Encode()))
|
||||
req, err := http.NewRequest(method, malUrl, strings.NewReader(body.Encode()))
|
||||
|
||||
if err != nil {
|
||||
message, _ := json.Marshal(struct {
|
||||
Message string `json:"message"`
|
||||
}{
|
||||
Message: "Failed to create request: " + err.Error(),
|
||||
})
|
||||
return message, "", fmt.Errorf("failed to create request: %w", err)
|
||||
}
|
||||
|
||||
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
|
||||
req.Header.Add("Authorization", "Bearer "+myAnimeListJwt.AccessToken)
|
||||
@@ -22,47 +31,57 @@ func MALHelper(method string, malUrl string, body url.Values) (json.RawMessage,
|
||||
resp, err := client.Do(req)
|
||||
|
||||
if err != nil {
|
||||
fmt.Println("Errored when sending request to the server")
|
||||
message, _ := json.Marshal(struct {
|
||||
Message string `json:"message" ts_type:"message"`
|
||||
Message string `json:"message"`
|
||||
}{
|
||||
Message: "Errored when sending request to the server" + err.Error(),
|
||||
Message: "Network error: " + err.Error(),
|
||||
})
|
||||
|
||||
return message, resp.Status
|
||||
return message, "", fmt.Errorf("network error: %w", err)
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
respBody, _ := io.ReadAll(resp.Body)
|
||||
respBody, err := io.ReadAll(resp.Body)
|
||||
|
||||
if err != nil {
|
||||
return nil, resp.Status, fmt.Errorf("failed to read response: %w", err)
|
||||
}
|
||||
|
||||
if resp.Status == "401 Unauthorized" {
|
||||
refreshMyAnimeListAuthorizationToken()
|
||||
MALHelper(method, malUrl, body)
|
||||
return MALHelper(method, malUrl, body)
|
||||
}
|
||||
|
||||
return respBody, resp.Status
|
||||
if resp.Status != "200 OK" && resp.Status != "201 Created" && resp.Status != "202 Accepted" {
|
||||
return respBody, resp.Status, fmt.Errorf("API returned status: %s", resp.Status)
|
||||
}
|
||||
return respBody, resp.Status, nil
|
||||
}
|
||||
|
||||
func (a *App) GetMyAnimeList(count int) MALWatchlist {
|
||||
func (a *App) GetMyAnimeList(count int) (MALWatchlist, error) {
|
||||
limit := strconv.Itoa(count)
|
||||
user := a.GetMyAnimeListLoggedInUser()
|
||||
malUrl := "https://api.myanimelist.net/v2/users/" + user.Name + "/animelist?fields=list_status&status=watching&limit=" + limit
|
||||
|
||||
var malList MALWatchlist
|
||||
|
||||
respBody, resStatus := MALHelper("GET", malUrl, nil)
|
||||
respBody, resStatus, err := MALHelper("GET", malUrl, nil)
|
||||
|
||||
if err != nil {
|
||||
return malList, fmt.Errorf("failed to get MAL watchlist: %w", err)
|
||||
}
|
||||
|
||||
if resStatus == "200 OK" {
|
||||
err := json.Unmarshal(respBody, &malList)
|
||||
if err != nil {
|
||||
log.Printf("Failed to unmarshal json response, %s\n", err)
|
||||
return malList, fmt.Errorf("failed to parse response: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return malList
|
||||
return malList, nil
|
||||
}
|
||||
|
||||
func (a *App) MyAnimeListUpdate(anime MALAnime, update MALUploadStatus) MalListStatus {
|
||||
func (a *App) MyAnimeListUpdate(anime MALAnime, update MALUploadStatus) (MalListStatus, error) {
|
||||
if update.NumTimesRewatched >= 1 {
|
||||
update.IsRewatching = true
|
||||
} else {
|
||||
@@ -78,39 +97,52 @@ func (a *App) MyAnimeListUpdate(anime MALAnime, update MALUploadStatus) MalListS
|
||||
|
||||
malUrl := "https://api.myanimelist.net/v2/anime/" + strconv.Itoa(anime.Id) + "/my_list_status"
|
||||
var status MalListStatus
|
||||
respBody, respStatus := MALHelper("PATCH", malUrl, body)
|
||||
respBody, respStatus, err := MALHelper("PATCH", malUrl, body)
|
||||
|
||||
if err != nil {
|
||||
return status, fmt.Errorf("failed to update MAL entry: %w", err)
|
||||
}
|
||||
|
||||
if respStatus == "200 OK" {
|
||||
err := json.Unmarshal(respBody, &status)
|
||||
if err != nil {
|
||||
log.Printf("Failed to unmarshal json response, %s\n", err)
|
||||
return status, fmt.Errorf("failed to parse response: %w", err)
|
||||
}
|
||||
}
|
||||
return status, nil
|
||||
}
|
||||
|
||||
return status
|
||||
}
|
||||
|
||||
func (a *App) GetMyAnimeListAnime(id int) MALAnime {
|
||||
func (a *App) GetMyAnimeListAnime(id int) (MALAnime, error) {
|
||||
malUrl := "https://api.myanimelist.net/v2/anime/" + strconv.Itoa(id) + "?fields=id,title,main_picture,alternative_titles,start_date,end_date,synopsis,mean,rank,popularity,num_list_users,num_scoring_users,nsfw,genres,created_at,updated_at,media_type,status,my_list_status,num_episodes,start_season,broadcast,source,average_episode_duration,rating,pictures,background,related_anime,recommendations,studios,statistics"
|
||||
respBody, respStatus := MALHelper("GET", malUrl, nil)
|
||||
var malAnime MALAnime
|
||||
respBody, respStatus, err := MALHelper("GET", malUrl, nil)
|
||||
|
||||
if err != nil {
|
||||
return malAnime, fmt.Errorf("failed to get MAL anime: %w", err)
|
||||
}
|
||||
|
||||
if respStatus == "200 OK" {
|
||||
err := json.Unmarshal(respBody, &malAnime)
|
||||
if err != nil {
|
||||
log.Printf("Failed to unmarshal json response, %s\n", err)
|
||||
return malAnime, fmt.Errorf("failed to parse response: %w", err)
|
||||
}
|
||||
}
|
||||
return malAnime, nil
|
||||
}
|
||||
|
||||
return malAnime
|
||||
}
|
||||
|
||||
func (a *App) DeleteMyAnimeListEntry(id int) bool {
|
||||
func (a *App) DeleteMyAnimeListEntry(id int) (bool, error) {
|
||||
malUrl := "https://api.myanimelist.net/v2/anime/" + strconv.Itoa(id) + "/my_list_status"
|
||||
_, respStatus := MALHelper("DELETE", malUrl, nil)
|
||||
_, respStatus, err := MALHelper("DELETE", malUrl, nil)
|
||||
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("failed to delete MAL entry: %w", err)
|
||||
}
|
||||
|
||||
if respStatus == "200 OK" {
|
||||
return true
|
||||
return true, nil
|
||||
} else {
|
||||
return false
|
||||
return false, fmt.Errorf("delete failed with status: %s", respStatus)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -176,7 +176,7 @@ func (a *App) handleMyAnimeListCallback(wg *sync.WaitGroup, verifier *CodeVerifi
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
|
||||
log.Fatalf("listen: %s\n", err)
|
||||
log.Printf("Server error: %s\n", err)
|
||||
}
|
||||
fmt.Println("Shutting down...")
|
||||
}()
|
||||
|
||||
166
RELEASE_NOTES_v0.7.0.md
Normal file
166
RELEASE_NOTES_v0.7.0.md
Normal file
@@ -0,0 +1,166 @@
|
||||
# 🎉 AniTrack v0.7.0 Release Notes
|
||||
|
||||
## 🚨 Major Feature: Comprehensive Error Handling
|
||||
|
||||
This release brings **massive improvements** to application stability and user experience. The app will **no longer crash** when API services are down or experiencing errors!
|
||||
|
||||
---
|
||||
|
||||
## ✨ What's New
|
||||
|
||||
### 🛡️ Graceful API Error Handling
|
||||
- **Application Stability**: App now remains running even when AniList, MAL, or Simkl APIs are down
|
||||
- **Visual Error Feedback**: Beautiful modal dialogs display error details instead of silent crashes
|
||||
- **Manual Retry**: Users can retry failed API connections with a single button click
|
||||
- **Continue Using App**: Dismiss error messages and continue with limited functionality
|
||||
|
||||
### 🔧 Backend Improvements
|
||||
|
||||
#### AniList Integration
|
||||
- Replaced all `log.Fatal()` calls with proper error returns
|
||||
- Added error handling for API failures, network issues, and authentication errors
|
||||
- `AniListSearch` now returns detailed error messages
|
||||
- `GetAniListUserWatchingList` gracefully handles 403/404 responses
|
||||
|
||||
#### MyAnimeList Integration
|
||||
- Updated `MALHelper` to return structured errors
|
||||
- All MAL API functions now return errors instead of crashing:
|
||||
- `GetMyAnimeList`
|
||||
- `MyAnimeListUpdate`
|
||||
- `GetMyAnimeListAnime`
|
||||
- `DeleteMyAnimeListEntry`
|
||||
- Fixed OAuth callback server to prevent shutdown on errors
|
||||
|
||||
#### Simkl Integration
|
||||
- Updated `SimklHelper` with status code validation (200-299 range)
|
||||
- All Simkl API functions now return errors:
|
||||
- `SimklGetUserWatchlist`
|
||||
- `SimklSyncEpisodes`
|
||||
- `SimklSyncRating`
|
||||
- `SimklSyncStatus`
|
||||
- `SimklSearch`
|
||||
- `SimklSyncRemove`
|
||||
- Replaced `log.Fatal()` with proper error logging
|
||||
|
||||
### 💻 Frontend Improvements
|
||||
|
||||
#### New Error Management System
|
||||
- **Centralized Error State**: New reactive stores for API errors
|
||||
- `apiError` - Tracks current error with service, message, and status
|
||||
- `isApiDown` - Quick flag for API availability
|
||||
- **Error Helper Functions**:
|
||||
- `setApiError()` - Set error state from any component
|
||||
- `clearApiError()` - Reset error state
|
||||
|
||||
#### New Error Modal Component
|
||||
- Auto-displays when API errors occur
|
||||
- Shows service name, error message, and HTTP status code
|
||||
- **"Retry Connection"** button - Attempts to reconnect to the failed service
|
||||
- **"Dismiss"** button - Close modal and continue using the app
|
||||
- Beautiful red-themed styling with Tailwind CSS
|
||||
- Supports all three services (AniList, MAL, Simkl)
|
||||
|
||||
#### Updated User Interface
|
||||
- Home page now displays "API Unavailable" state when services are down
|
||||
- Helpful warning messages guide users during outages
|
||||
- App structure remains visible and functional during errors
|
||||
|
||||
---
|
||||
|
||||
## 🐛 Bug Fixes
|
||||
|
||||
- **Critical**: Fixed app crashes when AniList API returns 403 Forbidden errors
|
||||
- **Critical**: Fixed app crashes when AniList API is down or unreachable
|
||||
- **Critical**: Fixed MAL OAuth server crashing on connection errors
|
||||
- **Critical**: Fixed Simkl JSON marshaling errors crashing the app
|
||||
- **Improved**: Better error messages for all API failures
|
||||
|
||||
---
|
||||
|
||||
## 📊 Technical Details
|
||||
|
||||
### Go Backend Changes
|
||||
- **AniListFunctions.go**: Updated 3 functions with error handling
|
||||
- **MALFunctions.go**: Updated 5 functions with error handling
|
||||
- **MALUserFunctions.go**: Fixed server error handling
|
||||
- **SimklFunctions.go**: Updated 7 functions with error handling
|
||||
- **SimklUserFunctions.go**: Fixed 2 critical error handling issues
|
||||
|
||||
### Frontend Changes
|
||||
- **ErrorModal.svelte**: New component (73 lines)
|
||||
- **GlobalVariablesAndHelperFunctions.svelte**: Added error state system (27 lines)
|
||||
- **App.svelte**: Integrated ErrorModal into main layout
|
||||
- **CheckIfAniListLoggedInAndLoadWatchList.svelte**: Added error handling
|
||||
- **Home.svelte**: Added API down state display
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Impact
|
||||
|
||||
### Before (v0.6.5)
|
||||
- ❌ App crashes to desktop when AniList API is down
|
||||
- ❌ Users see only console error messages
|
||||
- ❌ Must restart app after API failures
|
||||
- ❌ No way to retry failed connections
|
||||
|
||||
### After (v0.7.0)
|
||||
- ✅ App stays open during any API failures
|
||||
- ✅ Beautiful modal dialogs explain what went wrong
|
||||
- ✅ One-click retry to reconnect services
|
||||
- ✅ Continue using app with limited functionality
|
||||
|
||||
---
|
||||
|
||||
## 🔄 Upgrading
|
||||
|
||||
No migration needed! This release is fully backward compatible.
|
||||
|
||||
Simply run:
|
||||
```bash
|
||||
git pull
|
||||
wails build
|
||||
```
|
||||
|
||||
Or download the latest release from the releases page.
|
||||
|
||||
---
|
||||
|
||||
## 🙏 Acknowledgments
|
||||
|
||||
This release addresses a critical stability issue that was affecting users during API service outages. Special thanks to the community for reporting these crashes and providing detailed error logs.
|
||||
|
||||
---
|
||||
|
||||
## 📝 Full Changelog
|
||||
|
||||
### Added
|
||||
- Error modal component with retry/dismiss functionality
|
||||
- Centralized error state management system
|
||||
- API availability status tracking
|
||||
- Visual feedback for all API failures
|
||||
- Manual retry functionality for all services
|
||||
|
||||
### Changed
|
||||
- AniList API functions to return errors instead of crashing
|
||||
- MAL API functions to return errors instead of crashing
|
||||
- Simkl API functions to return errors instead of crashing
|
||||
- Error handling from console-only to UI modal dialogs
|
||||
- App behavior from crash-on-error to graceful-degradation
|
||||
|
||||
### Fixed
|
||||
- App crashes on AniList 403 errors
|
||||
- App crashes on network failures
|
||||
- MAL OAuth server shutdown on errors
|
||||
- Simkl JSON marshaling crashes
|
||||
- All `log.Fatal()` calls that terminated the app
|
||||
|
||||
### Removed
|
||||
- `alert()` calls in favor of modal system
|
||||
- Vite timestamp file (cleanup)
|
||||
|
||||
---
|
||||
|
||||
**Version**: 0.7.0
|
||||
**Release Date**: March 2026
|
||||
**Upgrade Difficulty**: ⭐ Easy (backward compatible)
|
||||
**Recommended**: ⭐⭐⭐⭐⭐ Highly recommended for all users
|
||||
@@ -14,16 +14,24 @@ import (
|
||||
|
||||
var SimklWatchList SimklWatchListType
|
||||
|
||||
func SimklHelper(method string, url string, body interface{}) json.RawMessage {
|
||||
reader, _ := json.Marshal(body)
|
||||
func SimklHelper(method string, url string, body interface{}) (json.RawMessage, error) {
|
||||
reader, err := json.Marshal(body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to marshal body: %w", err)
|
||||
}
|
||||
|
||||
var req *http.Request
|
||||
|
||||
client := &http.Client{}
|
||||
|
||||
if body != nil {
|
||||
req, _ = http.NewRequest(method, url, bytes.NewBuffer(reader))
|
||||
req, err = http.NewRequest(method, url, bytes.NewBuffer(reader))
|
||||
} else {
|
||||
req, _ = http.NewRequest(method, url, nil)
|
||||
req, err = http.NewRequest(method, url, nil)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create request: %w", err)
|
||||
}
|
||||
|
||||
req.Header.Add("Content-Type", "application/json")
|
||||
@@ -32,41 +40,45 @@ func SimklHelper(method string, url string, body interface{}) json.RawMessage {
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
fmt.Println("Errored when sending request to the server")
|
||||
message, _ := json.Marshal(struct {
|
||||
Message string `json:"message"`
|
||||
}{
|
||||
Message: "Errored when sending request to the server" + err.Error(),
|
||||
})
|
||||
|
||||
return message
|
||||
return nil, fmt.Errorf("network error: %w", err)
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
respBody, _ := io.ReadAll(resp.Body)
|
||||
respBody, err := io.ReadAll(resp.Body)
|
||||
|
||||
return respBody
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read response: %w", err)
|
||||
}
|
||||
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
||||
return respBody, fmt.Errorf("API returned status: %d", resp.StatusCode)
|
||||
}
|
||||
return respBody, nil
|
||||
}
|
||||
|
||||
func (a *App) SimklGetUserWatchlist() SimklWatchListType {
|
||||
func (a *App) SimklGetUserWatchlist() (SimklWatchListType, error) {
|
||||
method := "GET"
|
||||
url := "https://api.simkl.com/sync/all-items/anime"
|
||||
|
||||
respBody := SimklHelper(method, url, nil)
|
||||
respBody, err := SimklHelper(method, url, nil)
|
||||
|
||||
if err != nil {
|
||||
return SimklWatchListType{}, fmt.Errorf("failed to get Simkl watchlist: %w", err)
|
||||
}
|
||||
|
||||
var errCheck struct {
|
||||
Error string `json:"error"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
err := json.Unmarshal(respBody, &errCheck)
|
||||
err = json.Unmarshal(respBody, &errCheck)
|
||||
if err != nil {
|
||||
log.Printf("Failed at unmarshal, %s\n", err)
|
||||
return SimklWatchListType{}, fmt.Errorf("failed to parse error response: %w", err)
|
||||
}
|
||||
|
||||
if errCheck.Error != "" {
|
||||
a.LogoutSimkl()
|
||||
return SimklWatchListType{}
|
||||
return SimklWatchListType{}, fmt.Errorf("Simkl API error: %s", errCheck.Message)
|
||||
}
|
||||
|
||||
var watchlist SimklWatchListType
|
||||
@@ -74,14 +86,15 @@ func (a *App) SimklGetUserWatchlist() SimklWatchListType {
|
||||
err = json.Unmarshal(respBody, &watchlist)
|
||||
if err != nil {
|
||||
log.Printf("Failed at unmarshal, %s\n", err)
|
||||
return SimklWatchListType{}, fmt.Errorf("failed to parse watchlist: %w", err)
|
||||
}
|
||||
|
||||
SimklWatchList = watchlist
|
||||
|
||||
return watchlist
|
||||
return watchlist, nil
|
||||
}
|
||||
|
||||
func (a *App) SimklSyncEpisodes(anime SimklAnime, progress int) SimklAnime {
|
||||
func (a *App) SimklSyncEpisodes(anime SimklAnime, progress int) (SimklAnime, error) {
|
||||
var episodes []Episode
|
||||
var url string
|
||||
var shows []SimklPostShow
|
||||
@@ -112,13 +125,19 @@ func (a *App) SimklSyncEpisodes(anime SimklAnime, progress int) SimklAnime {
|
||||
|
||||
simklSync := SimklSyncHistoryType{shows}
|
||||
|
||||
respBody := SimklHelper("POST", url, simklSync)
|
||||
respBody, err := SimklHelper("POST", url, simklSync)
|
||||
|
||||
if err != nil {
|
||||
log.Printf("Failed to sync episodes: %s\n", err)
|
||||
return anime, fmt.Errorf("failed to sync episodes: %w", err)
|
||||
}
|
||||
|
||||
var success interface{}
|
||||
|
||||
err := json.Unmarshal(respBody, &success)
|
||||
err = json.Unmarshal(respBody, &success)
|
||||
if err != nil {
|
||||
log.Printf("Failed at unmarshal, %s\n", err)
|
||||
return anime, fmt.Errorf("failed to parse response: %w", err)
|
||||
}
|
||||
|
||||
for i, simklAnime := range SimklWatchList.Anime {
|
||||
@@ -131,10 +150,10 @@ func (a *App) SimklSyncEpisodes(anime SimklAnime, progress int) SimklAnime {
|
||||
|
||||
WatchListUpdate(anime)
|
||||
|
||||
return anime
|
||||
return anime, nil
|
||||
}
|
||||
|
||||
func (a *App) SimklSyncRating(anime SimklAnime, rating int) SimklAnime {
|
||||
func (a *App) SimklSyncRating(anime SimklAnime, rating int) (SimklAnime, error) {
|
||||
var url string
|
||||
showWithRating := ShowWithRating{
|
||||
Title: anime.Show.Title,
|
||||
@@ -169,13 +188,17 @@ func (a *App) SimklSyncRating(anime SimklAnime, rating int) SimklAnime {
|
||||
Shows []interface{} `json:"shows" ts_type:"shows"`
|
||||
}{shows}
|
||||
|
||||
respBody := SimklHelper("POST", url, simklSync)
|
||||
|
||||
respBody, err := SimklHelper("POST", url, simklSync)
|
||||
if err != nil {
|
||||
log.Printf("Failed to sync rating: %s\n", err)
|
||||
return anime, fmt.Errorf("failed to sync rating: %w", err)
|
||||
}
|
||||
var success interface{}
|
||||
|
||||
err := json.Unmarshal(respBody, &success)
|
||||
err = json.Unmarshal(respBody, &success)
|
||||
if err != nil {
|
||||
log.Printf("Failed at unmarshal, %s\n", err)
|
||||
return anime, fmt.Errorf("failed to parse response: %w", err)
|
||||
}
|
||||
|
||||
for i, simklAnime := range SimklWatchList.Anime {
|
||||
@@ -188,10 +211,10 @@ func (a *App) SimklSyncRating(anime SimklAnime, rating int) SimklAnime {
|
||||
|
||||
WatchListUpdate(anime)
|
||||
|
||||
return anime
|
||||
return anime, nil
|
||||
}
|
||||
|
||||
func (a *App) SimklSyncStatus(anime SimklAnime, status string) SimklAnime {
|
||||
func (a *App) SimklSyncStatus(anime SimklAnime, status string) (SimklAnime, error) {
|
||||
url := "https://api.simkl.com/sync/add-to-list"
|
||||
show := SimklShowStatus{
|
||||
Title: anime.Show.Title,
|
||||
@@ -211,13 +234,17 @@ func (a *App) SimklSyncStatus(anime SimklAnime, status string) SimklAnime {
|
||||
Shows []SimklShowStatus `json:"shows" ts_type:"shows"`
|
||||
}{shows}
|
||||
|
||||
respBody := SimklHelper("POST", url, simklSync)
|
||||
|
||||
respBody, err := SimklHelper("POST", url, simklSync)
|
||||
if err != nil {
|
||||
log.Printf("Failed to sync status: %s\n", err)
|
||||
return anime, fmt.Errorf("failed to sync status: %w", err)
|
||||
}
|
||||
var success interface{}
|
||||
|
||||
err := json.Unmarshal(respBody, &success)
|
||||
err = json.Unmarshal(respBody, &success)
|
||||
if err != nil {
|
||||
log.Printf("Failed at unmarshal, %s\n", err)
|
||||
return anime, fmt.Errorf("failed to parse response: %w", err)
|
||||
}
|
||||
|
||||
for i, simklAnime := range SimklWatchList.Anime {
|
||||
@@ -230,15 +257,20 @@ func (a *App) SimklSyncStatus(anime SimklAnime, status string) SimklAnime {
|
||||
|
||||
WatchListUpdate(anime)
|
||||
|
||||
return anime
|
||||
return anime, nil
|
||||
}
|
||||
|
||||
func (a *App) SimklSearch(aniListAnime MediaList) SimklAnime {
|
||||
func (a *App) SimklSearch(aniListAnime MediaList) (SimklAnime, error) {
|
||||
var result SimklAnime
|
||||
|
||||
if reflect.DeepEqual(SimklWatchList, SimklWatchListType{}) {
|
||||
fmt.Println("Watchlist empty. Calling...")
|
||||
SimklWatchList = a.SimklGetUserWatchlist()
|
||||
watchlist, err := a.SimklGetUserWatchlist()
|
||||
if err != nil {
|
||||
log.Printf("Failed to get watchlist: %s\n", err)
|
||||
return result, fmt.Errorf("failed to load watchlist for search: %w", err)
|
||||
}
|
||||
SimklWatchList = watchlist
|
||||
}
|
||||
|
||||
for _, anime := range SimklWatchList.Anime {
|
||||
@@ -255,22 +287,30 @@ func (a *App) SimklSearch(aniListAnime MediaList) SimklAnime {
|
||||
var anime SimklSearchType
|
||||
url := "https://api.simkl.com/search/id?anilist=" + strconv.Itoa(aniListAnime.Media.ID)
|
||||
|
||||
respBody := SimklHelper("GET", url, nil)
|
||||
|
||||
err := json.Unmarshal(respBody, &anime)
|
||||
respBody, err := SimklHelper("GET", url, nil)
|
||||
if err != nil {
|
||||
log.Printf("Failed to search Simkl: %s\n", err)
|
||||
return result, fmt.Errorf("failed to search Simkl by AniList ID: %w", err)
|
||||
}
|
||||
err = json.Unmarshal(respBody, &anime)
|
||||
|
||||
if len(anime) == 0 {
|
||||
url = "https://api.simkl.com/search/id?mal=" + strconv.Itoa(aniListAnime.Media.IDMal)
|
||||
respBody = SimklHelper("GET", url, nil)
|
||||
respBody, err = SimklHelper("GET", url, nil)
|
||||
if err != nil {
|
||||
log.Printf("Failed to search Simkl by MAL ID: %s\n", err)
|
||||
return result, fmt.Errorf("failed to search by MAL ID: %w", err)
|
||||
}
|
||||
err = json.Unmarshal(respBody, &anime)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
log.Printf("Failed at unmarshal, %s\n", err)
|
||||
return result, fmt.Errorf("failed to parse search results: %w", err)
|
||||
}
|
||||
|
||||
if len(anime) == 0 {
|
||||
return result
|
||||
return result, nil
|
||||
}
|
||||
|
||||
for _, watchListAnime := range SimklWatchList.Anime {
|
||||
@@ -288,10 +328,10 @@ func (a *App) SimklSearch(aniListAnime MediaList) SimklAnime {
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (a *App) SimklSyncRemove(anime SimklAnime) bool {
|
||||
func (a *App) SimklSyncRemove(anime SimklAnime) (bool, error) {
|
||||
url := "https://api.simkl.com/sync/history/remove"
|
||||
var showArray []SimklShowStatus
|
||||
|
||||
@@ -312,25 +352,28 @@ func (a *App) SimklSyncRemove(anime SimklAnime) bool {
|
||||
Shows: showArray,
|
||||
}
|
||||
|
||||
respBody := SimklHelper("POST", url, show)
|
||||
respBody, err := SimklHelper("POST", url, show)
|
||||
|
||||
if err != nil {
|
||||
log.Printf("Failed to sync remove: %s\n", err)
|
||||
return false, fmt.Errorf("failed to sync remove: %w", err)
|
||||
}
|
||||
var success SimklDeleteType
|
||||
|
||||
err := json.Unmarshal(respBody, &success)
|
||||
err = json.Unmarshal(respBody, &success)
|
||||
if err != nil {
|
||||
log.Printf("Failed at unmarshal, %s\n", err)
|
||||
return false, fmt.Errorf("failed to parse response: %w", err)
|
||||
}
|
||||
|
||||
if success.Deleted.Shows >= 1 {
|
||||
for i, simklAnime := range SimklWatchList.Anime {
|
||||
if simklAnime.Show.Ids.Simkl == anime.Show.Ids.Simkl {
|
||||
SimklWatchList.Anime = slices.Delete(SimklWatchList.Anime, i, i+1)
|
||||
}
|
||||
}
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
return true, nil
|
||||
}
|
||||
|
||||
return false, fmt.Errorf("no shows were deleted")
|
||||
}
|
||||
|
||||
func WatchListUpdate(anime SimklAnime) {
|
||||
|
||||
@@ -116,7 +116,7 @@ func (a *App) handleSimklCallback(wg *sync.WaitGroup) {
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
|
||||
log.Fatalf("listen: %s\n", err)
|
||||
log.Printf("Server error: %s\n", err)
|
||||
}
|
||||
fmt.Println("Shutting down...")
|
||||
}()
|
||||
@@ -138,7 +138,8 @@ func getSimklAuthorizationToken(content string) SimklJWT {
|
||||
}
|
||||
jsonData, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
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))
|
||||
|
||||
@@ -33,6 +33,7 @@ body:graphql {
|
||||
english
|
||||
native
|
||||
}
|
||||
genre
|
||||
description
|
||||
coverImage {
|
||||
large
|
||||
|
||||
5
frontend/.vscode/extensions.json
vendored
5
frontend/.vscode/extensions.json
vendored
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"svelte.svelte-vscode"
|
||||
]
|
||||
}
|
||||
@@ -1,27 +1,72 @@
|
||||
<script lang="ts">
|
||||
import {
|
||||
aniListLoggedIn,
|
||||
malLoggedIn,
|
||||
simklLoggedIn,
|
||||
watchlistNeedsRefresh,
|
||||
aniListPrimary,
|
||||
malPrimary,
|
||||
simklPrimary,
|
||||
malWatchList,
|
||||
simklWatchList,
|
||||
} from "./helperModules/GlobalVariablesAndHelperFunctions.svelte";
|
||||
import { onMount } from "svelte";
|
||||
import {userStore} from "./helperFunctions/userStore"
|
||||
import Router from "svelte-spa-router"
|
||||
import Router from "svelte-spa-router";
|
||||
import Home from "./routes/Home.svelte";
|
||||
import { wrap } from "svelte-spa-router/wrap";
|
||||
import Spinner from "./helperComponents/Spinner.svelte";
|
||||
import Header from "./helperComponents/Header.svelte";
|
||||
import { CheckIfAniListLoggedInAndLoadWatchList } from "./helperModules/CheckIfAniListLoggedInAndLoadWatchList.svelte";
|
||||
import { CheckIfMALLoggedInAndSetUser } from "./helperModules/CheckIfMyAnimeListLoggedIn.svelte";
|
||||
import { CheckIfSimklLoggedInAndSetUser } from "./helperModules/CheckIsSimklLoggedIn.svelte";
|
||||
import {
|
||||
CheckIfAniListLoggedIn,
|
||||
GetMyAnimeList,
|
||||
SimklGetUserWatchlist,
|
||||
} from "../wailsjs/go/main/App";
|
||||
import { loc } from "svelte-spa-router";
|
||||
import ErrorModal from "./helperComponents/ErrorModal.svelte";
|
||||
|
||||
onMount(async () => {
|
||||
await userStore.checkProvider('anilist')
|
||||
await userStore.checkProvider('mal')
|
||||
await userStore.checkProvider('simkl')
|
||||
})
|
||||
let isAniListLoggedIn: boolean;
|
||||
let isMALLoggedIn: boolean;
|
||||
let isSimklLoggedIn: boolean;
|
||||
aniListLoggedIn.subscribe((value) => (isAniListLoggedIn = value));
|
||||
malLoggedIn.subscribe((value) => (isMALLoggedIn = value));
|
||||
simklLoggedIn.subscribe((value) => (isSimklLoggedIn = value));
|
||||
|
||||
!isAniListLoggedIn && (await CheckIfAniListLoggedInAndLoadWatchList());
|
||||
!isMALLoggedIn && (await CheckIfMALLoggedInAndSetUser());
|
||||
!isSimklLoggedIn && (await CheckIfSimklLoggedInAndSetUser());
|
||||
});
|
||||
|
||||
$: if ($loc?.location === "/" && $watchlistNeedsRefresh) {
|
||||
(async () => {
|
||||
if ($aniListLoggedIn && $aniListPrimary) {
|
||||
await CheckIfAniListLoggedInAndLoadWatchList();
|
||||
}
|
||||
if ($malLoggedIn && $malPrimary) {
|
||||
await GetMyAnimeList(1000).then((w) => malWatchList.set(w));
|
||||
}
|
||||
if ($simklLoggedIn && $simklPrimary) {
|
||||
await SimklGetUserWatchlist().then((w) => simklWatchList.set(w));
|
||||
}
|
||||
|
||||
watchlistNeedsRefresh.set(false);
|
||||
})();
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if $userStore.anilist.isLoggedIn}
|
||||
<Header />
|
||||
<Router routes={{
|
||||
'/': Home,
|
||||
'/anime/:id': wrap({
|
||||
asyncComponent: () => import('./routes/AnimeRoutePage.svelte'),
|
||||
loadingComponent: Spinner
|
||||
<ErrorModal />
|
||||
<Router
|
||||
routes={{
|
||||
"/": Home,
|
||||
"/anime/:id": wrap({
|
||||
asyncComponent: () => import("./routes/AnimeRoutePage.svelte"),
|
||||
conditions: [async () => await CheckIfAniListLoggedIn()],
|
||||
loadingComponent: Spinner,
|
||||
}),
|
||||
// '*': "Not Found"
|
||||
}} />
|
||||
{/if}
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -44,6 +44,7 @@ export interface MediaList {
|
||||
timeUntilAiring: number;
|
||||
episode: number;
|
||||
};
|
||||
genres: string[];
|
||||
tags: [
|
||||
{
|
||||
id: number;
|
||||
@@ -1,89 +0,0 @@
|
||||
import type {AniListCurrentUserWatchList} from "../types/AniListCurrentUserWatchListType"
|
||||
|
||||
export const AniListWatchListDefaultData: AniListCurrentUserWatchList = {
|
||||
data: {
|
||||
Page: {
|
||||
pageInfo: {
|
||||
total: 0,
|
||||
perPage: 0,
|
||||
currentPage: 0,
|
||||
lastPage: 0,
|
||||
hasNextPage: false
|
||||
},
|
||||
mediaList: [
|
||||
{
|
||||
id: 0,
|
||||
mediaId: 0,
|
||||
userId: 0,
|
||||
media: {
|
||||
id: 0,
|
||||
idMal: 0,
|
||||
title: {
|
||||
romaji: "",
|
||||
english: "",
|
||||
native: "",
|
||||
},
|
||||
description: "",
|
||||
coverImage: {
|
||||
large: "",
|
||||
},
|
||||
season: "",
|
||||
seasonYear: 0,
|
||||
status: "",
|
||||
episodes: 0,
|
||||
nextAiringEpisode: {
|
||||
airingAt: 0,
|
||||
timeUntilAiring: 0,
|
||||
episode: 0,
|
||||
},
|
||||
tags: [
|
||||
{
|
||||
id: 0,
|
||||
name: "",
|
||||
description: "",
|
||||
rank: 0,
|
||||
isMediaSpoiler: false,
|
||||
isAdult: false,
|
||||
},
|
||||
],
|
||||
isAdult: false,
|
||||
},
|
||||
status: "",
|
||||
startedAt: {
|
||||
year: 0,
|
||||
month: 0,
|
||||
day: 0,
|
||||
},
|
||||
completedAt: {
|
||||
year: 0,
|
||||
month: 0,
|
||||
day: 0,
|
||||
},
|
||||
notes: "",
|
||||
progress: 0,
|
||||
score: 0,
|
||||
repeat: 0,
|
||||
user: {
|
||||
id: 0,
|
||||
name: "",
|
||||
avatar: {
|
||||
large: "",
|
||||
medium: "",
|
||||
},
|
||||
statistics: {
|
||||
anime: {
|
||||
count: 0,
|
||||
statuses: [
|
||||
{
|
||||
status: "",
|
||||
count: 0,
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
import type {AniListUser} from "../types/AniListTypes";
|
||||
import type {MyAnimeListUser} from "../types/MALTypes";
|
||||
import type {SimklUser} from "../types/simklTypes";
|
||||
|
||||
export const AniListUserDefaultData: AniListUser = {
|
||||
"data": {
|
||||
"Viewer": {
|
||||
id: 0,
|
||||
name: "",
|
||||
avatar: {
|
||||
large: "",
|
||||
medium: "",
|
||||
},
|
||||
bannerImage: "",
|
||||
siteUrl: ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const MALUserDefaultData: MyAnimeListUser = {
|
||||
id: 0,
|
||||
name: "",
|
||||
picture: "",
|
||||
gender: "",
|
||||
birthday: "",
|
||||
location: "",
|
||||
joinedAt: "",
|
||||
AnimeStatistics: {
|
||||
numItemsWatching: 0,
|
||||
numItemsCompleted: 0,
|
||||
numItemsOnHold: 0,
|
||||
numItemsDropped: 0,
|
||||
numItemsPlanToWatch: 0,
|
||||
numItems: 0,
|
||||
numDaysWatched: 0,
|
||||
numDaysWatching: 0,
|
||||
numDaysCompleted: 0,
|
||||
numDaysOnHold: 0,
|
||||
numDaysDropped: 0,
|
||||
numDays: 0,
|
||||
numEpisodes: 0,
|
||||
numTimesRewatched: 0,
|
||||
meanScore: 0
|
||||
},
|
||||
timeZone: "",
|
||||
isSupporter: false
|
||||
}
|
||||
|
||||
export const SimklUserDefaultData: SimklUser = {
|
||||
user: {
|
||||
name: "",
|
||||
joined_at: "",
|
||||
gender: "",
|
||||
avatar: "",
|
||||
bio: "",
|
||||
loc: "",
|
||||
age: "",
|
||||
},
|
||||
account: {
|
||||
id: 0,
|
||||
timezone: "",
|
||||
type: "",
|
||||
},
|
||||
connections: {
|
||||
facebook: false
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,16 @@
|
||||
<script lang="ts">
|
||||
import {
|
||||
aniListAnime,
|
||||
aniListLoggedIn,
|
||||
malAnime,
|
||||
malLoggedIn,
|
||||
simklAnime,
|
||||
simklLoggedIn,
|
||||
watchlistNeedsRefresh,
|
||||
} from "../helperModules/GlobalVariablesAndHelperFunctions.svelte";
|
||||
import {userStore} from "../helperFunctions/userStore"
|
||||
import { push } from "svelte-spa-router";
|
||||
import type { AniListGetSingleAnime } from "../types/AniListCurrentUserWatchListType";
|
||||
import WebsiteLink from "./WebsiteLink.svelte";
|
||||
import type { AniListGetSingleAnime } from "../anilist/types/AniListCurrentUserWatchListType";
|
||||
import Rating from "./Rating.svelte";
|
||||
import {
|
||||
convertAniListDateToString,
|
||||
@@ -17,14 +21,11 @@
|
||||
MALAnime,
|
||||
MalListStatus,
|
||||
MALUploadStatus,
|
||||
} from "../types/MALTypes";
|
||||
import type { SimklAnime } from "../types/simklTypes";
|
||||
} from "../mal/types/MALTypes";
|
||||
import type { SimklAnime } from "../simkl/types/simklTypes";
|
||||
import { writable } from "svelte/store";
|
||||
import type {
|
||||
StatusOption,
|
||||
StatusOptions,
|
||||
} from "../types/StatusTypes";
|
||||
import type { AniListUpdateVariables } from "../types/AniListTypes";
|
||||
import type { StatusOption, StatusOptions } from "../helperTypes/StatusTypes";
|
||||
import type { AniListUpdateVariables } from "../anilist/types/AniListTypes";
|
||||
import { convertDateToAniList } from "../helperFunctions/convertDateToAniList";
|
||||
import {
|
||||
AniListDeleteEntry,
|
||||
@@ -37,10 +38,14 @@
|
||||
SimklSyncStatus,
|
||||
} from "../../wailsjs/go/main/App";
|
||||
import { AddAnimeServiceToTable } from "../helperModules/AddAnimeServiceToTable.svelte";
|
||||
import { CheckIfAniListLoggedInAndLoadWatchList } from "../helperModules/CheckIfAniListLoggedInAndLoadWatchList.svelte";
|
||||
import Datepicker from "./Datepicker.svelte";
|
||||
import { Badge, Tooltip } from "flowbite-svelte";
|
||||
const re = /^([0-9]{4})-([0-9]{2})-([0-9]{2})/;
|
||||
|
||||
let isAniListLoggedIn: boolean;
|
||||
let isMalLoggedIn: boolean;
|
||||
let isSimklLoggedIn: boolean;
|
||||
let currentAniListAnime: AniListGetSingleAnime;
|
||||
let currentMalAnime: MALAnime;
|
||||
let currentSimklAnime: SimklAnime;
|
||||
@@ -48,6 +53,9 @@
|
||||
let isSubmitting: boolean;
|
||||
let submitSuccess = writable(false);
|
||||
|
||||
aniListLoggedIn.subscribe((value) => (isAniListLoggedIn = value));
|
||||
malLoggedIn.subscribe((value) => (isMalLoggedIn = value));
|
||||
simklLoggedIn.subscribe((value) => (isSimklLoggedIn = value));
|
||||
aniListAnime.subscribe((value) => (currentAniListAnime = value));
|
||||
malAnime.subscribe((value) => (currentMalAnime = value));
|
||||
simklAnime.subscribe((value) => (currentSimklAnime = value));
|
||||
@@ -71,8 +79,7 @@
|
||||
{ id: 5, aniList: "REPEATING", mal: "rewatching", simkl: "watching" },
|
||||
];
|
||||
let startingAnilistStatusOption: StatusOption = statusOptions.filter(
|
||||
(option) =>
|
||||
currentAniListAnime.data.MediaList.status === option.aniList,
|
||||
(option) => currentAniListAnime.data.MediaList.status === option.aniList,
|
||||
)[0];
|
||||
let startedAtDate: Date | null = convertAniListDateToDate(
|
||||
currentAniListAnime.data.MediaList.startedAt,
|
||||
@@ -81,7 +88,7 @@
|
||||
currentAniListAnime.data.MediaList.completedAt,
|
||||
);
|
||||
|
||||
if ($userStore.anilist.isLoggedIn)
|
||||
if (isAniListLoggedIn)
|
||||
AddAnimeServiceToTable({
|
||||
id: `a-${currentAniListAnime.data.MediaList.mediaId}`,
|
||||
title,
|
||||
@@ -99,19 +106,15 @@
|
||||
notes: currentAniListAnime.data.MediaList.notes,
|
||||
});
|
||||
|
||||
if ($userStore.mal.isLoggedIn) {
|
||||
if (isMalLoggedIn) {
|
||||
let startDate = "";
|
||||
let finishDate = "";
|
||||
if (currentMalAnime.my_list_status.start_date !== "") {
|
||||
const startArray = re.exec(
|
||||
currentMalAnime.my_list_status.start_date,
|
||||
);
|
||||
const startArray = re.exec(currentMalAnime.my_list_status.start_date);
|
||||
startDate = `${startArray[2]}-${startArray[3]}-${startArray[1]}`;
|
||||
}
|
||||
if (currentMalAnime.my_list_status.finish_date !== "") {
|
||||
const finishArray = re.exec(
|
||||
currentMalAnime.my_list_status.finish_date,
|
||||
);
|
||||
const finishArray = re.exec(currentMalAnime.my_list_status.finish_date);
|
||||
finishDate = `${finishArray[2]}-${finishArray[3]}-${finishArray[1]}`;
|
||||
}
|
||||
AddAnimeServiceToTable({
|
||||
@@ -128,7 +131,7 @@
|
||||
});
|
||||
}
|
||||
|
||||
if ($userStore.simkl.isLoggedIn && Object.keys(currentSimklAnime).length > 0)
|
||||
if (isSimklLoggedIn && Object.keys(currentSimklAnime).length > 0)
|
||||
AddAnimeServiceToTable({
|
||||
id: `s-${currentSimklAnime.show.ids.simkl}`,
|
||||
title: currentSimklAnime.show.title,
|
||||
@@ -188,8 +191,9 @@
|
||||
submitData[key] = value;
|
||||
}
|
||||
|
||||
try {
|
||||
if (
|
||||
$userStore.anilist.isLoggedIn &&
|
||||
isAniListLoggedIn &&
|
||||
currentAniListAnime.data.MediaList.mediaId !== 0
|
||||
) {
|
||||
let body: AniListUpdateVariables = {
|
||||
@@ -202,10 +206,11 @@
|
||||
startedAt: convertDateToAniList(startedAtDate),
|
||||
completedAt: convertDateToAniList(completedAtDate),
|
||||
};
|
||||
await AniListUpdateEntry(body).then(
|
||||
(value: AniListGetSingleAnime) => {
|
||||
await AniListUpdateEntry(body).then((value: AniListGetSingleAnime) => {
|
||||
value.data.MediaList.media.tags =
|
||||
currentAniListAnime.data.MediaList.media.tags;
|
||||
value.data.MediaList.media.genres =
|
||||
currentAniListAnime.data.MediaList.media.genres;
|
||||
aniListAnime.update((newValue) => {
|
||||
newValue = value;
|
||||
return newValue;
|
||||
@@ -226,11 +231,10 @@
|
||||
repeat: currentAniListAnime.data.MediaList.repeat,
|
||||
notes: currentAniListAnime.data.MediaList.notes,
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
if ($userStore.mal.isLoggedIn && currentMalAnime.id !== 0) {
|
||||
if (malLoggedIn && currentMalAnime.id !== 0) {
|
||||
let body: MALUploadStatus = {
|
||||
status: submitData.status.mal,
|
||||
is_rewatching: submitData.repeat > 0,
|
||||
@@ -244,8 +248,7 @@
|
||||
(malAnimeReturn: MalListStatus) => {
|
||||
malAnime.update((value) => {
|
||||
value.my_list_status.status = malAnimeReturn.status;
|
||||
value.my_list_status.is_rewatching =
|
||||
malAnimeReturn.is_rewatching;
|
||||
value.my_list_status.is_rewatching = malAnimeReturn.is_rewatching;
|
||||
value.my_list_status.score = malAnimeReturn.score;
|
||||
value.my_list_status.num_episodes_watched =
|
||||
malAnimeReturn.num_episodes_watched;
|
||||
@@ -272,28 +275,22 @@
|
||||
id: `m-${currentMalAnime.id}`,
|
||||
title: currentMalAnime.title,
|
||||
service: "MyAnimeList",
|
||||
progress:
|
||||
currentMalAnime.my_list_status.num_episodes_watched,
|
||||
progress: currentMalAnime.my_list_status.num_episodes_watched,
|
||||
status: currentMalAnime.my_list_status.status,
|
||||
startedAt: startDate,
|
||||
completedAt: finishDate,
|
||||
score: currentMalAnime.my_list_status.score,
|
||||
repeat: currentMalAnime.my_list_status
|
||||
.num_times_rewatched,
|
||||
repeat: currentMalAnime.my_list_status.num_times_rewatched,
|
||||
notes: currentMalAnime.my_list_status.comments,
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
if ($userStore.simkl.isLoggedIn && currentSimklAnime.show.ids.simkl !== 0) {
|
||||
if (
|
||||
currentSimklAnime.watched_episodes_count !== submitData.episodes
|
||||
) {
|
||||
await SimklSyncEpisodes(
|
||||
currentSimklAnime,
|
||||
submitData.episodes,
|
||||
).then((value: SimklAnime) => {
|
||||
if (simklLoggedIn && currentSimklAnime.show.ids.simkl !== 0) {
|
||||
if (currentSimklAnime.watched_episodes_count !== submitData.episodes) {
|
||||
await SimklSyncEpisodes(currentSimklAnime, submitData.episodes).then(
|
||||
(value: SimklAnime) => {
|
||||
AddAnimeServiceToTable({
|
||||
id: `s-${value.show.ids.simkl}`,
|
||||
title: value.show.title,
|
||||
@@ -310,14 +307,13 @@
|
||||
newValue = value;
|
||||
return newValue;
|
||||
});
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
if (currentSimklAnime.user_rating !== submitData.rating) {
|
||||
await SimklSyncRating(
|
||||
currentSimklAnime,
|
||||
submitData.rating,
|
||||
).then((value) => {
|
||||
await SimklSyncRating(currentSimklAnime, submitData.rating).then(
|
||||
(value) => {
|
||||
AddAnimeServiceToTable({
|
||||
id: `s-${value.show.ids.simkl}`,
|
||||
title: value.show.title,
|
||||
@@ -334,7 +330,8 @@
|
||||
newValue = value;
|
||||
return newValue;
|
||||
});
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
if (currentSimklAnime.status !== submitData.status.simkl) {
|
||||
@@ -361,16 +358,21 @@
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error("Error submitting changes:", error);
|
||||
} finally {
|
||||
submitting.set(false);
|
||||
submitSuccess.set(true);
|
||||
watchlistNeedsRefresh.set(true);
|
||||
setTimeout(() => submitSuccess.set(false), 2000);
|
||||
}
|
||||
};
|
||||
|
||||
const deleteEntries = async () => {
|
||||
submitting.set(true);
|
||||
try {
|
||||
if (
|
||||
$userStore.anilist.isLoggedIn &&
|
||||
isAniListLoggedIn &&
|
||||
currentAniListAnime.data.MediaList.mediaId !== 0
|
||||
) {
|
||||
await AniListDeleteEntry(currentAniListAnime.data.MediaList.id);
|
||||
@@ -387,7 +389,7 @@
|
||||
notes: "",
|
||||
});
|
||||
}
|
||||
if ($userStore.mal.isLoggedIn && currentMalAnime.id !== 0) {
|
||||
if (malLoggedIn && currentMalAnime.id !== 0) {
|
||||
await DeleteMyAnimeListEntry(currentMalAnime.id);
|
||||
AddAnimeServiceToTable({
|
||||
id: `m-${currentMalAnime.id}`,
|
||||
@@ -402,7 +404,7 @@
|
||||
notes: "",
|
||||
});
|
||||
}
|
||||
if ($userStore.simkl.isLoggedIn && currentSimklAnime.show.ids.simkl !== 0) {
|
||||
if (simklLoggedIn && currentSimklAnime.show.ids.simkl !== 0) {
|
||||
await SimklSyncRemove(currentSimklAnime);
|
||||
AddAnimeServiceToTable({
|
||||
id: `s-${currentSimklAnime.show.ids.simkl}`,
|
||||
@@ -417,9 +419,14 @@
|
||||
notes: "",
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error deleting entries:", error);
|
||||
} finally {
|
||||
submitting.set(false);
|
||||
submitSuccess.set(true);
|
||||
watchlistNeedsRefresh.set(true);
|
||||
setTimeout(() => submitSuccess.set(false), 2000);
|
||||
}
|
||||
};
|
||||
|
||||
let max = 999;
|
||||
@@ -432,10 +439,8 @@
|
||||
currentAniListAnime.data.MediaList.media.nextAiringEpisode.episode !== 0
|
||||
) {
|
||||
max =
|
||||
currentAniListAnime.data.MediaList.media.nextAiringEpisode.episode -
|
||||
1;
|
||||
currentAniListAnime.data.MediaList.media.nextAiringEpisode.episode - 1;
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<form on:submit|preventDefault={handleSubmit} class="container pt-3 pb-10">
|
||||
@@ -470,21 +475,18 @@
|
||||
on:click={() => {
|
||||
currentAniListAnime.data.MediaList.progress -= 1;
|
||||
if (
|
||||
currentAniListAnime.data.MediaList
|
||||
.progress <
|
||||
currentAniListAnime.data.MediaList.media
|
||||
.episodes
|
||||
currentAniListAnime.data.MediaList.progress <
|
||||
currentAniListAnime.data.MediaList.media.episodes
|
||||
) {
|
||||
startingAnilistStatusOption =
|
||||
statusOptions[0];
|
||||
if (
|
||||
currentAniListAnime.data.MediaList
|
||||
.repeat === 0
|
||||
)
|
||||
startingAnilistStatusOption = statusOptions[0];
|
||||
if (currentAniListAnime.data.MediaList.repeat === 0)
|
||||
completedAtDate = null;
|
||||
}
|
||||
}}
|
||||
class="bg-gray-700 hover:bg-gray-600 border-gray-600 border rounded-s-lg p-3 h-11 focus:ring-gray-700 focus:ring-2 focus:outline-none"
|
||||
disabled={currentAniListAnime.data.MediaList.progress <= 0}
|
||||
class={currentAniListAnime.data.MediaList.progress <= 0
|
||||
? "border-gray-600 border rounded-s-lg p-3 h-11 focus:ring-gray-700 focus:ring-2 focus:outline-none"
|
||||
: "bg-gray-700 hover:bg-gray-600 border-gray-600 border rounded-s-lg p-3 h-11 focus:ring-gray-700 focus:ring-2 focus:outline-none"}
|
||||
>
|
||||
<svg
|
||||
class="w-3 h-3 text-white"
|
||||
@@ -510,22 +512,18 @@
|
||||
id="episodes"
|
||||
class="border border-x-0 p-2.5 h-11 text-center text-sm block w-full placeholder-gray-400 [appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none
|
||||
{currentAniListAnime.data.MediaList.progress < 0 ||
|
||||
(currentAniListAnime.data.MediaList.media.episodes >
|
||||
0 &&
|
||||
(currentAniListAnime.data.MediaList.media.episodes > 0 &&
|
||||
currentAniListAnime.data.MediaList.progress >
|
||||
currentAniListAnime.data.MediaList.media
|
||||
.episodes) ||
|
||||
(currentAniListAnime.data.MediaList.media
|
||||
.nextAiringEpisode.episode > 0 &&
|
||||
currentAniListAnime.data.MediaList.media.episodes) ||
|
||||
(currentAniListAnime.data.MediaList.media.nextAiringEpisode
|
||||
.episode > 0 &&
|
||||
currentAniListAnime.data.MediaList.progress >
|
||||
currentAniListAnime.data.MediaList.media
|
||||
.nextAiringEpisode.episode -
|
||||
currentAniListAnime.data.MediaList.media.nextAiringEpisode
|
||||
.episode -
|
||||
1)
|
||||
? 'border-red-500 border-[2px] text-rose-300 focus:ring-red-500 focus:border-red-500'
|
||||
: 'bg-gray-700 hover:bg-gray-600 border-gray-600 text-white focus:ring-blue-500 focus:border-blue-500'}"
|
||||
bind:value={
|
||||
currentAniListAnime.data.MediaList.progress
|
||||
}
|
||||
: 'bg-gray-700 hover:bg-gray-600 border-gray-600 text-white focus:ring-blue-500 focus:border-blue-500'} w-24"
|
||||
bind:value={currentAniListAnime.data.MediaList.progress}
|
||||
required
|
||||
/>
|
||||
<button
|
||||
@@ -535,27 +533,38 @@
|
||||
on:click={() => {
|
||||
currentAniListAnime.data.MediaList.progress += 1;
|
||||
if (
|
||||
currentAniListAnime.data.MediaList.media
|
||||
.episodes ===
|
||||
currentAniListAnime.data.MediaList.media.episodes ===
|
||||
currentAniListAnime.data.MediaList.progress
|
||||
) {
|
||||
startingAnilistStatusOption =
|
||||
statusOptions[2];
|
||||
startingAnilistStatusOption = statusOptions[2];
|
||||
completedAtDate = new Date();
|
||||
}
|
||||
if (
|
||||
currentAniListAnime.data.MediaList
|
||||
.progress -
|
||||
1 ===
|
||||
0
|
||||
) {
|
||||
startingAnilistStatusOption =
|
||||
statusOptions[0];
|
||||
if (startedAtDate === null)
|
||||
startedAtDate = new Date();
|
||||
if (currentAniListAnime.data.MediaList.progress - 1 === 0) {
|
||||
startingAnilistStatusOption = statusOptions[0];
|
||||
if (startedAtDate === null) startedAtDate = new Date();
|
||||
}
|
||||
}}
|
||||
class="bg-gray-700 hover:bg-gray-600 border-gray-600 border rounded-e-lg p-3 h-11 focus:ring-gray-700 focus:ring-2 focus:outline-none"
|
||||
disabled={(currentAniListAnime.data.MediaList.media.episodes >
|
||||
0 &&
|
||||
currentAniListAnime.data.MediaList.progress >=
|
||||
currentAniListAnime.data.MediaList.media.episodes) ||
|
||||
(currentAniListAnime.data.MediaList.media.nextAiringEpisode
|
||||
.episode > 0 &&
|
||||
currentAniListAnime.data.MediaList.progress >
|
||||
currentAniListAnime.data.MediaList.media.nextAiringEpisode
|
||||
.episode -
|
||||
2)}
|
||||
class={(currentAniListAnime.data.MediaList.media.episodes > 0 &&
|
||||
currentAniListAnime.data.MediaList.progress >=
|
||||
currentAniListAnime.data.MediaList.media.episodes) ||
|
||||
(currentAniListAnime.data.MediaList.media.nextAiringEpisode
|
||||
.episode > 0 &&
|
||||
currentAniListAnime.data.MediaList.progress >
|
||||
currentAniListAnime.data.MediaList.media.nextAiringEpisode
|
||||
.episode -
|
||||
2)
|
||||
? "border-gray-600 border rounded-e-lg p-3 h-11 focus:ring-gray-700 focus:ring-2 focus:outline-none"
|
||||
: "bg-gray-700 hover:bg-gray-600 border-gray-600 border rounded-e-lg p-3 h-11 focus:ring-gray-700 focus:ring-2 focus:outline-none"}
|
||||
>
|
||||
<svg
|
||||
class="w-3 h-3 text-white"
|
||||
@@ -575,16 +584,15 @@
|
||||
</button>
|
||||
</div>
|
||||
<div>
|
||||
/ {currentAniListAnime.data.MediaList.media
|
||||
.nextAiringEpisode.episode !== 0
|
||||
? currentAniListAnime.data.MediaList.media
|
||||
.nextAiringEpisode.episode - 1
|
||||
/ {currentAniListAnime.data.MediaList.media.nextAiringEpisode
|
||||
.episode !== 0
|
||||
? currentAniListAnime.data.MediaList.media.nextAiringEpisode
|
||||
.episode - 1
|
||||
: currentAniListAnime.data.MediaList.media.episodes}
|
||||
</div>
|
||||
{#if currentAniListAnime.data.MediaList.media.nextAiringEpisode.episode !== 0}
|
||||
<div>
|
||||
of {currentAniListAnime.data.MediaList.media
|
||||
.episodes}
|
||||
of {currentAniListAnime.data.MediaList.media.episodes}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
@@ -656,10 +664,9 @@
|
||||
name="repeat"
|
||||
min="0"
|
||||
id="repeat"
|
||||
class="border {currentAniListAnime.data.MediaList
|
||||
.repeat < 0
|
||||
class="border {currentAniListAnime.data.MediaList.repeat < 0
|
||||
? 'border-red-500 border-[2px] text-rose-300 focus:ring-red-500 focus:border-red-500'
|
||||
: 'border-gray-500 focus:ring-blue-500 focus:border-blue-500'} text-sm rounded-lg block w-24 p-2.5 bg-gray-600 placeholder-gray-400 text-white"
|
||||
: 'border-gray-500 text-white focus:ring-blue-500 focus:border-blue-500'} text-sm rounded-lg block w-24 p-2.5 bg-gray-600 placeholder-gray-400 text-white"
|
||||
bind:value={currentAniListAnime.data.MediaList.repeat}
|
||||
required
|
||||
/>
|
||||
@@ -693,9 +700,9 @@
|
||||
<button
|
||||
disabled={isSubmitting}
|
||||
id="sync-button"
|
||||
class="text-white focus:ring-4 {$submitSuccess
|
||||
? 'bg-green-600 hover:bg-green-700 focus:ring-green-800'
|
||||
: 'bg-blue-600 hover:bg-blue-700 focus:ring-blue-800'} font-medium rounded-lg text-sm px-5 py-2.5 me-2 mb-2 focus:outline-none"
|
||||
class="text-white {$submitSuccess
|
||||
? 'bg-green-600 hover:bg-green-700 focus:ring-4 focus:ring-green-800'
|
||||
: 'bg-blue-600 hover:bg-blue-700 focus:ring-4 focus:ring-blue-800'} font-medium rounded-lg text-sm px-5 py-2.5 me-2 mb-2 focus:outline-none"
|
||||
type="submit"
|
||||
>
|
||||
<svg
|
||||
@@ -721,10 +728,12 @@
|
||||
Sync Changes
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="text-white bg-gray-800 border border-gray-600 focus:outline-none hover:bg-gray-700 focus:ring-4
|
||||
focus:ring-gray-700 font-medium rounded-lg text-sm px-5 py-2.5 me-2 mb-2
|
||||
hover:border-gray-600"
|
||||
on:click={async () => {
|
||||
await CheckIfAniListLoggedInAndLoadWatchList();
|
||||
return push("/");
|
||||
}}
|
||||
>
|
||||
@@ -741,9 +750,9 @@
|
||||
<button
|
||||
disabled={isSubmitting}
|
||||
id="delete-button"
|
||||
class="text-white focus:ring-4 {$submitSuccess
|
||||
? 'bg-green-600 hover:bg-green-700 focus:ring-green-800'
|
||||
: 'bg-red-600 hover:bg-red-700 focus:ring-red-800'} font-medium rounded-lg text-sm px-5 py-2.5 me-2 mb-2 focus:outline-none"
|
||||
class="text-white {$submitSuccess
|
||||
? 'bg-green-600 hover:bg-green-700 focus:ring-4 focus:ring-green-800'
|
||||
: 'bg-red-600 hover:bg-red-700 focus:ring-4 focus:ring-red-800'} font-medium rounded-lg text-sm px-5 py-2.5 me-2 mb-2 focus:outline-none"
|
||||
on:click={deleteEntries}
|
||||
>
|
||||
<svg
|
||||
@@ -775,9 +784,9 @@
|
||||
<button
|
||||
disabled={isSubmitting}
|
||||
id="sync-button"
|
||||
class="text-white focus:ring-4 {$submitSuccess
|
||||
? 'bg-green-600 hover:bg-green-700 focus:ring-green-800'
|
||||
: 'bg-blue-600 hover:bg-blue-700 focus:ring-blue-800'} font-medium rounded-lg text-sm px-5 py-2.5 me-2 mb-2 focus:outline-none"
|
||||
class="text-white {$submitSuccess
|
||||
? 'bg-green-600 hover:bg-green-700 focus:ring-4 focus:ring-green-800'
|
||||
: 'bg-blue-600 hover:bg-blue-700 focus:ring-4 focus:ring-blue-800'} font-medium rounded-lg text-sm px-5 py-2.5 me-2 mb-2 focus:outline-none"
|
||||
type="submit"
|
||||
>
|
||||
<svg
|
||||
@@ -807,6 +816,7 @@
|
||||
focus:ring-gray-700 font-medium rounded-lg text-sm px-5 py-2.5 me-2 mb-2
|
||||
hover:border-gray-600"
|
||||
on:click={async () => {
|
||||
await CheckIfAniListLoggedInAndLoadWatchList();
|
||||
return push("/");
|
||||
}}
|
||||
>
|
||||
@@ -817,14 +827,31 @@
|
||||
|
||||
<div class="flex m-5">
|
||||
<div>
|
||||
<h3 class="text-2xl">Genres</h3>
|
||||
{#each currentAniListAnime.data.MediaList.media.genres || [] as genre}
|
||||
<div>
|
||||
<Badge large border color="blue" class="m-1 w-52">
|
||||
<div>
|
||||
<WebsiteLink
|
||||
id={genre}
|
||||
url="https://anilist.co/search/anime/{genre}"
|
||||
/>
|
||||
</div>
|
||||
</Badge>
|
||||
<Tooltip>{genre}</Tooltip>
|
||||
</div>
|
||||
{/each}
|
||||
<h3 class="text-2xl">Tags</h3>
|
||||
<div class="mt-2">
|
||||
{#each currentAniListAnime.data.MediaList.media.tags as tag}
|
||||
<div>
|
||||
<Badge large border color="blue" class="m-1 w-52">
|
||||
<div>
|
||||
{tag.name} -
|
||||
<span class="text-xs">{tag.rank}%</span>
|
||||
<WebsiteLink
|
||||
id={tag.name}
|
||||
url="https://anilist.co/search/anime/?genres={tag.name}"
|
||||
/>
|
||||
<span class="text-xs">({tag.rank}%)</span>
|
||||
</div>
|
||||
</Badge>
|
||||
<Tooltip>{tag.description}</Tooltip>
|
||||
|
||||
@@ -1,36 +1,71 @@
|
||||
<script lang="ts">
|
||||
import { Avatar } from "flowbite-svelte";
|
||||
import {userStore} from "../helperFunctions/userStore"
|
||||
import type { AniListUser } from "../anilist/types/AniListTypes";
|
||||
import {
|
||||
aniListLoggedIn,
|
||||
aniListUser,
|
||||
malUser,
|
||||
simklUser,
|
||||
malLoggedIn,
|
||||
simklLoggedIn,
|
||||
loginToAniList,
|
||||
loginToMAL,
|
||||
loginToSimkl,
|
||||
logoutOfAniList,
|
||||
logoutOfMAL,
|
||||
logoutOfSimkl,
|
||||
} from "../helperModules/GlobalVariablesAndHelperFunctions.svelte";
|
||||
import * as runtime from "../../wailsjs/runtime";
|
||||
import type { MyAnimeListUser } from "../mal/types/MALTypes";
|
||||
import type { SimklUser } from "../simkl/types/simklTypes";
|
||||
import { ShowVersion } from "../../wailsjs/go/main/App";
|
||||
|
||||
let currentAniListUser: AniListUser;
|
||||
let currentMALUser: MyAnimeListUser;
|
||||
let currentSimklUser: SimklUser;
|
||||
let isAniListLoggedIn: boolean;
|
||||
let isSimklLoggedIn: boolean;
|
||||
let isMALLoggedIn: boolean;
|
||||
|
||||
aniListUser.subscribe((value) => (currentAniListUser = value));
|
||||
malUser.subscribe((value) => (currentMALUser = value));
|
||||
simklUser.subscribe((value) => (currentSimklUser = value));
|
||||
aniListLoggedIn.subscribe((value) => (isAniListLoggedIn = value));
|
||||
simklLoggedIn.subscribe((value) => (isSimklLoggedIn = value));
|
||||
malLoggedIn.subscribe((value) => (isMALLoggedIn = value));
|
||||
|
||||
function dropdownUser(): void {
|
||||
let dropdown = document.querySelector("#userDropdown");
|
||||
dropdown.classList.toggle("hidden");
|
||||
|
||||
if (!dropdown.classList.contains("hidden")) {
|
||||
document.addEventListener("click", clickOutside)
|
||||
document.addEventListener("click", clickOutside);
|
||||
}
|
||||
}
|
||||
|
||||
function clickOutside(event: Event): void {
|
||||
let dropdown = document.querySelector("#userDropdown")
|
||||
let toggleBtn = document.querySelector("#userDropdownButton")
|
||||
let dropdown = document.querySelector("#userDropdown");
|
||||
let toggleBtn = document.querySelector("#userDropdownButton");
|
||||
|
||||
if (!dropdown.contains(event.target as Node) && !toggleBtn.contains(event.target as Node)) {
|
||||
dropdown.classList.add("hidden")
|
||||
document.removeEventListener("click", clickOutside)
|
||||
if (
|
||||
!dropdown.contains(event.target as Node) &&
|
||||
!toggleBtn.contains(event.target as Node)
|
||||
) {
|
||||
dropdown.classList.add("hidden");
|
||||
document.removeEventListener("click", clickOutside);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="relative">
|
||||
<button id="userDropdownButton" on:click={dropdownUser}>
|
||||
{#if $userStore.anilist.isLoggedIn}
|
||||
{#if isAniListLoggedIn}
|
||||
<Avatar
|
||||
src={$userStore.anilist.user.data.Viewer.avatar.medium}
|
||||
src={currentAniListUser.data.Viewer.avatar.medium}
|
||||
class="cursor-pointer"
|
||||
dot={{ color: "green" }}
|
||||
dot={isAniListLoggedIn && isMALLoggedIn && isSimklLoggedIn
|
||||
? { color: "green" }
|
||||
: { color: "yellow" }}
|
||||
/>
|
||||
{:else}
|
||||
<Avatar class="cursor-pointer" dot={{ color: "red" }} />
|
||||
@@ -41,8 +76,8 @@
|
||||
class="absolute hidden right-0 2xl:left-1/2 2xl:-translate-x-1/2 z-10 divide-y rounded-lg shadow w-44 bg-gray-700 divide-gray-600"
|
||||
>
|
||||
<div class="px-4 py-3 text-sm text-white">
|
||||
{#if $userStore.anilist.isLoggedIn}
|
||||
<div>{$userStore.anilist.user.data.Viewer.name}</div>
|
||||
{#if isAniListLoggedIn}
|
||||
<div>{currentAniListUser.data.Viewer.name}</div>
|
||||
{:else}
|
||||
<div>You are not logged into AniList</div>
|
||||
{/if}
|
||||
@@ -51,62 +86,70 @@
|
||||
class="py-2 text-sm text-gray-200"
|
||||
aria-labelledby="dropdownUserAvatarButton"
|
||||
>
|
||||
{#if $userStore.anilist.isLoggedIn}
|
||||
{#if isAniListLoggedIn}
|
||||
<li>
|
||||
<button
|
||||
on:click={() => userStore.logout("anilist")}
|
||||
on:click={logoutOfAniList}
|
||||
class="block px-4 py-2 w-full hover:bg-gray-600 truncate bg-green-800 hover:text-white"
|
||||
>
|
||||
<span class="maple-font text-lg text-green-200 mr-4">A</span>Logout {$userStore.anilist.user.data.Viewer.name}
|
||||
<span class="maple-font text-lg text-green-200 mr-4">A</span>Logout {currentAniListUser
|
||||
.data.Viewer.name}
|
||||
</button>
|
||||
</li>
|
||||
{:else}
|
||||
<li>
|
||||
<button on:click={() => {
|
||||
dropdownUser()
|
||||
userStore.checkProvider("anilist")
|
||||
<button
|
||||
on:click={() => {
|
||||
dropdownUser();
|
||||
loginToAniList();
|
||||
}}
|
||||
class="block px-4 py-2 w-full hover:bg-gray-600 truncate hover:text-white">
|
||||
class="block px-4 py-2 w-full hover:bg-gray-600 truncate hover:text-white"
|
||||
>
|
||||
<span class="maple-font text-lg mr-4">A</span>Login to AniList
|
||||
</button>
|
||||
</li>
|
||||
{/if}
|
||||
{#if $userStore.mal.isLoggedIn}
|
||||
{#if isMALLoggedIn}
|
||||
<li>
|
||||
<button
|
||||
on:click={() => userStore.logout("mal")}
|
||||
on:click={logoutOfMAL}
|
||||
class="block px-4 py-2 w-full hover:bg-gray-600 truncate bg-blue-800 hover:text-white"
|
||||
>
|
||||
<span class="maple-font text-lg text-blue-200 mr-4">M</span>Logout {$userStore.mal.user.name}
|
||||
<span class="maple-font text-lg text-blue-200 mr-4">M</span>Logout {currentMALUser.name}
|
||||
</button>
|
||||
</li>
|
||||
{:else}
|
||||
<li>
|
||||
<button on:click={() => {
|
||||
dropdownUser()
|
||||
userStore.checkProvider("mal")
|
||||
<button
|
||||
on:click={() => {
|
||||
dropdownUser();
|
||||
loginToMAL();
|
||||
}}
|
||||
class="block px-4 py-2 w-full hover:bg-gray-600 truncate hover:text-white">
|
||||
class="block px-4 py-2 w-full hover:bg-gray-600 truncate hover:text-white"
|
||||
>
|
||||
<span class="maple-font text-lg mr-4">M</span>Login to MyAnimeList
|
||||
</button>
|
||||
</li>
|
||||
{/if}
|
||||
{#if $userStore.simkl.isLoggedIn}
|
||||
{#if isSimklLoggedIn}
|
||||
<li>
|
||||
<button
|
||||
on:click={() => userStore.logout("simkl")}
|
||||
on:click={logoutOfSimkl}
|
||||
class="block px-4 py-2 w-full hover:bg-gray-600 truncate bg-indigo-800 hover:text-white"
|
||||
>
|
||||
<span class="maple-font text-lg text-indigo-200 mr-4">S</span>Logout {$userStore.simkl.user.user.name}
|
||||
<span class="maple-font text-lg text-indigo-200 mr-4">S</span>Logout {currentSimklUser
|
||||
.user.name}
|
||||
</button>
|
||||
</li>
|
||||
{:else}
|
||||
<li>
|
||||
<button on:click={() => {
|
||||
dropdownUser()
|
||||
userStore.checkProvider("simkl")
|
||||
<button
|
||||
on:click={() => {
|
||||
dropdownUser();
|
||||
loginToSimkl();
|
||||
}}
|
||||
class="block px-4 py-2 w-full hover:bg-gray-600 truncate hover:text-white">
|
||||
class="block px-4 py-2 w-full hover:bg-gray-600 truncate hover:text-white"
|
||||
>
|
||||
<span class="maple-font text-lg mr-4">S</span>Login to Simkl
|
||||
</button>
|
||||
</li>
|
||||
@@ -115,8 +158,8 @@
|
||||
<div class="py-2">
|
||||
<button
|
||||
on:click={() => {
|
||||
dropdownUser()
|
||||
ShowVersion()
|
||||
dropdownUser();
|
||||
ShowVersion();
|
||||
}}
|
||||
class="block px-4 py-2 w-full text-sm hover:bg-gray-600 text-gray-200 over:text-white"
|
||||
>
|
||||
@@ -131,3 +174,4 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
73
frontend/src/helperComponents/ErrorModal.svelte
Normal file
73
frontend/src/helperComponents/ErrorModal.svelte
Normal file
@@ -0,0 +1,73 @@
|
||||
<script lang="ts">
|
||||
import {
|
||||
apiError,
|
||||
isApiDown,
|
||||
clearApiError,
|
||||
setApiError,
|
||||
} from "../helperModules/GlobalVariablesAndHelperFunctions.svelte";
|
||||
import { CheckIfAniListLoggedInAndLoadWatchList } from "../helperModules/CheckIfAniListLoggedInAndLoadWatchList.svelte";
|
||||
import { CheckIfMALLoggedInAndSetUser } from "../helperModules/CheckIfMyAnimeListLoggedIn.svelte";
|
||||
import { CheckIfSimklLoggedInAndSetUser } from "../helperModules/CheckIsSimklLoggedIn.svelte";
|
||||
import { Modal, Button } from "flowbite-svelte";
|
||||
let showModal = false;
|
||||
$: if ($apiError) {
|
||||
showModal = true;
|
||||
}
|
||||
async function handleRetry() {
|
||||
const service = $apiError?.service;
|
||||
clearApiError();
|
||||
|
||||
try {
|
||||
if (service === "anilist") {
|
||||
await CheckIfAniListLoggedInAndLoadWatchList();
|
||||
} else if (service === "mal") {
|
||||
await CheckIfMALLoggedInAndSetUser();
|
||||
} else if (service === "simkl") {
|
||||
await CheckIfSimklLoggedInAndSetUser();
|
||||
}
|
||||
} catch (err) {
|
||||
const errorMsg = err instanceof Error ? err.message : String(err);
|
||||
setApiError(
|
||||
service || "unknown",
|
||||
`Retry failed: ${errorMsg}`,
|
||||
undefined,
|
||||
true,
|
||||
);
|
||||
}
|
||||
}
|
||||
function handleDismiss() {
|
||||
clearApiError();
|
||||
showModal = false;
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if showModal && $apiError}
|
||||
<Modal
|
||||
open={showModal}
|
||||
title="{$apiError.service.toUpperCase()} API Error"
|
||||
size="md"
|
||||
>
|
||||
<div class="space-y-4">
|
||||
<div class="p-4 bg-red-50 border border-red-200 rounded-lg">
|
||||
<p class="text-red-800 font-medium">{$apiError.message}</p>
|
||||
{#if $apiError.statusCode}
|
||||
<p class="text-red-600 text-sm mt-2">
|
||||
Status: {$apiError.statusCode}
|
||||
</p>
|
||||
{/if}
|
||||
</div>
|
||||
<p class="text-gray-600">
|
||||
The application will remain open. You can retry the connection or
|
||||
dismiss this message to continue with limited functionality.
|
||||
</p>
|
||||
</div>
|
||||
<div slot="footer" class="flex gap-3 justify-end">
|
||||
{#if $apiError.canRetry}
|
||||
<Button on:click={handleRetry} class="bg-blue-600 hover:bg-blue-700">
|
||||
Retry Connection
|
||||
</Button>
|
||||
{/if}
|
||||
<Button on:click={handleDismiss} color="alternative">Dismiss</Button>
|
||||
</div>
|
||||
</Modal>
|
||||
{/if}
|
||||
@@ -1,55 +1,94 @@
|
||||
<script lang="ts">
|
||||
import Search from "./Search.svelte"
|
||||
import Search from "./Search.svelte";
|
||||
import {
|
||||
aniListLoggedIn,
|
||||
loginToAniList,
|
||||
loginToMAL,
|
||||
loginToSimkl,
|
||||
malLoggedIn,
|
||||
simklLoggedIn,
|
||||
} from "../helperModules/GlobalVariablesAndHelperFunctions.svelte";
|
||||
import AvatarMenu from "./AvatarMenu.svelte";
|
||||
import logo from "../assets/images/AniTrackLogo.svg"
|
||||
import {userStore} from "../helperFunctions/userStore"
|
||||
import logo from "../assets/images/AniTrackLogo.svg";
|
||||
import { link } from "svelte-spa-router";
|
||||
|
||||
let isAniListLoggedIn: boolean;
|
||||
let isSimklLoggedIn: boolean;
|
||||
let isMALLoggedIn: boolean;
|
||||
|
||||
aniListLoggedIn.subscribe((value) => (isAniListLoggedIn = value));
|
||||
simklLoggedIn.subscribe((value) => (isSimklLoggedIn = value));
|
||||
malLoggedIn.subscribe((value) => (isMALLoggedIn = value));
|
||||
</script>
|
||||
|
||||
<nav class="border-gray-200 bg-gray-900">
|
||||
<div class="max-w-screen-xl flex flex-wrap items-center justify-between mx-auto p-4">
|
||||
<div
|
||||
class="max-w-screen-xl flex flex-wrap items-center justify-between mx-auto p-4"
|
||||
>
|
||||
<div class="flex items-center space-x-3 rtl:space-x-reverse">
|
||||
<a href="/"><img src={logo} class="h-8" alt="AniTrack Logo"/></a>
|
||||
<a href="/" use:link
|
||||
><img src={logo} class="h-8" alt="AniTrack Logo" /></a
|
||||
>
|
||||
</div>
|
||||
<div class="flex items-center min-[950px]:order-2 space-x-3 min-[950px]:space-x-0 rtl:space-x-reverse">
|
||||
<div
|
||||
class="flex items-center min-[950px]:order-2 space-x-3 min-[950px]:space-x-0 rtl:space-x-reverse"
|
||||
>
|
||||
<div class="min-[950px]:block min-[950px]:mr-4">
|
||||
<Search />
|
||||
</div>
|
||||
<AvatarMenu />
|
||||
<button on:click={() => {
|
||||
let menu = document.querySelector("#navbar-user")
|
||||
menu.classList.toggle("hidden")
|
||||
}} type="button"
|
||||
<button
|
||||
on:click={() => {
|
||||
let menu = document.querySelector("#navbar-user");
|
||||
menu.classList.toggle("hidden");
|
||||
}}
|
||||
type="button"
|
||||
class="inline-flex items-center p-2 w-10 h-10 justify-center text-sm rounded-lg min-[950px]:hidden focus:outline-none focus:ring-2 text-gray-400 hover:bg-gray-700 focus:ring-gray-600"
|
||||
aria-controls="navbar-user" aria-expanded="false">
|
||||
aria-controls="navbar-user"
|
||||
aria-expanded="false"
|
||||
>
|
||||
<span class="sr-only">Open main menu</span>
|
||||
<svg class="w-5 h-5" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none"
|
||||
viewBox="0 0 17 14">
|
||||
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M1 1h15M1 7h15M1 13h15"/>
|
||||
<svg
|
||||
class="w-5 h-5"
|
||||
aria-hidden="true"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 17 14"
|
||||
>
|
||||
<path
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M1 1h15M1 7h15M1 13h15"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="hidden items-center justify-between w-full pb-4 min-[950px]:pb-0 min-[950px]:flex min-[950px]:w-auto min-[950px]:order-1 border border-gray-700 min-[950px]:border-0 bg-gray-800 min-[950px]:bg-transparent rounded-lg" id="navbar-user">
|
||||
<ul class="flex flex-col font-medium pb-6 min-[950px]:p-0 mt-4 min-[950px]:space-x-8 rtl:space-x-reverse min-[950px]:flex-row min-[950px]:mt-0">
|
||||
<div
|
||||
class="hidden items-center justify-between w-full pb-4 min-[950px]:pb-0 min-[950px]:flex min-[950px]:w-auto min-[950px]:order-1 border border-gray-700 min-[950px]:border-0 bg-gray-800 min-[950px]:bg-transparent rounded-lg"
|
||||
id="navbar-user"
|
||||
>
|
||||
<ul
|
||||
class="flex flex-col font-medium pb-6 min-[950px]:p-0 mt-4 min-[950px]:space-x-8 rtl:space-x-reverse min-[950px]:flex-row min-[950px]:mt-0"
|
||||
>
|
||||
<li>
|
||||
{#if !$userStore.anilist.isLoggedIn}
|
||||
<button on:click={() => userStore.checkProvider("anilist")}>
|
||||
{#if !isAniListLoggedIn}
|
||||
<button on:click={loginToAniList}>
|
||||
<!-- class="block py-2 px-3 w-full min-[950px]:w-auto rounded text-gray-300 min-[950px]:hover:text-blue-500 hover:bg-gray-700 hover:text-white min-[950px]:hover:bg-transparent border-gray-700">-->
|
||||
AniList Login
|
||||
</button>
|
||||
{/if}
|
||||
{#if !$userStore.mal.isLoggedIn}
|
||||
<button on:click={() => userStore.checkProvider("mal")}>
|
||||
{#if !isMALLoggedIn}
|
||||
<button on:click={loginToMAL}>
|
||||
<!-- class="block py-2 px-3 w-full min-[950px]:w-auto rounded min-[950px]:p-0 text-gray-300 min-[950px]:hover:text-blue-500 hover:bg-gray-700 hover:text-white min-[950px]:hover:bg-transparent border-gray-700">-->
|
||||
MyAnimeList Login
|
||||
</button>
|
||||
{/if}
|
||||
</li>
|
||||
<li>
|
||||
{#if !$userStore.simkl.isLoggedIn}
|
||||
<button on:click={() => userStore.checkProvider("simkl")}>
|
||||
{#if !isSimklLoggedIn}
|
||||
<button on:click={loginToSimkl}>
|
||||
<!-- class="block py-2 px-3 w-full min-[950px]:w-auto rounded min-[950px]:p-0 text-gray-300 min-[950px]:hover:text-blue-500 hover:bg-gray-700 hover:text-white min-[950px]:hover:bg-transparent border-gray-700">-->
|
||||
Simkl Login
|
||||
</button>
|
||||
|
||||
@@ -1,46 +1,57 @@
|
||||
<script lang="ts">
|
||||
import {
|
||||
aniListLoggedIn,
|
||||
aniListSort,
|
||||
aniListWatchlist,
|
||||
animePerPage,
|
||||
watchListPage,
|
||||
} from "../helperModules/GlobalVariablesAndHelperFunctions.svelte";
|
||||
|
||||
import type {AniListCurrentUserWatchList} from "../types/AniListCurrentUserWatchListType"
|
||||
import type { AniListCurrentUserWatchList } from "../anilist/types/AniListCurrentUserWatchListType";
|
||||
import { GetAniListUserWatchingList } from "../../wailsjs/go/main/App";
|
||||
import {MediaListSort} from "../types/AniListTypes";
|
||||
|
||||
let aniListWatchListLoaded: AniListCurrentUserWatchList
|
||||
let page: number
|
||||
let perPage: number
|
||||
let aniListWatchListLoaded: AniListCurrentUserWatchList;
|
||||
let page: number;
|
||||
let perPage: number;
|
||||
let sort: string;
|
||||
|
||||
watchListPage.subscribe(value => page = value)
|
||||
animePerPage.subscribe(value => perPage = value)
|
||||
aniListWatchlist.subscribe((value) => aniListWatchListLoaded = value)
|
||||
watchListPage.subscribe((value) => (page = value));
|
||||
animePerPage.subscribe((value) => (perPage = value));
|
||||
aniListWatchlist.subscribe((value) => (aniListWatchListLoaded = value));
|
||||
aniListSort.subscribe((value) => (sort = value));
|
||||
|
||||
const perPageOptions = [10, 20, 50]
|
||||
const perPageOptions = [10, 20, 50];
|
||||
|
||||
function ChangeWatchListPage(newPage: number) {
|
||||
GetAniListUserWatchingList(newPage, perPage, MediaListSort.UpdatedTimeDesc).then((result) => {
|
||||
watchListPage.set(newPage)
|
||||
aniListWatchlist.set(result)
|
||||
aniListLoggedIn.set(true)
|
||||
})
|
||||
GetAniListUserWatchingList(newPage, perPage, sort).then((result) => {
|
||||
watchListPage.set(newPage);
|
||||
aniListWatchlist.set(result);
|
||||
aniListLoggedIn.set(true);
|
||||
});
|
||||
}
|
||||
|
||||
function changePage(e): void {
|
||||
if ((e.key === "Enter" || e.key === "Tab") && Number(e.target.value) !== page) ChangeWatchListPage(Number(e.target.value))
|
||||
function changePage(
|
||||
e: KeyboardEvent & { currentTarget: HTMLInputElement },
|
||||
): void {
|
||||
if (
|
||||
(e.key === "Enter" || e.key === "Tab") &&
|
||||
Number(e.currentTarget.value) !== page
|
||||
)
|
||||
ChangeWatchListPage(Number(e.currentTarget.value));
|
||||
}
|
||||
|
||||
function changeCountPerPage(e): void {
|
||||
GetAniListUserWatchingList(1, Number(e.target.value), MediaListSort.UpdatedTimeDesc).then((result) => {
|
||||
animePerPage.set(Number(e.target.value))
|
||||
watchListPage.set(1)
|
||||
aniListWatchlist.set(result)
|
||||
aniListLoggedIn.set(true)
|
||||
})
|
||||
function changeCountPerPage(
|
||||
e: Event & { currentTarget: HTMLSelectElement },
|
||||
): void {
|
||||
GetAniListUserWatchingList(1, Number(e.currentTarget.value), sort).then(
|
||||
(result) => {
|
||||
animePerPage.set(Number(e.currentTarget.value));
|
||||
watchListPage.set(1);
|
||||
aniListWatchlist.set(result);
|
||||
aniListLoggedIn.set(true);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<div class="mb-8">
|
||||
@@ -49,15 +60,19 @@
|
||||
<ul class="inline-flex -space-x-px text-base h-10">
|
||||
{#if page === 1}
|
||||
<li>
|
||||
<button disabled
|
||||
class="flex items-center justify-center px-4 h-10 ms-0 leading-tight border border-e-0 rounded-s-lg border-gray-700 text-gray-400 cursor-default">
|
||||
<button
|
||||
disabled
|
||||
class="flex items-center justify-center px-4 h-10 ms-0 leading-tight border border-e-0 rounded-s-lg border-gray-700 text-gray-400 cursor-default"
|
||||
>
|
||||
Previous
|
||||
</button>
|
||||
</li>
|
||||
{:else}
|
||||
<li>
|
||||
<button on:click={() => ChangeWatchListPage(page-1)}
|
||||
class="flex items-center justify-center px-4 h-10 ms-0 leading-tight border border-e-0 rounded-s-lg border-gray-700 text-gray-400 hover:bg-gray-700 hover:text-white">
|
||||
<button
|
||||
on:click={() => ChangeWatchListPage(page - 1)}
|
||||
class="flex items-center justify-center px-4 h-10 ms-0 leading-tight border border-e-0 rounded-s-lg border-gray-700 text-gray-400 hover:bg-gray-700 hover:text-white"
|
||||
>
|
||||
Previous
|
||||
</button>
|
||||
</li>
|
||||
@@ -65,27 +80,37 @@
|
||||
{#each { length: aniListWatchListLoaded.data.Page.pageInfo.lastPage } as _, i}
|
||||
{#if i + 1 === page}
|
||||
<li>
|
||||
<button on:click={() => ChangeWatchListPage(i+1)}
|
||||
class="flex items-center justify-center px-4 h-10 leading-tight border bg-gray-100 border-gray-700 bg-gray-700 text-white">{i + 1}</button>
|
||||
<button
|
||||
on:click={() => ChangeWatchListPage(i + 1)}
|
||||
class="flex items-center justify-center px-4 h-10 leading-tight border hover:bg-gray-100 border-gray-700 bg-gray-700 text-white"
|
||||
>{i + 1}</button
|
||||
>
|
||||
</li>
|
||||
{:else}
|
||||
<li>
|
||||
<button on:click={() => ChangeWatchListPage(i+1)}
|
||||
class="flex items-center justify-center px-4 h-10 leading-tight border dark border-gray-700 text-gray-400 hover:bg-gray-700 hover:text-white">{i + 1}</button>
|
||||
<button
|
||||
on:click={() => ChangeWatchListPage(i + 1)}
|
||||
class="flex items-center justify-center px-4 h-10 leading-tight border dark border-gray-700 text-gray-400 hover:bg-gray-700 hover:text-white"
|
||||
>{i + 1}</button
|
||||
>
|
||||
</li>
|
||||
{/if}
|
||||
{/each}
|
||||
{#if page === aniListWatchListLoaded.data.Page.pageInfo.lastPage}
|
||||
<li>
|
||||
<button disabled
|
||||
class="flex items-center justify-center px-4 h-10 leading-tight border rounded-e-lg dark border-gray-700 text-gray-400 cursor-default">
|
||||
<button
|
||||
disabled
|
||||
class="flex items-center justify-center px-4 h-10 leading-tight border rounded-e-lg dark border-gray-700 text-gray-400 cursor-default"
|
||||
>
|
||||
Next
|
||||
</button>
|
||||
</li>
|
||||
{:else}
|
||||
<li>
|
||||
<button on:click={() => ChangeWatchListPage(page+1)}
|
||||
class="flex items-center justify-center px-4 h-10 leading-tight border rounded-e-lg dark border-gray-700 text-gray-400 hover:bg-gray-700 hover:text-white">
|
||||
<button
|
||||
on:click={() => ChangeWatchListPage(page + 1)}
|
||||
class="flex items-center justify-center px-4 h-10 leading-tight border rounded-e-lg dark border-gray-700 text-gray-400 hover:bg-gray-700 hover:text-white"
|
||||
>
|
||||
Next
|
||||
</button>
|
||||
</li>
|
||||
@@ -95,8 +120,12 @@
|
||||
{/if}
|
||||
<div class="flex mt-5">
|
||||
<div class="w-20 mx-auto">
|
||||
<select bind:value={perPage} on:change={(e) => changeCountPerPage(e)} id="countPerPage"
|
||||
class="border text-sm rounded-lg block w-full p-2.5 bg-gray-700 border-gray-600 placeholder-gray-400 text-white focus:ring-blue-500 focus:border-blue-500">
|
||||
<select
|
||||
bind:value={perPage}
|
||||
on:change={(e) => changeCountPerPage(e)}
|
||||
id="countPerPage"
|
||||
class="border text-sm rounded-lg block w-full p-2.5 bg-gray-700 border-gray-600 placeholder-gray-400 text-white focus:ring-blue-500 focus:border-blue-500"
|
||||
>
|
||||
{#each perPageOptions as option}
|
||||
<option value={option}>
|
||||
{option}
|
||||
@@ -108,35 +137,81 @@
|
||||
<div>
|
||||
<div>Total Anime: {aniListWatchListLoaded.data.Page.pageInfo.total}</div>
|
||||
{#if aniListWatchListLoaded.data.Page.pageInfo.lastPage <= 12}
|
||||
<div class="md:hidden">Page: {page} of {aniListWatchListLoaded.data.Page.pageInfo.lastPage}</div>
|
||||
<div class="md:hidden">
|
||||
Page: {page} of {aniListWatchListLoaded.data.Page.pageInfo.lastPage}
|
||||
</div>
|
||||
{:else}
|
||||
<div>Page: {page} of {aniListWatchListLoaded.data.Page.pageInfo.lastPage}</div>
|
||||
<div>
|
||||
Page: {page} of {aniListWatchListLoaded.data.Page.pageInfo.lastPage}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="max-w-xs mx-auto">
|
||||
<div class="relative flex items-center max-w-[11rem]">
|
||||
<button type="button" id="decrement-button" on:click={() => ChangeWatchListPage(page-1)}
|
||||
class="bg-gray-700 hover:bg-gray-600 border-gray-600 border rounded-s-lg p-3 h-11 focus:ring-gray-700 focus:ring-2 focus:outline-none">
|
||||
<svg class="w-3 h-3 text-white" aria-hidden="true"
|
||||
xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 18 2">
|
||||
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M1 1h16"/>
|
||||
<button
|
||||
type="button"
|
||||
id="decrement-button"
|
||||
on:click={() => ChangeWatchListPage(page - 1)}
|
||||
class={page <= 1
|
||||
? "border-gray-600 border rounded-s-lg p-3 h-11 focus:ring-gray-700 focus:ring-2 focus:outline-none"
|
||||
: "bg-gray-700 hover:bg-gray-600 border-gray-600 border rounded-s-lg p-3 h-11 focus:ring-gray-700 focus:ring-2 focus:outline-none"}
|
||||
disabled={page <= 1}
|
||||
>
|
||||
<svg
|
||||
class="w-3 h-3 text-white"
|
||||
aria-hidden="true"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 18 2"
|
||||
>
|
||||
<path
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M1 1h16"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
<input type="number" min="1" max="{aniListWatchListLoaded.data.Page.pageInfo.lastPage}"
|
||||
on:keydown={changePage} id="page-counter"
|
||||
<input
|
||||
type="number"
|
||||
min="1"
|
||||
max={aniListWatchListLoaded.data.Page.pageInfo.lastPage}
|
||||
on:keydown={changePage}
|
||||
id="page-counter"
|
||||
class="[appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none border-x-0 h-11 font-medium text-center text-sm block w-full pb-6 bg-gray-700 border-gray-600 placeholder-gray-400 text-white focus:ring-blue-500 focus:border-blue-500"
|
||||
value={page} required/>
|
||||
<div class="absolute bottom-1 start-1/2 -translate-x-1/2 rtl:translate-x-1/2 flex items-center text-xs text-gray-400 space-x-1 rtl:space-x-reverse">
|
||||
value={page}
|
||||
required
|
||||
/>
|
||||
<div
|
||||
class="absolute bottom-1 start-1/2 -translate-x-1/2 rtl:translate-x-1/2 flex items-center text-xs text-gray-400 space-x-1 rtl:space-x-reverse"
|
||||
>
|
||||
<span>Page #</span>
|
||||
</div>
|
||||
<button type="button" id="increment-button" on:click={() => ChangeWatchListPage(page+1)}
|
||||
class="hover:bg-gray-600 border-gray-600 border rounded-e-lg p-3 h-11 focus:ring-gray-700 focus:ring-2 focus:outline-none">
|
||||
<svg class="w-3 h-3 text-white" aria-hidden="true"
|
||||
xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 18 18">
|
||||
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M9 1v16M1 9h16"/>
|
||||
<button
|
||||
type="button"
|
||||
id="increment-button"
|
||||
on:click={() => ChangeWatchListPage(page + 1)}
|
||||
class={page >= aniListWatchListLoaded.data.Page.pageInfo.lastPage
|
||||
? "border-gray-600 border rounded-e-lg p-3 h-11 focus:ring-gray-700 focus:ring-2 focus:outline-none"
|
||||
: "bg-gray-700 hover:bg-gray-600 border-gray-600 border rounded-e-lg p-3 h-11 focus:ring-gray-700 focus:ring-2 focus:outline-none"}
|
||||
disabled={page >= aniListWatchListLoaded.data.Page.pageInfo.lastPage}
|
||||
>
|
||||
<svg
|
||||
class="w-3 h-3 text-white"
|
||||
aria-hidden="true"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 18 18"
|
||||
>
|
||||
<path
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M9 1v16M1 9h16"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
|
||||
import {AniListSearch} from "../../wailsjs/go/main/App";
|
||||
import type {AniSearchList} from "../types/AniListTypes";
|
||||
import type {AniSearchList} from "../anilist/types/AniListTypes";
|
||||
import {push} from "svelte-spa-router";
|
||||
|
||||
let aniSearch = ""
|
||||
|
||||
0
frontend/src/helperComponents/Sort.svelte
Normal file
0
frontend/src/helperComponents/Sort.svelte
Normal file
@@ -1,59 +1,99 @@
|
||||
<script lang="ts">
|
||||
import {
|
||||
aniListLoggedIn,
|
||||
aniListWatchlist,
|
||||
GetAnimeSingleItem,
|
||||
loading,
|
||||
} from "../helperModules/GlobalVariablesAndHelperFunctions.svelte";
|
||||
import { push } from "svelte-spa-router";
|
||||
import type {AniListCurrentUserWatchList} from "../types/AniListCurrentUserWatchListType"
|
||||
import type { AniListCurrentUserWatchList } from "../anilist/types/AniListCurrentUserWatchListType";
|
||||
import { Rating } from "flowbite-svelte";
|
||||
import loader from '../helperFunctions/loader'
|
||||
import loader from "../helperFunctions/loader";
|
||||
import { CheckIfAniListLoggedInAndLoadWatchList } from "../helperModules/CheckIfAniListLoggedInAndLoadWatchList.svelte";
|
||||
|
||||
let isAniListLoggedIn: boolean;
|
||||
let aniListWatchListLoaded: AniListCurrentUserWatchList;
|
||||
|
||||
let aniListWatchListLoaded: AniListCurrentUserWatchList
|
||||
|
||||
aniListWatchlist.subscribe((value) => aniListWatchListLoaded = value)
|
||||
|
||||
aniListLoggedIn.subscribe((value) => (isAniListLoggedIn = value));
|
||||
aniListWatchlist.subscribe((value) => (aniListWatchListLoaded = value));
|
||||
</script>
|
||||
|
||||
<div>
|
||||
<div class="mx-auto max-w-2xl p-4 sm:p-6 lg:max-w-7xl lg:px-8 relative items-center">
|
||||
<h1 class="text-left text-xl font-bold mb-4">Your AniList WatchList</h1>
|
||||
|
||||
<div class="grid grid-cols-1 gap-x-6 gap-y-10 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 xl:gap-x-8">
|
||||
{#each aniListWatchListLoaded.data.Page.mediaList as media}
|
||||
<div use:loader={loading}
|
||||
class="aspect-h-1 aspect-w-1 w-full overflow-hidden rounded-lg xl:aspect-h-8 xl:aspect-w-7">
|
||||
<div class="flex flex-col items-center group">
|
||||
<button on:click={() => {
|
||||
push(`#/anime/${media.media.id}`)
|
||||
{#if isAniListLoggedIn}
|
||||
<div
|
||||
class="mx-auto max-w-2xl p-4 sm:p-6 lg:max-w-7xl lg:px-8 relative items-center"
|
||||
>
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<h1 class="text-left text-xl font-bold">Your AniList WatchList</h1>
|
||||
<button
|
||||
type="button"
|
||||
class="py-2 px-4 bg-gray-700 rounded-lg"
|
||||
on:click={async () => {
|
||||
loading.set(true);
|
||||
await CheckIfAniListLoggedInAndLoadWatchList();
|
||||
loading.set(false);
|
||||
}}
|
||||
>
|
||||
<img class="rounded-lg" src={media.media.coverImage.large} alt={
|
||||
media.media.title.english === "" ?
|
||||
media.media.title.romaji :
|
||||
media.media.title.english
|
||||
}/>
|
||||
Refresh WatchList
|
||||
</button>
|
||||
<Rating id="anime-rating" total={5} size={35} rating={media.score/2.0}/>
|
||||
<button class="mt-4 text-md font-semibold text-white-700"
|
||||
on:click={() => GetAnimeSingleItem(media.media.id, true)}>
|
||||
{
|
||||
media.media.title.english === "" ?
|
||||
media.media.title.romaji :
|
||||
media.media.title.english
|
||||
}
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="grid grid-cols-1 gap-x-6 gap-y-10 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 xl:gap-x-8"
|
||||
>
|
||||
{#each aniListWatchListLoaded.data.Page.mediaList as media}
|
||||
<div
|
||||
use:loader={loading}
|
||||
class="aspect-h-1 aspect-w-1 w-full overflow-hidden rounded-lg xl:aspect-h-8 xl:aspect-w-7"
|
||||
>
|
||||
<div class="flex flex-col items-center group">
|
||||
<button
|
||||
on:click={() => {
|
||||
push(`#/anime/${media.media.id}`);
|
||||
// loading.set(true)
|
||||
// GetAniListSingleItem(media.media.id, true).then(() => {
|
||||
// loading.set(false)
|
||||
//
|
||||
// })
|
||||
}}
|
||||
>
|
||||
<img
|
||||
class="rounded-lg w-[230px] h-[330px] object-cover"
|
||||
src={media.media.coverImage.large}
|
||||
alt={media.media.title.english === ""
|
||||
? media.media.title.romaji
|
||||
: media.media.title.english}
|
||||
/>
|
||||
</button>
|
||||
<p class="mt-1 text-lg font-medium text-white-900">{media.progress}
|
||||
/ {media.media.nextAiringEpisode.episode !== 0 ?
|
||||
media.media.nextAiringEpisode.episode - 1 : media.media.episodes}</p>
|
||||
<Rating
|
||||
id="anime-rating"
|
||||
total={5}
|
||||
size={35}
|
||||
rating={media.score / 2.0}
|
||||
/>
|
||||
<button
|
||||
class="mt-4 text-md font-semibold text-white-700"
|
||||
on:click={() => GetAnimeSingleItem(media.media.id, true)}
|
||||
>
|
||||
{media.media.title.english === ""
|
||||
? media.media.title.romaji
|
||||
: media.media.title.english}
|
||||
</button>
|
||||
<p class="mt-1 text-lg font-medium text-white-900">
|
||||
{media.progress}
|
||||
/ {media.media.nextAiringEpisode.episode !== 0
|
||||
? media.media.nextAiringEpisode.episode - 1
|
||||
: media.media.episodes}
|
||||
</p>
|
||||
{#if media.media.episodes > 0}
|
||||
<p class="mt-1 text-lg font-medium text-white-900">Total
|
||||
Episodes: {media.media.episodes}</p>
|
||||
<p class="mt-1 text-lg font-medium text-white-900">
|
||||
Total Episodes: {media.media.episodes}
|
||||
</p>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -1,28 +1,32 @@
|
||||
<script lang="ts">
|
||||
import {BrowserOpenURL} from "../../wailsjs/runtime"
|
||||
import { BrowserOpenURL } from "../../wailsjs/runtime";
|
||||
|
||||
export let id: string
|
||||
let url = ""
|
||||
let isAniList = false
|
||||
let isMAL = false
|
||||
let isSimkl = false
|
||||
let newId = id
|
||||
let re = /[ams]?-?(.*)/
|
||||
export let id: string;
|
||||
export let url = "";
|
||||
let isAniList = false;
|
||||
let isMAL = false;
|
||||
let isSimkl = false;
|
||||
let newId = id;
|
||||
let re = /[ams]?-?(.*)/;
|
||||
if (id !== undefined && id.length > 0) {
|
||||
isAniList = id.includes("a-")
|
||||
isMAL = id.includes("m-")
|
||||
isSimkl = id.includes("s-")
|
||||
newId = id.match(re)[1]
|
||||
isAniList = id.includes("a-");
|
||||
isMAL = id.includes("m-");
|
||||
isSimkl = id.includes("s-");
|
||||
if (isAniList || isMAL || isSimkl) newId = id.match(re)[1];
|
||||
else newId = id;
|
||||
}
|
||||
|
||||
|
||||
if (isAniList) url = `https://anilist.co/anime/${newId}`
|
||||
if (isMAL) url = `https://myanimelist.net/anime/${newId}`
|
||||
if (isSimkl) url = `https://simkl.com/anime/${newId}`
|
||||
if (isAniList) url = `https://anilist.co/anime/${newId}`;
|
||||
if (isMAL) url = `https://myanimelist.net/anime/${newId}`;
|
||||
if (isSimkl) url = `https://simkl.com/anime/${newId}`;
|
||||
</script>
|
||||
|
||||
{#if url.length > 0}
|
||||
<button class="underline underline-offset-2 px-4 py-1" on:click={() => BrowserOpenURL(url)}>{newId}</button>
|
||||
<button
|
||||
type="button"
|
||||
class="underline underline-offset-2 px-4 py-1"
|
||||
on:click={() => BrowserOpenURL(url)}>{newId}</button
|
||||
>
|
||||
{:else}
|
||||
{id}
|
||||
{/if}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type {AniListGetSingleAnime} from "../types/AniListCurrentUserWatchListType";
|
||||
import type {AniListGetSingleAnime} from "../anilist/types/AniListCurrentUserWatchListType";
|
||||
|
||||
export const AniListGetSingleAnimeDefaultData: AniListGetSingleAnime = {
|
||||
data: {
|
||||
@@ -26,18 +26,7 @@ export const AniListGetSingleAnimeDefaultData: AniListGetSingleAnime = {
|
||||
airingAt: 0,
|
||||
timeUntilAiring: 0,
|
||||
episode: 0,
|
||||
},
|
||||
tags: [
|
||||
{
|
||||
id: 0,
|
||||
name: "",
|
||||
description: "",
|
||||
rank: 0,
|
||||
isMediaSpoiler: false,
|
||||
isAdult: false
|
||||
}
|
||||
],
|
||||
isAdult: false
|
||||
},
|
||||
status: "",
|
||||
startedAt: {
|
||||
@@ -1,121 +0,0 @@
|
||||
// stores/user.ts
|
||||
import {get, writable} from 'svelte/store';
|
||||
import type {SimklUser, SimklWatchList} from "../types/simklTypes";
|
||||
import type {AniListUser} from "../types/AniListTypes";
|
||||
import type {MALWatchlist, MyAnimeListUser} from "../types/MALTypes";
|
||||
import {
|
||||
GetAniListLoggedInUser,
|
||||
GetMyAnimeList,
|
||||
GetMyAnimeListLoggedInUser,
|
||||
GetSimklLoggedInUser,
|
||||
LogoutAniList,
|
||||
LogoutMyAnimeList,
|
||||
LogoutSimkl,
|
||||
SimklGetUserWatchlist
|
||||
} from "../../wailsjs/go/main/App";
|
||||
import {LoadAniListWatchList} from "../helperModules/LoadAniListWatchList.svelte";
|
||||
import {aniListWatchlist, malWatchList, simklWatchList} from "../helperModules/GlobalVariablesAndHelperFunctions.svelte"
|
||||
import type {AniListCurrentUserWatchList} from "../types/AniListCurrentUserWatchListType";
|
||||
import {AniListUserDefaultData, MALUserDefaultData, SimklUserDefaultData} from "../defaults/UserTypes";
|
||||
|
||||
let aniWatchlist: AniListCurrentUserWatchList
|
||||
aniListWatchlist.subscribe(value => aniWatchlist = value)
|
||||
|
||||
interface UserState {
|
||||
simkl: { user: SimklUser; isLoggedIn: boolean; isPrimary: boolean };
|
||||
anilist: { user: AniListUser; isLoggedIn: boolean; isPrimary: boolean };
|
||||
mal: { user: MyAnimeListUser; isLoggedIn: boolean; isPrimary: boolean };
|
||||
}
|
||||
|
||||
const createUserStore = () => {
|
||||
const {subscribe, update} = writable<UserState>({
|
||||
anilist: {user: AniListUserDefaultData, isLoggedIn: false, isPrimary: true},
|
||||
mal: {user: MALUserDefaultData, isLoggedIn: false, isPrimary: false},
|
||||
simkl: {user: SimklUserDefaultData, isLoggedIn: false, isPrimary: false}
|
||||
});
|
||||
|
||||
return {
|
||||
subscribe,
|
||||
setAniListUser: (user: AniListUser, isPrimary = true) =>
|
||||
update(s => ({
|
||||
...s,
|
||||
anilist: {user, isLoggedIn: true, isPrimary}
|
||||
})),
|
||||
setMalUser: (user: MyAnimeListUser, isPrimary = false) =>
|
||||
update(s => ({
|
||||
...s,
|
||||
mal: {user, isLoggedIn: true, isPrimary}
|
||||
})),
|
||||
setSimklUser: (user: SimklUser, isPrimary = false) =>
|
||||
update(s => ({
|
||||
...s,
|
||||
simkl: {user, isLoggedIn: true, isPrimary}
|
||||
})),
|
||||
setPrimary: (provider: 'simkl' | 'anilist' | 'mal') =>
|
||||
update(s => ({
|
||||
...s,
|
||||
simkl: {...s.simkl, isPrimary: provider === 'simkl'},
|
||||
anilist: {...s.anilist, isPrimary: provider === 'anilist'},
|
||||
mal: {...s.mal, isPrimary: provider === 'mal'}
|
||||
})),
|
||||
checkProvider: async (provider: 'simkl' | 'anilist' | 'mal') => {
|
||||
const state = get(userStore);
|
||||
if (state[provider].isLoggedIn) return;
|
||||
|
||||
if (provider === 'anilist') {
|
||||
const user: AniListUser = await GetAniListLoggedInUser();
|
||||
console.log(user)
|
||||
userStore.setAniListUser(user, state.anilist.isPrimary);
|
||||
console.log(state.anilist.isPrimary)
|
||||
if (state.anilist.isPrimary) await LoadAniListWatchList();
|
||||
} else if (provider === 'mal') {
|
||||
const user: MyAnimeListUser = await GetMyAnimeListLoggedInUser();
|
||||
userStore.setMalUser(user, state.mal.isPrimary);
|
||||
if (state.mal.isPrimary) {
|
||||
const watchList = await GetMyAnimeList(1000);
|
||||
malWatchList.set(watchList);
|
||||
}
|
||||
} else if (provider === 'simkl') {
|
||||
const user: SimklUser = await GetSimklLoggedInUser();
|
||||
userStore.setSimklUser(user, state.simkl.isPrimary);
|
||||
if (state.simkl.isPrimary) {
|
||||
const watchList = await SimklGetUserWatchlist();
|
||||
simklWatchList.set(watchList)
|
||||
}
|
||||
}
|
||||
},
|
||||
logout: async (provider: 'simkl' | 'anilist' | 'mal') => {
|
||||
update(s => {
|
||||
s[provider].user = {} as SimklUser | AniListUser | MyAnimeListUser
|
||||
s[provider].isLoggedIn = false;
|
||||
if (s[provider].isPrimary) {
|
||||
s[provider].isPrimary = false;
|
||||
const others = ['simkl', 'anilist', 'mal'] as const;
|
||||
const newPrimary = others.find(p => p !== provider && s[p].isLoggedIn);
|
||||
if (newPrimary) s[newPrimary].isPrimary = true;
|
||||
}
|
||||
return s;
|
||||
});
|
||||
|
||||
// Clear provider-specific watchlist
|
||||
if (provider === 'anilist') {
|
||||
if (Object.keys(aniWatchlist).length !== 0) {
|
||||
aniListWatchlist.set({} as AniListCurrentUserWatchList)
|
||||
}
|
||||
await LogoutAniList();
|
||||
} else if (provider === 'mal') {
|
||||
if (Object.keys(aniWatchlist).length !== 0) {
|
||||
malWatchList.set({} as MALWatchlist);
|
||||
}
|
||||
await LogoutMyAnimeList();
|
||||
} else if (provider === 'simkl') {
|
||||
if (Object.keys(aniWatchlist).length !== 0) {
|
||||
simklWatchList.set({} as SimklWatchList);
|
||||
}
|
||||
await LogoutSimkl();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const userStore = createUserStore();
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts" context="module">
|
||||
import type {TableItem} from "../types/TableTypes";
|
||||
import type {TableItem} from "../helperTypes/TableTypes";
|
||||
import { tableItems } from "./GlobalVariablesAndHelperFunctions.svelte"
|
||||
|
||||
export function AddAnimeServiceToTable(animeItem: TableItem) {
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
<script lang="ts" context="module">
|
||||
import {
|
||||
CheckIfAniListLoggedIn,
|
||||
GetAniListLoggedInUser,
|
||||
GetAniListUserWatchingList,
|
||||
} from "../../wailsjs/go/main/App";
|
||||
import {
|
||||
aniListUser,
|
||||
watchListPage,
|
||||
animePerPage,
|
||||
aniListPrimary,
|
||||
aniListLoggedIn,
|
||||
aniListWatchlist,
|
||||
aniListSort,
|
||||
clearApiError,
|
||||
setApiError,
|
||||
} from "./GlobalVariablesAndHelperFunctions.svelte";
|
||||
|
||||
let isAniListPrimary: boolean;
|
||||
let page: number;
|
||||
let perPage: number;
|
||||
let sort: string;
|
||||
|
||||
aniListPrimary.subscribe((value) => (isAniListPrimary = value));
|
||||
watchListPage.subscribe((value) => (page = value));
|
||||
animePerPage.subscribe((value) => (perPage = value));
|
||||
aniListSort.subscribe((value) => (sort = value));
|
||||
|
||||
export const LoadAniListUser = async () => {
|
||||
try {
|
||||
await GetAniListLoggedInUser().then((user) => {
|
||||
aniListUser.set(user);
|
||||
});
|
||||
} catch (err) {
|
||||
const errorMsg = err instanceof Error ? err.message : String(err);
|
||||
setApiError(
|
||||
"anilist",
|
||||
`Failed to load user: ${errorMsg}`,
|
||||
undefined,
|
||||
true,
|
||||
);
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
|
||||
export const LoadAniListWatchList = async () => {
|
||||
try {
|
||||
const watchList = await GetAniListUserWatchingList(page, perPage, sort);
|
||||
aniListWatchlist.set(watchList);
|
||||
clearApiError();
|
||||
} catch (err) {
|
||||
const errorMsg = err instanceof Error ? err.message : String(err);
|
||||
setApiError(
|
||||
"anilist",
|
||||
`Failed to load watch list: ${errorMsg}`,
|
||||
undefined,
|
||||
true,
|
||||
);
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
export const CheckIfAniListLoggedInAndLoadWatchList = async () => {
|
||||
try {
|
||||
const loggedIn = await CheckIfAniListLoggedIn();
|
||||
if (loggedIn) {
|
||||
await LoadAniListUser();
|
||||
if (isAniListPrimary) await LoadAniListWatchList();
|
||||
}
|
||||
aniListLoggedIn.set(loggedIn);
|
||||
} catch (err) {
|
||||
const errorMsg = err instanceof Error ? err.message : String(err);
|
||||
setApiError(
|
||||
"anilist",
|
||||
`Authentication failed: ${errorMsg}`,
|
||||
undefined,
|
||||
true,
|
||||
);
|
||||
aniListLoggedIn.set(false);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
25
frontend/src/helperModules/CheckIfMyAnimeListLoggedIn.svelte
Normal file
25
frontend/src/helperModules/CheckIfMyAnimeListLoggedIn.svelte
Normal file
@@ -0,0 +1,25 @@
|
||||
<script lang="ts" context="module">
|
||||
import {CheckIfMyAnimeListLoggedIn, GetMyAnimeList, GetMyAnimeListLoggedInUser} from "../../wailsjs/go/main/App";
|
||||
import {malUser, malPrimary, malWatchList, malLoggedIn} from "./GlobalVariablesAndHelperFunctions.svelte"
|
||||
|
||||
let isMalPrimary: boolean
|
||||
malPrimary.subscribe(value => isMalPrimary = value)
|
||||
|
||||
export const CheckIfMALLoggedInAndSetUser = async () => {
|
||||
await CheckIfMyAnimeListLoggedIn().then(loggedIn => {
|
||||
if (loggedIn) {
|
||||
GetMyAnimeListLoggedInUser().then(user => {
|
||||
malUser.set(user)
|
||||
if (isMalPrimary) {
|
||||
GetMyAnimeList(1000).then(watchList => {
|
||||
malWatchList.set(watchList)
|
||||
malLoggedIn.set(loggedIn)
|
||||
})
|
||||
} else {
|
||||
malLoggedIn.set(loggedIn)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
29
frontend/src/helperModules/CheckIsSimklLoggedIn.svelte
Normal file
29
frontend/src/helperModules/CheckIsSimklLoggedIn.svelte
Normal file
@@ -0,0 +1,29 @@
|
||||
<script lang="ts" context="module">
|
||||
import {CheckIfSimklLoggedIn, GetSimklLoggedInUser, SimklGetUserWatchlist} from "../../wailsjs/go/main/App";
|
||||
import { simklLoggedIn, simklUser, simklPrimary, simklWatchList } from "./GlobalVariablesAndHelperFunctions.svelte";
|
||||
|
||||
let isSimklPrimary: boolean
|
||||
simklPrimary.subscribe(value => isSimklPrimary = value)
|
||||
|
||||
export const CheckIfSimklLoggedInAndSetUser = async () => {
|
||||
await CheckIfSimklLoggedIn().then(loggedIn => {
|
||||
if (loggedIn) {
|
||||
GetSimklLoggedInUser().then(user => {
|
||||
if (Object.keys(user).length === 0) {
|
||||
simklLoggedIn.set(false)
|
||||
} else {
|
||||
simklUser.set(user)
|
||||
if (isSimklPrimary) {
|
||||
SimklGetUserWatchlist().then(result => {
|
||||
simklWatchList.set(result)
|
||||
simklLoggedIn.set(loggedIn)
|
||||
})
|
||||
} else {
|
||||
simklLoggedIn.set(loggedIn)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
@@ -1,73 +1,212 @@
|
||||
<script lang="ts" context="module">
|
||||
import {
|
||||
GetAniListItem,
|
||||
GetAniListLoggedInUser,
|
||||
GetAniListUserWatchingList,
|
||||
GetMyAnimeListAnime,
|
||||
SimklSearch
|
||||
GetMyAnimeListLoggedInUser,
|
||||
GetSimklLoggedInUser,
|
||||
LogoutAniList,
|
||||
LogoutMyAnimeList,
|
||||
LogoutSimkl,
|
||||
SimklGetUserWatchlist,
|
||||
SimklSearch,
|
||||
} from "../../wailsjs/go/main/App";
|
||||
import {userStore} from "../helperFunctions/userStore";
|
||||
import type {
|
||||
AniListGetSingleAnime
|
||||
} from "../types/AniListCurrentUserWatchListType.js";
|
||||
import {get, writable} from 'svelte/store'
|
||||
import type {SimklAnime, SimklWatchList} from "../types/simklTypes";
|
||||
import type {MALAnime, MALWatchlist} from "../types/MALTypes";
|
||||
import type {TableItems} from "../types/TableTypes";
|
||||
import {AniListGetSingleAnimeDefaultData} from "../defaults/AniListGetSingleAnime";
|
||||
import {AniListWatchListDefaultData} from "../defaults/AniListWatchListDefaultData";
|
||||
AniListCurrentUserWatchList,
|
||||
AniListGetSingleAnime,
|
||||
} from "../anilist/types/AniListCurrentUserWatchListType.js";
|
||||
import { writable } from "svelte/store";
|
||||
import type {
|
||||
SimklAnime,
|
||||
SimklUser,
|
||||
SimklWatchList,
|
||||
} from "../simkl/types/simklTypes";
|
||||
import {
|
||||
type AniListUser,
|
||||
MediaListSort,
|
||||
} from "../anilist/types/AniListTypes";
|
||||
import type {
|
||||
MALAnime,
|
||||
MALWatchlist,
|
||||
MyAnimeListUser,
|
||||
} from "../mal/types/MALTypes";
|
||||
import type { TableItems } from "../helperTypes/TableTypes";
|
||||
import { AniListGetSingleAnimeDefaultData } from "../helperDefaults/AniListGetSingleAnime";
|
||||
|
||||
export let aniListAnime = writable(AniListGetSingleAnimeDefaultData)
|
||||
export let title = writable("")
|
||||
export let aniListLoggedIn = writable(false)
|
||||
export let simklWatchList = writable({} as SimklWatchList)
|
||||
export let aniListWatchlist = writable(AniListWatchListDefaultData)
|
||||
export let malWatchList = writable({} as MALWatchlist)
|
||||
export let malAnime = writable({} as MALAnime)
|
||||
export let simklAnime = writable({} as SimklAnime)
|
||||
export let loading = writable(false)
|
||||
export let tableItems = writable([] as TableItems)
|
||||
export let aniListAnime = writable(AniListGetSingleAnimeDefaultData);
|
||||
export let title = writable("");
|
||||
export let aniListLoggedIn = writable(false);
|
||||
export let simklLoggedIn = writable(false);
|
||||
export let malLoggedIn = writable(false);
|
||||
export let simklWatchList = writable({} as SimklWatchList);
|
||||
export let aniListPrimary = writable(true);
|
||||
export let simklPrimary = writable(false);
|
||||
export let malPrimary = writable(false);
|
||||
export let simklUser = writable({} as SimklUser);
|
||||
export let aniListUser = writable({} as AniListUser);
|
||||
export let malUser = writable({} as MyAnimeListUser);
|
||||
export let aniListWatchlist = writable({} as AniListCurrentUserWatchList);
|
||||
export let malWatchList = writable({} as MALWatchlist);
|
||||
export let malAnime = writable({} as MALAnime);
|
||||
export let simklAnime = writable({} as SimklAnime);
|
||||
export let loading = writable(false);
|
||||
export let tableItems = writable([] as TableItems);
|
||||
export let watchlistNeedsRefresh = writable(false);
|
||||
export let aniListSort = writable(MediaListSort.UpdatedTimeDesc);
|
||||
|
||||
export let watchListPage = writable(1)
|
||||
export let animePerPage = writable(20)
|
||||
export let watchListPage = writable(1);
|
||||
export let animePerPage = writable(20);
|
||||
|
||||
let currentAniListAnime: AniListGetSingleAnime
|
||||
let isAniListPrimary: boolean;
|
||||
let page: number;
|
||||
let perPage: number;
|
||||
let sort: string;
|
||||
let aniWatchlist: AniListCurrentUserWatchList;
|
||||
let currentAniListAnime: AniListGetSingleAnime;
|
||||
|
||||
aniListAnime.subscribe(value => currentAniListAnime = value)
|
||||
let isMalLoggedIn: boolean;
|
||||
let isSimklLoggedIn: boolean;
|
||||
|
||||
aniListPrimary.subscribe((value) => (isAniListPrimary = value));
|
||||
watchListPage.subscribe((value) => (page = value));
|
||||
animePerPage.subscribe((value) => (perPage = value));
|
||||
aniListWatchlist.subscribe((value) => (aniWatchlist = value));
|
||||
malLoggedIn.subscribe((value) => (isMalLoggedIn = value));
|
||||
simklLoggedIn.subscribe((value) => (isSimklLoggedIn = value));
|
||||
aniListAnime.subscribe((value) => (currentAniListAnime = value));
|
||||
aniListSort.subscribe((value) => (sort = value));
|
||||
|
||||
export async function GetAnimeSingleItem(aniId: number, login: boolean): Promise<""> {
|
||||
const store = get(userStore)
|
||||
if (store.anilist.isLoggedIn)
|
||||
await GetAniListItem(aniId, login).then(aniListResult => {
|
||||
let finalResult: AniListGetSingleAnime
|
||||
finalResult = aniListResult
|
||||
export interface ApiError {
|
||||
service: string;
|
||||
message: string;
|
||||
statusCode?: string;
|
||||
canRetry: boolean;
|
||||
}
|
||||
export const apiError = writable<ApiError | null>(null);
|
||||
export const isApiDown = writable(false);
|
||||
export function setApiError(
|
||||
service: string,
|
||||
message: string,
|
||||
statusCode?: string,
|
||||
canRetry: boolean = true,
|
||||
) {
|
||||
apiError.set({
|
||||
service,
|
||||
message,
|
||||
statusCode,
|
||||
canRetry,
|
||||
});
|
||||
isApiDown.set(true);
|
||||
}
|
||||
export function clearApiError() {
|
||||
apiError.set(null);
|
||||
isApiDown.set(false);
|
||||
}
|
||||
|
||||
export async function GetAnimeSingleItem(
|
||||
aniId: number,
|
||||
login: boolean,
|
||||
): Promise<""> {
|
||||
await GetAniListItem(aniId, login).then((aniListResult) => {
|
||||
let finalResult: AniListGetSingleAnime;
|
||||
finalResult = aniListResult;
|
||||
if (login === false) {
|
||||
finalResult.data.MediaList.status = ""
|
||||
finalResult.data.MediaList.score = 0
|
||||
finalResult.data.MediaList.progress = 0
|
||||
finalResult.data.MediaList.notes = ""
|
||||
finalResult.data.MediaList.repeat = 0
|
||||
finalResult.data.MediaList.startedAt.day = 0
|
||||
finalResult.data.MediaList.startedAt.month = 0
|
||||
finalResult.data.MediaList.startedAt.year = 0
|
||||
finalResult.data.MediaList.completedAt.day = 0
|
||||
finalResult.data.MediaList.completedAt.month = 0
|
||||
finalResult.data.MediaList.completedAt.year = 0
|
||||
finalResult.data.MediaList.status = "";
|
||||
finalResult.data.MediaList.score = 0;
|
||||
finalResult.data.MediaList.progress = 0;
|
||||
finalResult.data.MediaList.notes = "";
|
||||
finalResult.data.MediaList.repeat = 0;
|
||||
finalResult.data.MediaList.startedAt.day = 0;
|
||||
finalResult.data.MediaList.startedAt.month = 0;
|
||||
finalResult.data.MediaList.startedAt.year = 0;
|
||||
finalResult.data.MediaList.completedAt.day = 0;
|
||||
finalResult.data.MediaList.completedAt.month = 0;
|
||||
finalResult.data.MediaList.completedAt.year = 0;
|
||||
}
|
||||
aniListAnime.set(finalResult)
|
||||
title.set(currentAniListAnime.data.MediaList.media.title.english === "" ?
|
||||
currentAniListAnime.data.MediaList.media.title.romaji :
|
||||
currentAniListAnime.data.MediaList.media.title.english)
|
||||
})
|
||||
if (store.mal.isLoggedIn) {
|
||||
await GetMyAnimeListAnime(currentAniListAnime.data.MediaList.media.idMal).then(malResult => {
|
||||
malAnime.set(malResult)
|
||||
})
|
||||
aniListAnime.set(finalResult);
|
||||
title.set(
|
||||
currentAniListAnime.data.MediaList.media.title.english === ""
|
||||
? currentAniListAnime.data.MediaList.media.title.romaji
|
||||
: currentAniListAnime.data.MediaList.media.title.english,
|
||||
);
|
||||
});
|
||||
if (isMalLoggedIn) {
|
||||
await GetMyAnimeListAnime(
|
||||
currentAniListAnime.data.MediaList.media.idMal,
|
||||
).then((malResult) => {
|
||||
malAnime.set(malResult);
|
||||
});
|
||||
}
|
||||
if (store.simkl.isLoggedIn) {
|
||||
await SimklSearch(currentAniListAnime.data.MediaList).then((value: SimklAnime) => {
|
||||
simklAnime.set(value)
|
||||
})
|
||||
if (isSimklLoggedIn) {
|
||||
await SimklSearch(currentAniListAnime.data.MediaList).then(
|
||||
(value: SimklAnime) => {
|
||||
simklAnime.set(value);
|
||||
},
|
||||
);
|
||||
}
|
||||
return ""
|
||||
return "";
|
||||
}
|
||||
|
||||
export function loginToSimkl(): void {
|
||||
GetSimklLoggedInUser().then((user) => {
|
||||
if (Object.keys(user).length === 0) {
|
||||
simklLoggedIn.set(false);
|
||||
} else {
|
||||
simklUser.set(user);
|
||||
SimklGetUserWatchlist().then((result) => {
|
||||
simklWatchList.set(result);
|
||||
simklLoggedIn.set(true);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function loginToAniList(): void {
|
||||
GetAniListLoggedInUser().then((result) => {
|
||||
aniListUser.set(result);
|
||||
if (isAniListPrimary) {
|
||||
GetAniListUserWatchingList(page, perPage, sort).then((result) => {
|
||||
aniListWatchlist.set(result);
|
||||
aniListLoggedIn.set(true);
|
||||
});
|
||||
} else {
|
||||
aniListLoggedIn.set(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function loginToMAL(): void {
|
||||
GetMyAnimeListLoggedInUser().then((result) => {
|
||||
malUser.set(result);
|
||||
malLoggedIn.set(true);
|
||||
});
|
||||
}
|
||||
|
||||
export function logoutOfAniList(): void {
|
||||
LogoutAniList().then((result) => {
|
||||
console.log(result);
|
||||
if (Object.keys(aniWatchlist).length !== 0) {
|
||||
aniListWatchlist.set({} as AniListCurrentUserWatchList);
|
||||
}
|
||||
aniListUser.set({} as AniListUser);
|
||||
aniListLoggedIn.set(false);
|
||||
});
|
||||
}
|
||||
|
||||
export function logoutOfMAL(): void {
|
||||
LogoutMyAnimeList().then((result) => {
|
||||
console.log(result);
|
||||
malUser.set({} as MyAnimeListUser);
|
||||
malLoggedIn.set(false);
|
||||
});
|
||||
}
|
||||
|
||||
export function logoutOfSimkl(): void {
|
||||
LogoutSimkl().then((result) => {
|
||||
console.log(result);
|
||||
simklUser.set({} as SimklUser);
|
||||
simklLoggedIn.set(false);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
@@ -1,17 +0,0 @@
|
||||
<script lang="ts" context="module">
|
||||
import {GetAniListUserWatchingList} from "../../wailsjs/go/main/App";
|
||||
import {MediaListSort} from "../types/AniListTypes";
|
||||
import { watchListPage, animePerPage, aniListWatchlist } from "./GlobalVariablesAndHelperFunctions.svelte"
|
||||
|
||||
let page: number
|
||||
let perPage: number
|
||||
|
||||
watchListPage.subscribe(value => page = value)
|
||||
animePerPage.subscribe(value => perPage = value)
|
||||
|
||||
export const LoadAniListWatchList = async () => {
|
||||
await GetAniListUserWatchingList(page, perPage, MediaListSort.UpdatedTimeDesc).then((watchList) => {
|
||||
aniListWatchlist.set(watchList)
|
||||
})
|
||||
}
|
||||
</script>
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { aniListAnime, GetAnimeSingleItem } from "../helperModules/GlobalVariablesAndHelperFunctions.svelte";
|
||||
import Anime from "../helperComponents/Anime.svelte"
|
||||
import { AniListGetSingleAnimeDefaultData } from "../defaults/AniListGetSingleAnime";
|
||||
import { AniListGetSingleAnimeDefaultData } from "../helperDefaults/AniListGetSingleAnime";
|
||||
import Spinner from "../helperComponents/Spinner.svelte";
|
||||
|
||||
export let params: Record<string, string>
|
||||
|
||||
@@ -2,13 +2,49 @@
|
||||
import Pagination from "../helperComponents/Pagination.svelte";
|
||||
import WatchList from "../helperComponents/WatchList.svelte";
|
||||
import {
|
||||
aniListLoggedIn,
|
||||
aniListPrimary,
|
||||
loading,
|
||||
isApiDown,
|
||||
apiError,
|
||||
} from "../helperModules/GlobalVariablesAndHelperFunctions.svelte";
|
||||
import loader from '../helperFunctions/loader'
|
||||
import {userStore} from "../helperFunctions/userStore";
|
||||
import loader from "../helperFunctions/loader";
|
||||
|
||||
let isAniListPrimary: boolean;
|
||||
let isAniListLoggedIn: boolean;
|
||||
|
||||
aniListPrimary.subscribe((value) => (isAniListPrimary = value));
|
||||
aniListLoggedIn.subscribe((value) => (isAniListLoggedIn = value));
|
||||
</script>
|
||||
{#if $userStore.anilist.isLoggedIn && $userStore.anilist.isPrimary}
|
||||
|
||||
{#if $isApiDown}
|
||||
<div class="container py-10">
|
||||
<div
|
||||
class="bg-yellow-50 border border-yellow-200 rounded-lg p-6 text-center"
|
||||
>
|
||||
<svg
|
||||
class="mx-auto h-12 w-12 text-yellow-600 mb-4"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"
|
||||
/>
|
||||
</svg>
|
||||
<h2 class="text-xl font-semibold text-yellow-900 mb-2">
|
||||
API Unavailable
|
||||
</h2>
|
||||
<p class="text-yellow-700 mb-4">
|
||||
The {$apiError?.service || "service"} is currently unavailable. The app will
|
||||
remain open, and you can retry when the service is back online.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{:else if isAniListLoggedIn && isAniListPrimary}
|
||||
<div class="container py-10">
|
||||
<Pagination />
|
||||
<WatchList />
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
// vite.config.ts
|
||||
import { defineConfig } from "file:///home/nymusicman/Code/AniTrack/frontend/node_modules/vite/dist/node/index.js";
|
||||
import { svelte } from "file:///home/nymusicman/Code/AniTrack/frontend/node_modules/@sveltejs/vite-plugin-svelte/src/index.js";
|
||||
var vite_config_default = defineConfig({
|
||||
plugins: [svelte()]
|
||||
});
|
||||
export {
|
||||
vite_config_default as default
|
||||
};
|
||||
//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsidml0ZS5jb25maWcudHMiXSwKICAic291cmNlc0NvbnRlbnQiOiBbImNvbnN0IF9fdml0ZV9pbmplY3RlZF9vcmlnaW5hbF9kaXJuYW1lID0gXCIvaG9tZS9ueW11c2ljbWFuL0NvZGUvQW5pVHJhY2svZnJvbnRlbmRcIjtjb25zdCBfX3ZpdGVfaW5qZWN0ZWRfb3JpZ2luYWxfZmlsZW5hbWUgPSBcIi9ob21lL255bXVzaWNtYW4vQ29kZS9BbmlUcmFjay9mcm9udGVuZC92aXRlLmNvbmZpZy50c1wiO2NvbnN0IF9fdml0ZV9pbmplY3RlZF9vcmlnaW5hbF9pbXBvcnRfbWV0YV91cmwgPSBcImZpbGU6Ly8vaG9tZS9ueW11c2ljbWFuL0NvZGUvQW5pVHJhY2svZnJvbnRlbmQvdml0ZS5jb25maWcudHNcIjtpbXBvcnQge2RlZmluZUNvbmZpZ30gZnJvbSAndml0ZSdcbmltcG9ydCB7c3ZlbHRlfSBmcm9tICdAc3ZlbHRlanMvdml0ZS1wbHVnaW4tc3ZlbHRlJ1xuXG4vLyBodHRwczovL3ZpdGVqcy5kZXYvY29uZmlnL1xuZXhwb3J0IGRlZmF1bHQgZGVmaW5lQ29uZmlnKHtcbiAgcGx1Z2luczogW3N2ZWx0ZSgpXVxufSlcbiJdLAogICJtYXBwaW5ncyI6ICI7QUFBdVMsU0FBUSxvQkFBbUI7QUFDbFUsU0FBUSxjQUFhO0FBR3JCLElBQU8sc0JBQVEsYUFBYTtBQUFBLEVBQzFCLFNBQVMsQ0FBQyxPQUFPLENBQUM7QUFDcEIsQ0FBQzsiLAogICJuYW1lcyI6IFtdCn0K
|
||||
@@ -364,7 +364,7 @@ export namespace main {
|
||||
id: number;
|
||||
mediaId: number;
|
||||
userId: number;
|
||||
// Go type: 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\""; Tags []struct { Id int "json:\"id\""; Name string "json:\"name\""; Description string "json:\"description\""; Rank int "json:\"rank\""; IsMediaSpoiler bool "json:\"isMediaSpoiler\""; IsAdult bool "json:\"isAdult\"" } "json:\"tags\""; IsAdult bool "json:\"isAdult\"" }
|
||||
// Go type: 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\""; Genres []string "json:\"genres\""; Tags []struct { Id int "json:\"id\""; Name string "json:\"name\""; Description string "json:\"description\""; Rank int "json:\"rank\""; IsMediaSpoiler bool "json:\"isMediaSpoiler\""; IsAdult bool "json:\"isAdult\"" } "json:\"tags\""; IsAdult bool "json:\"isAdult\"" }
|
||||
media: any;
|
||||
status: string;
|
||||
// Go type: struct { Year int "json:\"year\""; Month int "json:\"month\""; Day int "json:\"day\"" }
|
||||
|
||||
@@ -1,79 +0,0 @@
|
||||
# @name AniChart
|
||||
|
||||
POST https://graphql.anilist.co
|
||||
Accept: applicaton/json
|
||||
X-REQUEST-TYPE: Graphql
|
||||
|
||||
query ($page: Int, $perPage: Int, $airingAt_greater:Int) {
|
||||
Page(page: $page, perPage: $perPage) {
|
||||
pageInfo {
|
||||
total
|
||||
perPage
|
||||
currentPage
|
||||
lastPage
|
||||
hasNextPage
|
||||
}
|
||||
airingSchedules(airingAt_greater:$airingAt_greater){
|
||||
id
|
||||
airingAt
|
||||
timeUntilAiring
|
||||
episode
|
||||
mediaId
|
||||
media{
|
||||
id
|
||||
title{
|
||||
english
|
||||
romaji
|
||||
native
|
||||
}
|
||||
type
|
||||
format
|
||||
status
|
||||
startDate{
|
||||
year
|
||||
month
|
||||
day
|
||||
}
|
||||
endDate{
|
||||
year
|
||||
month
|
||||
day
|
||||
}
|
||||
season
|
||||
seasonYear
|
||||
episodes
|
||||
duration
|
||||
coverImage{
|
||||
medium
|
||||
large
|
||||
color
|
||||
extraLarge
|
||||
}
|
||||
bannerImage
|
||||
genres
|
||||
averageScore
|
||||
meanScore
|
||||
popularity
|
||||
trending
|
||||
favourites
|
||||
tags{
|
||||
id
|
||||
name
|
||||
description
|
||||
category
|
||||
rank
|
||||
isGeneralSpoiler
|
||||
isMediaSpoiler
|
||||
isAdult
|
||||
}
|
||||
isAdult
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
"page": 50,
|
||||
"perPage": 20,
|
||||
"airingAt_greater": 1730260800
|
||||
}
|
||||
@@ -1,83 +0,0 @@
|
||||
# @name AniList Item
|
||||
|
||||
POST https://graphql.anilist.co
|
||||
Accept: applicaton/json
|
||||
X-REQUEST-TYPE: Graphql
|
||||
Authorization: Bearer {{ANILIST_ACCESS_TOKEN}}
|
||||
|
||||
query ($userId: Int, $mediaId: Int, $listType: MediaType) {
|
||||
MediaList(mediaId: $mediaId, userId: $userId, type: $listType) {
|
||||
id
|
||||
mediaId
|
||||
userId
|
||||
media {
|
||||
id
|
||||
idMal
|
||||
tags {
|
||||
id
|
||||
name
|
||||
description
|
||||
rank
|
||||
isMediaSpoiler
|
||||
isAdult
|
||||
}
|
||||
title {
|
||||
romaji
|
||||
english
|
||||
native
|
||||
}
|
||||
description
|
||||
coverImage {
|
||||
large
|
||||
}
|
||||
season
|
||||
seasonYear
|
||||
status
|
||||
episodes
|
||||
nextAiringEpisode {
|
||||
airingAt
|
||||
timeUntilAiring
|
||||
episode
|
||||
}
|
||||
isAdult
|
||||
}
|
||||
status
|
||||
startedAt {
|
||||
year
|
||||
month
|
||||
day
|
||||
}
|
||||
completedAt {
|
||||
year
|
||||
month
|
||||
day
|
||||
}
|
||||
notes
|
||||
progress
|
||||
score
|
||||
repeat
|
||||
user {
|
||||
id
|
||||
name
|
||||
avatar {
|
||||
large
|
||||
medium
|
||||
}
|
||||
statistics {
|
||||
anime {
|
||||
count
|
||||
statuses {
|
||||
status
|
||||
count
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
"userId": 413504,
|
||||
"mediaId": 170998,
|
||||
"listType": "ANIME"
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
# @name AniList MediaList User Query
|
||||
|
||||
POST https://graphql.anilist.co
|
||||
Accept: applicaton/json
|
||||
X-REQUEST-TYPE: Graphql
|
||||
|
||||
query(
|
||||
$page: Int
|
||||
$perPage: Int
|
||||
$userId: Int
|
||||
$listType: MediaType
|
||||
$status: MediaListStatus
|
||||
) {
|
||||
Page(page: $page, perPage: $perPage) {
|
||||
pageInfo {
|
||||
total
|
||||
perPage
|
||||
currentPage
|
||||
lastPage
|
||||
hasNextPage
|
||||
}
|
||||
mediaList(userId: $userId, type: $listType, status: $status) {
|
||||
id
|
||||
mediaId
|
||||
userId
|
||||
media {
|
||||
id
|
||||
idMal
|
||||
title {
|
||||
romaji
|
||||
english
|
||||
native
|
||||
}
|
||||
description
|
||||
coverImage {
|
||||
large
|
||||
}
|
||||
season
|
||||
seasonYear
|
||||
episodes
|
||||
}
|
||||
status
|
||||
notes
|
||||
progress
|
||||
score
|
||||
repeat
|
||||
user {
|
||||
id
|
||||
statistics {
|
||||
anime {
|
||||
count
|
||||
statuses {
|
||||
status
|
||||
count
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
{
|
||||
"page": 1,
|
||||
"perPage": 20,
|
||||
"userId": 413504,
|
||||
"listType": "ANIME",
|
||||
"status": "CURRENT"
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
# @name AniList Search
|
||||
|
||||
POST https://graphql.anilist.co
|
||||
Accept: applicaton/json
|
||||
X-REQUEST-TYPE: Graphql
|
||||
|
||||
query ($search: String!, $listType: MediaType) {
|
||||
Page (page: 1, perPage: 100) {
|
||||
pageInfo {
|
||||
total
|
||||
currentPage
|
||||
lastPage
|
||||
hasNextPage
|
||||
perPage
|
||||
}
|
||||
media (search: $search, type: $listType) {
|
||||
id
|
||||
idMal
|
||||
title {
|
||||
romaji
|
||||
english
|
||||
native
|
||||
}
|
||||
description
|
||||
coverImage {
|
||||
large
|
||||
}
|
||||
season
|
||||
seasonYear
|
||||
status
|
||||
episodes
|
||||
nextAiringEpisode{
|
||||
airingAt
|
||||
timeUntilAiring
|
||||
episode
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
"search": "dan-da-dan",
|
||||
"listType": "ANIME"
|
||||
}
|
||||
@@ -1,93 +0,0 @@
|
||||
# @name GetAniListUserWatchList
|
||||
|
||||
POST https://graphql.anilist.co
|
||||
Accept: applicaton/json
|
||||
X-REQUEST-TYPE: Graphql
|
||||
|
||||
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
|
||||
startedAt {
|
||||
year
|
||||
month
|
||||
day
|
||||
}
|
||||
completedAt {
|
||||
year
|
||||
month
|
||||
day
|
||||
}
|
||||
notes
|
||||
progress
|
||||
score
|
||||
repeat
|
||||
user {
|
||||
id
|
||||
name
|
||||
avatar {
|
||||
large
|
||||
medium
|
||||
}
|
||||
statistics {
|
||||
anime {
|
||||
count
|
||||
statuses {
|
||||
status
|
||||
count
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
{
|
||||
"page": 1,
|
||||
"perPage": 20,
|
||||
"userId": 413504,
|
||||
"listType": "ANIME",
|
||||
"status": "CURRENT",
|
||||
"sort": "UPDATED_TIME_DESC"
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
# @name GetAuthorizationToken
|
||||
|
||||
GET https://anilist.co/api/v2/oauth/authorize?client_id={{ANILIST_APP_ID}}&redirect_uri=http://localhost:6734/callback&response_type=code
|
||||
@@ -1,11 +0,0 @@
|
||||
# @name Load AniList Oauth Token
|
||||
|
||||
POST https://anilist.co/api/v2/oauth/token
|
||||
Content-Type: application/x-www-form-urlencoded
|
||||
Accept: application/json
|
||||
|
||||
grant_type=authorization_code
|
||||
client_id={{ANILIST_APP_ID}}
|
||||
client_secret={{ANILIST_SECRET_ID}}
|
||||
redirect_uri=http://localhost:6734/callback
|
||||
code={{ANILIST_CODE}}
|
||||
@@ -1,76 +0,0 @@
|
||||
# @name AniList Change Episode Watched
|
||||
|
||||
POST https://graphql.anilist.co
|
||||
Content-Type: applicaton/json
|
||||
Accept: applicaton/json
|
||||
X-REQUEST-TYPE: Graphql
|
||||
Authorization: Bearer {{ANILIST_ACCESS_TOKEN}}
|
||||
|
||||
mutation($mediaId:Int, $progress:Int, $status:MediaListStatus){
|
||||
SaveMediaListEntry(mediaId:$mediaId, progress:$progress, status:$status){
|
||||
id
|
||||
mediaId
|
||||
userId
|
||||
media {
|
||||
id
|
||||
idMal
|
||||
title {
|
||||
romaji
|
||||
english
|
||||
native
|
||||
}
|
||||
description
|
||||
coverImage {
|
||||
large
|
||||
}
|
||||
season
|
||||
seasonYear
|
||||
status
|
||||
episodes
|
||||
nextAiringEpisode {
|
||||
airingAt
|
||||
timeUntilAiring
|
||||
episode
|
||||
}
|
||||
isAdult
|
||||
}
|
||||
status
|
||||
startedAt{
|
||||
year
|
||||
month
|
||||
day
|
||||
}
|
||||
completedAt{
|
||||
year
|
||||
month
|
||||
day
|
||||
}
|
||||
notes
|
||||
progress
|
||||
score
|
||||
repeat
|
||||
user {
|
||||
id
|
||||
name
|
||||
avatar{
|
||||
large
|
||||
medium
|
||||
}
|
||||
statistics{
|
||||
anime{
|
||||
count
|
||||
statuses{
|
||||
status
|
||||
count
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
"mediaId": 169417,
|
||||
"progress": 12,
|
||||
"status":"COMPLETED"
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
# @name AniList Change Status
|
||||
|
||||
POST https://graphql.anilist.co
|
||||
Content-Type: applicaton/json
|
||||
Accept: applicaton/json
|
||||
X-REQUEST-TYPE: Graphql
|
||||
Authorization: Bearer {{ANILIST_ACCESS_TOKEN}}
|
||||
|
||||
mutation($mediaId:Int, $status:MediaListStatus){
|
||||
SaveMediaListEntry(mediaId:$mediaId, status:$status){
|
||||
id
|
||||
status
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
"mediaId": 1,
|
||||
"status": "CURRENT"
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
# @name AniList Change Count
|
||||
|
||||
POST https://graphql.anilist.co
|
||||
Content-Type: applicaton/json
|
||||
Accept: applicaton/json
|
||||
X-REQUEST-TYPE: Graphql
|
||||
Authorization: Bearer {{ANILIST_ACCESS_TOKEN}}
|
||||
|
||||
mutation (
|
||||
$mediaId: Int
|
||||
$progress: Int
|
||||
$status: MediaListStatus
|
||||
$score: Float
|
||||
$repeat: Int
|
||||
$notes: String
|
||||
$startedAt: FuzzyDateInput
|
||||
$completedAt: FuzzyDateInput
|
||||
) {
|
||||
SaveMediaListEntry(
|
||||
mediaId: $mediaId
|
||||
progress: $progress
|
||||
status: $status
|
||||
score: $score
|
||||
repeat: $repeat
|
||||
notes: $notes
|
||||
startedAt: $startedAt
|
||||
completedAt: $completedAt
|
||||
) {
|
||||
mediaId
|
||||
progress
|
||||
status
|
||||
score
|
||||
repeat
|
||||
notes
|
||||
startedAt {
|
||||
year
|
||||
month
|
||||
day
|
||||
}
|
||||
completedAt {
|
||||
year
|
||||
month
|
||||
day
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
"mediaId": 170998,
|
||||
"progress": 5,
|
||||
"status": "CURRENT",
|
||||
"score": 9.0,
|
||||
"repeat": 0,
|
||||
"notes": ",malSync::eyJ1IjoiaHR0cHM6Ly93d3cuY3J1bmNoeXJvbGwuY29tL3Nlcmllcy9HVkRIWDg1Wk4vI3NlYXNvbj1HNjNWQzJHUUsiLCJwIjoiIn0=::",
|
||||
"startedAt": {
|
||||
"year": 2024,
|
||||
"month": 7,
|
||||
"day": 10
|
||||
},
|
||||
"completedAt": {
|
||||
"year": 0,
|
||||
"month": 0,
|
||||
"day": 0
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
# @name AniList Delete Media
|
||||
|
||||
POST https://graphql.anilist.co
|
||||
Content-Type: applicaton/json
|
||||
Accept: applicaton/json
|
||||
X-REQUEST-TYPE: Graphql
|
||||
Authorization: Bearer {{ANILIST_ACCESS_TOKEN}}
|
||||
|
||||
mutation ($id: Int) {
|
||||
DeleteMediaListEntry(id: $id) {
|
||||
deleted
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
"id": 430978266
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
# @name Get AnimeList
|
||||
|
||||
GET https://api.myanimelist.net/v2/users/{{MAL_USER}}/animelist?fields=list_status&status=watching&limit=1000
|
||||
Content-Type application/x-www-form-urlencoded
|
||||
Accept: application/json
|
||||
Authorization: Bearer {{MAL_ACCESS_TOKEN}}
|
||||
@@ -1,12 +0,0 @@
|
||||
# @name Get Authorization
|
||||
|
||||
POST https://myanimelist.net/v1/oauth2/token
|
||||
Content-Type: application/x-www-form-urlencoded
|
||||
Accept: application/json
|
||||
|
||||
grant_type=authorization_code&
|
||||
client_id={{MAL_CLIENT_ID}}&
|
||||
client_secret={{MAL_CLIENT_SECRET}}&
|
||||
redirect_uri=http://localhost:6734/callback&
|
||||
code={{MAL_CODE}}&
|
||||
code_verifier={{MAL_VERIFIER}}
|
||||
@@ -1,5 +0,0 @@
|
||||
# @name Get Single Anime
|
||||
|
||||
GET https://api.myanimelist.net/v2/anime/57380?fields=id,title,main_picture,alternative_titles,start_date,end_date,synopsis,mean,rank,popularity,num_list_users,num_scoring_users,nsfw,genres,created_at,updated_at,media_type,status,my_list_status,num_episodes,start_season,broadcast,source,average_episode_duration,rating,pictures,background,related_anime,recommendations,studios,statistics
|
||||
Accept: application/json
|
||||
Authorization: Bearer {{MAL_ACCESS_TOKEN}}
|
||||
@@ -1,5 +0,0 @@
|
||||
# @name MAL Oauth Page
|
||||
|
||||
GET https://myanimelist.net/v1/oauth2/authorize?response_type=code&client_id={{MAL_CLIENT_ID}}&redirect_uri={{MAL_CALLBACK_URI}}
|
||||
cookie MALSESSIONID=5ad688aafb78239bfd84752752ce193f; MALHLOGSESSID=632f67c3955267b4e57fc3d74b373ebb
|
||||
Accept: application/json
|
||||
@@ -1,8 +0,0 @@
|
||||
# @name Update Anime Status
|
||||
|
||||
PATCH https://api.myanimelist.net/v2/anime/50205/my_list_status
|
||||
Content-Type: application/x-www-form-urlencoded
|
||||
Accept: application/json
|
||||
Authorization: Bearer {{MAL_ACCESS_TOKEN}}
|
||||
|
||||
num_watched_episodes=3
|
||||
@@ -1,3 +0,0 @@
|
||||
# @name Get Code
|
||||
|
||||
GET https://simkl.com/oauth/authorize?response_type=code&client_id={{SIMKL_CLIENT_ID}}&redirect_uri=http://localhost:6734/callback
|
||||
@@ -1,5 +0,0 @@
|
||||
# @name Get Anime Full Info
|
||||
|
||||
GET https://api.simkl.com/anime/40084?extended=full
|
||||
Accept application/json
|
||||
simkl-api-key {{SIMKL_CLIENT_ID}}
|
||||
@@ -1,7 +0,0 @@
|
||||
# @name GetUser WatchList
|
||||
|
||||
GET https://api.simkl.com/sync/all-items/anime/
|
||||
Content-Type application/json
|
||||
Accept application/json
|
||||
simkl-api-key {{SIMKL_CLIENT_ID}}
|
||||
Authorization Bearer {{SIMKL_AUTH_TOKEN}}
|
||||
@@ -1,5 +0,0 @@
|
||||
# @name Search By MALID to Get Simkl ID
|
||||
|
||||
GET https://api.simkl.com/search/id?anilist=174576
|
||||
Accept application/json
|
||||
simkl-api-key {{SIMKL_CLIENT_ID}}
|
||||
@@ -1,17 +0,0 @@
|
||||
# @name Delete Entry
|
||||
|
||||
GET https://api.simkl.com/sync/history/remove
|
||||
Content-Type application/json
|
||||
Accept application/json
|
||||
simkl-api-key {{SIMKL_CLIENT_ID}}
|
||||
Authorization Bearer {{SIMKL_AUTH_TOKEN}}
|
||||
|
||||
{
|
||||
"shows": [
|
||||
{
|
||||
"ids": {
|
||||
"simkl": 909121
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
# @name Update Episode
|
||||
|
||||
GET https://api.simkl.com/sync/history
|
||||
Content-Type application/json
|
||||
Accept application/json
|
||||
simkl-api-key {{SIMKL_CLIENT_ID}}
|
||||
Authorization Bearer {{SIMKL_AUTH_TOKEN}}
|
||||
|
||||
{
|
||||
"shows": [
|
||||
{
|
||||
"title": "Ramen Aka Neko",
|
||||
"ids": {
|
||||
"simkl": 2307708,
|
||||
"mal": "57325",
|
||||
"anilist": "170998"
|
||||
},
|
||||
"episodes": [
|
||||
{
|
||||
"number": 1
|
||||
},
|
||||
{
|
||||
"number": 2
|
||||
},
|
||||
{
|
||||
"number": 3
|
||||
},
|
||||
{
|
||||
"number": 4
|
||||
},
|
||||
{
|
||||
"number": 5
|
||||
},
|
||||
{
|
||||
"number": 6
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
# @name SimklGetAuthorizationToken
|
||||
|
||||
POST https://api.simkl.com/oauth/token
|
||||
Content-Type application/json
|
||||
|
||||
{
|
||||
"grant_type": "authorization_code",
|
||||
"client_id": "{{SIMKL_CLIENT_ID}}",
|
||||
"client_secret": "{{SIMKL_CLIENT_SECRET}}",
|
||||
"redirect_uri": "http://localhost:6734/callback",
|
||||
"code": {{SIMKL_CODE}}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
{
|
||||
"$schema": "https://raw.githubusercontent.com/mistweaverco/kulala.nvim/main/schemas/http-client.env.schema.json",
|
||||
"dev": {
|
||||
"ANILIST_ACCESS_TOKEN": "",
|
||||
"ANILIST_APP_ID": "",
|
||||
"ANILIST_SECRET": "",
|
||||
"ANILSIT_CODE": "",
|
||||
"MAL_ACCESS_TOKEN": "",
|
||||
"MAL_CLIENT_ID": "",
|
||||
"MAL_CLIENT_SECRET": "",
|
||||
"MAL_CODE": "",
|
||||
"MAL_USER": "",
|
||||
"MAL_VERIFIER": "",
|
||||
"SIMKL_AUTH_TOKEN": "",
|
||||
"SIMKL_CODE": "",
|
||||
"SIMKL_CLIENT_ID": "",
|
||||
"SIMKL_CLIENT_SECRET": ""
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,6 @@
|
||||
},
|
||||
"info": {
|
||||
"productName": "AniTrack",
|
||||
"productVersion": "0.6.0"
|
||||
"productVersion": "0.7.0"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user