fix: discord RPC, file episode finder/picker

feat: title on releases cards, NC-Raws
This commit is contained in:
ThaUnknown 2022-12-27 18:10:38 +01:00
parent 9e5b239ea3
commit 5f4e711254
5 changed files with 183 additions and 175 deletions

View file

@ -38,7 +38,7 @@
</div>
</div>
{:else}
<div class='card m-0 p-0' on:click={card.onclick || (() => viewMedia(card.media))} style:--color={card.media.coverImage.color || '#1890ff'}>
<div class='card m-0 p-0' on:click={card.onclick || (() => viewMedia(card.media))} style:--color={card.media.coverImage.color || '#1890ff'} title={card.parseObject?.file_name}>
<div class='row h-full'>
<div class='col-4'>
<img loading='lazy' src={card.media.coverImage.extraLarge || ''} alt='cover' class='cover-img w-full h-full' />

View file

@ -1,198 +1,206 @@
<script context='module'>
import { writable, get } from 'svelte/store'
import { resolveFileMedia } from '@/modules/anime.js'
import { videoRx } from '@/modules/util.js'
import { title } from '../Menubar.svelte'
import { tick } from 'svelte'
import { state } from '../WatchTogether/WatchTogether.svelte'
import { writable, get } from 'svelte/store'
import { resolveFileMedia } from '@/modules/anime.js'
import { videoRx } from '@/modules/util.js'
import { title } from '../Menubar.svelte'
import { tick } from 'svelte'
import { state } from '../WatchTogether/WatchTogether.svelte'
const episodeRx = /Episode (\d+) - (.*)/
const episodeRx = /Episode (\d+) - (.*)/
export const media = writable(null)
export const media = writable(null)
const nowPlaying = writable({})
const nowPlaying = writable({})
export const files = writable([])
export const files = writable([])
const processed = writable([])
const processed = writable([])
const noop = () => {}
const noop = () => {}
let playFile
let playFile
media.subscribe((media) => {
handleMedia(media || {})
return noop
})
function handleCurrent ({ detail }) {
media.set(detail.media)
}
export function findInCurrent (obj) {
const oldNowPlaying = get(nowPlaying)
if (oldNowPlaying.media?.id === obj.media.id && oldNowPlaying.episode === obj.episode) return false
const fileList = get(files)
const targetFile = fileList.find(file => file.media?.media?.id === obj.media.id && file.media?.episode === obj.episode)
if (!targetFile) return false
if (oldNowPlaying.media?.id !== obj.media.id) {
// mediachange, filelist change
media.set({ media: obj.media, episode: obj.episode })
handleFiles(fileList)
} else {
playFile(targetFile)
}
return true
}
function handleMedia ({ media, episode, parseObject }) {
if (media) {
const ep = Number(episode || parseObject.episode_number) || null
const streamingEpisode = media?.streamingEpisodes.find(episode => {
const match = episodeRx.exec(episode.title)
return match && Number(match[1]) === ep
})
const np = {
media,
title: media?.title.userPreferred || parseObject.anime_title,
episode: ep,
episodeTitle: streamingEpisode && episodeRx.exec(streamingEpisode.title)[2],
thumbnail: streamingEpisode?.thumbnail || media?.coverImage.extraLarge
}
setDiscordRPC(np)
setMediaSession(np)
nowPlaying.set(np)
}
}
async function handleFiles (files) {
if (!files?.length) return processed.set(files)
const videoFiles = []
const otherFiles = []
for (const file of files) {
if (videoRx.test(file.name)) {
videoFiles.push(file)
} else {
otherFiles.push(file)
}
}
const resolved = await resolveFileMedia(videoFiles.map(file => file.name))
videoFiles.map(file => {
file.media = resolved.find(({ parseObject }) => file.name.includes(parseObject.file_name))
return file
media.subscribe((media) => {
handleMedia(media || {})
return noop
})
let nowPlaying = get(media)
if (!nowPlaying) {
const max = highest(videoFiles, (file) => file?.media?.media?.id).media
if (max?.media) {
nowPlaying = { media: max.media, episode: (max.media.mediaListEntry?.progress + 1 || 1) }
media.set(nowPlaying)
}
function handleCurrent ({ detail }) {
media.set(detail.media)
}
const filtered = nowPlaying?.media && videoFiles.filter(file => file.media?.media?.id && file.media?.media?.id === nowPlaying.media.id)
export function findInCurrent (obj) {
const oldNowPlaying = get(nowPlaying)
const result = (filtered?.length && filtered) || videoFiles
if (oldNowPlaying.media?.id === obj.media.id && oldNowPlaying.episode === obj.episode) return false
result.sort((a, b) => a.media.episode - b.media.episode)
const fileList = get(files)
processed.set([...result, ...otherFiles])
await tick()
const file = nowPlaying?.episode && (result.find(({ media }) => media.episode === nowPlaying.episode) || result.find(({ media }) => media.episode === 1) || 0)
playFile(file || 0)
}
const targetFile = fileList.find(file => file.media?.media?.id === obj.media.id && file.media?.episode === obj.episode)
if (!targetFile) return false
if (oldNowPlaying.media?.id !== obj.media.id) {
// mediachange, filelist change
media.set({ media: obj.media, episode: obj.episode })
handleFiles(fileList)
} else {
playFile(targetFile)
}
return true
}
const highest = (arr = [], mapfn = a => a) => arr.reduce((acc, el) => {
const mapped = mapfn(el)
acc.sums[mapped] = (acc.sums[mapped] || 0) + 1
acc.max = acc.sums[mapfn(acc.max)] > acc.sums[mapped] ? acc.max : el
return acc
}, { sums: {} }).max
files.subscribe((files = []) => {
handleFiles(files)
return noop
})
function setMediaSession (nowPlaying) {
const name = [nowPlaying.title, nowPlaying.episode, nowPlaying.episodeTitle, 'Miru'].filter(i => i).join(' - ')
title.set(name)
const metadata =
nowPlaying.thumbnail
? new MediaMetadata({
title: name,
artwork: [
{
src: nowPlaying.thumbnail,
sizes: '256x256',
type: 'image/jpg'
}
]
function handleMedia ({ media, episode, parseObject }) {
if (media) {
const ep = Number(episode || parseObject.episode_number) || null
const streamingEpisode = media?.streamingEpisodes.find(episode => {
const match = episodeRx.exec(episode.title)
return match && Number(match[1]) === ep
})
: new MediaMetadata({ title: name })
navigator.mediaSession.metadata = metadata
}
function setDiscordRPC (np = get(nowPlaying)) {
const w2g = get(state)
const activity = {
details: [np.title, np.episodeTitle].filter(i => i).join(' - '),
state: 'Watching Episode ' + ((!np.media?.episodes && np.episode) || ''),
timestamps: {
start: Date.now()
},
party: {
size: (np.episode && np.media?.episodes && [np.episode, np.media.episodes]) || undefined
},
assets: {
large_text: np.title,
large_image: np.thumbnail,
small_image: 'logo',
small_text: 'https://github.com/ThaUnknown/miru'
},
instance: true,
type: 3
}
// cannot have buttons and secrets at once
if (w2g) {
activity.secrets = {
join: w2g,
match: w2g + 'm'
}
activity.party.id = w2g + 'p'
} else {
activity.buttons = [
{
label: 'Download app',
url: 'https://github.com/ThaUnknown/miru/releases/latest'
},
{
label: 'Watch on Miru',
url: `miru://anime/${np.media?.id}`
const np = {
media,
title: media?.title.userPreferred || parseObject.anime_title,
episode: ep,
episodeTitle: streamingEpisode && episodeRx.exec(streamingEpisode.title)[2],
thumbnail: streamingEpisode?.thumbnail || media?.coverImage.extraLarge
}
]
setDiscordRPC(np)
setMediaSession(np)
nowPlaying.set(np)
}
}
window.IPC.emit('discord', { activity })
}
state.subscribe(() => {
setDiscordRPC()
return noop
})
async function handleFiles (files) {
if (!files?.length) return processed.set(files)
const videoFiles = []
const otherFiles = []
for (const file of files) {
if (videoRx.test(file.name)) {
videoFiles.push(file)
} else {
otherFiles.push(file)
}
}
const resolved = await resolveFileMedia(videoFiles.map(file => file.name))
videoFiles.map(file => {
file.media = resolved.find(({ parseObject }) => file.name.includes(parseObject.file_name))
return file
})
let nowPlaying = get(media)
if (!nowPlaying) {
const max = highestOccurence(videoFiles, file => file.media.media?.id).media
if (max?.media) {
nowPlaying = { media: max.media, episode: (max.media.mediaListEntry?.progress + 1 || 1) }
media.set(nowPlaying)
}
}
const filtered = nowPlaying?.media && videoFiles.filter(file => file.media?.media?.id && file.media?.media?.id === nowPlaying.media.id)
let result
if (filtered?.length) {
result = filtered
} else {
const max = highestOccurence(videoFiles, file => file.media.parseObject.anime_title).media.parseObject.anime_title
result = videoFiles.filter(file => file.media.parseObject.anime_title === max)
}
result.sort((a, b) => a.media.episode - b.media.episode)
processed.set([...result, ...otherFiles])
await tick()
const file = nowPlaying?.episode && (result.find(({ media }) => media.episode === nowPlaying.episode) || result.find(({ media }) => media.episode === 1) || 0)
playFile(file || 0)
}
// find element with most occurences in array according to map function
const highestOccurence = (arr = [], mapfn = a => a) => arr.reduce((acc, el) => {
const mapped = mapfn(el)
acc.sums[mapped] = (acc.sums[mapped] || 0) + 1
acc.max = (acc.max !== undefined ? acc.sums[mapfn(acc.max)] : -1) > acc.sums[mapped] ? acc.max : el
return acc
}, { sums: {} }).max
files.subscribe((files = []) => {
handleFiles(files)
return noop
})
function setMediaSession (nowPlaying) {
const name = [nowPlaying.title, nowPlaying.episode, nowPlaying.episodeTitle, 'Miru'].filter(i => i).join(' - ')
title.set(name)
const metadata =
nowPlaying.thumbnail
? new MediaMetadata({
title: name,
artwork: [
{
src: nowPlaying.thumbnail,
sizes: '256x256',
type: 'image/jpg'
}
]
})
: new MediaMetadata({ title: name })
navigator.mediaSession.metadata = metadata
}
function setDiscordRPC (np = get(nowPlaying)) {
const w2g = get(state)
const details = [np.title, np.episodeTitle].filter(i => i).join(' - ') || undefined
const activity = {
details,
state: details && 'Watching Episode ' + ((!np.media?.episodes && np.episode) || ''),
timestamps: {
start: Date.now()
},
party: {
size: (np.episode && np.media?.episodes && [np.episode, np.media.episodes]) || undefined
},
assets: {
large_text: np.title,
large_image: np.thumbnail,
small_image: 'logo',
small_text: 'https://github.com/ThaUnknown/miru'
},
instance: true,
type: 3
}
// cannot have buttons and secrets at once
if (w2g) {
activity.secrets = {
join: w2g,
match: w2g + 'm'
}
activity.party.id = w2g + 'p'
} else {
activity.buttons = [
{
label: 'Download app',
url: 'https://github.com/ThaUnknown/miru/releases/latest'
},
{
label: 'Watch on Miru',
url: `miru://anime/${np.media?.id}`
}
]
}
window.IPC.emit('discord', { activity })
}
state.subscribe(() => {
setDiscordRPC()
return noop
})
</script>
<script>
import Player from './Player.svelte'
import Player from './Player.svelte'
export let miniplayer = false
export let page = 'home'
export let miniplayer = false
export let page = 'home'
</script>
<Player files={$processed} {miniplayer} media={$nowPlaying} bind:playFile bind:page on:current={handleCurrent}/>
<Player files={$processed} {miniplayer} media={$nowPlaying} bind:playFile bind:page on:current={handleCurrent} />

View file

@ -1086,7 +1086,7 @@
}
datalist option {
background: #ff3c00;
background: rgba(255, 255, 255, 0.2);
top: 5px;
min-height: unset;
height: 6px;

View file

@ -94,7 +94,7 @@
const rssmap = {
SubsPlease: 'https://nyaa.si/?page=rss&c=0_0&f=0&u=subsplease&q=',
'Erai-raws [Multi-Sub]': 'https://nyaa.si/?page=rss&c=0_0&f=0&u=Erai-raws&q=',
NanDesuKa: 'https://nyaa.si/?page=rss&c=0_0&f=0&u=NanDesuKa&q='
'NC-Raws': 'https://nyaa.si/?page=rss&c=0_0&f=0&u=BraveSail&q='
}
const epstring = ep => `"E${zeropad(ep)}+"|"E${zeropad(ep)}v"|"+${zeropad(ep)}+"|"+${zeropad(ep)}v"`
export function getReleasesRSSurl (val) {

View file

@ -202,8 +202,8 @@
<input id='rss-feed-{i}' type='text' list='rss-feed-list-{i}' class='w-400 form-control form-control-lg' placeholder='https://nyaa.si/?page=rss&c=0_0&f=0&q=' autocomplete='off' bind:value={settings.rssFeeds[i][1]} />
<datalist id='rss-feed-list-{i}'>
<option value='SubsPlease'>https://nyaa.si/?page=rss&c=0_0&f=0&u=subsplease&q=</option>
<option value='NC-Raws'>https://nyaa.si/?page=rss&c=0_0&f=0&u=BraveSail&q=</option>
<option value='Erai-raws [Multi-Sub]'>https://nyaa.si/?page=rss&c=0_0&f=0&u=Erai-raws&q=</option>
<option value='NanDesuKa'>https://nyaa.si/?page=rss&c=0_0&f=0&u=NanDesuKa&q=</option>
</datalist>
<div class='input-group-append'>
<button type='button' on:click={() => { settings.rssFeeds.splice(i, 1); settings.rssFeeds = settings.rssFeeds }} class='btn btn-danger btn-lg input-group-append'>Remove</button>