diff --git a/index.html b/index.html index c6f4d667..cd808f2e 100644 --- a/index.html +++ b/index.html @@ -41,6 +41,8 @@ + + movie-web diff --git a/src/backend/helpers/fetch.ts b/src/backend/helpers/fetch.ts index b61ae55c..b2871c4f 100644 --- a/src/backend/helpers/fetch.ts +++ b/src/backend/helpers/fetch.ts @@ -16,11 +16,11 @@ export function makeUrl(url: string, data: Record) { return parsedUrl; } -export function mwFetch(url: string, ops: P[1]): R { +export function mwFetch(url: string, ops: P[1] = {}): R { return baseFetch(url, ops); } -export function proxiedFetch(url: string, ops: P[1]): R { +export function proxiedFetch(url: string, ops: P[1] = {}): R { let combinedUrl = ops?.baseURL ?? ""; if ( combinedUrl.length > 0 && diff --git a/src/backend/index.ts b/src/backend/index.ts index 46764b1f..6dbb95a6 100644 --- a/src/backend/index.ts +++ b/src/backend/index.ts @@ -3,6 +3,7 @@ import { initializeScraperStore } from "./helpers/register"; // providers import "./providers/gdriveplayer"; import "./providers/flixhq"; +import "./providers/gomostream"; // embeds // -- nothing here yet diff --git a/src/backend/metadata/getmeta.ts b/src/backend/metadata/getmeta.ts index cb622e3b..7fb14ae6 100644 --- a/src/backend/metadata/getmeta.ts +++ b/src/backend/metadata/getmeta.ts @@ -54,6 +54,7 @@ export async function getMetaFromId( throw err; } + console.log(data.external_ids); const imdbId = data.external_ids.find( (v) => v.provider === "imdb_latest" )?.external_id; diff --git a/src/backend/providers/gomostream.ts b/src/backend/providers/gomostream.ts new file mode 100644 index 00000000..275e6099 --- /dev/null +++ b/src/backend/providers/gomostream.ts @@ -0,0 +1,98 @@ +import { unpack } from "unpacker"; +import { proxiedFetch } from "../helpers/fetch"; +import { registerProvider } from "../helpers/register"; +import { MWStreamQuality, MWStreamType } from "../helpers/streams"; +import { MWMediaType } from "../metadata/types"; +import json5 from "json5"; + +const gomoBase = "https://gomo.to/"; + +registerProvider({ + id: "gomostream", + displayName: "gomostream", + rank: 999, + type: [MWMediaType.MOVIE], + + async scrape({ media, progress }) { + // get movie from gomostream + const contentResult = await proxiedFetch( + `/${media.meta.type}/${media.imdbId}`, + { + baseURL: gomoBase, + } + ); + + // movie doesn't exist + if ( + contentResult === "Movie not available." || + contentResult === "Episode not available." + ) + throw new Error("No watchable item found."); + + // decode stream + progress(25); + + const tc = contentResult.match(/var tc = '(.+)';/)?.[1] || ""; + const _token = contentResult.match(/"_token": "(.+)",/)?.[1] || ""; + + const fd = new FormData(); + fd.append("tokenCode", tc); + fd.append("_token", _token); + + const src = await proxiedFetch(`/decoding_v3.php`, { + baseURL: gomoBase, + method: "POST", + body: fd, + headers: { + "x-token": `${tc.slice(5, 13).split("").reverse().join("")}13574199`, + }, + parseResponse: JSON.parse, + }); + + // TODO should check all embed urls in future + const embedUrl = src.filter((url: string) => url.includes("gomo.to"))[1]; + + // get stream info + progress(50); + + const streamRes = await proxiedFetch(embedUrl); + + const streamResDom = new DOMParser().parseFromString( + streamRes, + "text/html" + ); + if (streamResDom.body.innerText === "File was deleted") + throw new Error("No watchable item found."); + + const script = Array.from(streamResDom.querySelectorAll("script")).find( + (s: HTMLScriptElement) => + s.innerHTML.includes("eval(function(p,a,c,k,e,d") + )?.innerHTML; + if (!script) throw new Error("Could not get packed data"); + + // unpack data + progress(75); + + const unpacked = unpack(script); + const rawSources = /sources:(\[.*?\])/.exec(unpacked); + if (!rawSources) throw new Error("Could not get stream URL"); + + const sources = json5.parse(rawSources[1]); + const streamUrl = sources[0].file; + + console.log(sources); + + const streamType = streamUrl.split(".").at(-1); + if (streamType !== "mp4" && streamType !== "m3u8") + throw new Error("Unsupported stream type"); + + return { + embeds: [], + stream: { + quality: streamType, + streamUrl: streamUrl, + type: streamType, + }, + }; + }, +}); diff --git a/src/components/video/DecoratedVideoPlayer.tsx b/src/components/video/DecoratedVideoPlayer.tsx index 41d2d224..702496d8 100644 --- a/src/components/video/DecoratedVideoPlayer.tsx +++ b/src/components/video/DecoratedVideoPlayer.tsx @@ -3,6 +3,7 @@ import { useCallback, useRef, useState } from "react"; import { CSSTransition } from "react-transition-group"; import { AirplayControl } from "./controls/AirplayControl"; import { BackdropControl } from "./controls/BackdropControl"; +import { ChromeCastControl } from "./controls/ChromeCastControl"; import { FullscreenControl } from "./controls/FullscreenControl"; import { LoadingControl } from "./controls/LoadingControl"; import { MiddlePauseControl } from "./controls/MiddlePauseControl"; @@ -93,6 +94,7 @@ export function DecoratedVideoPlayer(
+
diff --git a/src/components/video/controls/ChromeCastControl.tsx b/src/components/video/controls/ChromeCastControl.tsx new file mode 100644 index 00000000..27cd6dc0 --- /dev/null +++ b/src/components/video/controls/ChromeCastControl.tsx @@ -0,0 +1,15 @@ +declare global { + // eslint-disable-next-line @typescript-eslint/no-namespace + namespace JSX { + interface IntrinsicElements { + "google-cast-launcher": React.DetailedHTMLProps< + React.HTMLAttributes, + HTMLElement + >; + } + } +} + +export function ChromeCastControl() { + return ; +}