From 117da3335b382306ce5fac8668e797318d8f4300 Mon Sep 17 00:00:00 2001 From: mrjvs Date: Sun, 12 Nov 2023 16:54:32 +0100 Subject: [PATCH] reporting source selection menu Co-authored-by: Jip Frijlink --- src/backend/helpers/report.ts | 27 +++ .../atoms/settings/SourceSelectingView.tsx | 100 ++++------- .../player/hooks/useSourceSelection.ts | 156 ++++++++++++++++++ 3 files changed, 215 insertions(+), 68 deletions(-) create mode 100644 src/components/player/hooks/useSourceSelection.ts diff --git a/src/backend/helpers/report.ts b/src/backend/helpers/report.ts index 0408dd7a..31505c10 100644 --- a/src/backend/helpers/report.ts +++ b/src/backend/helpers/report.ts @@ -3,6 +3,7 @@ import { ofetch } from "ofetch"; import { useCallback } from "react"; import { ScrapingItems, ScrapingSegment } from "@/hooks/useProviderScrape"; +import { PlayerMeta } from "@/stores/player/slices/source"; const metricsEndpoint = "https://backend.movie-web.app/metrics/providers"; @@ -46,6 +47,32 @@ const segmentStatusMap: Record< waiting: null, }; +export function scrapeSourceOutputToProviderMetric( + media: PlayerMeta, + providerId: string, + embedId: string | null, + status: ProviderMetric["status"], + err: unknown | null +): ProviderMetric { + const episodeId = media.episode?.tmdbId; + const seasonId = media.season?.tmdbId; + let error: undefined | Error; + if (err instanceof Error) error = err; + + return { + status, + providerId, + title: media.title, + tmdbId: media.tmdbId, + type: media.type, + embedId: embedId ?? undefined, + episodeId, + seasonId, + errorMessage: error?.message, + fullError: error ? getStackTrace(error, 5) : undefined, + }; +} + export function scrapeSegmentToProviderMetric( media: ScrapeMedia, providerId: string, diff --git a/src/components/player/atoms/settings/SourceSelectingView.tsx b/src/components/player/atoms/settings/SourceSelectingView.tsx index 1519618f..644c573c 100644 --- a/src/components/player/atoms/settings/SourceSelectingView.tsx +++ b/src/components/player/atoms/settings/SourceSelectingView.tsx @@ -1,12 +1,13 @@ import { ReactNode, useEffect, useMemo, useRef } from "react"; -import { useAsyncFn } from "react-use"; import { Loading } from "@/components/layout/Loading"; +import { + useEmbedScraping, + useSourceScraping, +} from "@/components/player/hooks/useSourceSelection"; import { Menu } from "@/components/player/internals/ContextMenu"; import { SelectableLink } from "@/components/player/internals/ContextMenu/Links"; -import { convertRunoutputToSource } from "@/components/player/utils/convertRunoutputToSource"; import { useOverlayRouter } from "@/hooks/useOverlayRouter"; -import { metaToScrapeMedia } from "@/stores/player/slices/source"; import { usePlayerStore } from "@/stores/player/store"; import { providers } from "@/utils/providers"; @@ -23,15 +24,9 @@ export interface EmbedSelectionViewProps { export function EmbedOption(props: { embedId: string; url: string; - sourceId: string | null; + sourceId: string; routerId: string; }) { - const router = useOverlayRouter(props.routerId); - const meta = usePlayerStore((s) => s.meta); - const setSource = usePlayerStore((s) => s.setSource); - const setSourceId = usePlayerStore((s) => s.setSourceId); - const progress = usePlayerStore((s) => s.progress.time); - const unknownEmbedName = "Unknown"; const embedName = useMemo(() => { @@ -40,22 +35,15 @@ export function EmbedOption(props: { return sourceMeta?.name ?? unknownEmbedName; }, [props.embedId]); - const [request, run] = useAsyncFn(async () => { - const result = await providers.runEmbedScraper({ - id: props.embedId, - url: props.url, - }); - setSourceId(props.sourceId); - setSource(convertRunoutputToSource({ stream: result.stream }), progress); - router.close(); - }, [props.embedId, props.sourceId, meta, router]); + const { run, errored, loading } = useEmbedScraping( + props.routerId, + props.sourceId, + props.url, + props.embedId + ); return ( - + {embedName} @@ -63,48 +51,16 @@ export function EmbedOption(props: { ); } -// TODO refactor this file: cleanup + reporting - export function EmbedSelectionView({ sourceId, id }: EmbedSelectionViewProps) { const router = useOverlayRouter(id); - const meta = usePlayerStore((s) => s.meta); - const setSource = usePlayerStore((s) => s.setSource); - const setSourceId = usePlayerStore((s) => s.setSourceId); - const progress = usePlayerStore((s) => s.progress.time); + const { run, watching, notfound, loading, items, errored } = + useSourceScraping(sourceId, id); const sourceName = useMemo(() => { if (!sourceId) return "..."; const sourceMeta = providers.getMetadata(sourceId); return sourceMeta?.name ?? "..."; }, [sourceId]); - const [request, run] = useAsyncFn(async () => { - if (!sourceId || !meta) return null; - const scrapeMedia = metaToScrapeMedia(meta); - const result = await providers.runSourceScraper({ - id: sourceId, - media: scrapeMedia, - }); - - if (result.stream) { - setSource(convertRunoutputToSource({ stream: result.stream }), progress); - setSourceId(sourceId); - router.close(); - return null; - } - if (result.embeds.length === 1) { - const embedResult = await providers.runEmbedScraper({ - id: result.embeds[0].embedId, - url: result.embeds[0].url, - }); - setSourceId(sourceId); - setSource( - convertRunoutputToSource({ stream: embedResult.stream }), - progress - ); - router.close(); - } - return result.embeds; - }, [sourceId, meta, router]); const lastSourceId = useRef(null); useEffect(() => { @@ -115,27 +71,35 @@ export function EmbedSelectionView({ sourceId, id }: EmbedSelectionViewProps) { }, [run, sourceId]); let content: ReactNode = null; - if (request.loading) + if (loading) content = ( ); - else if (request.error) + else if (notfound) + content = ( + + This source has no streams for this movie or show. + + ); + else if (items?.length === 0) + content = ( + + We were unable to find any embeds for this source, please try another. + + ); + else if (errored) content = ( We were unable to find any videos for this source. Don't come bitchin' to us about it, just try another source. ); - else if (request.value && request.value.length === 0) - content = ( - - We were unable to find any embeds for this source, please try another. - - ); - else if (request.value) - content = request.value.map((v) => ( + else if (watching) + content = null; // when it starts watching, empty the display + else if (items && sourceId) + content = items.map((v) => ( s.setSource); + const setSourceId = usePlayerStore((s) => s.setSourceId); + const progress = usePlayerStore((s) => s.progress.time); + const meta = usePlayerStore((s) => s.meta); + const router = useOverlayRouter(routerId); + const { report } = useReportProviders(); + + const [request, run] = useAsyncFn(async () => { + let result: EmbedOutput | undefined; + if (!meta) return; + try { + result = await providers.runEmbedScraper({ + id: embedId, + url, + }); + } catch (err) { + console.error(`Failed to scrape ${embedId}`, err); + const notFound = err instanceof NotFoundError; + const status = notFound ? "notfound" : "failed"; + report([ + scrapeSourceOutputToProviderMetric( + meta, + sourceId, + embedId, + status, + err + ), + ]); + throw err; + } + report([ + scrapeSourceOutputToProviderMetric(meta, sourceId, null, "success", null), + ]); + setSourceId(sourceId); + setSource(convertRunoutputToSource({ stream: result.stream }), progress); + router.close(); + }, [embedId, sourceId, meta, router, report]); + + return { + run, + loading: request.loading, + errored: !!request.error, + }; +} + +export function useSourceScraping(sourceId: string | null, routerId: string) { + const meta = usePlayerStore((s) => s.meta); + const setSource = usePlayerStore((s) => s.setSource); + const setSourceId = usePlayerStore((s) => s.setSourceId); + const progress = usePlayerStore((s) => s.progress.time); + const router = useOverlayRouter(routerId); + const { report } = useReportProviders(); + + const [request, run] = useAsyncFn(async () => { + if (!sourceId || !meta) return null; + const scrapeMedia = metaToScrapeMedia(meta); + + let result: SourcererOutput | undefined; + try { + result = await providers.runSourceScraper({ + id: sourceId, + media: scrapeMedia, + }); + } catch (err) { + console.error(`Failed to scrape ${sourceId}`, err); + const notFound = err instanceof NotFoundError; + const status = notFound ? "notfound" : "failed"; + report([ + scrapeSourceOutputToProviderMetric(meta, sourceId, null, status, err), + ]); + throw err; + } + report([ + scrapeSourceOutputToProviderMetric(meta, sourceId, null, "success", null), + ]); + + if (result.stream) { + setSource(convertRunoutputToSource({ stream: result.stream }), progress); + setSourceId(sourceId); + router.close(); + return null; + } + if (result.embeds.length === 1) { + let embedResult: EmbedOutput | undefined; + if (!meta) return; + try { + embedResult = await providers.runEmbedScraper({ + id: result.embeds[0].embedId, + url: result.embeds[0].url, + }); + } catch (err) { + console.error(`Failed to scrape ${result.embeds[0].embedId}`, err); + const notFound = err instanceof NotFoundError; + const status = notFound ? "notfound" : "failed"; + report([ + scrapeSourceOutputToProviderMetric( + meta, + sourceId, + result.embeds[0].embedId, + status, + err + ), + ]); + throw err; + } + report([ + scrapeSourceOutputToProviderMetric( + meta, + sourceId, + result.embeds[0].embedId, + "success", + null + ), + ]); + setSourceId(sourceId); + setSource( + convertRunoutputToSource({ stream: embedResult.stream }), + progress + ); + router.close(); + } + return result.embeds; + }, [sourceId, meta, router]); + + return { + run, + watching: (request.value ?? null) === null, + loading: request.loading, + items: request.value, + notfound: !!(request.error instanceof NotFoundError), + errored: !!request.error, + }; +}