diff --git a/common/modules/anilist.js b/common/modules/anilist.js index 7586ee5..3328299 100644 --- a/common/modules/anilist.js +++ b/common/modules/anilist.js @@ -178,6 +178,7 @@ class AnilistClient { rateLimitPromise = null + /** @type {import('simple-store-svelte').Writable>} */ userLists = writable() userID @@ -282,16 +283,22 @@ class AnilistClient { * @param {{key: string, title: string, year?: string, isAdult: boolean}[]} flattenedTitles **/ async alSearchCompound (flattenedTitles) { + if (!flattenedTitles.length) return [] /** @type {Record<`v${number}`, string>} */ - const requestVariables = flattenedTitles.reduce((obj, { title }, i) => { + const requestVariables = flattenedTitles.reduce((obj, { title, isAdult }, i) => { + if (isAdult) return obj obj[`v${i}`] = title return obj }, {}) - const queryVariables = flattenedTitles.map((_, i) => `$v${i}: String`).join(', ') + const queryVariables = flattenedTitles.reduce((arr, { isAdult }, i) => { + if (isAdult) return arr + arr.push(`$v${i}: String`) + return arr + }, []).join(', ') const fragmentQueries = flattenedTitles.map(({ year, isAdult }, i) => /* js */` v${i}: Page(perPage: 10) { - media(type: ANIME, search: $v${i}, status_in: [RELEASING, FINISHED], isAdult: ${!!isAdult} ${year ? `, seasonYear: ${year}` : ''}) { + media(type: ANIME, search: $v${isAdult ? i - 1 : i}, status_in: [RELEASING, FINISHED], isAdult: ${!!isAdult} ${year ? `, seasonYear: ${year}` : ''}) { ...med } }`) diff --git a/common/modules/animeresolver.js b/common/modules/animeresolver.js index 60d7924..c408ae5 100644 --- a/common/modules/animeresolver.js +++ b/common/modules/animeresolver.js @@ -1,6 +1,7 @@ import { toast } from 'svelte-sonner' import { anilistClient } from './anilist.js' import { anitomyscript } from './anime.js' +import { chunks } from './util.js' const postfix = { 1: 'st', 2: 'nd', 3: 'rd' @@ -65,6 +66,7 @@ export default new class AnimeResolver { * @param {import('anitomyscript').AnitomyResult[]} parseObjects */ async findAnimesByTitle (parseObjects) { + if (!parseObjects.length) return const titleObjects = parseObjects.map(obj => { const key = this.getCacheKeyForTitle(obj) const titleObjects = this.alternativeTitles(obj.anime_title).map(title => ({ title, year: obj.anime_year, key, isAdult: false })) @@ -96,14 +98,16 @@ export default new class AnimeResolver { if (!fileName) return [{}] const parseObjs = await anitomyscript(fileName) - // batches promises in 10 at a time, because of CF burst protection, which still sometimes gets triggered :/ + /** @type {Record} */ const uniq = {} for (const obj of parseObjs) { const key = this.getCacheKeyForTitle(obj) if (key in this.animeNameCache) continue uniq[key] = obj } - await this.findAnimesByTitle(Object.values(uniq)) + for (const chunk of chunks(Object.values(uniq), 50)) { + await this.findAnimesByTitle(chunk) + } const fileAnimes = [] for (const parseObj of parseObjs) { diff --git a/common/modules/sections.js b/common/modules/sections.js index 6b932bb..91f9bc8 100644 --- a/common/modules/sections.js +++ b/common/modules/sections.js @@ -91,6 +91,7 @@ function createSections () { if (media.status === 'FINISHED') return true return media.mediaListEntry?.progress < media.nextAiringEpisode?.episode - 1 }).map(({ media }) => media.id) + if (!ids.length) return {} return anilistClient.searchIDS({ page, perPage, id: ids, ...SectionsManager.sanitiseObject(variables) }) }) return SectionsManager.wrapResponse(res, perPage) @@ -106,6 +107,7 @@ function createSections () { const ids = mediaList.flatMap(({ media }) => { return media.relations.edges.filter(edge => edge.relationType === 'SEQUEL') }).map(({ node }) => node.id) + if (!ids.length) return {} return anilistClient.searchIDS({ page, perPage, id: ids, ...SectionsManager.sanitiseObject(variables), status: ['FINISHED', 'RELEASING'], onList: false }) }) return SectionsManager.wrapResponse(res, perPage) diff --git a/common/modules/util.js b/common/modules/util.js index b57e7f2..36b78db 100644 --- a/common/modules/util.js +++ b/common/modules/util.js @@ -25,6 +25,18 @@ const ranges = { seconds: 1 } +/** + * @template T + * @param {T[]} arr + * @param {number} n + */ +export function * chunks (arr, n) { + for (let i = 0; i < arr.length; i += n) { + yield arr.slice(i, i + n) + } +} + +/** @param {Date} date */ export function since (date) { const secondsElapsed = (date.getTime() - Date.now()) / 1000 for (const key in ranges) { @@ -44,9 +56,7 @@ export function fastPrettyBytes (num) { return Number((num / Math.pow(1000, exponent)).toFixed(2)) + units[exponent] } -/** - * @type {DOMParser['parseFromString']} - */ +/** @type {DOMParser['parseFromString']} */ export const DOMPARSER = (typeof DOMParser !== 'undefined') && DOMParser.prototype.parseFromString.bind(new DOMParser()) export const sleep = t => new Promise(resolve => setTimeout(resolve, t).unref?.()) @@ -65,13 +75,9 @@ export function toTS (sec, full) { } } const hours = Math.floor(sec / 3600) - /** - * @type {any} - */ + /** @type {any} */ let minutes = Math.floor(sec / 60) - hours * 60 - /** - * @type {any} - */ + /** @type {any} */ let seconds = full === 1 ? (sec % 60).toFixed(2) : Math.floor(sec % 60) if (minutes < 10 && (hours > 0 || full)) minutes = '0' + minutes if (seconds < 10) seconds = '0' + seconds diff --git a/common/views/Home/Home.svelte b/common/views/Home/Home.svelte index f234732..a3247c2 100644 --- a/common/views/Home/Home.svelte +++ b/common/views/Home/Home.svelte @@ -18,7 +18,8 @@ if (alToken) { const userSections = ['Continue Watching', 'Sequels You Missed', 'Your List', 'Completed List', 'Paused List', 'Dropped List', 'Currently Watching List'] - anilistClient.userLists.subscribe(() => { + anilistClient.userLists.subscribe(value => { + if (!value) return for (const section of manager.sections) { // remove preview value, to force UI to re-request data, which updates it once in viewport if (userSections.includes(section.title)) section.preview.value = section.load(1, 15) diff --git a/electron/package.json b/electron/package.json index c75f2b4..b08cdbe 100644 --- a/electron/package.json +++ b/electron/package.json @@ -1,6 +1,6 @@ { "name": "Miru", - "version": "5.0.1", + "version": "5.0.2", "private": true, "author": "ThaUnknown_ ", "description": "Stream anime torrents, real-time with no waiting for downloads.",