From 22ff290a81e503cd8cf2f85a6ecd6a4d568caa61 Mon Sep 17 00:00:00 2001 From: John O'Keefe Date: Thu, 15 Aug 2024 21:27:31 -0400 Subject: [PATCH] added getting MAL watchlist --- MALFunctions.go | 56 +++++++++++ MALTypes.go | 28 ++++++ bruno/AniTrack/environments/Dev.bru | 4 +- frontend/src/App.svelte | 42 ++++---- .../GlobalVariablesAndHelperFunctions.svelte | 3 +- frontend/src/mal/types/MALTypes.ts | 36 ++++++- frontend/wailsjs/go/main/App.d.ts | 2 + frontend/wailsjs/go/main/App.js | 4 + frontend/wailsjs/go/models.ts | 99 +++++++++++++++++++ 9 files changed, 252 insertions(+), 22 deletions(-) create mode 100644 MALFunctions.go diff --git a/MALFunctions.go b/MALFunctions.go new file mode 100644 index 0000000..64b129e --- /dev/null +++ b/MALFunctions.go @@ -0,0 +1,56 @@ +package main + +import ( + "encoding/json" + "fmt" + "io" + "log" + "net/http" + "net/url" + "strconv" + "strings" +) + +func MALHelper(method string, malUrl string, body url.Values) json.RawMessage { + client := &http.Client{} + + req, _ := http.NewRequest(method, malUrl, strings.NewReader(body.Encode())) + + req.Header.Add("Content-Type", "application/x-www-form-urlencoded") + req.Header.Add("Authorization", "Bearer "+myAnimeListJwt.AccessToken) + + 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: "Errored when sending request to the server" + err.Error(), + }) + + return message + } + + defer resp.Body.Close() + respBody, _ := io.ReadAll(resp.Body) + + return respBody +} + +func (a *App) GetMyAnimeList(count int) MALWatchlist { + 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 := MALHelper("GET", malUrl, nil) + + err := json.Unmarshal(respBody, &malList) + if err != nil { + log.Printf("Failed to unmarshal json response, %s\n", err) + } + + return malList +} diff --git a/MALTypes.go b/MALTypes.go index 8dc91d3..6206917 100644 --- a/MALTypes.go +++ b/MALTypes.go @@ -1,5 +1,7 @@ package main +import "time" + type MyAnimeListJWT struct { TokenType string `json:"token_type"` ExpiresIn int `json:"expires_in"` @@ -37,3 +39,29 @@ type AnimeStatistics struct { NumTimesRewatched int `json:"num_times_rewatched" ts_type:"numTimesRewatched"` MeanScore float64 `json:"mean_score" ts_type:"meanScore"` } + +type MALWatchlist struct { + Data []struct { + Node struct { + Id int `json:"id" ts_type:"id"` + Title string `json:"title" ts_type:"title"` + MainPicture struct { + Medium string `json:"medium" json:"medium"` + Large string `json:"large" json:"large"` + } `json:"main_picture" json:"mainPicture"` + } `json:"node" json:"node"` + ListStatus struct { + Status string `json:"status" ts_type:"status"` + Score int `json:"score" ts_type:"score"` + NumEpisodesWatched int `json:"num_episodes_watched" ts_type:"numEpisodesWatched"` + IsRewatching bool `json:"is_rewatching" ts_type:"isRewatching"` + UpdatedAt time.Time `json:"updated_at" ts_type:"updatedAt"` + StartDate string `json:"start_date" ts_type:"startDate"` + FinishDate string `json:"finish_date" ts_type:"finishDate"` + } `json:"list_status" ts_type:"listStatus"` + } `json:"data" json:"data"` + Paging struct { + Previous string `json:"previous" ts_type:"previous"` + Next string `json:"next" ts_type:"next"` + } `json:"paging" ts_type:"paging"` +} diff --git a/bruno/AniTrack/environments/Dev.bru b/bruno/AniTrack/environments/Dev.bru index 60de753..94141dc 100644 --- a/bruno/AniTrack/environments/Dev.bru +++ b/bruno/AniTrack/environments/Dev.bru @@ -12,5 +12,7 @@ vars:secret [ SIMKL_AUTH_TOKEN, ANILIST_ACCESS_TOKEN, MAL_CODE, - MAL_VERIFIER + MAL_VERIFIER, + MAL_USER, + MAL_ACCESS_TOKEN ] diff --git a/frontend/src/App.svelte b/frontend/src/App.svelte index 7eb9086..df4de48 100644 --- a/frontend/src/App.svelte +++ b/frontend/src/App.svelte @@ -14,6 +14,7 @@ title, watchListPage, animePerPage, + malWatchList } from "./GlobalVariablesAndHelperFunctions.svelte"; import { CheckIfAniListLoggedIn, @@ -23,7 +24,7 @@ GetAniListUserWatchingList, GetSimklLoggedInUser, SimklGetUserWatchlist, - GetMyAnimeListLoggedInUser, + GetMyAnimeListLoggedInUser, GetMyAnimeList, } from "../wailsjs/go/main/App"; import {MediaListSort} from "./anilist/types/AniListTypes"; import type {AniListCurrentUserWatchList} from "./anilist/types/AniListCurrentUserWatchListType" @@ -51,38 +52,41 @@ const size = "xl" onMount(async () => { - await CheckIfAniListLoggedIn().then(result => { - if (result) { - GetAniListLoggedInUser().then(result => { - aniListUser.set(result) + await CheckIfAniListLoggedIn().then(loggedIn => { + if (loggedIn) { + GetAniListLoggedInUser().then(user => { + aniListUser.set(user) if (isAniListPrimary) { - GetAniListUserWatchingList(page, perPage, MediaListSort.UpdatedTimeDesc).then((result) => { - aniListWatchlist.set(result) - aniListLoggedIn.set(true) + GetAniListUserWatchingList(page, perPage, MediaListSort.UpdatedTimeDesc).then((watchList) => { + aniListWatchlist.set(watchList) + aniListLoggedIn.set(loggedIn) }) } else { - aniListLoggedIn.set(result) + aniListLoggedIn.set(loggedIn) } }) } }) - await CheckIfMyAnimeListLoggedIn().then(result => { - if (result) { - GetMyAnimeListLoggedInUser().then(result => { - malUser.set(result) - malLoggedIn.set(result) + await CheckIfMyAnimeListLoggedIn().then(loggedIn => { + if (loggedIn) { + GetMyAnimeListLoggedInUser().then(user => { + malUser.set(user) + GetMyAnimeList(1000).then(watchList => { + malWatchList.set(watchList) + malLoggedIn.set(loggedIn) + }) }) } }) - await CheckIfSimklLoggedIn().then(result => { - if (result) { - GetSimklLoggedInUser().then(result => { - simklUser.set(result) + await CheckIfSimklLoggedIn().then(loggedIn => { + if (loggedIn) { + GetSimklLoggedInUser().then(user => { + simklUser.set(user) SimklGetUserWatchlist().then(result => { simklWatchList.set(result) - simklLoggedIn.set(result) + simklLoggedIn.set(loggedIn) }) }) } diff --git a/frontend/src/GlobalVariablesAndHelperFunctions.svelte b/frontend/src/GlobalVariablesAndHelperFunctions.svelte index 76b1e34..8b0021e 100644 --- a/frontend/src/GlobalVariablesAndHelperFunctions.svelte +++ b/frontend/src/GlobalVariablesAndHelperFunctions.svelte @@ -11,7 +11,7 @@ import {writable} from 'svelte/store' import type {SimklUser, SimklWatchList} from "./simkl/types/simklTypes"; import {type AniListUser, MediaListSort} from "./anilist/types/AniListTypes"; - import type {MyAnimeListUser} from "./mal/types/MALTypes"; + import type {MALWatchlist, MyAnimeListUser} from "./mal/types/MALTypes"; export let aniListAnime: AniListGetSingleAnime export let title = writable("") @@ -25,6 +25,7 @@ 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 watchListPage = writable(1) export let animePerPage = writable(20) diff --git a/frontend/src/mal/types/MALTypes.ts b/frontend/src/mal/types/MALTypes.ts index a7f63c1..1f9e057 100644 --- a/frontend/src/mal/types/MALTypes.ts +++ b/frontend/src/mal/types/MALTypes.ts @@ -28,4 +28,38 @@ export interface AnimeStatistics { numEpisodes: number numTimesRewatched: number meanScore: number -} \ No newline at end of file +} + +export interface MALWatchlist { + data: { + node: Node + listStatus: ListStatus + }[] + paging: Paging +} + +export interface Node { + id: number + title: string + mainPicture: MainPicture +} + +export interface MainPicture { + medium: string + large: string +} + +export interface ListStatus { + status: string + score: number + numEpisodesWatched: number + isRewatching: boolean + updated_at: string + startDate: string + finishDate: string +} + +export interface Paging { + previous: string + next: string +} diff --git a/frontend/wailsjs/go/main/App.d.ts b/frontend/wailsjs/go/main/App.d.ts index 3cb297b..76e2717 100755 --- a/frontend/wailsjs/go/main/App.d.ts +++ b/frontend/wailsjs/go/main/App.d.ts @@ -20,6 +20,8 @@ export function GetAniListLoggedInUser():Promise; export function GetAniListUserWatchingList(arg1:number,arg2:number,arg3:string):Promise; +export function GetMyAnimeList(arg1:number):Promise; + export function GetMyAnimeListLoggedInUser():Promise; export function GetSimklLoggedInUser():Promise; diff --git a/frontend/wailsjs/go/main/App.js b/frontend/wailsjs/go/main/App.js index 680f17a..f3fc2f2 100755 --- a/frontend/wailsjs/go/main/App.js +++ b/frontend/wailsjs/go/main/App.js @@ -38,6 +38,10 @@ export function GetAniListUserWatchingList(arg1, arg2, arg3) { return window['go']['main']['App']['GetAniListUserWatchingList'](arg1, arg2, arg3); } +export function GetMyAnimeList(arg1) { + return window['go']['main']['App']['GetMyAnimeList'](arg1); +} + export function GetMyAnimeListLoggedInUser() { return window['go']['main']['App']['GetMyAnimeListLoggedInUser'](); } diff --git a/frontend/wailsjs/go/models.ts b/frontend/wailsjs/go/models.ts index 30cda94..68caacf 100755 --- a/frontend/wailsjs/go/models.ts +++ b/frontend/wailsjs/go/models.ts @@ -121,6 +121,38 @@ export namespace main { this.anime_type = source["anime_type"]; } } + export class MALWatchlist { + data: struct { Node struct { Id int "json:\"id\" ts_type:\"id\""; Title string "json:\"title\" ts_type:\"title\""; MainPicture struct { Medium string "json:\"medium\" json:\"medium\""; Large string "json:\"large\" json:\"large\"" } "json:\"main_picture\" json:\"mainPicture\"" } "json:\"node\" json:\"node\""; ListStatus struct { Status string "json:\"status\" ts_type:\"status\""; Score int "json:\"score\" ts_type:\"score\""; NumEpisodesWatched int "json:\"num_episodes_watched\" ts_type:\"numEpisodesWatched\""; IsRewatching bool "json:\"is_rewatching\" ts_type:\"isRewatching\""; UpdatedAt time.Time "json:\"updated_at\" ts_type:\"updatedAt\""; StartDate string "json:\"start_date\" ts_type:\"startDate\""; FinishDate string "json:\"finish_date\" ts_type:\"finishDate\"" } "json:\"list_status\" ts_type:\"listStatus\"" }[]; + paging: paging; + + static createFrom(source: any = {}) { + return new MALWatchlist(source); + } + + constructor(source: any = {}) { + if ('string' === typeof source) source = JSON.parse(source); + this.data = this.convertValues(source["data"], struct { Node struct { Id int "json:\"id\" ts_type:\"id\""; Title string "json:\"title\" ts_type:\"title\""; MainPicture struct { Medium string "json:\"medium\" json:\"medium\""; Large string "json:\"large\" json:\"large\"" } "json:\"main_picture\" json:\"mainPicture\"" } "json:\"node\" json:\"node\""; ListStatus struct { Status string "json:\"status\" ts_type:\"status\""; Score int "json:\"score\" ts_type:\"score\""; NumEpisodesWatched int "json:\"num_episodes_watched\" ts_type:\"numEpisodesWatched\""; IsRewatching bool "json:\"is_rewatching\" ts_type:\"isRewatching\""; UpdatedAt time.Time "json:\"updated_at\" ts_type:\"updatedAt\""; StartDate string "json:\"start_date\" ts_type:\"startDate\""; FinishDate string "json:\"finish_date\" ts_type:\"finishDate\"" } "json:\"list_status\" ts_type:\"listStatus\"" }); + this.paging = source["paging"]; + } + + convertValues(a: any, classs: any, asMap: boolean = false): any { + if (!a) { + return a; + } + if (a.slice && a.map) { + return (a as any[]).map(elem => this.convertValues(elem, classs)); + } else if ("object" === typeof a) { + if (asMap) { + for (const key of Object.keys(a)) { + a[key] = new classs(a[key]); + } + return a; + } + return new classs(a); + } + return a; + } + } export class MediaList { id: number; mediaId: number; @@ -301,3 +333,70 @@ export namespace struct { MediaList main { } +export namespace struct { Node struct { Id int "json:\"id\" ts_type:\"id\""; Title string "json:\"title\" ts_type:\"title\""; MainPicture struct { Medium string "json:\"medium\" json:\"medium\""; Large string "json:\"large\" json:\"large\"" } "json:\"main_picture\" json:\"mainPicture\"" } "json:\"node\" json:\"node\""; ListStatus struct { Status string "json:\"status\" ts_type:\"status\""; Score int "json:\"score\" ts_type:\"score\""; NumEpisodesWatched int "json:\"num_episodes_watched\" ts_type:\"numEpisodesWatched\""; IsRewatching bool "json:\"is_rewatching\" ts_type:\"isRewatching\""; UpdatedAt time { + + export class { + // Go type: struct { Id int "json:\"id\" ts_type:\"id\""; Title string "json:\"title\" ts_type:\"title\""; MainPicture struct { Medium string "json:\"medium\" json:\"medium\""; Large string "json:\"large\" json:\"large\"" } "json:\"main_picture\" json:\"mainPicture\"" } + node: any; + list_status: listStatus; + + static createFrom(source: any = {}) { + return new (source); + } + + constructor(source: any = {}) { + if ('string' === typeof source) source = JSON.parse(source); + this.node = this.convertValues(source["node"], Object); + this.list_status = source["list_status"]; + } + + convertValues(a: any, classs: any, asMap: boolean = false): any { + if (!a) { + return a; + } + if (a.slice && a.map) { + return (a as any[]).map(elem => this.convertValues(elem, classs)); + } else if ("object" === typeof a) { + if (asMap) { + for (const key of Object.keys(a)) { + a[key] = new classs(a[key]); + } + return a; + } + return new classs(a); + } + return a; + } + } + +} + +export namespace struct { Status string "json:\"status\" ts_type:\"status\""; Score int "json:\"score\" ts_type:\"score\""; NumEpisodesWatched int "json:\"num_episodes_watched\" ts_type:\"numEpisodesWatched\""; IsRewatching bool "json:\"is_rewatching\" ts_type:\"isRewatching\""; UpdatedAt time { + + export class { + status: status; + score: score; + num_episodes_watched: numEpisodesWatched; + is_rewatching: isRewatching; + updated_at: updatedAt; + start_date: startDate; + finish_date: finishDate; + + static createFrom(source: any = {}) { + return new (source); + } + + constructor(source: any = {}) { + if ('string' === typeof source) source = JSON.parse(source); + this.status = source["status"]; + this.score = source["score"]; + this.num_episodes_watched = source["num_episodes_watched"]; + this.is_rewatching = source["is_rewatching"]; + this.updated_at = source["updated_at"]; + this.start_date = source["start_date"]; + this.finish_date = source["finish_date"]; + } + } + +} +