1
0

rewrite proxy

This commit is contained in:
Snipcola 2024-02-11 06:22:55 +00:00
parent b86c61be28
commit cabd7a42a9
18 changed files with 300 additions and 277 deletions

@ -1,2 +1,5 @@
ORIGIN=https://fh.snipcola.com
PORT=1000
# ORIGIN=https://fh.snipcola.com
PORT=1000
CACHE_TIMEOUT=300
TIMEOUT=4

@ -3,16 +3,20 @@
"author": "Snipcola",
"license": "MIT",
"private": true,
"main": "./proxy.js",
"main": "./src/index.js",
"type": "module",
"scripts": {
"start": "node .",
"dev": "nodemon ."
},
"dependencies": {
"nodemon": "^3.0.3",
"cors-anywhere": "^0.4.4",
"dotenv": "^16.4.1",
"fastify": "^3.29.0",
"@fastify/cors": "^7.0.0",
"fastify-response-caching": "^0.0.4",
"node-fetch": "^3.3.2"
},
"devDependencies": {
"nodemon": "^3.0.3"
}
}
}

@ -1,149 +0,0 @@
// Config
const config = {
hosts: [
"vidsrc.to",
"vidsrc.me",
"flixon.click",
"2embed.me",
"databasegdriveplayer.xyz"
],
blacklist: {
status: [
404, // Not Found
500 // Internal Server Error
],
text: [
"not found", // Generic
"no sources", // Generic
"onionplay streaming mirrors", // flixon.click
"no movie found", // 2embed.me
"no tv show found", // 2embed.me
`,"file":"","kind"` // databasegdriveplayer.xyz
]
},
cacheMaxAge: 21600,
timeout: 5000
};
// Imports
import dotenv from "dotenv";
import fetch from "node-fetch";
import proxy from "cors-anywhere";
import { fileURLToPath } from "url";
import path, { dirname } from "path";
import { URL } from "url";
// DotEnv
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
dotenv.config({ path: path.join(__dirname, ".env") });
// Variables
const allowedOrigins = process.env.ORIGIN?.split(",") || ["*"];
const allowedHosts = config.hosts || [];
const host = process.env.HOST || "0.0.0.0";
const port = process.env.PORT || 5000;
// Functions
function promiseWithTimeout(promise, timeout) {
return Promise.race([promise, new Promise((res) => setTimeout(res, timeout))]);
}
async function handleRequest(req, res, _url) {
// Parse URL
let url;
try {
const tempURL = new URL(`http://localhost${req?.url}`);
url = new URL(tempURL.searchParams.get("url"));
}
catch {
url = null;
}
// Variables
const hostname = url?.hostname;
const origin = req?.headers?.origin;
const allowedHost = allowedHosts.includes(hostname);
const allowedOrigin = !origin || allowedOrigins.includes("*") || allowedOrigins.includes(origin);
// Functions
function sendJSON(status, data) {
res.writeHead(status, { "Content-Type": "application/json" });
res.end(JSON.stringify(data));
}
function unauthorized() {
sendJSON(401, { success: false, message: "Unauthorized" });
}
function notFound() {
sendJSON(404, { success: false, message: "Not Found" });
}
function send(valid) {
if (valid) sendJSON(200, { success: true });
else sendJSON(500, { success: false, message: "Invalid" });
}
// Origin Check
if (!allowedOrigin) {
unauthorized();
return true;
} else {
res.setHeader("Access-Control-Allow-Origin", origin || "*");
}
// Host Check
if (!allowedHost) {
notFound();
return true;
}
// Response Check
let response;
try { response = await promiseWithTimeout(fetch(url?.href), config.timeout) }
catch {
send(false);
return true;
};
// Timeout Check
if (!response) {
send(false);
return true;
}
// Blacklist Check
const text = (await response.text())?.toLowerCase() || "";
const blacklistedStatus = config.blacklist?.status.includes(response.status);
const blacklistedText = config.blacklist?.text.some((t) => text.includes(t));
if (blacklistedStatus || blacklistedText) {
send(false);
return true;
}
// Valid
send(true);
return true;
}
function handleLoad() {
console.log(`Running on ${host}:${port}, origin(s): ${allowedOrigins.join(", ")}`);
}
// Server
const server = proxy.createServer({
handleInitialRequest: handleRequest,
corsMaxAge: config.cacheMaxAge || 3600
});
// Listen
server.listen(port, host, handleLoad);

