mirror of
https://github.com/NoCrypt/migu.git
synced 2026-04-19 15:42:05 +00:00
feat: verbose debug logging
This commit is contained in:
parent
eeed27ffcf
commit
fdec580314
16 changed files with 179 additions and 90 deletions
|
|
@ -11,7 +11,12 @@
|
|||
page = 'home'
|
||||
}
|
||||
|
||||
const debug = persisted('debug', '')
|
||||
const debug = persisted('debug', '', {
|
||||
serializer: {
|
||||
parse: e => e,
|
||||
stringify: e => e
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<div class='w-full z-101 navbar bg-transparent border-0 p-0 d-flex'>
|
||||
|
|
|
|||
|
|
@ -6,6 +6,9 @@ import { alToken } from '@/modules/settings.js'
|
|||
import { toast } from 'svelte-sonner'
|
||||
import { sleep } from './util.js'
|
||||
import IPC from '@/modules/ipc.js'
|
||||
import Debug from 'debug'
|
||||
|
||||
const debug = Debug('ui:anilist')
|
||||
|
||||
const codes = {
|
||||
400: 'Bad Request',
|
||||
|
|
@ -34,7 +37,7 @@ const codes = {
|
|||
}
|
||||
|
||||
function printError (error) {
|
||||
console.warn(error)
|
||||
debug(`Error: ${error.status || 429} - ${error.message || codes[error.status || 429]}`)
|
||||
toast.error('Search Failed', {
|
||||
description: `Failed making request to anilist!\nTry again in a minute.\n${error.status || 429} - ${error.message || codes[error.status || 429]}`,
|
||||
duration: 3000
|
||||
|
|
@ -191,6 +194,7 @@ class AnilistClient {
|
|||
lastNotificationDate = Date.now() / 1000
|
||||
|
||||
constructor () {
|
||||
debug('Initializing Anilist Client for ID ' + this.userID?.viewer?.data?.Viewer.id)
|
||||
this.limiter.on('failed', async (error, jobInfo) => {
|
||||
printError(error)
|
||||
|
||||
|
|
@ -282,10 +286,12 @@ class AnilistClient {
|
|||
}
|
||||
|
||||
async findNewNotifications () {
|
||||
debug('Checking for new notifications')
|
||||
const res = await this.getNotifications()
|
||||
const notifications = res.data.Page.notifications
|
||||
const newNotifications = notifications.filter(({ createdAt }) => createdAt > this.lastNotificationDate)
|
||||
this.lastNotificationDate = Date.now() / 1000
|
||||
debug(`Found ${newNotifications.length} new notifications`)
|
||||
for (const { media, episode, type } of newNotifications) {
|
||||
const options = {
|
||||
title: media.title.userPreferred,
|
||||
|
|
@ -301,6 +307,7 @@ class AnilistClient {
|
|||
* @param {{key: string, title: string, year?: string, isAdult: boolean}[]} flattenedTitles
|
||||
**/
|
||||
async alSearchCompound (flattenedTitles) {
|
||||
debug(`Searching for ${flattenedTitles.length} titles via compound search`)
|
||||
if (!flattenedTitles.length) return []
|
||||
// isAdult doesn't need an extra variable, as the title is the same regardless of type, so we re-use the same variable for adult and non-adult requests
|
||||
/** @type {Record<`v${number}`, string>} */
|
||||
|
|
@ -361,7 +368,9 @@ class AnilistClient {
|
|||
// check if values exist
|
||||
if (filemedia.media && alToken) {
|
||||
const { media, failed } = filemedia
|
||||
debug(`Checking entry for ${media.title.userPreferred}`)
|
||||
|
||||
debug(`Media viability: ${media.status}, Is from failed resolve: ${failed}`)
|
||||
if (failed) return
|
||||
if (media.status !== 'FINISHED' && media.status !== 'RELEASING') return
|
||||
|
||||
|
|
@ -371,6 +380,7 @@ class AnilistClient {
|
|||
const videoEpisode = Number(filemedia.episode) || singleEpisode
|
||||
const mediaEpisode = media.episodes || media.nextAiringEpisode?.episode || singleEpisode
|
||||
|
||||
debug(`Episode viability: ${videoEpisode}, ${mediaEpisode}, ${singleEpisode}`)
|
||||
if (!videoEpisode || !mediaEpisode) return
|
||||
// check episode range, safety check if `failed` didn't catch this
|
||||
if (videoEpisode > mediaEpisode) return
|
||||
|
|
@ -380,10 +390,12 @@ class AnilistClient {
|
|||
const status = media.mediaListEntry?.status === 'REPEATING' ? 'REPEATING' : 'CURRENT'
|
||||
const progress = media.mediaListEntry?.progress
|
||||
|
||||
debug(`User's progress: ${progress}, Media's progress: ${videoEpisode}`)
|
||||
// check user's own watch progress
|
||||
if (progress > videoEpisode) return
|
||||
if (progress === videoEpisode && videoEpisode !== mediaEpisode && !singleEpisode) return
|
||||
|
||||
debug(`Updating entry for ${media.title.userPreferred}`)
|
||||
const variables = {
|
||||
repeat: media.mediaListEntry?.repeat || 0,
|
||||
id: media.id,
|
||||
|
|
@ -404,6 +416,7 @@ class AnilistClient {
|
|||
}
|
||||
|
||||
async searchName (variables = {}) {
|
||||
debug(`Searching name for ${variables.name}`)
|
||||
const query = /* js */`
|
||||
query($page: Int, $perPage: Int, $sort: [MediaSort], $name: String, $status: [MediaStatus], $year: Int, $isAdult: Boolean) {
|
||||
Page(page: $page, perPage: $perPage) {
|
||||
|
|
@ -427,6 +440,7 @@ class AnilistClient {
|
|||
}
|
||||
|
||||
async searchIDSingle (variables) {
|
||||
debug(`Searching for ID: ${variables.id}`)
|
||||
const query = /* js */`
|
||||
query($id: Int) {
|
||||
Media(id: $id, type: ANIME) {
|
||||
|
|
@ -443,6 +457,7 @@ class AnilistClient {
|
|||
}
|
||||
|
||||
async searchIDS (variables) {
|
||||
debug(`Searching for IDs: ${variables.id.length}`)
|
||||
const query = /* js */`
|
||||
query($id: [Int], $page: Int, $perPage: Int, $status: [MediaStatus], $onList: Boolean, $sort: [MediaSort], $search: String, $season: MediaSeason, $year: Int, $genre: String, $format: MediaFormat) {
|
||||
Page(page: $page, perPage: $perPage) {
|
||||
|
|
@ -465,6 +480,7 @@ class AnilistClient {
|
|||
|
||||
/** @returns {Promise<import('./al.d.ts').PagedQuery<{ notifications: { id: number, type: string, createdAt: number, episode: number, media: import('./al.d.ts').Media}[] }>>} */
|
||||
getNotifications (variables = {}) {
|
||||
debug('Getting notifications')
|
||||
const query = /* js */`
|
||||
query($page: Int, $perPage: Int) {
|
||||
Page(page: $page, perPage: $perPage) {
|
||||
|
|
@ -507,6 +523,7 @@ class AnilistClient {
|
|||
|
||||
/** @returns {Promise<import('./al.d.ts').Query<{ Viewer: import('./al.d.ts').Viewer }>>} */
|
||||
viewer (variables = {}) {
|
||||
debug('Getting viewer')
|
||||
const query = /* js */`
|
||||
query {
|
||||
Viewer {
|
||||
|
|
@ -528,6 +545,7 @@ class AnilistClient {
|
|||
|
||||
/** @returns {Promise<import('./al.d.ts').Query<{ MediaListCollection: import('./al.d.ts').MediaListCollection }>>} */
|
||||
async getUserLists (variables = {}) {
|
||||
debug('Getting user lists')
|
||||
const userId = this.userID?.viewer?.data?.Viewer.id
|
||||
variables.id = userId
|
||||
const query = /* js */`
|
||||
|
|
@ -565,6 +583,7 @@ class AnilistClient {
|
|||
|
||||
/** @returns {Promise<import('./al.d.ts').Query<{ MediaList: { status: string, progress: number, repeat: number }}>>} */
|
||||
async searchIDStatus (variables = {}) {
|
||||
debug(`Searching for ID status: ${variables.id}`)
|
||||
const userId = this.userID?.viewer?.data?.Viewer.id
|
||||
variables.id = userId
|
||||
const query = /* js */`
|
||||
|
|
@ -580,6 +599,7 @@ class AnilistClient {
|
|||
}
|
||||
|
||||
async searchAiringSchedule (variables = {}) {
|
||||
debug('Searching for airing schedule')
|
||||
variables.to = (variables.from + 7 * 24 * 60 * 60)
|
||||
const query = /* js */`
|
||||
query($page: Int, $perPage: Int, $from: Int, $to: Int) {
|
||||
|
|
@ -607,7 +627,8 @@ class AnilistClient {
|
|||
}
|
||||
|
||||
/** @returns {Promise<import('./al.d.ts').PagedQuery<{ airingSchedules: { airingAt: number, episode: number }[]}>>} */
|
||||
episodes (variables) {
|
||||
episodes (variables = {}) {
|
||||
debug(`Searching for episodes: ${variables.id}`)
|
||||
const query = /* js */`
|
||||
query($id: Int) {
|
||||
Page(page: 1, perPage: 1000) {
|
||||
|
|
@ -622,6 +643,7 @@ class AnilistClient {
|
|||
}
|
||||
|
||||
async search (variables = {}) {
|
||||
debug(`Searching ${JSON.stringify(variables)}`)
|
||||
variables.sort ||= 'SEARCH_MATCH'
|
||||
const query = /* js */`
|
||||
query($page: Int, $perPage: Int, $sort: [MediaSort], $search: String, $onList: Boolean, $status: MediaStatus, $status_not: MediaStatus, $season: MediaSeason, $year: Int, $genre: String, $format: MediaFormat) {
|
||||
|
|
@ -645,6 +667,7 @@ class AnilistClient {
|
|||
|
||||
/** @returns {Promise<import('./al.d.ts').Query<{ AiringSchedule: { airingAt: number }}>>} */
|
||||
episodeDate (variables) {
|
||||
debug(`Searching for episode date: ${variables.id}, ${variables.ep}`)
|
||||
const query = /* js */`
|
||||
query($id: Int, $ep: Int) {
|
||||
AiringSchedule(mediaId: $id, episode: $ep) {
|
||||
|
|
@ -657,6 +680,7 @@ class AnilistClient {
|
|||
|
||||
/** @returns {Promise<import('./al.d.ts').PagedQuery<{ mediaList: import('./al.d.ts').Following[]}>>} */
|
||||
following (variables) {
|
||||
debug('Getting following')
|
||||
const query = /* js */`
|
||||
query($id: Int) {
|
||||
Page {
|
||||
|
|
@ -678,6 +702,7 @@ class AnilistClient {
|
|||
}
|
||||
|
||||
entry (variables) {
|
||||
debug(`Updating entry for ${variables.id}`)
|
||||
const query = /* js */`
|
||||
mutation($lists: [String], $id: Int, $status: MediaListStatus, $episode: Int, $repeat: Int, $score: Int) {
|
||||
SaveMediaListEntry(mediaId: $id, status: $status, progress: $episode, repeat: $repeat, scoreRaw: $score, customLists: $lists) {
|
||||
|
|
@ -692,6 +717,7 @@ class AnilistClient {
|
|||
}
|
||||
|
||||
delete (variables) {
|
||||
debug(`Deleting entry for ${variables.id}`)
|
||||
const query = /* js */`
|
||||
mutation($id: Int) {
|
||||
DeleteMediaListEntry(id: $id) {
|
||||
|
|
@ -703,6 +729,7 @@ class AnilistClient {
|
|||
}
|
||||
|
||||
favourite (variables) {
|
||||
debug(`Toggling favourite for ${variables.id}`)
|
||||
const query = /* js */`
|
||||
mutation($id: Int) {
|
||||
ToggleFavourite(animeId: $id) { anime { nodes { id } } }
|
||||
|
|
@ -712,6 +739,7 @@ class AnilistClient {
|
|||
}
|
||||
|
||||
customList (variables = {}) {
|
||||
debug('Updating custom list')
|
||||
variables.lists = [...variables.lists, 'Watched using Miru']
|
||||
const query = /* js */`
|
||||
mutation($lists: [String]) {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
import { anilistClient } from './anilist.js'
|
||||
import { anitomyscript } from './anime.js'
|
||||
import { chunks } from './util.js'
|
||||
import Debug from 'debug'
|
||||
|
||||
const debug = Debug('ui:animeresolver')
|
||||
|
||||
const postfix = {
|
||||
1: 'st', 2: 'nd', 3: 'rd'
|
||||
|
|
@ -73,8 +76,11 @@ export default new class AnimeResolver {
|
|||
return titleObjects
|
||||
}).flat()
|
||||
|
||||
debug(`Finding ${titleObjects.length} titles: ${titleObjects.map(obj => obj.title).join(', ')}`)
|
||||
|
||||
for (const chunk of chunks(titleObjects, 62)) { // single title has a complexity of 8.1, al limits complexity to 500
|
||||
for (const [key, media] of await anilistClient.alSearchCompound(chunk)) {
|
||||
debug(`Found ${key} as ${media.id}: ${media.title.userPreferred}`)
|
||||
this.animeNameCache[key] = media
|
||||
}
|
||||
}
|
||||
|
|
@ -118,10 +124,12 @@ export default new class AnimeResolver {
|
|||
let media = this.animeNameCache[this.getCacheKeyForTitle(parseObj)]
|
||||
// resolve episode, if movie, dont.
|
||||
const maxep = media?.nextAiringEpisode?.episode || media?.episodes
|
||||
debug(`Resolving ${parseObj.anime_title} ${parseObj.episode_number} ${maxep} ${media?.title.userPreferred} ${media?.format}`)
|
||||
if ((media?.format !== 'MOVIE' || maxep) && parseObj.episode_number) {
|
||||
if (Array.isArray(parseObj.episode_number)) {
|
||||
// is an episode range
|
||||
if (parseInt(parseObj.episode_number[0]) === 1) {
|
||||
debug('Range starts at 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
|
||||
episode = `${parseObj.episode_number[0]} ~ ${parseObj.episode_number[1]}`
|
||||
} else {
|
||||
|
|
@ -132,14 +140,18 @@ export default new class AnimeResolver {
|
|||
// parent check is to break out of those incorrectly resolved OVA's
|
||||
// if we used anime season to resolve anime name, then there's no need to march into prequel!
|
||||
const prequel = !parseObj.anime_season && (this.findEdge(media, 'PREQUEL')?.node || ((media.format === 'OVA' || media.format === 'ONA') && this.findEdge(media, 'PARENT')?.node))
|
||||
debug(`Prequel ${prequel && prequel.id}:${prequel && prequel.title.userPreferred}`)
|
||||
const root = prequel && (await this.resolveSeason({ media: await this.getAnimeById(prequel.id), force: true })).media
|
||||
debug(`Root ${root && root.id}:${root && root.title.userPreferred}`)
|
||||
|
||||
// if highest value is bigger than episode count or latest streamed episode +1 for safety, parseint to math.floor a number like 12.5 - specials - in 1 go
|
||||
const result = await this.resolveSeason({ media: root || media, episode: parseObj.episode_number[1], increment: !parseObj.anime_season ? null : true })
|
||||
debug(`Found rootMedia for ${parseObj.anime_title}: ${result.rootMedia.id}:${result.rootMedia.title.userPreferred} from ${media.id}:${media.title.userPreferred}`)
|
||||
media = result.rootMedia
|
||||
const diff = parseObj.episode_number[1] - result.episode
|
||||
episode = `${parseObj.episode_number[0] - diff} ~ ${result.episode}`
|
||||
failed = result.failed
|
||||
if (failed) debug(`Failed to resolve ${parseObj.anime_title} ${parseObj.episode_number} ${media?.title.userPreferred}`)
|
||||
} else {
|
||||
// cant find ep count or range seems fine
|
||||
episode = `${Number(parseObj.episode_number[0])} ~ ${Number(parseObj.episode_number[1])}`
|
||||
|
|
@ -149,19 +161,24 @@ export default new class AnimeResolver {
|
|||
if (maxep && parseInt(parseObj.episode_number) > maxep) {
|
||||
// see big comment above
|
||||
const prequel = !parseObj.anime_season && (this.findEdge(media, 'PREQUEL')?.node || ((media.format === 'OVA' || media.format === 'ONA') && this.findEdge(media, 'PARENT')?.node))
|
||||
debug(`Prequel ${prequel && prequel.id}:${prequel && prequel.title.userPreferred}`)
|
||||
const root = prequel && (await this.resolveSeason({ media: await this.getAnimeById(prequel.id), force: true })).media
|
||||
debug(`Root ${root && root.id}:${root && root.title.userPreferred}`)
|
||||
|
||||
// value bigger than episode count
|
||||
const result = await this.resolveSeason({ media: root || media, episode: parseInt(parseObj.episode_number), increment: !parseObj.anime_season ? null : true })
|
||||
debug(`Found rootMedia for ${parseObj.anime_title}: ${result.rootMedia.id}:${result.rootMedia.title.userPreferred} from ${media.id}:${media.title.userPreferred}`)
|
||||
media = result.rootMedia
|
||||
episode = result.episode
|
||||
failed = result.failed
|
||||
if (failed) debug(`Failed to resolve ${parseObj.anime_title} ${parseObj.episode_number} ${media?.title.userPreferred}`)
|
||||
} else {
|
||||
// cant find ep count or episode seems fine
|
||||
episode = Number(parseObj.episode_number)
|
||||
}
|
||||
}
|
||||
}
|
||||
debug(`Resolved ${parseObj.anime_title} ${parseObj.episode_number} ${episode} ${media?.id}:${media?.title.userPreferred}`)
|
||||
fileAnimes.push({
|
||||
episode: episode || parseObj.episode_number,
|
||||
parseObject: parseObj,
|
||||
|
|
@ -172,6 +189,12 @@ export default new class AnimeResolver {
|
|||
return fileAnimes
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import('./al.js').Media} media
|
||||
* @param {string} type
|
||||
* @param {string[]} [formats]
|
||||
* @param {boolean} [skip]
|
||||
*/
|
||||
findEdge (media, type, formats = ['TV', 'TV_SHORT'], skip) {
|
||||
let res = media.relations.edges.find(edge => {
|
||||
if (edge.relationType === type) {
|
||||
|
|
@ -186,9 +209,8 @@ export default new class AnimeResolver {
|
|||
|
||||
// note: this doesnt cover anime which uses partially relative and partially absolute episode number, BUT IT COULD!
|
||||
/**
|
||||
*
|
||||
* @param {{ media:any, episode?:number, force?:boolean, increment?:boolean, offset?: number, rootMedia?:any }} opts
|
||||
* @returns
|
||||
* @param {{ media: import('./al.js').Media , episode?:number, force?:boolean, increment?:boolean, offset?: number, rootMedia?: import('./al.js').Media }} opts
|
||||
* @returns {Promise<{ media: import('./al.js').Media, episode: number, offset: number, increment: boolean, rootMedia: import('./al.js').Media, failed?: boolean }>}
|
||||
*/
|
||||
async resolveSeason (opts) {
|
||||
// media, episode, increment, offset, force
|
||||
|
|
@ -205,7 +227,7 @@ export default new class AnimeResolver {
|
|||
|
||||
if (!edge) {
|
||||
const obj = { media, episode: episode - offset, offset, increment, rootMedia, failed: true }
|
||||
if (!force) console.warn('Error in parsing!', obj)
|
||||
if (!force) debug(`Failed to resolve ${media.id}:${media.title.userPreferred} ${episode} ${increment} ${offset} ${rootMedia.id}:${rootMedia.title.userPreferred}`)
|
||||
return obj
|
||||
}
|
||||
media = await this.getAnimeById(edge.id)
|
||||
|
|
|
|||
|
|
@ -6,6 +6,9 @@ import { anitomyscript } from '../anime.js'
|
|||
import { client } from '@/modules/torrent.js'
|
||||
import { extensionsWorker } from '@/views/Settings/TorrentSettings.svelte'
|
||||
import { toast } from 'svelte-sonner'
|
||||
import Debug from 'debug'
|
||||
|
||||
const debug = Debug('ui:extensions')
|
||||
|
||||
/** @typedef {import('@thaunknown/ani-resourced/sources/types.d.ts').Options} Options */
|
||||
/** @typedef {import('@thaunknown/ani-resourced/sources/types.d.ts').Result} Result */
|
||||
|
|
@ -16,11 +19,17 @@ import { toast } from 'svelte-sonner'
|
|||
* **/
|
||||
export default async function getResultsFromExtensions ({ media, episode, batch, movie, resolution }) {
|
||||
const worker = await /** @type {ReturnType<import('@/modules/extensions/worker.js').loadExtensions>} */(extensionsWorker)
|
||||
if (!(await worker.metadata)?.length) throw new Error('No torrent sources configured. Add extensions in settings.')
|
||||
if (!(await worker.metadata)?.length) {
|
||||
debug('No torrent sources configured')
|
||||
throw new Error('No torrent sources configured. Add extensions in settings.')
|
||||
}
|
||||
|
||||
debug(`Fetching sources for ${media.id}:${media.title.userPreferred} ${episode} ${batch} ${movie} ${resolution}`)
|
||||
|
||||
const aniDBMeta = await ALToAniDB(media)
|
||||
const anidbAid = aniDBMeta?.mappings?.anidb_id
|
||||
const anidbEid = anidbAid && (await ALtoAniDBEpisode({ media, episode }, aniDBMeta))?.anidbEid
|
||||
debug(`AniDB Mapping: ${anidbAid} ${anidbEid}`)
|
||||
|
||||
/** @type {Options} */
|
||||
const options = {
|
||||
|
|
@ -36,8 +45,10 @@ export default async function getResultsFromExtensions ({ media, episode, batch,
|
|||
|
||||
const { results, errors } = await worker.query(options, { movie, batch }, settings.value.sources)
|
||||
|
||||
debug(`Found ${results.length} results`)
|
||||
|
||||
for (const error of errors) {
|
||||
console.error(error)
|
||||
debug(`Source Fetch Failed: ${error}`)
|
||||
toast.error('Source Fetch Failed!', {
|
||||
description: error
|
||||
})
|
||||
|
|
@ -56,11 +67,13 @@ export default async function getResultsFromExtensions ({ media, episode, batch,
|
|||
|
||||
async function updatePeerCounts (entries) {
|
||||
const id = Math.trunc(Math.random() * Number.MAX_SAFE_INTEGER).toString()
|
||||
debug(`Updating peer counts for ${entries.length} entries`)
|
||||
|
||||
const updated = await Promise.race([
|
||||
new Promise(resolve => {
|
||||
function check ({ detail }) {
|
||||
if (detail.id !== id) return
|
||||
debug('Got scrape response')
|
||||
client.removeListener('scrape', check)
|
||||
resolve(detail.result)
|
||||
}
|
||||
|
|
@ -69,6 +82,7 @@ async function updatePeerCounts (entries) {
|
|||
}),
|
||||
sleep(15000)
|
||||
])
|
||||
debug('Scrape complete')
|
||||
|
||||
for (const { hash, complete, downloaded, incomplete } of updated || []) {
|
||||
const found = entries.find(mapped => mapped.hash === hash)
|
||||
|
|
@ -76,6 +90,8 @@ async function updatePeerCounts (entries) {
|
|||
found.leechers = incomplete
|
||||
found.seeders = complete
|
||||
}
|
||||
|
||||
debug(`Found ${updated.length} entries: ${JSON.stringify(updated)}`)
|
||||
return entries
|
||||
}
|
||||
|
||||
|
|
@ -110,12 +126,18 @@ function getRelation (list, type) {
|
|||
* @param {{episodes: any, episodeCount: number, specialCount: number}} param1
|
||||
* */
|
||||
async function ALtoAniDBEpisode ({ media, episode }, { episodes, episodeCount, specialCount }) {
|
||||
debug(`Fetching AniDB episode for ${media.id}:${media.title.userPreferred} ${episode}`)
|
||||
if (!episode || !Object.values(episodes).length) return
|
||||
// if media has no specials or their episode counts don't match
|
||||
if (!specialCount || (media.episodes && media.episodes === episodeCount && episodes[Number(episode)])) return episodes[Number(episode)]
|
||||
if (!specialCount || (media.episodes && media.episodes === episodeCount && episodes[Number(episode)])) {
|
||||
debug('No specials found, or episode count matches between AL and AniDB')
|
||||
return episodes[Number(episode)]
|
||||
}
|
||||
debug(`Episode count mismatch between AL and AniDB for ${media.id}:${media.title.userPreferred}`)
|
||||
const res = await anilistClient.episodeDate({ id: media.id, ep: episode })
|
||||
// TODO: if media only has one episode, and airdate doesn't exist use start/release/end dates
|
||||
const alDate = new Date((res.data.AiringSchedule?.airingAt || 0) * 1000)
|
||||
debug(`AL Airdate: ${alDate}`)
|
||||
|
||||
return episodeByAirDate(alDate, episodes, episode)
|
||||
}
|
||||
|
|
@ -126,6 +148,7 @@ async function ALtoAniDBEpisode ({ media, episode }, { episodes, episodeCount, s
|
|||
* @param {number} episode
|
||||
**/
|
||||
export function episodeByAirDate (alDate, episodes, episode) {
|
||||
// TODO handle special cases where anilist reports that 3 episodes aired at the same time because of pre-releases
|
||||
if (!+alDate) return episodes[Number(episode)] || episodes[1] // what the fuck, are you braindead anilist?, the source episode number to play is from an array created from AL ep count, so how come it's missing?
|
||||
// 1 is key for episod 1, not index
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,11 @@
|
|||
import Metadata from 'matroska-metadata'
|
||||
import Debug from 'debug'
|
||||
import { arr2hex, hex2bin } from 'uint8-util'
|
||||
import { fontRx } from './util.js'
|
||||
import { SUPPORTS } from '@/modules/support.js'
|
||||
|
||||
const debug = Debug('torrent:parser')
|
||||
|
||||
export default class Parser {
|
||||
parsed = false
|
||||
/** @type {Metadata} */
|
||||
|
|
@ -10,13 +13,16 @@ export default class Parser {
|
|||
client = null
|
||||
file = null
|
||||
destroyed = false
|
||||
|
||||
constructor (client, file) {
|
||||
debug('Initializing parser for file: ' + file.name)
|
||||
this.client = client
|
||||
this.file = file
|
||||
this.metadata = new Metadata(file)
|
||||
|
||||
this.metadata.getTracks().then(tracks => {
|
||||
if (this.destroyed) return
|
||||
debug('Tracks received: ' + tracks)
|
||||
if (!tracks.length) {
|
||||
this.parsed = true
|
||||
this.destroy()
|
||||
|
|
@ -27,17 +33,20 @@ export default class Parser {
|
|||
|
||||
this.metadata.getChapters().then(chapters => {
|
||||
if (this.destroyed) return
|
||||
debug(`Found ${chapters.length} chapters`)
|
||||
this.client.dispatch('chapters', chapters)
|
||||
})
|
||||
|
||||
this.metadata.getAttachments().then(files => {
|
||||
if (this.destroyed) return
|
||||
debug(`Found ${files.length} attachments`)
|
||||
for (const file of files) {
|
||||
if (fontRx.test(file.filename) || file.mimetype?.toLowerCase().includes('font')) {
|
||||
// this is cursed, but required, as capacitor-node's IPC hangs for 2mins when runnig on 32bit android when sending uint8's
|
||||
const data = hex2bin(arr2hex(file.data))
|
||||
// IPC crashes if the message is >16MB, wild
|
||||
if (SUPPORTS.isAndroid && data.length > 15_000_000) continue
|
||||
if (SUPPORTS.isAndroid && data.length > 15_000_000) {
|
||||
debug('Skipping large font file on Android: ' + file.filename)
|
||||
continue
|
||||
}
|
||||
this.client.dispatch('file', data)
|
||||
}
|
||||
}
|
||||
|
|
@ -45,6 +54,7 @@ export default class Parser {
|
|||
|
||||
this.metadata.on('subtitle', (subtitle, trackNumber) => {
|
||||
if (this.destroyed) return
|
||||
debug(`Found subtitle for track: ${trackNumber}: ${subtitle.text}`)
|
||||
this.client.dispatch('subtitle', { subtitle, trackNumber })
|
||||
})
|
||||
|
||||
|
|
@ -53,20 +63,14 @@ export default class Parser {
|
|||
if (this.destroyed) return cb(iterator)
|
||||
cb(this.metadata.parseStream(iterator))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async parseSubtitles () {
|
||||
if (this.file.name.endsWith('.mkv') || this.file.name.endsWith('.webm')) {
|
||||
console.log('Sub parsing started')
|
||||
await this.metadata.parseFile()
|
||||
console.log('Sub parsing finished')
|
||||
} else {
|
||||
debug('Unsupported file format: ' + this.file.name)
|
||||
}
|
||||
}
|
||||
|
||||
destroy () {
|
||||
debug('Destroying Parser')
|
||||
this.destroyed = true
|
||||
this.metadata?.destroy()
|
||||
this.metadata = undefined
|
||||
// Add any additional cleanup code here
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,9 @@ import { getEpisodeMetadataForMedia } from './anime.js'
|
|||
import AnimeResolver from '@/modules/animeresolver.js'
|
||||
import { hasNextPage } from '@/modules/sections.js'
|
||||
import IPC from '@/modules/ipc.js'
|
||||
import Debug from 'debug'
|
||||
|
||||
const debug = Debug('ui:rss')
|
||||
|
||||
export const exclusions = ['DTS', 'TrueHD', '[EMBER]']
|
||||
const isDev = location.hostname === 'localhost'
|
||||
|
|
@ -54,6 +57,7 @@ export async function getRSSContent (url) {
|
|||
if (!url) return null
|
||||
const res = await fetch(url)
|
||||
if (!res.ok) {
|
||||
debug(`Failed to fetch RSS feed: ${res.statusText}`)
|
||||
throw new Error(res.statusText)
|
||||
}
|
||||
return DOMPARSER(await res.text(), 'text/xml')
|
||||
|
|
@ -69,9 +73,9 @@ class RSSMediaManager {
|
|||
if (!ignoreErrors) {
|
||||
res.catch(e => {
|
||||
toast.error('Search Failed', {
|
||||
description: 'Failed to loading media for home feed!\n' + e.message
|
||||
description: 'Failed to load media for home feed!\n' + e.message
|
||||
})
|
||||
console.error('Failed to loading media for home feed', e)
|
||||
debug('Failed to load media for home feed', e.stack)
|
||||
})
|
||||
}
|
||||
return Array.from({ length: perPage }, (_, i) => ({ type: 'episode', data: this.fromPending(res, i) }))
|
||||
|
|
@ -93,8 +97,10 @@ class RSSMediaManager {
|
|||
}
|
||||
|
||||
async _getMediaForRSS (page, perPage, url) {
|
||||
debug(`Getting media for RSS feed ${url} page ${page} perPage ${perPage}`)
|
||||
const changed = await this.getContentChanged(page, perPage, url)
|
||||
if (!changed) return this.resultMap[url].result
|
||||
debug(`Feed ${url} has changed, updating`)
|
||||
|
||||
const index = (page - 1) * perPage
|
||||
const targetPage = [...changed.content.querySelectorAll('item')].slice(index, index + perPage)
|
||||
|
|
@ -116,6 +122,7 @@ class RSSMediaManager {
|
|||
|
||||
const res = await Promise.all(await results)
|
||||
const newReleases = res.filter(({ date }) => date > oldDate)
|
||||
debug(`Found ${newReleases.length} new releases, notifying...`)
|
||||
|
||||
for (const { media, parseObject, episode } of newReleases) {
|
||||
const options = {
|
||||
|
|
@ -141,7 +148,7 @@ class RSSMediaManager {
|
|||
try {
|
||||
res.episodeData = (await getEpisodeMetadataForMedia(res.media))?.[res.episode]
|
||||
} catch (e) {
|
||||
console.warn('failed fetching episode metadata', e)
|
||||
debug(`Warn: failed fetching episode metadata for ${res.media.title.userPreferred} episode ${res.episode}: ${e.stack}`)
|
||||
}
|
||||
}
|
||||
res.date = items[i].date
|
||||
|
|
|
|||
|
|
@ -3,6 +3,10 @@ import { defaults } from './util.js'
|
|||
import IPC from '@/modules/ipc.js'
|
||||
import { anilistClient } from './anilist.js'
|
||||
import { toast } from 'svelte-sonner'
|
||||
import Debug from 'debug'
|
||||
|
||||
const debug = Debug('ui:anilist')
|
||||
|
||||
/** @type {{viewer: import('./al').Query<{Viewer: import('./al').Viewer}>, token: string} | null} */
|
||||
export let alToken = JSON.parse(localStorage.getItem('ALviewer')) || null
|
||||
|
||||
|
|
@ -54,7 +58,7 @@ async function handleToken (token) {
|
|||
const viewer = await anilistClient.viewer({ token })
|
||||
if (!viewer.data?.Viewer) {
|
||||
toast.error('Failed to sign in with AniList. Please try again.', { description: JSON.stringify(viewer) })
|
||||
console.error(viewer)
|
||||
debug(`Failed to sign in with AniList: ${JSON.stringify(viewer)}`)
|
||||
return
|
||||
}
|
||||
const lists = viewer?.data?.Viewer?.mediaListOptions?.animeList?.customLists || []
|
||||
|
|
|
|||
|
|
@ -4,6 +4,9 @@ import { toast } from 'svelte-sonner'
|
|||
import clipboard from './clipboard.js'
|
||||
import IPC from '@/modules/ipc.js'
|
||||
import 'browser-event-target-emitter'
|
||||
import Debug from 'debug'
|
||||
|
||||
const debug = Debug('ui:torrent')
|
||||
|
||||
const torrentRx = /(^magnet:){1}|(^[A-F\d]{8,40}$){1}|(.*\.torrent$){1}/i
|
||||
|
||||
|
|
@ -42,7 +45,7 @@ class TorrentWorker extends EventTarget {
|
|||
|
||||
async send (type, data, transfer) {
|
||||
await this.ready
|
||||
console.info('Torrent: sending message', { type, data })
|
||||
debug(`Sending message ${type}`, data)
|
||||
this.port.postMessage({ type, data }, transfer)
|
||||
}
|
||||
}
|
||||
|
|
@ -56,18 +59,18 @@ client.on('files', ({ detail }) => {
|
|||
})
|
||||
|
||||
client.on('error', ({ detail }) => {
|
||||
console.error(detail)
|
||||
debug(`Error: ${detail.message || detail}`)
|
||||
toast.error('Torrent Error', { description: '' + (detail.message || detail) })
|
||||
})
|
||||
|
||||
client.on('warn', ({ detail }) => {
|
||||
console.error(detail)
|
||||
debug(`Warn: ${detail.message || detail}`)
|
||||
toast.warning('Torrent Warning', { description: '' + (detail.message || detail) })
|
||||
})
|
||||
|
||||
export async function add (torrentID, hide) {
|
||||
if (torrentID) {
|
||||
console.info('Torrent: adding torrent', { torrentID })
|
||||
debug('Adding torrent', { torrentID })
|
||||
if (torrentID.startsWith?.('magnet:')) {
|
||||
localStorage.setItem('torrent', JSON.stringify(torrentID))
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -18,6 +18,8 @@ const querystringStringify = obj => {
|
|||
return ret
|
||||
}
|
||||
|
||||
const debug = Debug('torrent:worker')
|
||||
|
||||
const ANNOUNCE = [
|
||||
atob('d3NzOi8vdHJhY2tlci5vcGVud2VidG9ycmVudC5jb20='),
|
||||
atob('d3NzOi8vdHJhY2tlci53ZWJ0b3JyZW50LmRldg=='),
|
||||
|
|
@ -53,6 +55,7 @@ export default class TorrentClient extends WebTorrent {
|
|||
|
||||
constructor (ipc, storageQuota, serverMode, torrentPath, controller) {
|
||||
const settings = { ...defaults, ...storedSettings }
|
||||
debug('Initializing TorrentClient with settings: ' + JSON.stringify(settings))
|
||||
super({
|
||||
dht: !settings.torrentDHT,
|
||||
maxConns: settings.maxConns,
|
||||
|
|
@ -68,6 +71,7 @@ export default class TorrentClient extends WebTorrent {
|
|||
ipc.on('port', ({ ports }) => {
|
||||
this.message = ports[0].postMessage.bind(ports[0])
|
||||
ports[0].onmessage = ({ data }) => {
|
||||
debug(`Received IPC message ${data.type}: ${data.data}`)
|
||||
if (data.type === 'load') this.loadLastTorrent(data.data)
|
||||
if (data.type === 'destroy') this.destroy()
|
||||
this.handleMessage({ data })
|
||||
|
|
@ -120,6 +124,7 @@ export default class TorrentClient extends WebTorrent {
|
|||
}
|
||||
|
||||
loadLastTorrent (t) {
|
||||
debug('Loading last torrent: ' + t)
|
||||
if (!t) return
|
||||
let torrent
|
||||
// this can be a magnet string, or a stringified array, lazy way of makign sure it works
|
||||
|
|
@ -137,6 +142,7 @@ export default class TorrentClient extends WebTorrent {
|
|||
}
|
||||
|
||||
async handleTorrent (torrent) {
|
||||
debug('Got torrent metadata: ' + torrent.name)
|
||||
const files = torrent.files.map(file => {
|
||||
return {
|
||||
infoHash: torrent.infoHash,
|
||||
|
|
@ -166,6 +172,8 @@ export default class TorrentClient extends WebTorrent {
|
|||
map[file.name] = file
|
||||
}
|
||||
|
||||
debug(`Found ${Object.keys(map).length} font files`)
|
||||
|
||||
for (const file of Object.values(map)) {
|
||||
const data = await file.arrayBuffer()
|
||||
if (targetFile !== this.current) return
|
||||
|
|
@ -181,6 +189,7 @@ export default class TorrentClient extends WebTorrent {
|
|||
const subfiles = files.filter(file => {
|
||||
return subRx.test(file.name) && (videoFiles.length === 1 ? true : file.name.includes(videoName))
|
||||
})
|
||||
debug(`Found ${subfiles.length} subtitle files`)
|
||||
for (const file of subfiles) {
|
||||
const data = await file.arrayBuffer()
|
||||
if (targetFile !== this.current) return
|
||||
|
|
@ -189,6 +198,7 @@ export default class TorrentClient extends WebTorrent {
|
|||
}
|
||||
|
||||
async _scrape ({ id, infoHashes }) {
|
||||
debug(`Scraping ${infoHashes.length} hashes, id: ${id}`)
|
||||
// this seems to give the best speed, and lowest failure rate
|
||||
const MAX_ANNOUNCE_LENGTH = 1300 // it's likely 2048, but lets undercut it
|
||||
const RATE_LIMIT = 200 // ms
|
||||
|
|
@ -206,11 +216,13 @@ export default class TorrentClient extends WebTorrent {
|
|||
this.tracker._request(this.tracker.scrapeUrl, { info_hash: batch }, (err, data) => {
|
||||
if (err) {
|
||||
const error = this._errorToString(err)
|
||||
debug('Failed to scrape: ' + error)
|
||||
this.dispatch('warn', `Failed to update seeder counts: ${error}`)
|
||||
return resolve([])
|
||||
}
|
||||
const { files } = data
|
||||
const result = []
|
||||
debug(`Scraped ${Object.keys(files || {}).length} hashes, id: ${id}`)
|
||||
for (const [key, data] of Object.entries(files || {})) {
|
||||
result.push({ hash: key.length !== 40 ? arr2hex(text2arr(key)) : key, ...data })
|
||||
}
|
||||
|
|
@ -234,6 +246,8 @@ export default class TorrentClient extends WebTorrent {
|
|||
}
|
||||
if (batch.length) await scrape()
|
||||
|
||||
debug(`Scraped ${results.length} hashes, id: ${id}`)
|
||||
|
||||
this.dispatch('scrape', { id, result: results })
|
||||
}
|
||||
|
||||
|
|
@ -260,11 +274,12 @@ export default class TorrentClient extends WebTorrent {
|
|||
for (const exclude of TorrentClient.excludedErrorMessages) {
|
||||
if (error.startsWith(exclude)) return
|
||||
}
|
||||
console.error(error)
|
||||
debug('Error: ' + error)
|
||||
this.dispatch('error', error)
|
||||
}
|
||||
|
||||
async addTorrent (data, skipVerify = false) {
|
||||
debug('Adding torrent: ' + data)
|
||||
const existing = await this.get(data)
|
||||
if (existing) {
|
||||
if (existing.ready) this.handleTorrent(existing)
|
||||
|
|
@ -347,6 +362,7 @@ export default class TorrentClient extends WebTorrent {
|
|||
}
|
||||
|
||||
destroy () {
|
||||
debug('Destroying TorrentClient')
|
||||
if (this.destroyed) return
|
||||
this.parser?.destroy()
|
||||
this.server.close()
|
||||
|
|
|
|||
|
|
@ -1,38 +0,0 @@
|
|||
<script>
|
||||
import { settings } from '@/modules/settings.js'
|
||||
import { click } from '@/modules/click.js'
|
||||
import IPC from '@/modules/ipc.js'
|
||||
let block = false
|
||||
|
||||
async function testConnection () {
|
||||
try {
|
||||
for (let i = 0; i < 2; ++i) {
|
||||
// fetch twice, sometimes it will go tru once if ISP is shitty
|
||||
await fetch($settings.toshoURL + 'json?show=torrent&id=1')
|
||||
}
|
||||
block = false
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
block = true
|
||||
}
|
||||
}
|
||||
testConnection()
|
||||
</script>
|
||||
|
||||
{#if block}
|
||||
<div class='w-full h-full left-0 z-50 px-10 position-absolute content-wrapper bg-dark d-flex align-items-center justify-content-center flex-column'>
|
||||
<div>
|
||||
<h1 class='font-weight-bold'>Could not connect to Torrent API!</h1>
|
||||
<div class='font-size-16'>This happens either because the API is down, or because your ISP blocks the API, the latter being more likely.</div>
|
||||
<div class='font-size-16'>Most features of Miru will not function correctly without being able to connect to an API.</div>
|
||||
<div class='font-size-16'>If you enable a VPN a restart might be required for it to take effect.</div>
|
||||
<!-- eslint-disable-next-line svelte/valid-compile -->
|
||||
<div class='font-size-16'>Visit <a class='text-primary pointer' use:click={() => { IPC.emit('open', 'https://thewiki.moe/tutorials/unblock/') }}>this guide</a> for a tutorial on how to bypass ISP blocks.</div>
|
||||
<div class='d-flex w-full mt-20 pt-20'>
|
||||
<button class='btn ml-auto mr-5' type='button' use:click={() => { block = false }}>I Understand</button>
|
||||
<button class='btn btn-primary mr-5' type='button' use:click={() => { IPC.emit('open', 'https://thewiki.moe/tutorials/unblock/') }}>Open Guide</button>
|
||||
<button class='btn btn-primary' type='button' use:click={testConnection}>Reconnect</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
|
@ -5,6 +5,9 @@
|
|||
import { tick } from 'svelte'
|
||||
import { state } from '../WatchTogether/WatchTogether.svelte'
|
||||
import IPC from '@/modules/ipc.js'
|
||||
import Debug from 'debug'
|
||||
|
||||
const debug = Debug('ui:mediahandler')
|
||||
|
||||
const episodeRx = /Episode (\d+) - (.*)/
|
||||
|
||||
|
|
@ -108,8 +111,12 @@
|
|||
}
|
||||
}
|
||||
|
||||
function fileListToDebug (files) {
|
||||
return files.map(({ name, media, url }) => `\n${name} ${media?.parseObject.anime_title} ${media?.parseObject.episode_number} ${media?.media?.title.userPreferred} ${media?.episode}`).join('')
|
||||
}
|
||||
|
||||
async function handleFiles (files) {
|
||||
console.info('MediaHandler: got files', files)
|
||||
debug(`Got ${files.length} files`, fileListToDebug(files))
|
||||
if (!files?.length) return processed.set(files)
|
||||
let videoFiles = []
|
||||
const otherFiles = []
|
||||
|
|
@ -132,34 +139,35 @@
|
|||
videoFiles = videoFiles.filter(file => !TYPE_EXCLUSIONS.includes(file.media.parseObject.anime_type?.toUpperCase()))
|
||||
|
||||
if (nowPlaying?.verified && videoFiles.length === 1) {
|
||||
console.info('Media was verified, skipping verification')
|
||||
debug('Media was verified, skipping verification')
|
||||
videoFiles[0].media.media = nowPlaying.media
|
||||
if (nowPlaying.episode) videoFiles[0].media.episode = nowPlaying.episode
|
||||
}
|
||||
|
||||
console.info('MediaHandler: resolved video files', { videoFiles })
|
||||
debug(`Resolved ${videoFiles.length} video files`, fileListToDebug(videoFiles))
|
||||
|
||||
if (!nowPlaying) {
|
||||
nowPlaying = findPreferredPlaybackMedia(videoFiles)
|
||||
debug(`Found preferred playback media: ${nowPlaying.media?.id}:${nowPlaying.media?.title.userPreferred} ${nowPlaying.episode}`)
|
||||
}
|
||||
|
||||
const filtered = nowPlaying?.media && videoFiles.filter(file => file.media?.media?.id && file.media?.media?.id === nowPlaying.media.id)
|
||||
|
||||
console.info('MediaHandler: filtered files based on media', filtered)
|
||||
debug(`Filtered ${filtered?.length} files based on media`, fileListToDebug(filtered))
|
||||
|
||||
let result
|
||||
if (filtered?.length) {
|
||||
result = filtered
|
||||
} else {
|
||||
const max = highestOccurence(videoFiles, file => file.media.parseObject.anime_title).media.parseObject.anime_title
|
||||
console.info('MediaHandler: filtering based on highest occurence', max)
|
||||
debug(`Highest occurence anime title: ${max}`)
|
||||
result = videoFiles.filter(file => file.media.parseObject.anime_title === max)
|
||||
}
|
||||
|
||||
result.sort((a, b) => a.media.episode - b.media.episode)
|
||||
result.sort((a, b) => (b.media.parseObject.anime_season ?? 1) - (a.media.parseObject.anime_season ?? 1))
|
||||
|
||||
console.info('MediaHandler: final resolve result', { result })
|
||||
debug(`Sorted ${result.length} files`, fileListToDebug(result))
|
||||
|
||||
processed.set([...result, ...otherFiles])
|
||||
await tick()
|
||||
|
|
|
|||
|
|
@ -49,7 +49,12 @@
|
|||
import { client } from '@/modules/torrent.js'
|
||||
import { onDestroy } from 'svelte'
|
||||
|
||||
const debug = persisted('debug', '')
|
||||
const debug = persisted('debug', '', {
|
||||
serializer: {
|
||||
parse: e => e,
|
||||
stringify: e => e
|
||||
}
|
||||
})
|
||||
|
||||
export let version = ''
|
||||
export let settings
|
||||
|
|
@ -90,7 +95,7 @@
|
|||
<select class='form-control bg-dark w-300 mw-full' bind:value={$debug}>
|
||||
<option value='' selected>None</option>
|
||||
<option value='*'>All</option>
|
||||
<option value='torrent:worker,webtorrent:*,simple-peer,bittorrent-protocol,bittorrent-dht,bittorrent-lsd,torrent-discovery,bittorrent-tracker:*,ut_metadata,nat-pmp,nat-api'>Torrent</option>
|
||||
<option value='torrent:*,webtorrent:*,simple-peer,bittorrent-protocol,bittorrent-dht,bittorrent-lsd,torrent-discovery,bittorrent-tracker:*,ut_metadata,nat-pmp,nat-api'>Torrent</option>
|
||||
<option value='ui:*'>Interface</option>
|
||||
</select>
|
||||
</SettingCard>
|
||||
|
|
|
|||
|
|
@ -3,6 +3,9 @@
|
|||
import { settings } from '@/modules/settings.js'
|
||||
import { click } from '@/modules/click.js'
|
||||
import getResultsFromExtensions from '@/modules/extensions/index.js'
|
||||
import Debug from 'debug'
|
||||
|
||||
const debug = Debug('ui:extensions')
|
||||
|
||||
/** @typedef {import('@/modules/al.d.ts').Media} Media */
|
||||
|
||||
|
|
@ -83,7 +86,7 @@
|
|||
$: autoPlay(best, $settings.rssAutoplay)
|
||||
|
||||
$: lookup.catch(err => {
|
||||
console.error(err)
|
||||
debug(`Error fetching torrents for ${search.media.title.userPreferred} Episode ${search.episode}, ${err.stack}`)
|
||||
toast.error(`No torrent found for ${search.media.title.userPreferred} Episode ${search.episode}!`, { description: err.message })
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,9 @@
|
|||
import { click } from '@/modules/click.js'
|
||||
import IPC from '@/modules/ipc.js'
|
||||
import 'browser-event-target-emitter'
|
||||
import Debug from 'debug'
|
||||
|
||||
const debug = Debug('ui:w2g')
|
||||
|
||||
export const w2gEmitter = new EventTarget()
|
||||
|
||||
|
|
@ -19,6 +22,7 @@
|
|||
let p2pt = null
|
||||
|
||||
function joinLobby (code = generateRandomHexCode(16)) {
|
||||
debug('Joining lobby with code: ' + code)
|
||||
if (p2pt) cleanup()
|
||||
p2pt = new P2PT([
|
||||
atob('d3NzOi8vdHJhY2tlci5vcGVud2VidG9ycmVudC5jb20='),
|
||||
|
|
@ -27,8 +31,7 @@
|
|||
atob('d3NzOi8vdHJhY2tlci5idG9ycmVudC54eXov')
|
||||
], code)
|
||||
p2pt.on('peerconnect', peer => {
|
||||
console.log(peer.id)
|
||||
console.log('connect')
|
||||
debug(`Peer connected: ${peer.id}`)
|
||||
const user = anilistClient.userID?.viewer?.data?.Viewer || {}
|
||||
p2pt.send(peer,
|
||||
JSON.stringify({
|
||||
|
|
@ -40,18 +43,16 @@
|
|||
})
|
||||
p2pt.on('peerclose', peer => {
|
||||
peers.update(object => {
|
||||
console.log(peer.id)
|
||||
console.log('close', object[peer.id])
|
||||
debug(`Peer disconnected: ${peer.id}`)
|
||||
delete object[peer.id]
|
||||
return object
|
||||
})
|
||||
})
|
||||
p2pt.on('msg', (peer, data) => {
|
||||
console.log(data)
|
||||
debug(`Received message from ${peer.id}: ${data.type} ${data}`)
|
||||
data = typeof data === 'string' ? JSON.parse(data) : data
|
||||
switch (data.type) {
|
||||
case 'init':
|
||||
console.log('init', data.user)
|
||||
peers.update(object => {
|
||||
object[peer.id] = {
|
||||
peer,
|
||||
|
|
@ -79,7 +80,7 @@
|
|||
break
|
||||
}
|
||||
default: {
|
||||
console.error('Invalid message type', data)
|
||||
debug('Invalid message type: ' + data.type)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
@ -117,6 +118,7 @@
|
|||
})
|
||||
|
||||
function emit (type, data) {
|
||||
debug(`Emitting ${type} with data: ${JSON.stringify(data)}`)
|
||||
if (p2pt) {
|
||||
for (const { peer } of Object.values(peers.value)) {
|
||||
p2pt.send(peer, { type, ...data })
|
||||
|
|
@ -164,7 +166,6 @@
|
|||
if (!invite) return
|
||||
const match = invite?.match(inviteRx)?.[1]
|
||||
if (!match) return
|
||||
console.log(match)
|
||||
page.set('watchtogether')
|
||||
joinLobby(match)
|
||||
joinText = ''
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import log from 'electron-log'
|
|||
import { autoUpdater } from 'electron-updater'
|
||||
|
||||
log.initialize({ spyRendererConsole: true })
|
||||
log.transports.file.level = 'info'
|
||||
log.transports.file.level = 'debug'
|
||||
log.transports.file.maxSize = 10485760 // 10MB
|
||||
autoUpdater.logger = log
|
||||
|
||||
|
|
|
|||
|
|
@ -14,8 +14,6 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<svelte:window on:load={console.log} />
|
||||
|
||||
<style>
|
||||
@keyframes transition {
|
||||
0% {
|
||||
|
|
|
|||
Loading…
Reference in a new issue