From c7c4cb48f4b0d348e72b16e77475ab6a9ae6cec4 Mon Sep 17 00:00:00 2001 From: Devin Bot Date: Sun, 25 Oct 2026 18:56:22 -0700 Subject: [PATCH] added hdhub, frost and watchout ( ty movieslay ) --- src/config.js | 36 ++++++++++++++++++++++++++++++++ src/sources/frost.js | 30 +++++++++++++++++++++++++++ src/sources/hdhub.js | 36 ++++++++++++++++++++++++++++++++ src/sources/watchout.js | 46 +++++++++++++++++++++++++++++++++++++++++ src/utils/helpers.js | 17 +++++++++++++++ 5 files changed, 165 insertions(+) create mode 100644 src/sources/frost.js create mode 100644 src/sources/hdhub.js create mode 100644 src/sources/watchout.js diff --git a/src/config.js b/src/config.js index e2e1705..684f9b3 100644 --- a/src/config.js +++ b/src/config.js @@ -187,6 +187,18 @@ export const SOURCES = [ }], }, + { + key: 'frost', + label: 'FrostStream', + sourceFile: 'frost', + proxyParam: 'fst', + timeout: 25000, + jitter: 500, + retries: 2, + multiUrl: true, + skipProxy: true, + }, + { key: 'fsonline', label: 'FSOnline', @@ -199,6 +211,18 @@ export const SOURCES = [ skipProxy: true, }, + { + key: 'hdhub', + label: 'HDHub', + sourceFile: 'hdhub', + proxyParam: 'hdh', + timeout: 25000, + jitter: 500, + retries: 2, + multiUrl: true, + skipProxy: true, + }, + { key: 'hexa', label: 'Hexa', @@ -608,6 +632,18 @@ export const SOURCES = [ }, }, + { + key: 'watchout', + label: 'Watchout', + sourceFile: 'watchout', + proxyParam: 'wo', + timeout: 25000, + jitter: 500, + retries: 2, + multiUrl: true, + skipProxy: true, + }, + { key: 'xdownloader', label: 'XDownloader', diff --git a/src/sources/frost.js b/src/sources/frost.js new file mode 100644 index 0000000..4c8cd96 --- /dev/null +++ b/src/sources/frost.js @@ -0,0 +1,30 @@ +import { fetchJson, resolveImdbId } from '../utils/helpers.js'; + +export async function getStream({ id, s, e, tmdbApiKey, sdk }) { + const isTv = s != null && e != null; + const type = isTv ? 'series' : 'movie'; + const imdb = await resolveImdbId(id, isTv, tmdbApiKey || sdk?.tmdbApiKey); + if (!imdb) return null; + + const url = isTv + ? `https://froststream.cloutteam.com/stream/series/${imdb}:${s}:${e}.json` + : `https://froststream.cloutteam.com/stream/movie/${imdb}.json`; + + const res = await fetchJson(url).catch(() => null); + if (!res || !Array.isArray(res.streams) || !res.streams.length) return null; + + const allUrls = res.streams.filter(st => st.url).map(st => ({ + url: st.url, + server: `Frost - ${st.name || 'Server'} ${st.title ? `- ${st.title}` : ''}`, + headers: st.behaviorHints?.proxyHeaders?.request || undefined, + skipProxy: st.behaviorHints?.notWebReady === false + })); + + if (!allUrls.length) return null; + + return { + url: allUrls[0].url, + headers: allUrls[0].headers, + allUrls + }; +} \ No newline at end of file diff --git a/src/sources/hdhub.js b/src/sources/hdhub.js new file mode 100644 index 0000000..ba5a6e8 --- /dev/null +++ b/src/sources/hdhub.js @@ -0,0 +1,36 @@ +import { fetchJson, resolveImdbId } from '../utils/helpers.js'; + +export async function getStream({ id, s, e, tmdbApiKey, sdk }) { + const isTv = s != null && e != null; + const type = isTv ? 'series' : 'movie'; + const imdb = await resolveImdbId(id, isTv, tmdbApiKey || sdk?.tmdbApiKey); + if (!imdb) return null; + + const CONFIG = btoa(JSON.stringify({ "torbox": "unset", "qualities": "2160p,1080p,720p", "sort": "desc", "catalogs": "featured,popular" })); + const url = `https://hdhub.thevolecitor.qzz.io/${CONFIG}/stream/${type}/${imdb}${isTv ? `:${s}:${e}` : ""}.json`; + + const res = await fetchJson(url).catch(() => null); + if (!res || !Array.isArray(res.streams) || !res.streams.length) return null; + + const validStreams = res.streams.filter(st => { + if (!st.url || !st.url.trim()) return false; + const lower = st.url.toLowerCase(); + if (lower.includes('hubcloud') || lower.includes('pixeldrain.dev') || lower.endsWith('.zip')) return false; + return true; + }); + + if (!validStreams.length) return null; + + const allUrls = validStreams.map(st => ({ + url: st.url, + server: `HDHub - ${st.name || 'Server'}`, + headers: st.behaviorHints?.proxyHeaders?.request || undefined, + skipProxy: st.behaviorHints?.notWebReady === false + })); + + return { + url: allUrls[0].url, + headers: allUrls[0].headers, + allUrls + }; +} diff --git a/src/sources/watchout.js b/src/sources/watchout.js new file mode 100644 index 0000000..63ea7ac --- /dev/null +++ b/src/sources/watchout.js @@ -0,0 +1,46 @@ +import { fetchJson, fetchText } from '../utils/helpers.js'; + +const API_URL = "https://raw.githubusercontent.com/Watchout2025/api/refs/heads/main/hls/"; + +export async function getStream({ id, s, e }) { + const isTv = s != null && e != null; + const type = isTv ? 'tv' : 'movie'; + + const targetUrl = isTv + ? `${API_URL}tv/${id}/S${s}.json` + : `${API_URL}movie/${id}`; + + const headers = { + 'Origin': 'https://vidout.pages.dev', + 'Referer': 'https://vidout.pages.dev' + }; + + if (type === 'movie') { + const data = await fetchText(targetUrl).catch(() => null); + if (!data || !data.includes('://')) return null; + + return { + url: data.trim(), + headers, + allUrls: [{ + url: data.trim(), + server: 'Watchout - Direct', + headers + }] + }; + } else { + const data = await fetchJson(targetUrl).catch(() => null); + const streamUrl = data?.[String(e)] || null; + if (!streamUrl) return null; + + return { + url: streamUrl, + headers, + allUrls: [{ + url: streamUrl, + server: 'Watchout - Direct', + headers + }] + }; + } +} diff --git a/src/utils/helpers.js b/src/utils/helpers.js index 2b64311..33f8305 100644 --- a/src/utils/helpers.js +++ b/src/utils/helpers.js @@ -52,6 +52,23 @@ function pruneHelperCache(cache) { } } +export async function resolveImdbId(id, isTv, tmdbApiKey) { + if (typeof id === 'string' && id.startsWith('tt')) return id; + const apiKey = tmdbApiKey || process.env.TMDB_API_KEY; + if (!apiKey) return `tt${id}`; + const type = isTv ? 'tv' : 'movie'; + try { + const res = await fetch(`https://api.themoviedb.org/3/${type}/${id}?api_key=${apiKey}&append_to_response=external_ids`, { + signal: AbortSignal.timeout(5000), + }); + if (!res.ok) return `tt${id}`; + const data = await res.json(); + return data.imdb_id || data.external_ids?.imdb_id || `tt${id}`; + } catch { + return `tt${id}`; + } +} + setInterval(() => { pruneHelperCache(tmdbValidationCache); pruneHelperCache(tmdbInfoCache);