diff --git a/jsconfig.json b/jsconfig.json index 08f3c87..014bb1e 100644 --- a/jsconfig.json +++ b/jsconfig.json @@ -7,7 +7,9 @@ "checkJs": true, "target": "ESNext", "moduleResolution": "node", - "module": "ESNext" + "module": "ESNext", + "types": ["./types.d.ts"], + "allowSyntheticDefaultImports": true }, "exclude": ["node_modules/**", "**/node_modules", "dist", "build"] } \ No newline at end of file diff --git a/package.json b/package.json index f11e2d8..2cadb5b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "Miru", - "version": "4.4.6", + "version": "4.4.7", "author": "ThaUnknown_ ", "description": "Stream anime torrents, real-time with no waiting for downloads.", "main": "build/main.js", diff --git a/src/renderer/modules/anilist.js b/src/renderer/modules/anilist.js index d2cdb4b..093da51 100644 --- a/src/renderer/modules/anilist.js +++ b/src/renderer/modules/anilist.js @@ -306,12 +306,12 @@ export async function alRequest (opts) { case 'SearchName': { variables.search = opts.name query = /* js */` -query($page: Int, $perPage: Int, $sort: [MediaSort], $search: String, $status: [MediaStatus]){ +query($page: Int, $perPage: Int, $sort: [MediaSort], $search: String, $status: [MediaStatus], $year: Int){ Page(page: $page, perPage: $perPage){ pageInfo{ hasNextPage }, - media(type: ANIME, search: $search, sort: $sort, status_in: $status, isAdult: false, format_not: MUSIC){ + media(type: ANIME, search: $search, sort: $sort, status_in: $status, isAdult: false, format_not: MUSIC, seasonYear: $year){ ${queryObjects} } } diff --git a/src/renderer/modules/anime.js b/src/renderer/modules/anime.js index dc9ae9f..cffd7d7 100644 --- a/src/renderer/modules/anime.js +++ b/src/renderer/modules/anime.js @@ -1,6 +1,6 @@ import { DOMPARSER, PromiseBatch, binarySearch } from './util.js' import { alRequest, alSearch } from './anilist.js' -import anitomyscript from 'anitomyscript' +import _anitomyscript from 'anitomyscript' import { toast } from 'svelte-sonner' import Sections from './sections.js' import { page } from '@/App.svelte' @@ -86,9 +86,7 @@ export async function traceAnime (image) { // WAIT lookup logic key.value = {} page.value = 'search' } else { - throw new Error('Search Failed', { - message: 'Couldn\'t find anime for specified image! Try to remove black bars, or use a more detailed image.' - }) + throw new Error('Search Failed \n Couldn\'t find anime for specified image! Try to remove black bars, or use a more detailed image.') } } @@ -170,8 +168,10 @@ const postfix = { 3: 'rd' } -async function resolveTitle (name) { +async function resolveTitle (parseObject) { + const name = parseObject.anime_title const method = { name, method: 'SearchName', perPage: 10, status: ['RELEASING', 'FINISHED'], sort: 'SEARCH_MATCH' } + if (parseObject.anime_year) method.year = parseObject.anime_year // inefficient but readable @@ -212,50 +212,64 @@ async function resolveTitle (name) { media = (await alSearch(method)).data.Page.media[0] } } - // remove 2020 - if (!media) { - const match = method.name.match(/ (19[5-9]\d|20\d{2})/) - if (match) { - method.name = method.name.replace(/ (19[5-9]\d|20\d{2})/, '') - media = (await alSearch(method)).data.Page.media[0] - } - } } catch (e) { } - if (media) relations[name] = media + if (media) relations[getRelationKey(parseObject)] = media } -function getParseObjTitle (obj) { - let title = obj.anime_title - if (obj.anime_year) title += ` ${obj.anime_year}` - if (obj.anime_season > 1) title += ' S' + obj.anime_season - const match = title.match(/S(\d{2})E(\d{2})/) - if (match) { - obj.anime_season = Number(match[1]) - obj.episode_number = Number(match[2]) - title = title.replace(/S(\d{2})E(\d{2})/, '') +// utility method for correcting anitomyscript woes for what's needed +export async function anitomyscript (...args) { + // @ts-ignore + const res = await _anitomyscript(...args) + + const parseObjs = Array.isArray(res) ? res : [res] + + for (const obj of parseObjs) { + const seasonMatch = obj.anime_title.match(/S(\d{2})E(\d{2})/) + if (seasonMatch) { + obj.anime_season = seasonMatch[1] + obj.episode_number = seasonMatch[2] + obj.anime_title = obj.anime_title.replace(/S(\d{2})E(\d{2})/, '') + } + const yearMatch = obj.anime_title.match(/ (19[5-9]\d|20\d{2})/) + if (yearMatch && Number(yearMatch[1]) <= (new Date().getUTCFullYear() + 1)) { + obj.anime_year = yearMatch[1] + obj.anime_title = obj.anime_title.replace(/ (19[5-9]\d|20\d{2})/, '') + } + if (Number(obj.anime_season) > 1) obj.anime_title += ' S' + obj.anime_season } - return title + + return parseObjs } -window.xd = resolveFileMedia +function getRelationKey (obj) { + let key = obj.anime_title + if (obj.anime_year) key += obj.anime_year + return key +} // TODO: anidb aka true episodes need to be mapped to anilist episodes a bit better export async function resolveFileMedia (fileName) { - let parseObjs = await anitomyscript(fileName) + const parseObjs = await anitomyscript(fileName) - if (parseObjs.constructor !== Array) parseObjs = [parseObjs] // batches promises in 10 at a time, because of CF burst protection, which still sometimes gets triggered :/ - await PromiseBatch(resolveTitle, [...new Set(parseObjs.map(obj => getParseObjTitle(obj)))].filter(title => !(title in relations)), 10) + const uniq = {} + for (const obj of parseObjs) { + const key = getRelationKey(obj) + if (key in relations) continue + uniq[key] = obj + } + await PromiseBatch(resolveTitle, Object.values(uniq), 10) + const fileMedias = [] for (const parseObj of parseObjs) { let failed = false let episode - let media = relations[getParseObjTitle(parseObj)] + let media = relations[getRelationKey(parseObj)] // resolve episode, if movie, dont. const maxep = media?.nextAiringEpisode?.episode || media?.episodes if ((media?.format !== 'MOVIE' || maxep) && parseObj.episode_number) { - if (parseObj.episode_number.constructor === Array) { + if (Array.isArray(parseObj.episode_number)) { // is an episode range if (parseInt(parseObj.episode_number[0]) === 1) { // if it starts with #1 and overflows then it includes more than 1 season in a batch, cant fix this cleanly, name is parsed per file basis so this shouldnt be an issue diff --git a/src/renderer/modules/click.js b/src/renderer/modules/click.js index 7e6c30d..bb23887 100644 --- a/src/renderer/modules/click.js +++ b/src/renderer/modules/click.js @@ -1,6 +1,6 @@ let lastTapElement = null -const noop = () => {} +const noop = _ => {} document.addEventListener('pointerup', () => { lastTapElement?.dispatchEvent(new Event('custom-pointerleave')) diff --git a/src/renderer/modules/providers/cat.js b/src/renderer/modules/providers/cat.js index bb2b836..de62de0 100644 --- a/src/renderer/modules/providers/cat.js +++ b/src/renderer/modules/providers/cat.js @@ -1,8 +1,7 @@ import { alRequest } from '@/modules/anilist.js' import { set } from '@/views/Settings.svelte' -import { findEdge, resolveSeason, getMediaMaxEp } from './anime.js' +import { findEdge, resolveSeason, getMediaMaxEp, mapBestRelease } from '../anime.js' import { exclusions, getRSSContent, parseRSSNodes } from '../rss.js' -import { mapBestRelease } from '../anime.js' export default async function getRSSEntries ({ media, episode, mode, ignoreQuality }) { // mode cuts down on the amt of queries made 'check' || 'batch' diff --git a/src/renderer/modules/providers/tosho.js b/src/renderer/modules/providers/tosho.js index e860aa7..33d6cce 100644 --- a/src/renderer/modules/providers/tosho.js +++ b/src/renderer/modules/providers/tosho.js @@ -1,12 +1,10 @@ -import { mapBestRelease } from '../anime.js' +import { mapBestRelease, anitomyscript } from '../anime.js' import { fastPrettyBytes } from '../util.js' import { exclusions } from '../rss.js' import { set } from '@/views/Settings.svelte' import { alRequest } from '../anilist.js' import { client } from '@/modules/torrent.js' -import anitomyscript from 'anitomyscript' - export default async function ({ media, episode }) { const json = await getAniDBFromAL(media) @@ -101,8 +99,8 @@ export function getEpisodeNumberByAirDate (alDate, episodes, episode) { // ineffcient but reliable const closestEpisodes = Object.values(episodes).reduce((prev, curr) => { if (!prev[0]) return [curr] - const prevDate = Math.abs(new Date(prev[0]?.airdate) - alDate) - const currDate = Math.abs(new Date(curr.airdate) - alDate) + const prevDate = Math.abs(+new Date(prev[0]?.airdate) - alDate) + const currDate = Math.abs(+new Date(curr.airdate) - alDate) if (prevDate === currDate) { prev.push(curr) return prev @@ -176,10 +174,10 @@ function isTitleSplitCour (media) { } const seasons = ['WINTER', 'SPRING', 'SUMMER', 'FALL'] -const getDate = ({ seasonYear, season }) => new Date(`${seasonYear}-${seasons.indexOf(season) * 4 || 1}-01`) +const getDate = ({ seasonYear, season }) => +new Date(`${seasonYear}-${seasons.indexOf(season) * 4 || 1}-01`) function getMediaDate (media) { - if (media.startDate) return new Date(Object.values(media.startDate).join(' ')) + if (media.startDate) return +new Date(Object.values(media.startDate).join(' ')) return getDate(media) } @@ -242,7 +240,7 @@ function buildQuery (quality) { return query } -async function fetchBatches ({ episodeCount, id, quality, movie }) { +async function fetchBatches ({ episodeCount, id, quality, movie = null }) { try { const queryString = buildQuery(quality) const torrents = await fetch(set.toshoURL + 'json?order=size-d&aid=' + id + queryString) diff --git a/src/renderer/modules/rss.js b/src/renderer/modules/rss.js index 96115d4..3a35be7 100644 --- a/src/renderer/modules/rss.js +++ b/src/renderer/modules/rss.js @@ -86,7 +86,7 @@ class RSSMediaManager { if (!content) return false - const pubDate = new Date(content.querySelector('pubDate').textContent) * page * perPage + const pubDate = +(new Date(content.querySelector('pubDate').textContent)) * page * perPage if (this.resultMap[url]?.date === pubDate) return false return { content, pubDate } } diff --git a/src/renderer/modules/scroll.js b/src/renderer/modules/scroll.js index b48e971..3ef9fc4 100644 --- a/src/renderer/modules/scroll.js +++ b/src/renderer/modules/scroll.js @@ -35,6 +35,6 @@ export default function (t, { speed = 120, smooth = 10 } = {}) { scrollTop += delta t.scrollTo(0, scrollTop < 1.3 ? 0 : scrollTop) - moving = Math.abs(delta) > 0.1 && requestAnimationFrame(update) + moving = Math.abs(delta) > 0.1 && !!requestAnimationFrame(update) } } diff --git a/src/renderer/modules/subtitles.js b/src/renderer/modules/subtitles.js index 9dab23c..eb44996 100644 --- a/src/renderer/modules/subtitles.js +++ b/src/renderer/modules/subtitles.js @@ -122,19 +122,6 @@ export default class Subtitles { clipboard.on('files', this.handleClipboardFiles) } - findSubtitleFiles (targetFile) { - const videoName = targetFile.name.substring(0, targetFile.name.lastIndexOf('.')) || targetFile.name - // array of subtitle files that match video name, or all subtitle files when only 1 vid file - const subfiles = this.files.filter(file => { - return !this.subtitleFiles.some(sub => { // exclude already existing files - return sub.lastModified === file.lastModified && sub.name === file.name && sub.size === file.size - }) && subRx.test(file.name) && (this.videoFiles.length === 1 ? true : file.name.includes(videoName)) - }) - for (const file of subfiles) { - this.addSingleSubtitleFile(file) - } - } - async addSingleSubtitleFile (file) { const index = this.headers.length this.subtitleFiles[index] = file diff --git a/src/renderer/modules/torrent.js b/src/renderer/modules/torrent.js index 6c08555..9143980 100644 --- a/src/renderer/modules/torrent.js +++ b/src/renderer/modules/torrent.js @@ -12,7 +12,7 @@ class TorrentWorker extends EventTarget { this.ready = new Promise(resolve => { window.IPC.once('port', () => { this.port = window.port - this.port.onmessage((...args) => this.handleMessage(...args)) + this.port.onmessage(this.handleMessage.bind(this)) resolve() }) window.IPC.emit('portRequest') diff --git a/src/renderer/modules/util.js b/src/renderer/modules/util.js index 44c5c88..cfc652e 100644 --- a/src/renderer/modules/util.js +++ b/src/renderer/modules/util.js @@ -41,6 +41,9 @@ export function fastPrettyBytes (num) { return Number((num / Math.pow(1000, exponent)).toFixed(2)) + units[exponent] } +/** + * @type {DOMParser['parseFromString']} + */ export const DOMPARSER = DOMParser.prototype.parseFromString.bind(new DOMParser()) export const sleep = t => new Promise(resolve => setTimeout(resolve, t)) @@ -59,7 +62,13 @@ export function toTS (sec, full) { } } const hours = Math.floor(sec / 3600) + /** + * @type {any} + */ let minutes = Math.floor(sec / 60) - hours * 60 + /** + * @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/src/renderer/views/Player/Player.svelte b/src/renderer/views/Player/Player.svelte index 10d4c78..87b4a8c 100644 --- a/src/renderer/views/Player/Player.svelte +++ b/src/renderer/views/Player/Player.svelte @@ -113,7 +113,6 @@ if (videos?.length) { if (subs) { subs.files = files || [] - subs.findSubtitleFiles(current) } } } else { diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..014bb1e --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "baseUrl": "./", + "paths": { + "@/*": ["src/renderer/*"], + }, + "checkJs": true, + "target": "ESNext", + "moduleResolution": "node", + "module": "ESNext", + "types": ["./types.d.ts"], + "allowSyntheticDefaultImports": true + }, + "exclude": ["node_modules/**", "**/node_modules", "dist", "build"] +} \ No newline at end of file diff --git a/types.d.ts b/types.d.ts new file mode 100644 index 0000000..1fb8125 --- /dev/null +++ b/types.d.ts @@ -0,0 +1,16 @@ +export {} + +declare global { + interface Window { + IPC: any; + port: MessagePort + } + interface EventTarget { + on: (type: string, callback: (any) => void, options?: boolean | {}) => void + once: (type: string, callback: (any) => void, options?: boolean | {}) => void + emit: (type: string, data?: any) => void + dispatch: (type: string, data?: any) => void + removeListener: (type: string, callback: (any) => void) => void + off: (type: string, callback: (any) => void) => void + } +}