require("dotenv").config(); var torrentStream = require("torrent-stream"); const parseTorrent = require("parse-torrent"); const express = require("express"); const app = express(); const fetch = require("node-fetch"); // var WebTorrent = require("webtorrent"); var torrentStream = require("torrent-stream"); const { XMLParser } = require("fast-xml-parser"); const { checkCached, getDirectDl, checkTorrentFileinPM, pmFolderDetails, addMagnetToPM, pmFolderId, } = require("./helper"); let nbreAdded = 0; let containEandS = (name = "", s, e, abs = false, abs_season, abs_episode) => //SxxExx ./ /~/- //SxExx //SxExx //axb //Sxx - Exx //Sxx.Exx //Season xx Exx //SasEae selon abs //SasEaex selon abs //SasEaexx selon abs //SxxEaexx selon abs //SxxEaexxx selon abs name?.includes(`s${s?.padStart(2, "0")}e${e?.padStart(2, "0")} `) || name?.includes(`s${s?.padStart(2, "0")}e${e?.padStart(2, "0")}.`) || name?.includes(`s${s?.padStart(2, "0")}e${e?.padStart(2, "0")}-`) || name?.includes(`s${s}e${e?.padStart(2, "0")} `) || name?.includes(`s${s}e${e?.padStart(2, "0")}.`) || name?.includes(`s${s}e${e?.padStart(2, "0")}-`) || name?.includes(`${s}x${e}`) || name?.includes(`s${s?.padStart(2, "0")} - e${e?.padStart(2, "0")}`) || name?.includes(`s${s?.padStart(2, "0")}.e${e?.padStart(2, "0")}`) || name?.includes(`s${s}e${e?.padStart(2, "0")} `) || name?.includes(`s${s}e${e?.padStart(2, "0")}.`) || name?.includes(`s${s}e${e?.padStart(2, "0")}-`) || name?.includes(`s${s?.padStart(2, "0")}e${e} `) || name?.includes(`s${s?.padStart(2, "0")}e${e}.`) || name?.includes(`s${s?.padStart(2, "0")}e${e}-`) || name?.includes(`season ${s} e${e}`) || (abs && (name?.includes( `s${abs_season?.padStart(2, "0")}e${abs_episode?.padStart(2, "0")}` ) || name?.includes( `s${s?.padStart(2, "0")}e${abs_episode?.padStart(2, "0")}` ) || name?.includes( `s${s?.padStart(2, "0")}e${abs_episode?.padStart(3, "0")}` ) || name?.includes( `s${abs_season?.padStart(2, "0")}e${abs_episode?.padStart(3, "0")}` ) || name?.includes( `s${abs_season?.padStart(2, "0")}e${abs_episode?.padStart(4, "0")}` ))); let containE_S = (name = "", s, e, abs = false, abs_season, abs_episode) => //Sxx - xx //Sx - xx //Sx - x //Season x - x //Season x - xx name?.includes(`s${s?.padStart(2, "0")} - ${e?.padStart(2, "0")}`) || name?.includes(`s${s} - ${e?.padStart(2, "0")}`) || // name?.includes(`s${s} - ${e}`) || // name?.includes(`season ${s} - ${e}`) || name?.includes(`season ${s} - ${e?.padStart(2, "0")}`) || name?.includes(`season ${s} - ${e?.padStart(2, "0")}`); let containsAbsoluteE = ( name = "", s, e, abs = false, abs_season, abs_episode ) => { //- xx //- xxx //- xxxx //- 0x return ( name?.includes(` ${abs_episode?.padStart(2, "0")} `) || name?.includes(` ${abs_episode?.padStart(3, "0")} `) || name?.includes(` 0${abs_episode} `) || name?.includes(` ${abs_episode?.padStart(4, "0")} `) ); }; let containsAbsoluteE_ = ( name = "", s, e, abs = false, abs_season, abs_episode ) => // xx. // xxx. // xxxx. // 0x. name?.includes(` ${abs_episode?.padStart(2, "0")}.`) || name?.includes(` ${abs_episode?.padStart(3, "0")}.`) || name?.includes(` 0${abs_episode}.`) || name?.includes(` ${abs_episode?.padStart(4, "0")}.`); let hosts = []; const raw_content = require("fs").readFileSync("./servers.txt"); let content = Buffer.isBuffer(raw_content) ? raw_content.toString() : raw_content; hosts = content .split("\n") .map((el) => el.trim()) .map((el) => { if (!el.includes("|")) return null; return { host: el.split("|")[0], apiKey: el.split("|").pop(), }; }); hosts = hosts.filter((el) => !!el); let fetchTorrent = async (query, type = "series") => { let hostdata = hosts[Math.floor(Math.random() * hosts.length)]; if (!hostdata) return []; let url = `${ hostdata.host }/api/v2.0/indexers/abnormal/results/torznab/api?apikey=${hostdata.apiKey}&${ type == "movie" ? "t=movie" : "t=tvsearch" }&${type == "movie" ? "cat=2000" : "cat=5000"}&q=${query}&cache=false`; return await fetch(url, { headers: { accept: "*/*", "accept-language": "en-US,en;q=0.9", "x-requested-with": "XMLHttpRequest", cookie: "Jackett=CfDJ8AG_XUDhxS5AsRKz0FldsDJIHUJANrfynyi54VzmYuhr5Ha5Uaww2hSQytMR8fFWjPvDH2lKCzaQhRYI9RuK613PZxJWz2tgHqg1wUAcPTMfi8b_8rm1Igw1-sZB_MnimHHK7ZSP7HfkWicMDaJ4bFGZwUf0xJOwcgjrwcUcFzzsVSTALt97-ibhc7PUn97v5AICX2_jsd6khO8TZosaPFt0cXNgNofimAkr5l6yMUjShg7R3TpVtJ1KxD8_0_OyBjR1mwtcxofJam2aZeFqVRxluD5hnzdyxOWrMRLSGzMPMKiaPXNCsxWy_yQhZhE66U_bVFadrsEeQqqaWb3LIFA", }, referrerPolicy: "no-referrer", method: "GET", }) .then(async (res) => { try { // return await res.json(); const parser = new XMLParser({ ignoreAttributes: false }); let jObj = parser.parse(await res.text()); let results = "rss" in jObj && "channel" in jObj["rss"] && "item" in jObj["rss"]["channel"] ? jObj["rss"]["channel"]["item"] : []; return results; } catch (error) { console.log({ error }); return []; } }) .then(async (results) => { results = Array.isArray(results) ? results : [results]; console.log({ Initial: results?.length }); if (results.length != 0) { // return []; torrent_results = await Promise.all( results.map((result) => { let torznab_attr = {}; result["torznab:attr"]?.length ? result["torznab:attr"]?.forEach((el) => { torznab_attr[el["@_name"]] = el["@_value"]; }) : false; return new Promise((resolve, reject) => { resolve({ Tracker: "#text" in result["jackettindexer"] ? result["jackettindexer"]["#text"] : "Torrent", Title: result["title"], Seeders: torznab_attr ? torznab_attr["seeders"] : "", Peers: torznab_attr ? torznab_attr["peers"] : "", Link: result["link"], MagnetUri: "@_url" in result["enclosure"] ? result["enclosure"]["@_url"] : null, }); }); }) ); return torrent_results; } else { return []; } }) .catch((err) => { return []; }); }; let fetchTorrent2 = async (query, type = "series") => { let hostdata = hosts[Math.floor(Math.random() * hosts.length)]; if (!hostdata) return []; let url = `${hostdata.host}/api/v2.0/indexers/all/results?apikey=${hostdata.apiKey}&Query=${query}&Tracker%5B%5D=btsow&Tracker%5B%5D=torrentz2nz&Tracker%5B%5D=torrentscsv&Category%5B%5D=2000&Category%5B%5D=5000&Category%5B%5D=8000&cache=false`; const controller = new AbortController(); const TIMEOUT = +process.env.TIMEOUT ?? 5000; const timeoutId = setTimeout(() => controller.abort(), TIMEOUT); try { return await fetch(url, { headers: { accept: "*/*", "accept-language": "en-US,en;q=0.9", "x-requested-with": "XMLHttpRequest", }, referrerPolicy: "no-referrer", method: "GET", signal: controller.signal, }) .then((res) => res.json()) .then(async (results) => { console.log({ Initial: results["Results"]?.length }); if (results["Results"].length != 0) { torrent_results = await Promise.all( results["Results"].map((result) => { return new Promise((resolve, reject) => { resolve({ Tracker: result["Tracker"], Category: result["CategoryDesc"], Title: result["Title"], Seeders: result["Seeders"], Peers: result["Peers"], Link: result["Link"], MagnetUri: result["MagnetUri"], }); }); }) ); clearTimeout(timeoutId); return torrent_results; } else { clearTimeout(timeoutId); return []; } }) .catch((err) => { clearTimeout(timeoutId); return []; }); } catch (error) { clearTimeout(timeoutId); return []; } }; function getMeta(id, type) { var [tt, s, e] = id.split(":"); return fetch(`https://v3-cinemeta.strem.io/meta/${type}/${tt}.json`) .then((res) => res.json()) .then((json) => { return { name: json.meta["name"], year: json.meta["releaseInfo"]?.substring(0, 4) ?? 0, }; }) .catch((err) => fetch(`https://v2.sg.media-imdb.com/suggestion/t/${tt}.json`) .then((res) => res.json()) .then((json) => { return json.d[0]; }) .then(({ l, y }) => ({ name: l, year: y })) ); } async function getImdbFromKitsu(id) { var [kitsu, _id, e] = id.split(":"); return fetch(`https://anime-kitsu.strem.fun/meta/anime/${kitsu}:${_id}.json`) .then((_res) => _res.json()) .then((json) => { return json["meta"]; }) .then((json) => { try { let imdb = json["imdb_id"]; let meta = json["videos"].find((el) => el.id == id); return [ imdb, (meta["imdbSeason"] ?? 1).toString(), (meta["imdbEpisode"] ?? 1).toString(), (meta["season"] ?? 1).toString(), (meta["imdbSeason"] ?? 1).toString() == 1 ? (meta["imdbEpisode"] ?? 1).toString() : (meta["episode"] ?? 1).toString(), meta["imdbEpisode"] != meta["episode"] || meta["imdbSeason"] == 1, ]; } catch (error) { return null; } }) .catch((err) => null); } let isRedirect = async (url) => { try { const controller = new AbortController(); // 5 second timeout: const timeoutId = setTimeout(() => controller.abort(), 6000); const response = await fetch(url, { redirect: "manual", signal: controller.signal, }); clearTimeout(timeoutId); if (response.status === 301 || response.status === 302) { const locationURL = new URL( response.headers.get("location"), response.url ); if (response.headers.get("location").startsWith("http")) { await isRedirect(locationURL); } else { return response.headers.get("location"); } } else if (response.status >= 200 && response.status < 300) { return response.url; } else { return response.url; // return null; } } catch (error) { // console.log({ error }); return null; } }; const getParsedFromMagnetorTorrentFile = async (tor, uri) => { return new Promise(async (resolve, reject) => { //follow redirection cause some http url sent magnet url let realUrl = uri?.startsWith("magnet:?") ? uri : await isRedirect(uri); realUrl = realUrl ?? null; if (realUrl) { let parsedTorrent = null; if (realUrl?.startsWith("magnet:?")) { parsedTorrent = parseTorrent(realUrl); } else if (realUrl?.startsWith("http")) { parsedTorrent = await new Promise((resolve, reject) => { parseTorrent.remote(realUrl, (err, parsed) => { if (!err) { resolve(parsed); } else { // console.log({ err }); resolve(null); } }); }); } else { console.log({ WhatTF: realUrl }); resolve(null); } if (!parsedTorrent?.infoHash) resolve(null); if (!parsedTorrent?.files) { console.log("no files"); if (!parsedTorrent?.files && realUrl.startsWith("magnet:?")) { try { let res = await new Promise((resolve, reject) => { var engine = torrentStream(realUrl, { connections: 40, }); engine.on("ready", function () { resolve(engine.files); }); setTimeout(() => { resolve([]); }, 15000); // }); if (res && res.length > 0) { console.log("got no files but parsed"); } parsedTorrent.files = [...res]; } catch (error) { console.log("Done with that error"); } } } else { console.log("got files directly"); } if (!(parsedTorrent?.files && parsedTorrent?.files.length)) { resolve(null); } resolve({ parsedTor: parsedTorrent, ...tor }); } else { resolve(null); } }); }; const toStream = async ( tor, type, s, e, abs_season, abs_episode, abs, max_element ) => { let parsed = tor?.parsedTor; if (!parsed) return null; const infoHash = parsed.infoHash.toLowerCase(); let title = tor.extraTag || parsed.name; let index = -1; if (!parsed.files) { return null; } if (media == "series") { index = (parsed?.files ?? []).findIndex((element, index) => { if (!element["name"]) { return false; } let name = element["name"].toLowerCase(); if (name.includes("live") || name.includes("ova")) { return false; } return ( isVideo(element["name"] ?? "") && getFittedFile(name, s, e, abs, abs_season, abs_episode) ); }); if (index == -1) { return null; } title = !!title ? title + "\n" + parsed.files[index]["name"] : null; } else if (media == "movie") { index = (parsed?.files ?? []).findIndex((element, index) => { return isVideo(element["name"] ?? ""); }); // if (index == -1) { return null; } } // ======================== PM ================================ console.log("Trynna some PM"); let folderId = null; let details = []; let isCached = await checkCached(infoHash); console.log({ isCached }); if (isCached) { let cache = await getDirectDl(infoHash); if (cache && cache.length) { if (media == "series") { index = (cache ?? []).findIndex((element, _) => { element["name"] = element["path"].toLowerCase()?.split("/")?.pop() ?? (isCached ?? "").toLowerCase(); if (!element["name"]) return false; if ( // element["name"].match(/\W+movie\W+/) || element["name"].includes("live") || element["name"].includes("ova") ) { return false; } return ( isVideo(element["name"] ?? "") && getFittedFile(element["name"], s, e, abs, abs_season, abs_episode) ); }); if (index == -1) { return null; } } else if (media == "movie") { index = (cache ?? []).findIndex((element, index) => { element["name"] = element["path"].toLowerCase() ?? (isCached ?? "").toLowerCase(); return isVideo(element["name"] ?? ""); }); if (index == -1) { return null; } } details = [cache[index]]; console.log(`Cached index: ${index}`); } } else { let data = null; data = await checkTorrentFileinPM(parsed.name); if (data) { if (data["type"] == "folder") { folderId = data["id"]; if (folderId) { details = await pmFolderDetails(folderId); console.log({ status: details.length ? "found" : "nothing" }); } } else if (data["type"] == "file") { details = [data]; } } else { console.log("should add to pm"); // let addRes = null; if (nbreAdded <= 5) { let addRes = await addMagnetToPM(parseTorrent.toMagnetURI(parsed)); console.log({ added: !!addRes }); !!addRes ? nbreAdded++ : null; folderId = !!addRes ? await pmFolderId(addRes ?? parsed["name"]) : null; if (folderId) { details = await pmFolderDetails(folderId); console.log({ status: details.length ? "found2" : "nothing2" }); } } } } title = title ?? parsed.files[index]["name"]; title += "\n" + getQuality(title); const subtitle = "S:" + tor["Seeders"] + " | P:" + tor["Peers"]; title += ` | ${ index == -1 || parsed.files == [] ? `${getSize(0)}` : `${getSize(parsed.files[index]["length"] ?? 0)}` } | ${subtitle}`; if ( details.length > 0 && details[details.length > 1 ? index : 0]["stream_link"] ) { return { name: `PM-${tor["Tracker"]}`, url: details[details.length > 1 ? index : 0]["link"] ?? details[details.length > 1 ? index : 0]["stream_link"], title: title ?? details[details.length > 1 ? index : 0]["name"], behaviorHints: { bingeGroup: `Jackett-Addon|${infoHash}`, }, }; } if (process.env.PUBLIC == "1") return { name: `${tor["Tracker"]}`, type: type, infoHash: infoHash, fileIdx: index == -1 ? 0 : index, sources: (parsed.announce || []) .map((x) => { return "tracker:" + x; }) .concat(["dht:" + infoHash]), title: title + getFlagFromName(title), behaviorHints: { bingeGroup: `Jackett-Addon|${infoHash}`, notWebReady: true, }, }; }; const qualities = { "4k": "🌟4k", fhd: "πŸŽ₯FHD", hd: "πŸ“ΊHD", sd: "πŸ“±SD", unknown: "none", }; const vf = ["vf", "vff", "french", "frn"]; const multi = ["multi"]; const vostfr = ["vostfr", "english", "eng"]; let isVideo = (str) => { if (!str) return false; let name = `${str}`.toLowerCase(); return ( name?.toLowerCase()?.includes(`.mkv`) || name?.toLowerCase()?.includes(`.mp4`) || name?.toLowerCase()?.includes(`.avi`) || name?.toLowerCase()?.includes(`.flv`) ); }; function getSize(size) { var gb = 1024 * 1024 * 1024; var mb = 1024 * 1024; return ( "πŸ’Ύ " + (size / gb > 1 ? `${(size / gb).toFixed(2)} GB` : `${(size / mb).toFixed(2)} MB`) ); } function getQuality(name) { if (!name) { return name; } name = name.toLowerCase(); if (["2160", "4k", "uhd"].filter((x) => name.includes(x)).length > 0) return " " + qualities["4k"]; if (["1080", "fhd"].filter((x) => name.includes(x)).length > 0) return " " + qualities.fhd; if (["720", "hd"].filter((x) => name.includes(x)).length > 0) return " " + qualities.hd; if (["480p", "380p", "sd"].filter((x) => name.includes(x)).length > 0) return " " + qualities.sd; return ""; } const isSomeContent = (file_name = "", langKeywordsArray = []) => { file_name = file_name.toLowerCase(); return langKeywordsArray.some((word) => file_name.includes(word)); }; const isVfContent = (file_name) => isSomeContent(file_name, vf); const isMultiContent = (file_name) => isSomeContent(file_name, multi); const isVostfrContent = (file_name) => isSomeContent(file_name, vostfr); const bringFrenchVideoToTheTopOfList = (streams = []) => { streams.sort((a, b) => { let a_lower = a.title.toLowerCase(); let b_lower = b.title.toLowerCase(); return isVfContent(a_lower) || isVostfrContent(a_lower) || isMultiContent(a_lower) ? -1 : isVfContent(b_lower) || isVostfrContent(b_lower) || isMultiContent(a_lower) ? 1 : 0; }); return streams; }; const filterBasedOnQuality = (streams = [], quality = "") => { if (!quality) return []; if (!Object.values(qualities).includes(quality)) return []; if (quality == qualities.unknown) { streams = streams.filter((el) => { const l = `${el?.title}`; return ( !l.includes(qualities["4k"]) && !l.includes(qualities.fhd) && !l.includes(qualities.hd) && !l.includes(qualities.sd) ); }); } else { streams = streams.filter((el) => el.title.includes(quality)); } return bringFrenchVideoToTheTopOfList(streams); }; const getFlagFromName = (file_name) => { switch (true) { case isVfContent(file_name): return "| πŸ‡«πŸ‡·"; case isMultiContent(file_name): return "| 🌐"; case isVostfrContent(file_name): return "| πŸ‡¬πŸ‡§"; default: return "| πŸ΄σ °σ ΄σ €°σ €Άσ Ώ"; } }; let cleanName = (name = "") => { return name.replace(/[^a-zA-Z0-9 ]/g, "").replace(/\s{2,}/g, " "); }; let simplifiedName = (name = "") => { name = name.includes("-") ? name.split("-")[0] : name; name = name.includes(":") ? name.split(":")[0] : name; name = name.trim(); console.log(cleanName(name)); return cleanName(name); }; const getFittedFile = (name, s, e, abs = false, abs_season, abs_episode) => { return ( containEandS(name, s, e, abs, abs_season, abs_episode) || containE_S(name, s, e, abs, abs_season, abs_episode) || (s == 1 && (containsAbsoluteE(name, s, e, true, s, e) || containsAbsoluteE_(name, s, e, true, s, e))) || (((abs && containsAbsoluteE(name, s, e, abs, abs_season, abs_episode)) || (abs && containsAbsoluteE_(name, s, e, abs, abs_season, abs_episode))) && !( name?.includes("s0") || name?.includes(`s${abs_season}`) || name?.includes("e0") || name?.includes(`e${abs_episode}`) || name?.includes("season") )) ); }; module.exports = { containEandS, containE_S, containsAbsoluteE, containsAbsoluteE_, fetchTorrent, fetchTorrent2, getMeta, getImdbFromKitsu, isRedirect, getParsedFromMagnetorTorrentFile, toStream, isVideo, getSize, getQuality, filterBasedOnQuality, qualities, bringFrenchVideoToTheTopOfList, getFlagFromName, cleanName, simplifiedName, getFittedFile, };