feat: resolve anime by year

fix: remove dead findSubFiles legacy code
chore: improve type declarations
This commit is contained in:
ThaUnknown 2023-09-12 22:06:26 +02:00
parent fb6bdcab8a
commit e92341ef9a
15 changed files with 101 additions and 62 deletions

View file

@ -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"]
}

View file

@ -1,6 +1,6 @@
{
"name": "Miru",
"version": "4.4.6",
"version": "4.4.7",
"author": "ThaUnknown_ <ThaUnknown@users.noreply.github.com>",
"description": "Stream anime torrents, real-time with no waiting for downloads.",
"main": "build/main.js",

View file

@ -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}
}
}

View file

@ -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

View file

@ -1,6 +1,6 @@
let lastTapElement = null
const noop = () => {}
const noop = _ => {}
document.addEventListener('pointerup', () => {
lastTapElement?.dispatchEvent(new Event('custom-pointerleave'))

View file

@ -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'

View file

@ -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)

View file

@ -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 }
}

View file

@ -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)
}
}

View file

@ -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

View file

@ -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')

View file

@ -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

View file

@ -113,7 +113,6 @@
if (videos?.length) {
if (subs) {
subs.files = files || []
subs.findSubtitleFiles(current)
}
}
} else {

15
tsconfig.json Normal file
View file

@ -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"]
}

16
types.d.ts vendored Normal file
View file

@ -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
}
}