mirror of
https://github.com/NoCrypt/migu.git
synced 2026-03-29 13:59:09 +00:00
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:
parent
19cf481541
commit
b7c80afbc9
5 changed files with 169 additions and 128 deletions
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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}> × </button>
|
||||
<table class="table table-hover">
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
})
|
||||
|
|
|
|||
Loading…
Reference in a new issue