diff --git a/README.md b/README.md index 4451be6..47f4af5 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,3 @@ -# [NOT WORKING - NEEDS WORK] - ### USAGE - base url: https://your-vercel-domain diff --git a/index.js b/index.js index 5f0a742..e9be1c8 100644 --- a/index.js +++ b/index.js @@ -1,6 +1,7 @@ import express from "express"; -import { getVidsrcSourcesId, getVidsrctoStreams } from "./src/vidsrcto.js"; import { port } from "./src/constants.js"; +import { getmovie, getserie } from "./src/vidsrcto.js"; + const app = express() @@ -20,18 +21,29 @@ app.get('/vidsrc/:tmdbId', async (req, res) => { const season = req.query.s; const episode = req.query.e; - const sourcesId = await getVidsrcSourcesId(id, season, episode); - const response = await getVidsrctoStreams(sourcesId); - const responseData = response.data; - if (responseData.file === null) { - res.status(404).send({ - status: 404, - return: "Oops reached rate limit of this api" - }) + if (season && episode) { + const response = await getserie(id, season, episode); + if (!response) { + res.status(404).send({ + status: 404, + return: "Oops reached rate limit of this api" + }) + } else { + res.status(200).json([response]); + } } else { + const response = await getmovie(id); - res.status(200).json([response]); + if (!response) { + res.status(404).send({ + status: 404, + return: "Oops reached rate limit of this api" + }) + } else { + res.status(200).json([response]); + } } + }); app.listen(port, () => { diff --git a/package-lock.json b/package-lock.json index b3f22ca..e740e8b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ "express": "^4.18.2", "html-entities": "^2.4.0", "jsdom": "^24.0.0", + "node-fetch": "^3.3.2", "random-useragent": "^0.5.0" } }, @@ -235,6 +236,15 @@ "node": ">=18" } }, + "node_modules/data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, "node_modules/data-urls": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz", @@ -427,6 +437,29 @@ "node": ">= 0.10.0" } }, + "node_modules/fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20 || >= 14.13" + } + }, "node_modules/finalhandler": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", @@ -457,6 +490,18 @@ "node": ">= 6" } }, + "node_modules/formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "license": "MIT", + "dependencies": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -812,6 +857,43 @@ "node": ">= 0.6" } }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "engines": { + "node": ">=10.5.0" + } + }, + "node_modules/node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "license": "MIT", + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, "node_modules/nth-check": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", @@ -1197,6 +1279,15 @@ "node": ">=18" } }, + "node_modules/web-streams-polyfill": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, "node_modules/webidl-conversions": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", diff --git a/package.json b/package.json index ed0acc8..65e96cd 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "express": "^4.18.2", "html-entities": "^2.4.0", "jsdom": "^24.0.0", + "node-fetch": "^3.3.2", "random-useragent": "^0.5.0" } } diff --git a/src/getKeys.js b/src/getKeys.js new file mode 100644 index 0000000..2519fb3 --- /dev/null +++ b/src/getKeys.js @@ -0,0 +1,27 @@ +import fetch from "node-fetch"; + +export const getKeys = async () => { + const url = "https://raw.githubusercontent.com/Ciarands/vidsrc-keys/main/keys.json"; + + try { + const response = await fetch(url); + if (response.status !== 200) { + console.error("Failed to fetch decryption keys:", response.statusText); + return null; + } + + const keys = await response.json(); + + if (keys && keys.encrypt && keys.decrypt) { + const allKeys = [...keys.encrypt, ...keys.decrypt]; + console.log("All keys:", allKeys); + return allKeys; + } else { + console.error("Keys object is empty or malformed"); + return null; + } + } catch (error) { + console.error("Error:", error.message); + return null; + } +}; diff --git a/src/vidsrcto.js b/src/vidsrcto.js index ef6a262..3ba1025 100644 --- a/src/vidsrcto.js +++ b/src/vidsrcto.js @@ -1,113 +1,132 @@ -import { load } from "cheerio"; -import { decode } from "html-entities"; -import randomUserAgent from 'random-useragent'; - -import { vidsrcBase, vidplayBase } from "./constants.js"; - -export async function getVidsrctoStreams(sourcesId) { - - let vidplay; - - const sources = await getVidsrcSources(sourcesId); - - console.log(sources); - - try { - vidplay = sources.result.find((v) => v.title.toLowerCase() === 'f2cloud'); - } catch (error) { - return { data: sources }; - } - - - if (!vidplay) { - const data = { - file: sources - }; - return { data: data }; - } - - const vidplayLink = await getVidsrcSourceDetails(vidplay.id); - - const key = await encodeId(vidplayLink.split('/e/')[1].split('?')[0]); - const dataFutoken = await getFutoken(key, vidplayLink); - - const subtitles = await getSubtitles(vidplayLink); - - const response = await (await fetch(`https://vidplay.online/mediainfo/${dataFutoken}?${vidplayLink.split('?')[1]}&autostart=true`, { - headers: { - "Origin": generateRandomIp(), - "Referer": vidplayLink, - "Host": "vidplay.online", - "User-Agent": randomUserAgent.getRandom() - } - })).json(); - - const result = response.result; - const source = result.sources?.[0]?.file; - - if (!source) { - const data = { - file: null, - sub: subtitles - }; - return { data: data }; - } else { - const data = { - file: source, - sub: subtitles - }; - return { data: data }; - } - +import fetch from 'node-fetch'; +import { getKeys } from './getKeys.js'; +const keys = await getKeys(); +function rc4(key, inp) { + let e = []; + e[4] = []; + e[3] = 0; + let i = 0; + e[8] = ""; + for (i = 0; i < 256; i++) { + e[4][i] = i; + } + for (i = 0; i < 256; i++) { + e[3] = (e[3] + e[4][i] + key.charCodeAt(i % key.length)) % 256; + e[2] = e[4][i]; + e[4][i] = e[4][e[3]]; + e[4][e[3]] = e[2]; + } + i = 0; + e[3] = 0; + let j = 0; + for (j = 0; j < inp.length; j++) { + i = (i + 1) % 256; + e[3] = (e[3] + e[4][i]) % 256; + e[2] = e[4][i]; + e[4][i] = e[4][e[3]]; + e[4][e[3]] = e[2]; + e[8] += String.fromCharCode(inp.charCodeAt(j) ^ e[4][(e[4][i] + e[4][e[3]]) % 256]); + } + return e[8]; } -export async function getVidsrcSourcesId(tmdbId, seasonNumber, episodeNumber) { - const type = seasonNumber && episodeNumber ? "tv" : "movie"; - const url = `${vidsrcBase}/embed/${type}/${tmdbId}${type === "tv" ? `/${seasonNumber}/${episodeNumber}` : ''}` - try { - const data = await (await fetch(url)).text(); - const doc = load(data); - const sourcesCode = doc('a[data-id]').attr('data-id'); - - return sourcesCode; - } catch (err) { - return; - } +function enc(inp) { + inp = encodeURIComponent(inp); + const e = rc4(keys[0], inp); + const out = btoa(e).replace(/\//g, "_").replace(/\+/g, '-'); + return out; } -async function getVidsrcSources(sourceId) { - try { - const response = await fetch(`${vidsrcBase}/ajax/embed/episode/${sourceId}/sources`); - - if (!response.ok) { - return 'No Sources Found'; - } - - const contentType = response.headers.get("content-type"); - if (contentType && contentType.includes("application/json")) { - const data = await response.json(); - return data; - } else { - const text = await response.text(); - console.error("Unexpected content type or response:", text); - return 'Unexpected content type or response'; - } - - } catch (error) { - console.error("Error fetching or parsing JSON:", error); - return null; // or handle the error as needed - } +function embed_enc(inp) { + inp = encodeURIComponent(inp); + const e = rc4(keys[1], inp); + const out = btoa(e).replace(/\//g, "_").replace(/\+/g, '-'); + return out; } -async function getVidsrcSourceDetails(sourceId) { - const data = await (await fetch(`${vidsrcBase}/ajax/embed/source/${sourceId}`)).json(); +function h_enc(inp) { + inp = encodeURIComponent(inp); + const e = rc4(keys[2], inp); + const out = btoa(e).replace(/\//g, "_").replace(/\+/g, '-'); + return out; +} - const encryptedUrl = data.result.url; - const decryptedUrl = decryptSourceUrl(encryptedUrl); - return decodeURIComponent(decryptedUrl); +function dec(inp) { + const i = atob((inp).replace(/_/g, "/").replace(/-/g, "+")); + let e = rc4(keys[3], i); + e = decodeURIComponent(e); + return e; +} + +function embed_dec(inp) { + const i = atob((inp).replace(/_/g, "/").replace(/-/g, "+")); + let e = rc4(keys[4], i); + e = decodeURIComponent(e); + return e; +} + +async function episode(data_id) { + let url = `https://vidsrc.to/ajax/embed/episode/${data_id}/sources?token=${encodeURIComponent(enc(data_id))}`; + let resp = await (await fetch(url)).json(); + + console.log(resp); + + let f2cloud_id = resp['result'][0]['id']; + url = `https://vidsrc.to/ajax/embed/source/${f2cloud_id}?token=${encodeURIComponent(enc(f2cloud_id))}`; + resp = await (await fetch(url)).json(); + + console.log(resp); + + let f2cloud_url = resp['result']['url']; + let f2cloud_url_dec = dec(f2cloud_url); + + console.log(f2cloud_url_dec); + + const subtitles = await getSubtitles(f2cloud_url_dec); + + url = new URL(f2cloud_url_dec); + let embed_id = url.pathname.split("/")[2]; + let h = h_enc(embed_id); + let mediainfo_url = `https://vid2v11.site/mediainfo/${embed_enc(embed_id)}${url.search}&ads=0&h=${encodeURIComponent(h)}`; + resp = await (await fetch(mediainfo_url)).json(); + + console.log(resp); + + let playlist = embed_dec(resp['result']); + if (typeof playlist === 'string') { + playlist = JSON.parse(playlist); + } + + const source = playlist.sources?.[0]?.file; + + if (!source) { + const data = { + file: null, + sub: subtitles + }; + return { data: data }; + } else { + const data = { + file: source, + sub: subtitles + }; + return { data: data }; + } +} + +export async function getmovie(id) { + let resp = await (await fetch(`https://vidsrc.to/embed/movie/${id}`)).text(); + let data_id = (/data-id="(.*?)"/g).exec(resp)[1]; + return episode(data_id); +} + +export async function getserie(id, s, e) { + let resp = await (await fetch(`https://vidsrc.to/embed/tv/${id}/${s}/${e}`)).text(); + let data_id = (/data-id="(.*?)"/g).exec(resp)[1]; + return episode(data_id); } async function getSubtitles(vidplayLink) { @@ -122,91 +141,4 @@ async function getSubtitles(vidplayLink) { return subtitles; } -} - -function adecode(str) { - const keyBytes = Buffer.from('WXrUARXb1aDLaZjI', 'utf-8'); - let j = 0; - const s = Buffer.from(Array(256).fill().map((_, i) => i)); - - for (let i = 0; i < 256; i++) { - j = (j + s[i] + keyBytes[i % keyBytes.length]) & 0xff; - [s[i], s[j]] = [s[j], s[i]]; - } - - const decoded = Buffer.alloc(str.length); - let i = 0; - let k = 0; - - for (let index = 0; index < str.length; index++) { - i = (i + 1) & 0xff; - k = (k + s[i]) & 0xff; - [s[i], s[k]] = [s[k], s[i]]; - const t = (s[i] + s[k]) & 0xff; - decoded[index] = str[index] ^ s[t]; - } - - return decoded; -} - -function decodeBase64UrlSafe(s) { - const standardizedInput = s.replace('_', '/').replace('-', '+'); - const binaryData = Buffer.from(standardizedInput, 'base64'); - - return Buffer.from(binaryData); -} - -function decryptSourceUrl(sourceUrl) { - const encoded = decodeBase64UrlSafe(sourceUrl); - const decoded = adecode(encoded); - - const decodedText = decoded.toString('utf-8'); - return decode(decodedText); -} - -async function encodeId(v_id) { - const resp = await (await fetch('https://raw.githubusercontent.com/Inside4ndroid/vidkey-js/main/keys.json')).json(); - const [key1, key2] = resp; - const decoded_id = keyPermutation(key1, v_id).toString('latin1'); - const encoded_result = keyPermutation(key2, decoded_id).toString('latin1'); - const encoded_base64 = btoa(encoded_result); - return encoded_base64.replace('/', '_'); -} - -async function getFutoken(key, url) { - const response = await (await fetch(`${vidplayBase}/futoken`, { headers: { "Referer": `${url}/` } })).text(); - const fuKey = response.match(/var\s+k\s*=\s*'([^']+)'/)[1]; - const fuToken = `${fuKey},${Array.from({ length: key.length }, (_, i) => (fuKey.charCodeAt(i % fuKey.length) + key.charCodeAt(i)).toString()).join(',')}`; - return fuToken; -} - -const generateRandomIp = () => { - return (Math.floor(Math.random() * 255) + 1) + "." + (Math.floor(Math.random() * 255)) + "." + (Math.floor(Math.random() * 255)) + "." + (Math.floor(Math.random() * 255)); -} - -function keyPermutation(key, data) { - var state = Array.from(Array(256).keys()); - var index_1 = 0; - for (var i = 0; i < 256; i++) { - index_1 = ((index_1 + state[i]) + key.charCodeAt(i % key.length)) % 256; - var temp = state[i]; - state[i] = state[index_1]; - state[index_1] = temp; - } - var index_1 = 0; - var index_2 = 0; - var final_key = ''; - for (var char = 0; char < data.length; char++) { - index_1 = (index_1 + 1) % 256; - index_2 = (index_2 + state[index_1]) % 256; - var temp = state[index_1]; - state[index_1] = state[index_2]; - state[index_2] = temp; - if (typeof data[char] === 'string') { - final_key += String.fromCharCode(data[char].charCodeAt(0) ^ state[(state[index_1] + state[index_2]) % 256]); - } else if (typeof data[char] === 'number') { - final_key += String.fromCharCode(data[char] ^ state[(state[index_1] + state[index_2]) % 256]); - } - } - return final_key; -} +} \ No newline at end of file