14
api/src/index.js Normal file

@ -0,0 +1,14 @@
import { initializeEnvironmentVariables } from "./lib/other/env.js";
import { initializeServer } from "./lib/server.js";
initializeEnvironmentVariables();
const { success, address, error } = await initializeServer();
switch (success) {
case true:
console.log(`[Server] Running at ${address}`);
break;
case false:
console.error(`[Server] Error caught:\n`, error);
process.exit(1);
}

@ -0,0 +1,39 @@
import config from "./config.js";
import fetch from "node-fetch";
function promiseWithTimeout(promise, timeout) {
return Promise.race([
promise,
new Promise(function (res) {
setTimeout(res, (timeout || 5) * 1000);
})
]);
}
async function get(url) {
try {
const response = await promiseWithTimeout(fetch(url), process.env.TIMEOUT);
const text = await response.text();
return {
status: response.status,
text: text.toLowerCase() || ""
};
} catch {
return null;
}
}
export async function check(url) {
const response = await get(url);
switch (response) {
case null:
return false;
default:
const blacklistedStatus = config.blacklist.status.includes(response.status);
const blacklistedText = config.blacklist.text.some((t) => response.text.includes(t));
return !(blacklistedStatus || blacklistedText);
}
}

@ -0,0 +1,74 @@
export default {
providers: [
{
base: "vidsrc.to",
url: function (type, params) {
switch (type) {
case "movie":
return `https://${this.base}/embed/movie/${params.id}`;
case "tv":
return `https://${this.base}/embed/tv/${params.id}/${params.season}/${params.episode}`;
}
}
},
{
base: "vidsrc.me",
url: function (type, params) {
switch (type) {
case "movie":
return `https://${this.base}/embed/movie?tmdb=${params.id}`;
case "tv":
return `https://${this.base}/embed/tv?tmdb=${params.id}&season=${params.season}&episode=${params.episode}`;
}
}
},
{
base: "flixon.click",
url: function (type, params) {
switch (type) {
case "movie":
return `https://${this.base}/${params.imdbId}`;
case "tv":
return `https://${this.base}/${params.id}-${params.season}-${params.episode}`;
}
}
},
{
base: "2embed.me",
url: function (type, params) {
switch (type) {
case "movie":
return `https://${this.base}/player/movie/${params.imdbId}`;
case "tv":
return `https://${this.base}/player/tv/${params.id}/S${params.season}/E${params.episode}`;
}
}
},
{
base: "databasegdriveplayer.xyz",
url: function (type, params) {
switch (type) {
case "movie":
return `https://${this.base}/player.php?tmdb=${params.id}`;
case "tv":
return `https://${this.base}/player.php?type=series&tmdb=${params.id}&season=${params.season}&episode=${params.episode}`;
}
}
}
],
blacklist: {
status: [
404, // Not Found
500 // Internal Server Error
],
text: [
"not found", // Generic
"no sources", // Generic
"media is unavailable", // vidsrc.xyz
"onionplay streaming mirrors", // flixon.click
"no movie found", // 2embed.me
"no tv show found", // 2embed.me
`,"file":"","kind"` // databasegdriveplayer.xyz
]
}
};

10
api/src/lib/other/env.js Normal file

@ -0,0 +1,10 @@
import dotenv from "dotenv";
import { fileURLToPath } from "url";
import path, { dirname } from "path";
export function initializeEnvironmentVariables() {
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
dotenv.config({ path: path.join(__dirname, "..", "..", "..", ".env") });
}

30
api/src/lib/server.js Normal file

