diff --git a/.eslintrc.js b/.eslintrc.js index 8e91e06d..6f5cb2dc 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,5 +1,5 @@ const a11yOff = Object.keys(require('eslint-plugin-jsx-a11y').rules) - .reduce((acc, rule) => { acc[`jsx-a11y/${rule}`] = 'off'; return acc }, {}) + .reduce((acc, rule) => { acc[`jsx-a11y/${rule}`] = 'off'; return acc }, {}) module.exports = { extends: [ @@ -37,6 +37,10 @@ module.exports = { "@typescript-eslint/no-shadow": ["error"], "no-restricted-syntax": "off", "react/jsx-props-no-spreading": "off", + "consistent-return": "off", + "no-continue": "off", + "no-eval": "off", + "no-await-in-loop": "off", "react/jsx-filename-extension": [ "error", { extensions: [".js", ".tsx", ".jsx"] }, diff --git a/src/providers/list/xemovie/index.ts b/src/providers/list/xemovie/index.ts new file mode 100644 index 00000000..7fe16ab2 --- /dev/null +++ b/src/providers/list/xemovie/index.ts @@ -0,0 +1,103 @@ +import { + MWMediaProvider, + MWMediaType, + MWPortableMedia, + MWMediaStream, + MWQuery, + MWProviderMediaResult, + MWMediaCaption +} from "providers/types"; + +import { CORS_PROXY_URL } from "mw_constants"; + +export const xemovieScraper: MWMediaProvider = { + id: "xemovie", + enabled: true, + type: [MWMediaType.MOVIE], + displayName: "xemovie", + + async getMediaFromPortable(media: MWPortableMedia): Promise { + const res = await fetch( + `${CORS_PROXY_URL}https://xemovie.co/movies/${media.mediaId}/watch`, + ).then(d => d.text()); + + const DOM = new DOMParser().parseFromString(res, "text/html"); + + const title = DOM.querySelector(".text-primary.text-lg.font-extrabold")?.textContent || ""; + const year = DOM.querySelector("div.justify-between:nth-child(3) > div:nth-child(2)")?.textContent || ""; + + return { + ...media, + title, + year, + } as MWProviderMediaResult; + }, + + async searchForMedia(query: MWQuery): Promise { + const term = query.searchQuery.toLowerCase(); + + const searchUrl = `${CORS_PROXY_URL}https://xemovie.co/search?q=${encodeURIComponent(term)}`; + const searchRes = await fetch(searchUrl).then((d) => d.text()); + + const parser = new DOMParser(); + const doc = parser.parseFromString(searchRes, "text/html"); + + const movieContainer = doc.querySelectorAll(".py-10")[0].querySelector(".grid"); + if (!movieContainer) return []; + const movieNodes = Array.from(movieContainer.querySelectorAll("a")).filter(link => !link.className); + + const results: MWProviderMediaResult[] = movieNodes.map((node) => { + const parent = node.parentElement; + if (!parent) return; + + const aElement = parent.querySelector("a"); + if (!aElement) return; + + return { + title: parent.querySelector("div > div > a > h6")?.textContent, + year: parent.querySelector("div.float-right")?.textContent, + mediaId: aElement.href.split('/').pop() || "", + } + }).filter((d): d is MWProviderMediaResult => !!d); + + return results; + }, + + async getStream(media: MWPortableMedia): Promise { + if (media.mediaType !== MWMediaType.MOVIE) throw new Error("Incorrect type") + + const url = `${CORS_PROXY_URL}https://xemovie.co/movies/${media.mediaId}/watch`; + + let streamUrl = ""; + const subtitles: MWMediaCaption[] = []; + + const res = await fetch(url).then(d => d.text()); + const scripts = Array.from(new DOMParser().parseFromString(res, "text/html").querySelectorAll("script")); + + for (const script of scripts) { + if (!script.textContent) continue; + + if (script.textContent.match(/https:\/\/[a-z][0-9]\.xemovie\.com/)) { + const data = JSON.parse(JSON.stringify(eval(`(${script.textContent.replace("const data = ", "").split("};")[0]}})`))); + streamUrl = data.playlist[0].file; + + for (const [index, subtitleTrack] of data.playlist[0].tracks.entries()) { + const subtitleBlob = URL.createObjectURL( + await fetch(`${CORS_PROXY_URL}${subtitleTrack.file}`).then((captionRes) => captionRes.blob()) + ); // do this so no need for CORS errors + + subtitles.push({ + id: index, + url: subtitleBlob, + label: subtitleTrack.label + }) + } + } + } + + const streamType = streamUrl.split('.').at(-1); + if (streamType !== "mp4" && streamType !== "m3u8") throw new Error("Unsupported stream type"); + + return { url: streamUrl, type: streamType, captions: subtitles } as MWMediaStream; + } +}; diff --git a/src/providers/methods/providers.ts b/src/providers/methods/providers.ts index 720b42c2..142f068b 100644 --- a/src/providers/methods/providers.ts +++ b/src/providers/methods/providers.ts @@ -2,11 +2,13 @@ import { theFlixScraper } from "providers/list/theflix"; import { gDrivePlayerScraper } from "providers/list/gdriveplayer"; import { MWWrappedMediaProvider, WrapProvider } from "providers/wrapper"; import { gomostreamScraper } from "providers/list/gomostream"; +import { xemovieScraper } from "providers/list/xemovie"; export const mediaProvidersUnchecked: MWWrappedMediaProvider[] = [ WrapProvider(theFlixScraper), WrapProvider(gDrivePlayerScraper), WrapProvider(gomostreamScraper), + WrapProvider(xemovieScraper), ]; export const mediaProviders: MWWrappedMediaProvider[] =