diff --git a/common/modules/al.d.ts b/common/modules/al.d.ts index a113fa8..38c7ec4 100644 --- a/common/modules/al.d.ts +++ b/common/modules/al.d.ts @@ -109,7 +109,7 @@ export type Media = { } } -export type MediaList = { +export type Following = { status: string score: number progress: number @@ -121,28 +121,30 @@ export type MediaList = { } } +export type MediaListMedia = { + id: number + status: string + mediaListEntry: { + progress: number + } + nextAiringEpisode?: { + episode: number + } + relations?: { + edges: { + relationType: string + node: { + id: number + } + }[] + } +} + export type MediaListCollection = { lists: { status: string entries: { - media: { - id: number - status: string - mediaListEntry: { - progress: number - } - nextAiringEpisode?: { - episode: number - } - relations?: { - edges: { - relationType: string - node: { - id: number - } - }[] - } - } + media: MediaListMedia }[] }[] } diff --git a/common/modules/anilist.js b/common/modules/anilist.js index cde8e67..6cefdf6 100644 --- a/common/modules/anilist.js +++ b/common/modules/anilist.js @@ -182,6 +182,9 @@ class AnilistClient { userID + /** @type {Record} */ + mediaCache = {} + constructor () { this.limiter.on('failed', async (error, jobInfo) => { printError(error) @@ -315,8 +318,7 @@ class AnilistClient { } } - /** @returns {Promise>} */ - searchName (variables = {}) { + async searchName (variables = {}) { const query = /* js */` query($page: Int, $perPage: Int, $sort: [MediaSort], $name: String, $status: [MediaStatus], $year: Int, $isAdult: Boolean){ Page(page: $page, perPage: $perPage){ @@ -331,11 +333,15 @@ class AnilistClient { variables.isAdult = variables.isAdult ?? false - return this.alRequest(query, variables) + /** @type {import('./al.d.ts').PagedQuery<{media: import('./al.d.ts').Media[]}>} */ + const res = await this.alRequest(query, variables) + + this.updateCache(res.data.Page.media) + + return res } - /** @returns {Promise>} */ - searchIDSingle (variables) { + async searchIDSingle (variables) { const query = /* js */` query($id: Int){ Media(id: $id, type: ANIME){ @@ -343,24 +349,33 @@ class AnilistClient { } }` - return this.alRequest(query, variables) + /** @type {import('./al.d.ts').Query<{Media: import('./al.d.ts').Media}>} */ + const res = await this.alRequest(query, variables) + + this.updateCache([res.data.Media]) + + return res } - /** @returns {Promise>} */ - searchIDS (variables) { + async searchIDS (variables) { const query = /* js */` - query($id: [Int], $page: Int, $perPage: Int, $status: [MediaStatus], $onList: Boolean, $sort: [MediaSort], $search: String, $season: MediaSeason, $year: Int, $genre: String, $format: MediaFormat){ - Page(page: $page, perPage: $perPage){ - pageInfo{ - hasNextPage - }, - media(id_in: $id, type: ANIME, status_in: $status, onList: $onList, search: $search, sort: $sort, season: $season, seasonYear: $year, genre: $genre, format: $format){ - ${queryObjects} - } + query($id: [Int], $page: Int, $perPage: Int, $status: [MediaStatus], $onList: Boolean, $sort: [MediaSort], $search: String, $season: MediaSeason, $year: Int, $genre: String, $format: MediaFormat){ + Page(page: $page, perPage: $perPage){ + pageInfo{ + hasNextPage + }, + media(id_in: $id, type: ANIME, status_in: $status, onList: $onList, search: $search, sort: $sort, season: $season, seasonYear: $year, genre: $genre, format: $format){ + ${queryObjects} } - }` + } + }` - return this.alRequest(query, variables) + /** @type {import('./al.d.ts').PagedQuery<{media: import('./al.d.ts').Media[]}>} */ + const res = await this.alRequest(query, variables) + + this.updateCache(res.data.Page.media) + + return res } /** @returns {Promise>} */ @@ -418,6 +433,7 @@ class AnilistClient { } }` + // this doesn't need to be cached, as SearchIDStatus is already cached, which is the only thing that uses this return await this.alRequest(query, variables) } @@ -437,27 +453,31 @@ class AnilistClient { return await this.alRequest(query, variables) } - /** @returns {Promise>} */ - searchAiringSchedule (variables = {}) { + async searchAiringSchedule (variables = {}) { variables.to = (variables.from + 7 * 24 * 60 * 60) const query = /* js */` - query($page: Int, $perPage: Int, $from: Int, $to: Int){ - Page(page: $page, perPage: $perPage){ - pageInfo{ - hasNextPage - }, - airingSchedules(airingAt_greater: $from, airingAt_lesser: $to){ - episode, - timeUntilAiring, - airingAt, - media{ - ${queryObjects} - } + query($page: Int, $perPage: Int, $from: Int, $to: Int){ + Page(page: $page, perPage: $perPage){ + pageInfo{ + hasNextPage + }, + airingSchedules(airingAt_greater: $from, airingAt_lesser: $to){ + episode, + timeUntilAiring, + airingAt, + media{ + ${queryObjects} } } - }` + } + }` - return this.alRequest(query, variables) + /** @type {import('./al.d.ts').PagedQuery<{ airingSchedules: { timeUntilAiring: number, airingAt: number, episode: number, media: import('./al.d.ts').Media}[]}>} */ + const res = await this.alRequest(query, variables) + + this.updateCache(res.data.Page.airingSchedules?.map(({ media }) => media)) + + return res } /** @returns {Promise>} */ @@ -475,21 +495,26 @@ class AnilistClient { return this.alRequest(query, variables) } - /** @returns {Promise>} */ - search (variables = {}) { + async search (variables = {}) { variables.sort ||= 'SEARCH_MATCH' const query = /* js */` - query($page: Int, $perPage: Int, $sort: [MediaSort], $search: String, $onList: Boolean, $status: MediaStatus, $season: MediaSeason, $year: Int, $genre: String, $format: MediaFormat){ - Page(page: $page, perPage: $perPage){ - pageInfo{ - hasNextPage - }, - media(type: ANIME, search: $search, sort: $sort, onList: $onList, status: $status, season: $season, seasonYear: $year, genre: $genre, format: $format, format_not: MUSIC){ - ${queryObjects} - } + query($page: Int, $perPage: Int, $sort: [MediaSort], $search: String, $onList: Boolean, $status: MediaStatus, $season: MediaSeason, $year: Int, $genre: String, $format: MediaFormat){ + Page(page: $page, perPage: $perPage){ + pageInfo{ + hasNextPage + }, + media(type: ANIME, search: $search, sort: $sort, onList: $onList, status: $status, season: $season, seasonYear: $year, genre: $genre, format: $format, format_not: MUSIC){ + ${queryObjects} } - }` - return this.alRequest(query, variables) + } + }` + + /** @type {import('./al.d.ts').PagedQuery<{media: import('./al.d.ts').Media[]}>} */ + const res = await this.alRequest(query, variables) + + this.updateCache(res.data.Page.media) + + return res } /** @returns {Promise>} */ @@ -504,7 +529,7 @@ class AnilistClient { return this.alRequest(query, variables) } - /** @returns {Promise>} */ + /** @returns {Promise>} */ following (variables) { const query = /* js */` query($id: Int){ @@ -571,6 +596,11 @@ class AnilistClient { return this.alRequest(query, variables) } + + /** @param {import('./al.d.ts').Media[]} medias */ + updateCache (medias) { + this.mediaCache = { ...this.mediaCache, ...Object.fromEntries(medias.map(media => [media.id, media])) } + } } export const anilistClient = new AnilistClient() diff --git a/common/modules/animeprogress.js b/common/modules/animeprogress.js index 66e3603..c45d119 100644 --- a/common/modules/animeprogress.js +++ b/common/modules/animeprogress.js @@ -5,12 +5,12 @@ const maxEntries = 1000 // LocalStorage is structured as an array of objects with the following properties: // mediaId, episode, currentTime, safeduration, createdAt, updatedAt -function loadFromLocalStorage() { +function loadFromLocalStorage () { const data = localStorage.getItem('animeEpisodeProgress') return data ? JSON.parse(data) : [] } -function saveToLocalStorage(data) { +function saveToLocalStorage (data) { localStorage.setItem('animeEpisodeProgress', JSON.stringify(data)) animeProgressStore.set(data) } @@ -18,7 +18,7 @@ function saveToLocalStorage(data) { const animeProgressStore = writable(loadFromLocalStorage()) // Return an object with the progress of each episode in percent (0-100), keyed by episode number -export function liveAnimeProgress (mediaId){ +export function liveAnimeProgress (mediaId) { return derived(animeProgressStore, (data) => { if (!mediaId) return {} const results = data.filter(item => item.mediaId === mediaId) @@ -42,13 +42,13 @@ export function liveAnimeEpisodeProgress (mediaId, episode) { } // Return an individual episode's record { mediaId, episode, currentTime, safeduration, createdAt, updatedAt } -export function getAnimeProgress(mediaId, episode) { +export function getAnimeProgress (mediaId, episode) { const data = loadFromLocalStorage() return data.find(item => item.mediaId === mediaId && item.episode === episode) } // Set an individual episode's progress -export function setAnimeProgress({ mediaId, episode, currentTime, safeduration }) { +export function setAnimeProgress ({ mediaId, episode, currentTime, safeduration }) { if (!mediaId || !episode || !currentTime || !safeduration) return const data = loadFromLocalStorage() // Update the existing entry or create a new one