15 changed files with 3075 additions and 3019 deletions
@ -0,0 +1,6 @@
@@ -0,0 +1,6 @@
|
||||
.git |
||||
node_modules |
||||
build |
||||
.env.local |
||||
.github |
||||
.vscode |
@ -0,0 +1,13 @@
@@ -0,0 +1,13 @@
|
||||
FROM node:16.15-alpine as build |
||||
WORKDIR /app |
||||
ENV PATH /app/node_modules/.bin:$PATH |
||||
COPY package*.json ./ |
||||
RUN yarn install |
||||
COPY . ./ |
||||
RUN yarn build |
||||
|
||||
# production environment |
||||
FROM nginx:stable-alpine |
||||
COPY --from=build /app/build /usr/share/nginx/html |
||||
EXPOSE 80 |
||||
CMD ["nginx", "-g", "daemon off;"] |
@ -0,0 +1,103 @@
@@ -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<MWProviderMediaResult> { |
||||
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<MWProviderMediaResult[]> { |
||||
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<MWMediaStream> { |
||||
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; |
||||
} |
||||
}; |
Loading…
Reference in new issue