@ -0,0 +1,30 @@
import fastify from "fastify";
import { applyCors } from "./server/cors.js";
import { applyCaching } from "./server/cache.js";
import { applyRoutes } from "./server/routes.js";
function runServer(server, host, port) {
return new Promise(function (res) {
server.listen({
host: host || "0.0.0.0",
port: port || 5000
}, function (error, address) {
if (error) res({ success: false, error });
res({ success: true, address });
});
});
}
export function initializeServer() {
return new Promise(async function (res) {
const server = fastify();
await applyCors(server, process.env.ORIGIN);
await applyCaching(server, process.env.CACHE_TIMEOUT);
applyRoutes(server);
const response = await runServer(server, process.env.HOST, process.env.PORT);
res(response);
});
}

@ -0,0 +1,5 @@
import caching from "fastify-response-caching";
export async function applyCaching(server, time) {
await server.register(caching, { ttl: (time || 300) * 60 * 1000 });
}

@ -0,0 +1,5 @@
import cors from "@fastify/cors";
export async function applyCors(server, origin) {
await server.register(cors, { origin: origin || "*" });
}

@ -0,0 +1,31 @@
import config from "../other/config.js";
import { check } from "../other/check.js";
export function onRootRequest() {
return { success: true, providers: config.providers.map((p) => p.base) };
}
export async function onRequest(type, req) {
// Provider Check
const provider = config.providers.find((p) => p.base === req.params.provider);
if (!provider) return { success: false, message: "Unsupported provider" };
// Parameters
const id = req.params.id;
const imdbId = req.params.imdbId;
const season = req.params.season;
const episode = req.params.episode;
// Empty Check
if (id === "" || imdbId === "" || season === "" || episode === "") {
return { success: false, message: "Missing parameters" };
}
// URL
const url = provider.url(type, { id, imdbId, season, episode });
// Return
return await check(url)
? { success: true, url }
: { success: false };
}

@ -0,0 +1,13 @@
import { onRootRequest, onRequest } from "./request.js";
export function applyRoutes(server) {
server.get("/", onRootRequest);
server.get("/:provider/:id/:imdbId?", async function (...args) {
return await onRequest("movie", ...args);
});
server.get("/:provider/:id/:season/:episode/:imdbId?", async function (...args) {
return await onRequest("tv", ...args);
});
}

