fix: edge case when a final ep from the wrong season was played, continue played EP out of bounds, a torrent got overwritten with auto-start torrent, anime with weird numbers was being found, anime for old DVD shows wasn't being found, duplicate torrents showed up

feat: more batches are now found
This commit is contained in:
ThaUnknown 2022-06-09 19:15:48 +02:00
parent 19cf481541
commit b7c80afbc9
5 changed files with 169 additions and 128 deletions

View file

@ -1,6 +1,6 @@
{
"name": "Miru",
"version": "2.4.7",
"version": "2.5.0",
"author": "ThaUnknown_ <ThaUnknown@users.noreply.github.com>",
"main": "src/index.js",
"homepage": "https://github.com/ThaUnknown/miru#readme",

View file

@ -4,6 +4,7 @@
import { set } from './pages/Settings.svelte'
import { addToast } from '@/lib/Toasts.svelte'
import { alRequest } from '@/modules/anilist.js'
import { episodeRx, findEdge, resolveSeason, getMediaMaxEp } from '@/modules/anime.js'
import { writable } from 'svelte/store'
@ -11,11 +12,28 @@
const settings = set
const exclusions = ['DTS', '[ASW]']
const video = document.createElement('video')
if (!video.canPlayType('video/mp4; codecs="hev1.1.6.L93.B0"')) {
exclusions.push('HEVC', 'x265', 'H.265')
}
if (!video.canPlayType('audio/mp4; codecs="ac-3"')) {
exclusions.push('AC3', 'AC-3')
}
video.remove()
export function playAnime (media, episode = 1) {
episode = isNaN(episode) ? 1 : episode
rss.set({ media, episode })
}
// padleft a variable with 0 ex: 1 => '01'
function pl (v = 1) {
return (typeof v === 'string' ? v : v.toString()).padStart(2, '0')
}
export function getRSSContent (url) {
return fetch(url)
.then(res => {
@ -44,14 +62,128 @@
const rss = rssmap[settings.rssFeed] || settings.rssFeed
return new URL(`${rss}${settings.rssQuality ? `"${settings.rssQuality}"` : ''}`)
}
</script>
// matches: OP01 ED01 EP01 E01 01v -01- _01_ with spaces and stuff
const epNumRx = /[EO]?[EPD _-]\d{2}[v _-]|\d{2}[-~]\d{2}/i
async function getRSSEntries ({ media, episode, mode, ignoreQuality }) {
// mode cuts down on the amt of queries made 'check' || 'batch'
const titles = createTitle(media).join(')|(')
<script>
import { add } from '@/modules/torrent.js'
import { episodeRx, findEdge, resolveSeason } from '@/modules/anime.js'
const prequel = findEdge(media, 'PREQUEL')?.node
const sequel = findEdge(media, 'SEQUEL')?.node
const isBatch = media.status === 'FINISHED' && settings.rssBatch && media.episodes !== 1
// if media has multiple seasons, and this S is > 1, then get the absolute episode number of the episode
const absolute = prequel && !mode && (await resolveSeason({ media, episode, force: true }))
const absoluteep = absolute?.offset + episode
const episodes = [episode]
// only use absolute episode number if its smaller than max episodes this series has, ex:
// looking for E1 of S2, S1 has 12 ep and S2 has 13, absolute will be 13
// so this would find the 13th ep of the 2nd season too if this check wasnt here
if (absolute && absoluteep < (getMediaMaxEp(media) || episode)) {
episodes.push(absoluteep)
}
let ep = ''
if (media.episodes !== 1 && mode !== 'batch') {
if (isBatch) {
ep = `"01-${pl(media.episodes)}"|"01~${pl(media.episodes)}"|"Batch"|"Complete"|"${pl(episode)}+"|"${pl(episode)}v"|"S01"`
} else {
ep = `(${episodes.map(episode => `"E${pl(episode)}+"|"${pl(episode)}+"|"${pl(episode)}v"`).join('|')})`
}
}
const excl = exclusions.join('|')
const quality = (!ignoreQuality && (`"${settings.rssQuality}"` || '"1080"')) || ''
const trusted = settings.rssTrusted === true ? 2 : 0
const url = new URL(
`https://nyaa.si/?page=rss&c=1_2&f=${trusted}&s=seeders&o=desc&q=(${titles})${ep}${quality}-(${excl})`
)
let nodes = [...(await getRSSContent(url)).querySelectorAll('item')]
if (absolute) {
// if this is S > 1 aka absolute ep number exists get entries for S1title + absoluteEP
// the reason this isnt done with recursion like sequelEntries is because that would include the S1 media dates
// we want the dates of the target media as the S1 title might be used for SX releases
const titles = createTitle(absolute.media).join(')|(')
const ep = `"E${pl(absoluteep)}+"|"${pl(absoluteep)}+"|"${pl(absoluteep)}v"`
const url = new URL(
`https://nyaa.si/?page=rss&c=1_2&f=${trusted}&s=seeders&o=desc&q=(${titles})${ep}${quality}-(${excl})`
)
nodes = [...nodes, ...(await getRSSContent(url)).querySelectorAll('item')]
}
let entries = parseRSSNodes(nodes)
$: parseRss($rss)
const checkSequelDate = media.status === 'FINISHED' && (sequel?.status === 'FINISHED' || sequel?.status === 'RELEASING') && sequel.startDate
const sequelStartDate = checkSequelDate && new Date(Object.values(checkSequelDate).join(' '))
// recursive, get all entries for media sequel, and its sequel, and its sequel
const sequelEntries =
(sequel?.status === 'FINISHED' || sequel?.status === 'RELEASING') &&
(await getRSSEntries({ media: (await alRequest({ method: 'SearchIDSingle', id: sequel.id })).data.Media, episode, mode: mode || 'check' }))
const checkPrequelDate = (media.status === 'FINISHED' || media.status === 'RELEASING') && prequel?.status === 'FINISHED' && prequel?.endDate
const prequelEndDate = checkPrequelDate && new Date(Object.values(checkPrequelDate).join(' '))
// 1 month in MS, a bit of jitter for pre-releases and releasers being late as fuck, lets hope it doesnt cause issues
const month = 2674848460
if (prequelEndDate) {
entries = entries.filter(entry => entry.date > new Date(+prequelEndDate + month))
}
if (sequelStartDate && media.format === 'TV') {
entries = entries.filter(entry => entry.date < new Date(+sequelStartDate - month))
}
if (sequelEntries?.length) {
if (mode === 'check') {
entries = [...entries, ...sequelEntries]
} else {
entries = entries.filter(entry => !sequelEntries.find(sequel => sequel.link === entry.link))
}
}
// this gets entries without any episode limiting, and for batches
const batchEntries = !mode && isBatch && (await getRSSEntries({ media, episode, ignoreQuality, mode: 'batch' })).filter(entry => {
return !epNumRx.test(entry.title)
})
if (batchEntries?.length) {
entries = [...entries, ...batchEntries]
}
// some archaic shows only have shit DVD's in weird qualities, so try to look up without any quality restrictions when there are no results
if (!entries.length && !ignoreQuality && !mode) {
entries = await getRSSEntries({ media, episode, ignoreQuality: true })
}
// dedupe
const ids = entries.map(e => e.link)
return entries.filter(({ link }, index) => !ids.includes(link, index + 1))
}
function parseRSSNodes (nodes) {
return nodes.map(item => {
const pubDate = item.querySelector('pubDate')?.textContent
return {
title: item.querySelector('title')?.textContent || '?',
link: item.querySelector('link')?.textContent || '?',
seeders: item.querySelector('seeders')?.textContent ?? '?',
leechers: item.querySelector('leechers')?.textContent ?? '?',
downloads: item.querySelector('downloads')?.textContent ?? '?',
size: item.querySelector('size')?.textContent ?? '?',
date: pubDate && new Date(pubDate)
}
})
}
// create an array of potentially valid titles from a given media
function createTitle (media) {
@ -81,112 +213,18 @@
}
return titles
}
</script>
<script>
import { add } from '@/modules/torrent.js'
$: parseRss($rss)
let table = null
let fileMedia = null
const exclusions = ['DTS', '[ASW]']
const video = document.createElement('video')
if (!video.canPlayType('video/mp4; codecs="hev1.1.6.L93.B0"')) {
exclusions.push('HEVC', 'x265', 'H.265')
}
if (!video.canPlayType('audio/mp4; codecs="ac-3"')) {
exclusions.push('AC3', 'AC-3')
}
video.remove()
async function getRSSEntries ({ media, episode, mode }) {
// mode cuts down on the amt of queries made
const titles = createTitle(media).join(')|(')
const prequel = findEdge(media, 'PREQUEL')?.node
const sequel = findEdge(media, 'SEQUEL')?.node
const absolute = prequel && mode !== 'check' && (await resolveSeason({ media, episode, force: true }))
const episodes = [episode]
if (absolute) episodes.push(absolute.offset + episode)
let ep = ''
if (media.episodes !== 1) {
if (media.status === 'FINISHED' && settings.rssBatch) {
ep = `"01-${media.episodes}"|"01~${media.episodes}"|"Batch"|"Complete"|"${episode > 9 ? episode : `0${episode}`}+"|"${episode > 9 ? episode : `0${episode}`}v"|"S01"`
} else {
ep = `(${episodes
.map(episode => `"${episode > 9 ? episode : `E0${episode}`}+"|"${episode > 9 ? episode : `+0${episode}`}+"|"${episode > 9 ? episode : `0${episode}`}v"`)
.join('|')})`
}
}
const excl = exclusions.join('|')
const quality = `"${settings.rssQuality}"` || '"1080p"'
const trusted = settings.rssTrusted === true ? 2 : 0
const url = new URL(
`https://nyaa.si/?page=rss&c=1_2&f=${trusted}&s=seeders&o=desc&q=(${titles})${ep}${quality}-(${excl})`
)
let nodes = [...(await getRSSContent(url)).querySelectorAll('item')]
if (absolute && !settings.rssBatch) {
const titles = createTitle(absolute.media).join(')|(')
const ep = `"+${episodes[1] > 9 ? episodes[1] : `0${episodes[1]}`}+"|"+${episodes[1] > 9 ? episodes[1] : `0${episodes[1]}`}v"`
const url = new URL(
`https://nyaa.si/?page=rss&c=1_2&f=${trusted}&s=seeders&o=desc&q=(${titles})${ep}${quality}-(${excl})`
)
nodes = [...nodes, ...(await getRSSContent(url)).querySelectorAll('item')]
}
if (!nodes?.length) return null
const checkSequelDate = media.status === 'FINISHED' && (sequel?.status === 'FINISHED' || sequel?.status === 'RELEASING') && sequel.startDate
const sequelStartDate = checkSequelDate && new Date(Object.values(checkSequelDate).join(' '))
const sequelEntries =
(sequel?.status === 'FINISHED' || sequel?.status === 'RELEASING') &&
(await getRSSEntries({ media: (await alRequest({ method: 'SearchIDSingle', id: sequel.id })).data.Media, episode, mode: 'check' }))
const checkPrequelDate = (media.status === 'FINISHED' || media.status === 'RELEASING') && prequel?.status === 'FINISHED' && prequel?.endDate
const prequelEndDate = checkPrequelDate && new Date(Object.values(checkPrequelDate).join(' '))
let entries = nodes.map(item => {
const pubDate = item.querySelector('pubDate')?.textContent
return {
title: item.querySelector('title')?.textContent || '?',
link: item.querySelector('link')?.textContent || '?',
seeders: item.querySelector('seeders')?.textContent ?? '?',
leechers: item.querySelector('leechers')?.textContent ?? '?',
downloads: item.querySelector('downloads')?.textContent ?? '?',
size: item.querySelector('size')?.textContent ?? '?',
date: pubDate && new Date(pubDate)
}
})
// 1 month in MS, a bit of jitter for pre-releases and releasers being late as fuck, lets hope it doesnt cause issues
const month = 2674848460
if (prequelEndDate) {
entries = entries.filter(entry => entry.date > new Date(+prequelEndDate + month))
}
if (sequelStartDate && media.format === 'TV') {
entries = entries.filter(entry => entry.date < new Date(+sequelStartDate - month))
}
if (sequelEntries?.length) {
if (mode === 'check') {
entries = [...entries, ...sequelEntries]
} else {
entries = entries.filter(entry => !sequelEntries.find(sequel => sequel.link === entry.link))
}
}
return entries
}
export async function parseRss ({ media, episode }) {
if (!media) return
const entries = await getRSSEntries({ media, episode })
@ -237,7 +275,7 @@
<div class="modal" class:show={table} id="viewAnime" on:keydown={checkClose} tabindex="-1">
{#if table}
<div class="modal-dialog" role="document" on:click|self={close}>
<div class="modal-dialog p-20" role="document" on:click|self={close}>
<div class="modal-content w-auto">
<button class="close pointer" type="button" on:click={close}> &times; </button>
<table class="table table-hover">

View file

@ -1,16 +1,17 @@
<script>
import { playAnime } from './RSSView.svelte'
import { alRequest } from '@/modules/anilist.js'
import { getMediaMaxEp } from '@/modules/anime.js'
import { getContext } from 'svelte'
import { alToken } from '@/lib/pages/Settings.svelte'
import { countdown } from '@/modules/util.js'
let view = getContext('view')
function close() {
const view = getContext('view')
function close () {
$view = null
}
function checkClose({ keyCode }) {
if (keyCode == 27) close()
function checkClose ({ keyCode }) {
if (keyCode === 27) close()
}
const detailsMap = [
{ property: 'episode', label: 'Airing', icon: 'schedule', custom: 'property' },
@ -27,7 +28,7 @@
{ property: 'romaji', label: 'Romaji', icon: 'translate' },
{ property: 'native', label: 'Native', icon: '語', custom: 'icon' }
]
function getCustomProperty(detail, media) {
function getCustomProperty (detail, media) {
if (detail.property === 'episodes') {
if (media.mediaListEntry?.progress) {
return `Watched <b>${media.mediaListEntry.progress}</b> of <b>${media.episodes}</b>`
@ -45,7 +46,7 @@
return media[detail.property]
}
}
function getProperty(property, media) {
function getProperty (property, media) {
if (property === 'episode') {
return media.nextAiringEpisode?.episode
} else if (property === 'english' || property === 'romaji' || property === 'native') {
@ -53,7 +54,7 @@
}
return media[property]
}
async function addToList(media) {
async function addToList (media) {
if (media.mediaListEntry?.status !== 'CURRENT' && media.mediaListEntry?.status !== 'COMPLETED') {
const variables = {
method: media.mediaListEntry?.status !== 'PLANNING' ? 'Entry' : 'Delete',
@ -64,17 +65,10 @@
$view = (await alRequest({ method: 'SearchIDSingle', id: media.id })).data.Media
}
}
let trailer = getContext('trailer')
function viewTrailer(media) {
const trailer = getContext('trailer')
function viewTrailer (media) {
$trailer = media.trailer.id
}
function getMediaMaxEp(media, playable) {
if (playable) {
return media.nextAiringEpisode?.episode - 1 || media.airingSchedule?.nodes?.[0]?.episode - 1 || media.episodes
} else {
return media.episodes || media.nextAiringEpisode?.episode - 1 || media.airingSchedule?.nodes?.[0]?.episode - 1
}
}
</script>
<div class="modal modal-full" class:show={$view} on:keydown={checkClose} tabindex="-1">
@ -105,7 +99,7 @@
{/if}
<span class="material-icons mx-10 font-size-24"> monitor </span>
<span>
Format: {'TV' ? $view.format : $view.format?.toLowerCase()}
Format: {$view.format === 'TV' ? $view.format : $view.format?.toLowerCase()}
<span class="font-weight-bold mr-20 text-capitalize" />
</span>
{#if $view.episodes !== 1 && getMediaMaxEp($view)}
@ -137,7 +131,7 @@
class="btn btn-primary d-flex align-items-center font-weight-bold font-size-24 h-50 mb-5"
type="button"
on:click={() => {
playAnime($view, $view.mediaListEntry?.progress + 1)
playAnime($view, Math.min(getMediaMaxEp($view), $view.mediaListEntry?.progress + 1))
close()
}}>
<span class="material-icons mr-10 font-size-24 w-30"> play_arrow </span>

View file

@ -81,6 +81,15 @@ function traceAnime (image, type) { // WAIT lookup logic
}
})
}
export function getMediaMaxEp (media, playable) {
if (playable) {
return media.nextAiringEpisode?.episode - 1 || media.airingSchedule?.nodes?.[0]?.episode - 1 || media.episodes
} else {
return media.episodes || media.nextAiringEpisode?.episode - 1 || media.airingSchedule?.nodes?.[0]?.episode - 1
}
}
export const episodeRx = /Episode (\d+) - (.*)/
// resolve anime name based on file name and store it

View file

@ -47,7 +47,7 @@ client.on('torrent', ({ detail }) => {
queueMicrotask(() => {
setTimeout(() => {
if (localStorage.getItem('torrent')) {
client.send('torrent', JSON.parse(localStorage.getItem('torrent')))
if (!files.length) client.send('torrent', JSON.parse(localStorage.getItem('torrent')))
}
}, 3000)
}, 1000)
})