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 ;
+}