1
0
Fork 0
mirror of https://github.com/sussy-code/smov.git synced 2024-12-29 16:07:40 +01:00

Metadata fetching

This commit is contained in:
mrjvs 2023-12-19 00:10:46 +01:00
parent e48af381c5
commit 2bf0b5b03c
5 changed files with 89 additions and 41 deletions

View file

@ -1,4 +1,22 @@
import { ScrapeMedia } from "@movie-web/providers";
import { MetaOutput, ScrapeMedia } from "@movie-web/providers";
import { mwFetch } from "@/backend/helpers/fetch";
let metaDataCache: MetaOutput[] | null = null;
export function setCachedMetadata(data: MetaOutput[]) {
metaDataCache = data;
}
export function getCachedMetadata(): MetaOutput[] {
return metaDataCache ?? [];
}
export async function fetchMetadata(base: string) {
if (metaDataCache) return;
const data = await mwFetch<MetaOutput[][]>(`${base}/metadata`);
metaDataCache = data.flat();
}
function scrapeMediaToQueryMedia(media: ScrapeMedia) {
let extra: Record<string, string> = {};
@ -15,6 +33,7 @@ function scrapeMediaToQueryMedia(media: ScrapeMedia) {
type: media.type,
releaseYear: media.releaseYear.toString(),
imdbId: media.imdbId,
tmdbId: media.tmdbId,
title: media.title,
...extra,
};
@ -48,8 +67,31 @@ export function makeProviderUrl(base: string) {
};
}
export function connectServerSideEvents(url: string, endEvents: string[]) {
const;
export function connectServerSideEvents<T>(url: string, endEvents: string[]) {
const eventSource = new EventSource(url);
let promReject: (reason?: any) => void;
let promResolve: (value: T) => void;
const promise = new Promise<T>((resolve, reject) => {
promResolve = resolve;
promReject = reject;
});
return {};
endEvents.forEach((evt) => {
eventSource.addEventListener(evt, (e) => {
eventSource.close();
promResolve(JSON.parse(e.data));
});
});
eventSource.addEventListener("error", (err) => {
console.error("Failed to connect to SSE", err);
promReject(err);
});
return {
promise: () => promise,
on<Data>(event: string, cb: (data: Data) => void) {
eventSource.addEventListener(event, (e) => cb(JSON.parse(e.data)));
},
};
}

View file

@ -1,6 +1,7 @@
import { useMemo } from "react";
import { useTranslation } from "react-i18next";
import { getCachedMetadata } from "@/backend/helpers/providerApi";
import { Toggle } from "@/components/buttons/Toggle";
import { Icon, Icons } from "@/components/Icon";
import { useCaptions } from "@/components/player/hooks/useCaptions";
@ -10,7 +11,6 @@ import { useOverlayRouter } from "@/hooks/useOverlayRouter";
import { usePlayerStore } from "@/stores/player/store";
import { qualityToString } from "@/stores/player/utils/qualities";
import { useSubtitleStore } from "@/stores/subtitles";
import { providers } from "@/utils/providers";
export function SettingsMenu({ id }: { id: string }) {
const { t } = useTranslation();
@ -23,7 +23,10 @@ export function SettingsMenu({ id }: { id: string }) {
const currentSourceId = usePlayerStore((s) => s.sourceId);
const sourceName = useMemo(() => {
if (!currentSourceId) return "...";
return providers.getMetadata(currentSourceId)?.name ?? "...";
const source = getCachedMetadata().find(
(src) => src.id === currentSourceId
);
return source?.name ?? "...";
}, [currentSourceId]);
const { toggleLastUsed } = useCaptions();

View file

@ -1,6 +1,7 @@
import { ReactNode, useEffect, useMemo, useRef } from "react";
import { useTranslation } from "react-i18next";
import { getCachedMetadata } from "@/backend/helpers/providerApi";
import { Loading } from "@/components/layout/Loading";
import {
useEmbedScraping,
@ -33,7 +34,7 @@ export function EmbedOption(props: {
const embedName = useMemo(() => {
if (!props.embedId) return unknownEmbedName;
const sourceMeta = providers.getMetadata(props.embedId);
const sourceMeta = getCachedMetadata().find((s) => s.id === props.embedId);
return sourceMeta?.name ?? unknownEmbedName;
}, [props.embedId, unknownEmbedName]);
@ -61,7 +62,7 @@ export function EmbedSelectionView({ sourceId, id }: EmbedSelectionViewProps) {
const sourceName = useMemo(() => {
if (!sourceId) return "...";
const sourceMeta = providers.getMetadata(sourceId);
const sourceMeta = getCachedMetadata().find((s) => s.id === sourceId);
return sourceMeta?.name ?? "...";
}, [sourceId]);

View file

@ -5,7 +5,11 @@ import {
} from "@movie-web/providers";
import { RefObject, useCallback, useEffect, useRef, useState } from "react";
import { makeProviderUrl } from "@/backend/helpers/providerApi";
import {
connectServerSideEvents,
getCachedMetadata,
makeProviderUrl,
} from "@/backend/helpers/providerApi";
import { getLoadbalancedProviderApiUrl, providers } from "@/utils/providers";
export interface ScrapingItems {
@ -37,7 +41,7 @@ function useBaseScrape() {
setSources(
evt.sourceIds
.map((v) => {
const source = providers.getMetadata(v);
const source = getCachedMetadata().find((s) => s.id === v);
if (!source) throw new Error("invalid source id");
const out: ScrapingSegment = {
name: source.name,
@ -80,7 +84,9 @@ function useBaseScrape() {
(evt: ScraperEvent<"discoverEmbeds">) => {
setSources((s) => {
evt.embeds.forEach((v) => {
const source = providers.getMetadata(v.embedScraperId);
const source = getCachedMetadata().find(
(src) => src.id === v.embedScraperId
);
if (!source) throw new Error("invalid source id");
const out: ScrapingSegment = {
embedId: v.embedScraperId,
@ -149,37 +155,18 @@ export function useScrape() {
const providerApiUrl = getLoadbalancedProviderApiUrl();
if (providerApiUrl) {
startScrape();
const sseOutput = await new Promise<RunOutput | null>(
(resolve, reject) => {
const baseUrlMaker = makeProviderUrl(providerApiUrl);
const scrapeEvents = new EventSource(baseUrlMaker.scrapeAll(media));
scrapeEvents.addEventListener("init", (e) => {
initEvent(JSON.parse(e.data));
});
scrapeEvents.addEventListener("error", (err) => {
console.error("failed to use provider api", err);
reject(err);
});
scrapeEvents.addEventListener("start", (e) =>
startEvent(JSON.parse(e.data))
);
scrapeEvents.addEventListener("update", (e) =>
updateEvent(JSON.parse(e.data))
);
scrapeEvents.addEventListener("discoverEmbeds", (e) =>
discoverEmbedsEvent(JSON.parse(e.data))
);
scrapeEvents.addEventListener("completed", (e) => {
scrapeEvents.close();
resolve(JSON.parse(e.data));
});
scrapeEvents.addEventListener("noOutput", () => {
scrapeEvents.close();
resolve(null);
});
}
const baseUrlMaker = makeProviderUrl(providerApiUrl);
const conn = connectServerSideEvents<RunOutput | "">(
baseUrlMaker.scrapeAll(media),
["completed", "noOutput"]
);
return getResult(sseOutput);
conn.on("init", initEvent);
conn.on("start", startEvent);
conn.on("update", updateEvent);
conn.on("discoverEmbeds", discoverEmbedsEvent);
const sseOutput = await conn.promise();
return getResult(sseOutput === "" ? null : sseOutput);
}
if (!providers) return null;

View file

@ -3,6 +3,10 @@ import { useHistory, useParams } from "react-router-dom";
import { useAsync } from "react-use";
import type { AsyncReturnType } from "type-fest";
import {
fetchMetadata,
setCachedMetadata,
} from "@/backend/helpers/providerApi";
import { DetailedMeta, getMetaFromId } from "@/backend/metadata/getmeta";
import { decodeTMDBId } from "@/backend/metadata/tmdb";
import { MWMediaType } from "@/backend/metadata/types/mw";
@ -14,6 +18,7 @@ import { Paragraph } from "@/components/text/Paragraph";
import { Title } from "@/components/text/Title";
import { ErrorContainer, ErrorLayout } from "@/pages/layouts/ErrorLayout";
import { conf } from "@/setup/config";
import { getLoadbalancedProviderApiUrl, providers } from "@/utils/providers";
export interface MetaPartProps {
onGetMeta?: (meta: DetailedMeta, episodeId?: string) => void;
@ -36,6 +41,16 @@ export function MetaPart(props: MetaPartProps) {
const history = useHistory();
const { error, value, loading } = useAsync(async () => {
const providerApiUrl = getLoadbalancedProviderApiUrl();
if (providerApiUrl) {
await fetchMetadata(providerApiUrl);
} else {
setCachedMetadata([
...providers.listSources(),
...providers.listEmbeds(),
]);
}
let data: ReturnType<typeof decodeTMDBId> = null;
try {
data = decodeTMDBId(params.media);