From a433018660dd715814c917694c4a4e7c0fadbb1d Mon Sep 17 00:00:00 2001 From: James Hawkins Date: Mon, 2 May 2022 17:30:48 +0100 Subject: [PATCH 1/3] Add xemovie for movies This provider also supports series but it has not yet been implemented --- src/providers/list/xemovie/index.ts | 105 ++++++++++++++++++++++++++++ src/providers/methods/providers.ts | 2 + 2 files changed, 107 insertions(+) create mode 100644 src/providers/list/xemovie/index.ts diff --git a/src/providers/list/xemovie/index.ts b/src/providers/list/xemovie/index.ts new file mode 100644 index 00000000..6690b0e0 --- /dev/null +++ b/src/providers/list/xemovie/index.ts @@ -0,0 +1,105 @@ +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; + + // eslint-disable-next-line consistent-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 mediaUrl = ""; + 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) { + // eslint-disable-next-line no-continue + if (!script.textContent) continue; + + if (script.textContent.match(/https:\/\/[a-z][0-9]\.xemovie\.com/)) { + // eslint-disable-next-line + let data = JSON.parse(JSON.stringify(eval(`(${script.textContent.replace("const data = ", "").split("};")[0]}})`))); + mediaUrl = data.playlist[0].file; + + // eslint-disable-next-line + for (const [index, subtitleTrack] of data.playlist[0].tracks.entries()) { + const subtitleBlob = URL.createObjectURL( + // eslint-disable-next-line no-await-in-loop + 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 + }) + } + } + } + + return { url: mediaUrl, type: "mp4", 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[] = From e400107d215f8ee525361f597fb6435c60fd7cff Mon Sep 17 00:00:00 2001 From: James Hawkins Date: Mon, 2 May 2022 17:32:42 +0100 Subject: [PATCH 2/3] Small cleanup --- src/providers/list/xemovie/index.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/providers/list/xemovie/index.ts b/src/providers/list/xemovie/index.ts index 6690b0e0..24e14bde 100644 --- a/src/providers/list/xemovie/index.ts +++ b/src/providers/list/xemovie/index.ts @@ -69,7 +69,7 @@ export const xemovieScraper: MWMediaProvider = { const url = `${CORS_PROXY_URL}https://xemovie.co/movies/${media.mediaId}/watch`; - let mediaUrl = ""; + let streamUrl = ""; const subtitles: MWMediaCaption[] = []; const res = await fetch(url).then(d => d.text()); @@ -82,9 +82,8 @@ export const xemovieScraper: MWMediaProvider = { if (script.textContent.match(/https:\/\/[a-z][0-9]\.xemovie\.com/)) { // eslint-disable-next-line let data = JSON.parse(JSON.stringify(eval(`(${script.textContent.replace("const data = ", "").split("};")[0]}})`))); - mediaUrl = data.playlist[0].file; + streamUrl = data.playlist[0].file; - // eslint-disable-next-line for (const [index, subtitleTrack] of data.playlist[0].tracks.entries()) { const subtitleBlob = URL.createObjectURL( // eslint-disable-next-line no-await-in-loop @@ -100,6 +99,9 @@ export const xemovieScraper: MWMediaProvider = { } } - return { url: mediaUrl, type: "mp4", captions: subtitles } as MWMediaStream; + 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; } }; From fde90d4e8f88bc51bcd0869cd846110aeea344b0 Mon Sep 17 00:00:00 2001 From: James Hawkins Date: Mon, 2 May 2022 19:12:04 +0100 Subject: [PATCH 3/3] Review comments --- .eslintrc.js | 6 +++++- src/providers/list/xemovie/index.ts | 6 +----- 2 files changed, 6 insertions(+), 6 deletions(-) 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 index 24e14bde..7fe16ab2 100644 --- a/src/providers/list/xemovie/index.ts +++ b/src/providers/list/xemovie/index.ts @@ -53,7 +53,6 @@ export const xemovieScraper: MWMediaProvider = { const aElement = parent.querySelector("a"); if (!aElement) return; - // eslint-disable-next-line consistent-return return { title: parent.querySelector("div > div > a > h6")?.textContent, year: parent.querySelector("div.float-right")?.textContent, @@ -76,17 +75,14 @@ export const xemovieScraper: MWMediaProvider = { const scripts = Array.from(new DOMParser().parseFromString(res, "text/html").querySelectorAll("script")); for (const script of scripts) { - // eslint-disable-next-line no-continue if (!script.textContent) continue; if (script.textContent.match(/https:\/\/[a-z][0-9]\.xemovie\.com/)) { - // eslint-disable-next-line - let data = JSON.parse(JSON.stringify(eval(`(${script.textContent.replace("const data = ", "").split("};")[0]}})`))); + 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( - // eslint-disable-next-line no-await-in-loop await fetch(`${CORS_PROXY_URL}${subtitleTrack.file}`).then((captionRes) => captionRes.blob()) ); // do this so no need for CORS errors