diff --git a/src/backend/index.ts b/src/backend/index.ts index 812a0558..eb0ad897 100644 --- a/src/backend/index.ts +++ b/src/backend/index.ts @@ -8,6 +8,7 @@ import "./providers/netfilm"; import "./providers/m4ufree"; import "./providers/hdwatched"; import "./providers/2embed"; +import "./providers/sflix"; // embeds import "./embeds/streamm4u"; diff --git a/src/backend/providers/sflix.ts b/src/backend/providers/sflix.ts new file mode 100644 index 00000000..4121046b --- /dev/null +++ b/src/backend/providers/sflix.ts @@ -0,0 +1,99 @@ +import { proxiedFetch } from "../helpers/fetch"; +import { registerProvider } from "../helpers/register"; +import { MWStreamQuality, MWStreamType } from "../helpers/streams"; +import { MWMediaType } from "../metadata/types"; + +const sflixBase = "https://sflix.video"; + +registerProvider({ + id: "sflix", + displayName: "Sflix", + rank: 50, + type: [MWMediaType.MOVIE, MWMediaType.SERIES], + async scrape({ media, episode, progress }) { + let searchQuery = `${media.meta.title} `; + + if (media.meta.type === MWMediaType.MOVIE) + searchQuery += media.meta.year ?? ""; + + if (media.meta.type === MWMediaType.SERIES) + searchQuery += `S${String(media.meta.seasonData.number).padStart( + 2, + "0" + )}`; + + const search = await proxiedFetch( + `/?s=${encodeURIComponent(searchQuery)}`, + { + baseURL: sflixBase, + } + ); + const searchPage = new DOMParser().parseFromString(search, "text/html"); + + const moviePageUrl = searchPage + .querySelector(".movies-list .ml-item:first-child a") + ?.getAttribute("href"); + if (!moviePageUrl) throw new Error("Movie does not exist"); + + progress(25); + + const movie = await proxiedFetch(moviePageUrl); + const moviePage = new DOMParser().parseFromString(movie, "text/html"); + + progress(45); + + let outerEmbedSrc = null; + if (media.meta.type === MWMediaType.MOVIE) { + outerEmbedSrc = moviePage + .querySelector("iframe") + ?.getAttribute("data-lazy-src"); + } else if (media.meta.type === MWMediaType.SERIES) { + const series = Array.from(moviePage.querySelectorAll(".desc p a")).map( + (a) => ({ + title: a.getAttribute("title"), + link: a.getAttribute("href"), + }) + ); + + const episodeNumber = media.meta.seasonData.episodes.find( + (e) => e.id === episode + )?.number; + + const targetSeries = series.find((s) => + s.title?.endsWith(String(episodeNumber).padStart(2, "0")) + ); + if (!targetSeries) throw new Error("Episode does not exist"); + + outerEmbedSrc = targetSeries.link; + } + if (!outerEmbedSrc) throw new Error("Outer embed source not found"); + + progress(65); + + const outerEmbed = await proxiedFetch(outerEmbedSrc); + const outerEmbedPage = new DOMParser().parseFromString( + outerEmbed, + "text/html" + ); + + const embedSrc = outerEmbedPage + .querySelector("iframe") + ?.getAttribute("src"); + if (!embedSrc) throw new Error("Embed source not found"); + + const embed = await proxiedFetch(embedSrc); + + const streamUrl = embed.match(/file\s*:\s*"([^"]+\.mp4)"/)?.[1]; + if (!streamUrl) throw new Error("Unable to get stream"); + + return { + embeds: [], + stream: { + streamUrl, + quality: MWStreamQuality.Q1080P, + type: MWStreamType.MP4, + captions: [], + }, + }; + }, +});