@ -1,23 +1,27 @@
export async function isValidProxy(proxy) {
let response;
let json;
try {
const response = await fetch(proxy);
const json = await response.json();
try { response = await fetch(proxy.base) }
catch { return false };
try { json = await response.json() }
catch { return false };
return json?.success !== null && json?.message !== null;
return json.success
? json.providers
: false;
} catch {
return false;
}
}
export async function isValidUrl(proxy, url) {
let response;
export async function isValidUrl(proxy, provider, info, season, episode) {
try {
const response = (info.type === "movie")
? await fetch(`${proxy}/${provider}/${info.id}/${info.imdbId}`)
: await fetch(`${proxy}/${provider}/${info.id}/${season}/${episode}/${info.imdbId}`);
const json = await response.json();
try { response = await fetch(proxy.url(url)) }
catch {};
return response
? response.status === 200
: true;
return json.success
? json.url
: false;
} catch {
return false;
}
}

@ -2,13 +2,12 @@ import { getQuery, onQueryChange, setQuery, removeQuery } from "../query.js";
import { setModal, showModal, changeHeaderText, hideModal } from "./modal.js";
import { getDetails } from "../api/details.js";
import { elementExists, onWindowResize, removeWindowResize, splitArray, getCenteringDirection, onKeyPress, promiseTimeout, onSwipe } from "../functions.js";
import { config, providers, proxies, proxy as proxyConfig } from "../config.js";
import { config, proxies, proxy as proxyConfig } from "../config.js";
import { getProvider, setProvider } from "../store/provider.js";
import { preloadImages, getNonCachedImages, unloadImages } from "../cache.js";
import { getLastPlayed, setLastPlayed } from "../store/last-played.js";
import { addContinueWatching } from "../store/continue.js";
import { getWatchSection } from "../store/watch-sections.js";
import { getThemeAbsolute } from "../store/theme.js";
import { initializeArea } from "./area.js";
import { isValidProxy, isValidUrl } from "../api/proxy.js";
import { toggleDim } from "./dim.js";
@ -227,29 +226,23 @@ function modal(info, recommendationImages) {
let disabled = false;
let seasonsDisabled = false;
let validProviders = {};
let providers = [];
let validProviders = [];
let forceProvider;
let validProxy = null;
let proxiesChecked = false;
function getUrl(provider) {
const theme = getThemeAbsolute();
return info.type === "movie"
? provider.movieUrl({ id: info.id, imdbId: info.imdbId, theme })
: provider.showUrl({ id: info.id, season: seasonNumber, episode: episodeNumber, theme });
}
function getValidProvider() {
const provider = validProviders[getProvider()];
if (!provider) forceProvider = Object.values(validProviders)[0];
return forceProvider || provider;
const currentProvider = getProvider();
const provider = validProviders.find((p) => p.provider === currentProvider);
if (!provider) forceProvider = validProviders[0]?.provider;
return forceProvider || provider?.provider;
}
function getValidProviderKey() {
const provider = getValidProvider();
return Object.keys(validProviders)[Object.values(validProviders).indexOf(provider)];
function getUrl(provider) {
return validProviders.find((p) => p.provider === provider)?.url;
}
function videoAlert(toggle, icon, text) {
@ -259,7 +252,7 @@ function modal(info, recommendationImages) {
}
async function checkProviders() {
validProviders = {};
validProviders = [];
forceProvider = null;
disabled = true;
@ -276,19 +269,29 @@ function modal(info, recommendationImages) {
const promises = Object.values(proxies).map(function (proxy) {
return new Promise(async function (res, rej) {
if (await isValidProxy(proxy)) res(proxy);
const providers = await isValidProxy(proxy);
if (providers) res({ proxy, providers });
else rej();
});
});
const response = await Promise.any([...promises, promiseTimeout(proxyConfig.checkTimeout)]);
if (response) validProxy = response;
if (response) {
providers = response.providers;
validProxy = response.proxy;
if (getProvider() === null && providers[0]) {
setProvider(providers[0]);
}
}
proxiesChecked = true;
}
async function providersCheck(proxy) {
const total = Object.keys(providers).length;
const total = providers.length;
let checked = 0;
function updateAlert() {
@ -297,18 +300,20 @@ function modal(info, recommendationImages) {
updateAlert();
const promises = Object.entries(providers).map(async function ([key, value]) {
const url = getUrl(value);
const valid = await Promise.any([isValidUrl(proxy, url), promiseTimeout(proxyConfig.validCheckTimeout)]);
const promises = providers.map(async function (provider) {
const url = await Promise.any([
isValidUrl(proxy, provider, info, seasonNumber, episodeNumber),
promiseTimeout(proxyConfig.validCheckTimeout)
]);
if (valid) validProviders[key] = value;
if (url) validProviders.push({ provider, url });
checked++;
updateAlert();
});
await Promise.all(promises);
validProviders = Object.fromEntries(Object.entries(validProviders).sort(([a], [b]) => Object.keys(providers).indexOf(a) - Object.keys(providers).indexOf(b)));
validProviders = validProviders.sort(({ provider: a }, { provider: b }) => providers.indexOf(a) - providers.indexOf(b));
}
if (!proxiesChecked) await proxiesCheck();
@ -318,8 +323,8 @@ function modal(info, recommendationImages) {
providersSelect.innerHTML = "";
if (Object.keys(validProviders).length > 0) {
Object.keys(validProviders).forEach(function (providerName) {
if (validProviders.length > 0 && validProxy) {
validProviders.forEach(function ({ provider: providerName }) {
const provider = document.createElement("option");
provider.value = providerName.toLowerCase();
@ -328,13 +333,14 @@ function modal(info, recommendationImages) {
providersSelect.append(provider);
});
providersSelect.value = getValidProviderKey();
providersSelect.value = getValidProvider();
providersElem.classList.remove("disabled");
disabled = false;
playVideo();
} else {
videoAlert(true, "censor", "No providers available");
if (validProxy) videoAlert(true, "censor", "No providers available");
else videoAlert(true, "censor", "No proxies available");
}
seasonsDisabled = false;
@ -400,7 +406,7 @@ function modal(info, recommendationImages) {
function nextProvider() {
if (disabled) return;
const provider = getValidProviderKey();
const provider = getValidProvider();
const providers = Array.from(providersSelect.children);
const providerElem = providers.find((p) => p.value === provider);
@ -416,7 +422,7 @@ function modal(info, recommendationImages) {
function previousProvider() {
if (disabled) return;
const provider = getValidProviderKey();
const provider = getValidProvider();
const providers = Array.from(providersSelect.children);
const providerElem = providers.find((p) => p.value === provider);

@ -144,63 +144,10 @@ export const proxy = {
};
export const proxies = [
{
base: "/proxy",
url: function (path) {
return `/proxy?url=${path}`;
}
},
{
base: "https://fh-site.vercel.app/proxy",
url: function (path) {
return `https://fh-site.vercel.app/proxy?url=${path}`;
}
}
"/api",
"https://fh-site.vercel.app/api"
];
export const providers = {
"vidsrc.to": {
movieUrl: function ({ id }) {
return `https://vidsrc.to/embed/movie/${id}`;
},
showUrl: function ({ id, season, episode }) {
return `https://vidsrc.to/embed/tv/${id}/${season}/${episode}`;
}
},
"vidsrc.me": {
movieUrl: function ({ id }) {
return `https://vidsrc.me/embed/movie?tmdb=${id}`;
},
showUrl: function ({ id, season, episode }) {
return `https://vidsrc.me/embed/tv?tmdb=${id}&season=${season}&episode=${episode}`;
}
},
"flixon.click": {
movieUrl: function ({ imdbId }) {
return `https://flixon.click/${imdbId}`;
},
showUrl: function ({ id, season, episode }) {
return `https://flixon.click/${id}-${season}-${episode}`;
}
},
"2embed.me": {
movieUrl: function ({ imdbId }) {
return `https://2embed.me/player/movie/${imdbId}`;
},
showUrl: function ({ id, season, episode }) {
return `https://2embed.me/player/tv/${id}/S${season}/E${episode}`;
}
},
"databasegdriveplayer.xyz": {
movieUrl: function ({ id }) {
return `https://databasegdriveplayer.xyz/player.php?tmdb=${id}`;
},
showUrl: function ({ id, season, episode }) {
return `https://databasegdriveplayer.xyz/player.php?type=series&tmdb=${id}&season=${season}&episode=${episode}`;
}
}
};
export const store = {
names: {
cache: function (key) {

@ -1,4 +1,4 @@
import { store, providers } from "../config.js";
import { store } from "../config.js";
function get() {
const provider = localStorage.getItem(store.names.provider);
@ -15,9 +15,8 @@ export function getProvider() {
if (provider) {
return provider;
} else {
const defaultProvider = Object.keys(providers)[0];
setProvider(defaultProvider);
return defaultProvider;
setProvider(null);
return null;
}
}

@ -22,18 +22,6 @@ export function getTheme() {
}
}
export function getThemeAbsolute() {
const theme = getTheme();
if (theme === "auto") {
return (window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches)
? "dark"
: "light";
} else {
return theme;
}
}
export function setTheme(theme) {
set(theme);
initializeTheme();

@ -4,8 +4,8 @@
"buildCommand": "npm run build",
"routes": [
{
"src": "/proxy",
"dest": "/api/proxy"
"src": "/api",
"dest": "/api/index"
}
],
"env": {