diff options
| author | Fuwn <[email protected]> | 2023-09-07 21:05:20 -0700 |
|---|---|---|
| committer | Fuwn <[email protected]> | 2023-09-07 21:05:20 -0700 |
| commit | 724e1d1b642059784e70894fa10647cd1c2b5206 (patch) | |
| tree | 52fe2b02c6c4087192bbd495accf9f2e4f0291d9 /src | |
| parent | feat(routes): profile page (diff) | |
| download | due.moe-724e1d1b642059784e70894fa10647cd1c2b5206.tar.xz due.moe-724e1d1b642059784e70894fa10647cd1c2b5206.zip | |
feat(media): public media list collection
Diffstat (limited to 'src')
| -rw-r--r-- | src/lib/AniList/media.ts | 32 | ||||
| -rw-r--r-- | src/lib/List/User/AnimeList.svelte | 186 |
2 files changed, 218 insertions, 0 deletions
diff --git a/src/lib/AniList/media.ts b/src/lib/AniList/media.ts index 1fddbbe5..341e09a4 100644 --- a/src/lib/AniList/media.ts +++ b/src/lib/AniList/media.ts @@ -138,3 +138,35 @@ export const mediaListCollection = async ( return flattenLists(userIdResponse['data']['MediaListCollection']['lists']); }; + +export const publicMediaListCollection = async (userId: number, type: Type): Promise<Media[]> => { + const userIdResponse = await ( + await fetch('https://graphql.anilist.co', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Accept: 'application/json' + }, + body: JSON.stringify({ + query: `{ MediaListCollection(userId: ${userId}, type: ${ + type === Type.Anime ? 'ANIME' : 'MANGA' + }, status_not_in: [ COMPLETED ]) { + lists { entries { media { + id + status + type + episodes + format + title { romaji english native } + nextAiringEpisode { episode timeUntilAiring } + mediaListEntry { progress status } + startDate { year } + } } } + } + }` + }) + }) + ).json(); + + return flattenLists(userIdResponse['data']['MediaListCollection']['lists']); +}; diff --git a/src/lib/List/User/AnimeList.svelte b/src/lib/List/User/AnimeList.svelte new file mode 100644 index 00000000..43608fc9 --- /dev/null +++ b/src/lib/List/User/AnimeList.svelte @@ -0,0 +1,186 @@ +<script lang="ts"> + /* eslint svelte/no-at-html-tags: "off" */ + + import { Type, type Media, publicMediaListCollection } from '$lib/AniList/media'; + import { onDestroy, onMount } from 'svelte'; + import settings from '../../../stores/settings'; + + export let userId: number; + export let displayUnresolved: boolean; + + let animeLists: Promise<Media[]>; + let startTime: number; + let endTime: number; + + const keyCacher = setInterval(() => { + startTime = performance.now(); + endTime = -1; + animeLists = publicMediaListCollection(userId, Type.Anime); + }, $settings.cacheMinutes * 1000 * 60); + + onMount(async () => { + startTime = performance.now(); + animeLists = publicMediaListCollection(userId, Type.Anime); + }); + + onDestroy(() => { + clearInterval(keyCacher); + }); + + const cleanMedia = (media: Media[], displayUnresolved: boolean) => { + if (media === undefined) { + return []; + } + + const releasingMedia = media.filter((media: Media) => media.status == 'RELEASING'); + const outdatedMedia = releasingMedia.filter((media: Media) => { + return ( + (media.nextAiringEpisode || { episode: 0 }).episode - 1 != + (media.mediaListEntry || { progress: 0 }).progress + ); + }); + let finalMedia = outdatedMedia.map((media: Media) => { + if ((media.nextAiringEpisode || { episode: 0 }).episode - 1 <= 0) { + media.nextAiringEpisode = { episode: -1 }; + } + + return media; + }); + + if (!displayUnresolved) { + finalMedia = finalMedia.filter((media: Media) => media.nextAiringEpisode?.episode !== -1); + } + + finalMedia.sort((a: Media, b: Media) => { + if ($settings.sortByDifference === true) { + const difference = (anime: Media) => { + return ( + (anime.nextAiringEpisode?.episode === -1 + ? 99999 + : anime.nextAiringEpisode?.episode || -1) - + (anime.mediaListEntry || { progress: 0 }).progress + ); + }; + + return difference(a) - difference(b); + } else { + return ( + (a.nextAiringEpisode?.timeUntilAiring || 9999) - + (b.nextAiringEpisode?.timeUntilAiring || 9999) + ); + } + }); + + finalMedia = finalMedia.filter((item, index, array) => { + return ( + array.findIndex((i) => { + return i.id === item.id; + }) === index + ); + }); + + if (!endTime || endTime === -1) { + endTime = performance.now() - startTime; + } + + return finalMedia; + }; + + const airingTime = (anime: Media) => { + const untilAiring = anime.nextAiringEpisode?.timeUntilAiring; + let timeFrame; + + if (untilAiring !== undefined) { + let hours = untilAiring / 3600; + + if (hours >= 24) { + let weeks = Math.floor(Math.floor(hours / 24) / 7); + + if (weeks >= 1) { + weeks = Math.round(weeks); + + timeFrame = `${weeks} week${weeks === 1 ? '' : 's'}`; + } else { + const days = Math.round(Math.floor(hours / 24)); + + timeFrame = `${days} day${days === 1 ? '' : 's'}`; + } + } else { + hours = Math.round(hours); + + timeFrame = `${hours} hour${hours === 1 ? '' : 's'}`; + } + + return `<span style="opacity: 50%">${anime.nextAiringEpisode?.episode} in ${timeFrame}</span>`; + } + + return ''; + }; + + const totalEpisodes = (anime: Media) => { + return anime.episodes === null ? '' : `<span style="opacity: 50%">/${anime.episodes}</span>`; + }; + + const cleanCache = () => { + animeLists = publicMediaListCollection(userId, Type.Anime); + }; +</script> + +{#await animeLists} + <summary>Anime [...] <small style="opacity: 50%">...s</small></summary> + + <ul><li>Loading ...</li></ul> +{:then media} + {@const cleanedMedia = cleanMedia(media, displayUnresolved)} + + <summary + >Anime [{cleanedMedia.length}] + <small style="opacity: 50%">{endTime / 1000}s</small></summary + > + + {#if cleanedMedia.length === 0} + <ul> + <li>No anime to display. <a href={'#'} on:click={cleanCache}>Force refresh</a></li> + </ul> + {/if} + + <ul> + {#each cleanedMedia as anime} + <li> + <a + href={$settings.linkToAniList + ? `https://anilist.co/anime/${anime.id}` + : `https://www.livechart.me/search?q=${ + anime.title.native || anime.title.english || anime.title.romaji + }`} + target="_blank" + > + {anime.title.english || anime.title.romaji || anime.title.native} + </a> + <span style="opacity: 50%;">|</span> + {(anime.mediaListEntry || { progress: 0 }).progress}{@html totalEpisodes(anime)} + [{anime.nextAiringEpisode?.episode === -1 + ? '?' + : (anime.nextAiringEpisode?.episode || 1) - 1}] + {@html airingTime(anime)} + </li> + {/each} + </ul> +{:catch} + <summary>Anime [?] <small style="opacity: 50%">0s</small></summary> + + <ul> + <li> + <p> + Media could not be loaded. You might have been <a + href="https://en.wikipedia.org/wiki/Rate_limiting" + target="_blank">rate limited</a + >. + </p> + <p> + Try again in a few minutes. If the problem persists, please contact + <a href="https://anilist.co/user/fuwn" target="_blank">@fuwn</a> on AniList. + </p> + </li> + </ul> +{/await} |