Update web.ts
Browse files
web.ts
CHANGED
|
@@ -65,7 +65,7 @@ function createApiErrorObject(errorMessage: string, statusProviderName: string =
|
|
| 65 |
};
|
| 66 |
}
|
| 67 |
function createProviderErrorObject(providerName: string, errorMessage: string): ProviderErrorResult {
|
| 68 |
-
|
| 69 |
}
|
| 70 |
|
| 71 |
function stringAtob(input: string): string {
|
|
@@ -105,17 +105,19 @@ function getSizeQuality(url: string): number {
|
|
| 105 |
// --- Provider Specific Logic ---
|
| 106 |
|
| 107 |
// == EmbedSu Provider ==
|
| 108 |
-
const EMBED_SU_DOMAIN = "https://
|
|
|
|
|
|
|
| 109 |
const EMBED_SU_HEADERS = {
|
| 110 |
'User-Agent': USER_AGENT,
|
| 111 |
-
'Referer':
|
| 112 |
-
'Origin':
|
| 113 |
};
|
| 114 |
|
| 115 |
async function getEmbedSu(tmdb_id: string, s?: string, e?: string): Promise<ProviderFunctionReturn> {
|
| 116 |
const providerName = PROVIDERS.embedsu.displayName;
|
| 117 |
try {
|
| 118 |
-
const urlSearch = s && e ? `${EMBED_SU_DOMAIN}/embed/tv/${tmdb_id}/${s}/${e}` : `${EMBED_SU_DOMAIN}/embed/movie/${tmdb_id}`;
|
| 119 |
const htmlSearchResponse = await fetch(urlSearch, { method: 'GET', headers: EMBED_SU_HEADERS });
|
| 120 |
if (!htmlSearchResponse.ok) return createProviderErrorObject(providerName, `Failed to fetch initial page: HTTP ${htmlSearchResponse.status}`);
|
| 121 |
|
|
@@ -146,7 +148,7 @@ async function getEmbedSu(tmdb_id: string, s?: string, e?: string): Promise<Prov
|
|
| 146 |
for (const item of secondDecode) {
|
| 147 |
try {
|
| 148 |
if (!item || !item.hash) continue;
|
| 149 |
-
const urlDirect = `${EMBED_SU_DOMAIN}/api/e/${item.hash}`;
|
| 150 |
const dataDirect = await requestGet(urlDirect, { "Referer": EMBED_SU_DOMAIN, "User-Agent": USER_AGENT, "Origin": EMBED_SU_DOMAIN });
|
| 151 |
if (!dataDirect || !dataDirect.source) { console.warn(`${providerName}: No source found for hash ${item.hash}`); continue; }
|
| 152 |
|
|
@@ -154,7 +156,7 @@ async function getEmbedSu(tmdb_id: string, s?: string, e?: string): Promise<Prov
|
|
| 154 |
url: sub.file, lang: sub.label ? sub.label.split('-')[0].trim().toLowerCase() : 'en'
|
| 155 |
})).filter((track: Subtitle) => track.url);
|
| 156 |
|
| 157 |
-
const requestDirectSize = await fetch(dataDirect.source
|
| 158 |
if (!requestDirectSize.ok) { console.warn(`${providerName}: Failed to fetch source ${dataDirect.source}: HTTP ${requestDirectSize.status}`); continue; }
|
| 159 |
|
| 160 |
const parseRequest = await requestDirectSize.text();
|
|
@@ -163,7 +165,7 @@ async function getEmbedSu(tmdb_id: string, s?: string, e?: string): Promise<Prov
|
|
| 163 |
const directQuality: MediaFile[] = patternSize.map(patternItem => {
|
| 164 |
try {
|
| 165 |
const sizeQuality = getSizeQuality(patternItem);
|
| 166 |
-
let dURL = `${EMBED_SU_DOMAIN}${patternItem}`;
|
| 167 |
dURL = dURL.replace(".png", ".m3u8");
|
| 168 |
const fileObj: MediaFile = { file: dURL, type: 'hls', quality: `${sizeQuality}p`, lang: 'en' };
|
| 169 |
if (s && e) { // Add season and episode if they exist (TV show)
|
|
@@ -176,7 +178,7 @@ async function getEmbedSu(tmdb_id: string, s?: string, e?: string): Promise<Prov
|
|
| 176 |
|
| 177 |
if (!directQuality.length) { console.warn(`${providerName}: No valid qualities found for source ${dataDirect.source}`); continue; }
|
| 178 |
|
| 179 |
-
return { source: { provider: providerName, files: directQuality, subtitles: tracks, headers: { "Referer":
|
| 180 |
} catch (error) { console.error(`${providerName}: Error processing item ${item.hash}:`, error); }
|
| 181 |
}
|
| 182 |
return createProviderErrorObject(providerName, "No valid sources found after processing all available items");
|
|
@@ -187,21 +189,20 @@ async function getEmbedSu(tmdb_id: string, s?: string, e?: string): Promise<Prov
|
|
| 187 |
}
|
| 188 |
|
| 189 |
// == VidSrc.SU Provider ==
|
| 190 |
-
|
| 191 |
-
const
|
| 192 |
-
const VIDSRC_SU_HEADERS = { 'User-Agent': USER_AGENT, 'Referer': "https://vidsrc.su/", 'Origin': "https://vidsrc.su" };
|
| 193 |
|
| 194 |
async function getVidSrcSu(tmdb_id: string, s?: string, e?: string): Promise<ProviderFunctionReturn> {
|
| 195 |
const providerName = PROVIDERS.vidsrcsu.displayName;
|
| 196 |
const embedUrl = s && e ? `${VIDSRC_SU_DOMAIN}embed/tv/${tmdb_id}/${s}/${e}` : `${VIDSRC_SU_DOMAIN}embed/movie/${tmdb_id}`;
|
| 197 |
-
|
| 198 |
try {
|
| 199 |
const response = await fetch(embedUrl, { headers: VIDSRC_SU_HEADERS });
|
| 200 |
if (!response.ok) return createProviderErrorObject(providerName, `Failed to fetch embed page: HTTP ${response.status}`);
|
| 201 |
-
|
| 202 |
const html = await response.text();
|
| 203 |
let subtitles: Subtitle[] = [];
|
| 204 |
-
|
| 205 |
const servers: MediaFile[] = [...html.matchAll(/label: 'Server (?:[^']*)', url: '(https?:\/\/[^']+\.m3u8[^']*)'/gi)].map(match => {
|
| 206 |
const fileObj: MediaFile = { file: match[1], type: "hls", lang: "en" };
|
| 207 |
if (s && e) { // Add season and episode if they exist
|
|
@@ -225,7 +226,7 @@ async function getVidSrcSu(tmdb_id: string, s?: string, e?: string): Promise<Pro
|
|
| 225 |
}
|
| 226 |
|
| 227 |
if (servers.length === 0) return createProviderErrorObject(providerName, "No valid video streams found in embed page");
|
| 228 |
-
return { source: { provider: providerName, files: servers, subtitles: subtitles, headers: { "Referer":
|
| 229 |
} catch (error) {
|
| 230 |
console.error(`${providerName}: Unexpected error:`, error);
|
| 231 |
return createProviderErrorObject(providerName, `Unexpected error: ${error.message}`);
|
|
@@ -238,117 +239,117 @@ const AUTOEMBED_API_URL_BASE = "https://tom.autoembed.cc/api/getVideoSource";
|
|
| 238 |
|
| 239 |
// Modified parseAutoEmbedM3U8 to accept s and e
|
| 240 |
function parseAutoEmbedM3U8(m3u8Content: string, s?: string, e?: string, m3u8Url?: string): MediaFile[] {
|
| 241 |
-
|
| 242 |
-
|
| 243 |
-
|
| 244 |
-
|
| 245 |
-
|
| 246 |
-
let baseUrl = '';
|
| 247 |
-
let domain = '';
|
| 248 |
-
if (m3u8Url) {
|
| 249 |
-
try {
|
| 250 |
-
const urlObj = new URL(m3u8Url);
|
| 251 |
-
domain = `${urlObj.protocol}//${urlObj.hostname}`;
|
| 252 |
-
|
| 253 |
-
// Get base URL by removing the filename from the full URL
|
| 254 |
-
const urlPath = m3u8Url.split('/');
|
| 255 |
-
urlPath.pop(); // Remove the filename
|
| 256 |
-
baseUrl = urlPath.join('/') + '/';
|
| 257 |
-
} catch (error) {
|
| 258 |
-
console.error('AutoEmbed: Error extracting URL information:', error);
|
| 259 |
-
}
|
| 260 |
-
}
|
| 261 |
|
| 262 |
-
|
| 263 |
-
|
| 264 |
-
|
| 265 |
-
|
| 266 |
-
|
| 267 |
-
|
| 268 |
-
|
| 269 |
-
|
| 270 |
-
|
| 271 |
-
|
| 272 |
-
|
| 273 |
-
|
| 274 |
-
|
| 275 |
-
|
| 276 |
-
|
| 277 |
-
|
| 278 |
-
|
| 279 |
-
|
| 280 |
-
|
| 281 |
-
|
| 282 |
-
|
| 283 |
-
|
| 284 |
-
|
| 285 |
-
|
| 286 |
-
|
| 287 |
-
|
| 288 |
-
|
| 289 |
-
|
| 290 |
-
|
| 291 |
-
|
| 292 |
-
|
| 293 |
-
|
| 294 |
-
|
| 295 |
-
|
| 296 |
-
|
| 297 |
-
|
| 298 |
-
|
|
|
|
| 299 |
}
|
| 300 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 301 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
| 302 |
}
|
| 303 |
-
|
| 304 |
-
|
|
|
|
|
|
|
|
|
|
| 305 |
}
|
| 306 |
|
| 307 |
function mapAutoEmbedSubtitles(apiSubtitles: any[]): Subtitle[] {
|
| 308 |
-
|
| 309 |
-
|
| 310 |
-
|
| 311 |
-
|
| 312 |
-
|
| 313 |
-
|
| 314 |
-
|
| 315 |
-
|
| 316 |
-
|
| 317 |
-
|
| 318 |
-
|
| 319 |
}
|
| 320 |
|
| 321 |
async function getAutoEmbed(tmdb_id: string, s?: string, e?: string): Promise<ProviderFunctionReturn> {
|
| 322 |
-
|
| 323 |
-
|
| 324 |
-
|
| 325 |
-
|
| 326 |
-
|
| 327 |
-
|
| 328 |
-
|
| 329 |
-
|
| 330 |
-
|
| 331 |
-
|
| 332 |
-
|
| 333 |
-
}
|
| 334 |
-
const data = await response.json();
|
| 335 |
-
if (data.error || !data.videoSource) return createProviderErrorObject(providerName, data.error || "No videoSource found in API response");
|
| 336 |
-
|
| 337 |
-
const m3u8Url = data.videoSource;
|
| 338 |
-
const m3u8Response = await fetch(m3u8Url, { headers: { 'Referer': AUTOEMBED_DOMAIN, 'User-Agent': USER_AGENT } });
|
| 339 |
-
if (!m3u8Response.ok) return createProviderErrorObject(providerName, `Failed to fetch m3u8 from ${m3u8Url}: HTTP ${m3u8Response.status}`);
|
| 340 |
-
|
| 341 |
-
const m3u8Content = await m3u8Response.text();
|
| 342 |
-
// Pass s and e to the M3U8 parser
|
| 343 |
-
const files = parseAutoEmbedM3U8(m3u8Content, s, e, m3u8Url);
|
| 344 |
-
if (files.length === 0) return createProviderErrorObject(providerName, "No valid streams found after parsing m3u8");
|
| 345 |
-
|
| 346 |
-
const subtitles = data.subtitles ? mapAutoEmbedSubtitles(data.subtitles) : [];
|
| 347 |
-
return { source: { provider: providerName, files: files, subtitles: subtitles, headers: { "Referer": AUTOEMBED_DOMAIN, "User-Agent": USER_AGENT, "Origin": AUTOEMBED_DOMAIN } } };
|
| 348 |
-
} catch (error) {
|
| 349 |
-
console.error(`${providerName}: Unexpected error - `, error);
|
| 350 |
-
return createProviderErrorObject(providerName, `Network or processing error: ${error.message}`);
|
| 351 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 352 |
}
|
| 353 |
|
| 354 |
// --- Provider Configuration ---
|
|
|
|
| 65 |
};
|
| 66 |
}
|
| 67 |
function createProviderErrorObject(providerName: string, errorMessage: string): ProviderErrorResult {
|
| 68 |
+
return createApiErrorObject(errorMessage, providerName);
|
| 69 |
}
|
| 70 |
|
| 71 |
function stringAtob(input: string): string {
|
|
|
|
| 105 |
// --- Provider Specific Logic ---
|
| 106 |
|
| 107 |
// == EmbedSu Provider ==
|
| 108 |
+
const EMBED_SU_DOMAIN = "https://embed.su";
|
| 109 |
+
const EMBED_SU_PROXY = "https://iqslgbok.deploy.cx/param/User-Agent=" + encodeURIComponent(USER_AGENT) + "/param/Origin=" + encodeURIComponent(EMBED_SU_DOMAIN) + "/param/Referer=" + encodeURIComponent(EMBED_SU_DOMAIN) + "/";
|
| 110 |
+
|
| 111 |
const EMBED_SU_HEADERS = {
|
| 112 |
'User-Agent': USER_AGENT,
|
| 113 |
+
'Referer': EMBED_SU_DOMAIN,//EMBED_SU_DOMAIN
|
| 114 |
+
'Origin': EMBED_SU_DOMAIN,//EMBED_SU_DOMAIN
|
| 115 |
};
|
| 116 |
|
| 117 |
async function getEmbedSu(tmdb_id: string, s?: string, e?: string): Promise<ProviderFunctionReturn> {
|
| 118 |
const providerName = PROVIDERS.embedsu.displayName;
|
| 119 |
try {
|
| 120 |
+
const urlSearch = s && e ? `${EMBED_SU_PROXY}${EMBED_SU_DOMAIN}/embed/tv/${tmdb_id}/${s}/${e}` : `${EMBED_SU_PROXY}${EMBED_SU_DOMAIN}/embed/movie/${tmdb_id}`;
|
| 121 |
const htmlSearchResponse = await fetch(urlSearch, { method: 'GET', headers: EMBED_SU_HEADERS });
|
| 122 |
if (!htmlSearchResponse.ok) return createProviderErrorObject(providerName, `Failed to fetch initial page: HTTP ${htmlSearchResponse.status}`);
|
| 123 |
|
|
|
|
| 148 |
for (const item of secondDecode) {
|
| 149 |
try {
|
| 150 |
if (!item || !item.hash) continue;
|
| 151 |
+
const urlDirect = `${EMBED_SU_PROXY}${EMBED_SU_DOMAIN}/api/e/${item.hash}`;
|
| 152 |
const dataDirect = await requestGet(urlDirect, { "Referer": EMBED_SU_DOMAIN, "User-Agent": USER_AGENT, "Origin": EMBED_SU_DOMAIN });
|
| 153 |
if (!dataDirect || !dataDirect.source) { console.warn(`${providerName}: No source found for hash ${item.hash}`); continue; }
|
| 154 |
|
|
|
|
| 156 |
url: sub.file, lang: sub.label ? sub.label.split('-')[0].trim().toLowerCase() : 'en'
|
| 157 |
})).filter((track: Subtitle) => track.url);
|
| 158 |
|
| 159 |
+
const requestDirectSize = await fetch(EMBED_SU_PROXY + dataDirect.source, { headers: EMBED_SU_HEADERS, method: "GET" });
|
| 160 |
if (!requestDirectSize.ok) { console.warn(`${providerName}: Failed to fetch source ${dataDirect.source}: HTTP ${requestDirectSize.status}`); continue; }
|
| 161 |
|
| 162 |
const parseRequest = await requestDirectSize.text();
|
|
|
|
| 165 |
const directQuality: MediaFile[] = patternSize.map(patternItem => {
|
| 166 |
try {
|
| 167 |
const sizeQuality = getSizeQuality(patternItem);
|
| 168 |
+
let dURL = `${EMBED_SU_PROXY}${EMBED_SU_DOMAIN}${patternItem}`;
|
| 169 |
dURL = dURL.replace(".png", ".m3u8");
|
| 170 |
const fileObj: MediaFile = { file: dURL, type: 'hls', quality: `${sizeQuality}p`, lang: 'en' };
|
| 171 |
if (s && e) { // Add season and episode if they exist (TV show)
|
|
|
|
| 178 |
|
| 179 |
if (!directQuality.length) { console.warn(`${providerName}: No valid qualities found for source ${dataDirect.source}`); continue; }
|
| 180 |
|
| 181 |
+
return { source: { provider: providerName, files: directQuality, subtitles: tracks, headers: { "Referer": EMBED_SU_DOMAIN, "User-Agent": USER_AGENT, "Origin": EMBED_SU_DOMAIN } } };
|
| 182 |
} catch (error) { console.error(`${providerName}: Error processing item ${item.hash}:`, error); }
|
| 183 |
}
|
| 184 |
return createProviderErrorObject(providerName, "No valid sources found after processing all available items");
|
|
|
|
| 189 |
}
|
| 190 |
|
| 191 |
// == VidSrc.SU Provider ==
|
| 192 |
+
const VIDSRC_SU_DOMAIN = "https://vidsrc.su/";
|
| 193 |
+
const VIDSRC_SU_HEADERS = { 'User-Agent': USER_AGENT, 'Referer': VIDSRC_SU_DOMAIN, 'Origin': VIDSRC_SU_DOMAIN };
|
|
|
|
| 194 |
|
| 195 |
async function getVidSrcSu(tmdb_id: string, s?: string, e?: string): Promise<ProviderFunctionReturn> {
|
| 196 |
const providerName = PROVIDERS.vidsrcsu.displayName;
|
| 197 |
const embedUrl = s && e ? `${VIDSRC_SU_DOMAIN}embed/tv/${tmdb_id}/${s}/${e}` : `${VIDSRC_SU_DOMAIN}embed/movie/${tmdb_id}`;
|
| 198 |
+
|
| 199 |
try {
|
| 200 |
const response = await fetch(embedUrl, { headers: VIDSRC_SU_HEADERS });
|
| 201 |
if (!response.ok) return createProviderErrorObject(providerName, `Failed to fetch embed page: HTTP ${response.status}`);
|
| 202 |
+
|
| 203 |
const html = await response.text();
|
| 204 |
let subtitles: Subtitle[] = [];
|
| 205 |
+
|
| 206 |
const servers: MediaFile[] = [...html.matchAll(/label: 'Server (?:[^']*)', url: '(https?:\/\/[^']+\.m3u8[^']*)'/gi)].map(match => {
|
| 207 |
const fileObj: MediaFile = { file: match[1], type: "hls", lang: "en" };
|
| 208 |
if (s && e) { // Add season and episode if they exist
|
|
|
|
| 226 |
}
|
| 227 |
|
| 228 |
if (servers.length === 0) return createProviderErrorObject(providerName, "No valid video streams found in embed page");
|
| 229 |
+
return { source: { provider: providerName, files: servers, subtitles: subtitles, headers: { "Referer": VIDSRC_SU_DOMAIN, "User-Agent": USER_AGENT, "Origin": VIDSRC_SU_DOMAIN } } };
|
| 230 |
} catch (error) {
|
| 231 |
console.error(`${providerName}: Unexpected error:`, error);
|
| 232 |
return createProviderErrorObject(providerName, `Unexpected error: ${error.message}`);
|
|
|
|
| 239 |
|
| 240 |
// Modified parseAutoEmbedM3U8 to accept s and e
|
| 241 |
function parseAutoEmbedM3U8(m3u8Content: string, s?: string, e?: string, m3u8Url?: string): MediaFile[] {
|
| 242 |
+
try {
|
| 243 |
+
const lines = m3u8Content.split('\n');
|
| 244 |
+
const sources: MediaFile[] = [];
|
| 245 |
+
let currentQuality: string | undefined = undefined;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 246 |
|
| 247 |
+
let baseUrl = '';
|
| 248 |
+
let domain = '';
|
| 249 |
+
if (m3u8Url) {
|
| 250 |
+
try {
|
| 251 |
+
const urlObj = new URL(m3u8Url);
|
| 252 |
+
domain = `${urlObj.protocol}//${urlObj.hostname}`;
|
| 253 |
+
|
| 254 |
+
// Get base URL by removing the filename from the full URL
|
| 255 |
+
const urlPath = m3u8Url.split('/');
|
| 256 |
+
urlPath.pop(); // Remove the filename
|
| 257 |
+
baseUrl = urlPath.join('/') + '/';
|
| 258 |
+
} catch (error) {
|
| 259 |
+
console.error('AutoEmbed: Error extracting URL information:', error);
|
| 260 |
+
}
|
| 261 |
+
}
|
| 262 |
+
|
| 263 |
+
for (let i = 0; i < lines.length; i++) {
|
| 264 |
+
const trimmedLine = lines[i].trim();
|
| 265 |
+
if (trimmedLine.startsWith('#EXT-X-STREAM-INF:')) {
|
| 266 |
+
const resolutionMatch = trimmedLine.match(/RESOLUTION=\d+x(\d+)/);
|
| 267 |
+
currentQuality = resolutionMatch && resolutionMatch[1] ? `${resolutionMatch[1]}p` : undefined;
|
| 268 |
+
for (let j = i + 1; j < lines.length; j++) {
|
| 269 |
+
const nextLineTrimmed = lines[j].trim();
|
| 270 |
+
if (nextLineTrimmed && !nextLineTrimmed.startsWith('#')) {
|
| 271 |
+
// Handle different types of paths
|
| 272 |
+
let filePath = nextLineTrimmed;
|
| 273 |
+
|
| 274 |
+
// Skip URLs that already have protocol
|
| 275 |
+
if (!nextLineTrimmed.match(/^https?:\/\//)) {
|
| 276 |
+
if (nextLineTrimmed.startsWith('/')) {
|
| 277 |
+
// Absolute path starting with '/'
|
| 278 |
+
if (domain) {
|
| 279 |
+
filePath = `${domain}${nextLineTrimmed}`;
|
| 280 |
+
}
|
| 281 |
+
} else {
|
| 282 |
+
// Relative path
|
| 283 |
+
if (baseUrl) {
|
| 284 |
+
filePath = `${baseUrl}${nextLineTrimmed}`;
|
| 285 |
}
|
| 286 |
+
}
|
| 287 |
+
}
|
| 288 |
+
const fileObj: MediaFile = {
|
| 289 |
+
file: filePath, type: "hls",
|
| 290 |
+
quality: currentQuality || 'unknown', lang: "en"
|
| 291 |
+
};
|
| 292 |
+
if (s && e) { // Add season and episode if they exist
|
| 293 |
+
fileObj.season = s;
|
| 294 |
+
fileObj.episode = e;
|
| 295 |
}
|
| 296 |
+
sources.push(fileObj);
|
| 297 |
+
i = j; break;
|
| 298 |
+
}
|
| 299 |
+
if (nextLineTrimmed.startsWith('#EXT')) break;
|
| 300 |
}
|
| 301 |
+
currentQuality = undefined;
|
| 302 |
+
}
|
| 303 |
+
}
|
| 304 |
+
return sources;
|
| 305 |
+
} catch (error) { console.error('AutoEmbed: Error parsing m3u8:', error); return []; }
|
| 306 |
}
|
| 307 |
|
| 308 |
function mapAutoEmbedSubtitles(apiSubtitles: any[]): Subtitle[] {
|
| 309 |
+
if (!apiSubtitles || !Array.isArray(apiSubtitles)) return [];
|
| 310 |
+
try {
|
| 311 |
+
return apiSubtitles.map(subtitle => {
|
| 312 |
+
const lang = (subtitle.label || 'unknown').split(' ')[0].toLowerCase();
|
| 313 |
+
const fileUrl = subtitle.file || '';
|
| 314 |
+
if (!fileUrl) return null;
|
| 315 |
+
const fileExtension = fileUrl.split('.').pop()?.toLowerCase();
|
| 316 |
+
const type = fileExtension === 'vtt' ? 'vtt' : (fileExtension === 'srt' ? 'srt' : undefined);
|
| 317 |
+
return { url: fileUrl, lang: lang, type: type };
|
| 318 |
+
}).filter((sub): sub is Subtitle => sub !== null && !!sub.url);
|
| 319 |
+
} catch (error) { console.error('AutoEmbed: Error mapping subtitles:', error); return []; }
|
| 320 |
}
|
| 321 |
|
| 322 |
async function getAutoEmbed(tmdb_id: string, s?: string, e?: string): Promise<ProviderFunctionReturn> {
|
| 323 |
+
const providerName = PROVIDERS.autoembed.displayName;
|
| 324 |
+
const params = new URLSearchParams();
|
| 325 |
+
if (s && e) { params.append("type", "tv"); params.append("id", `${tmdb_id}/${s}/${e}`); }
|
| 326 |
+
else { params.append("type", "movie"); params.append("id", tmdb_id); }
|
| 327 |
+
const apiUrl = `${AUTOEMBED_API_URL_BASE}?${params.toString()}`;
|
| 328 |
+
|
| 329 |
+
try {
|
| 330 |
+
const response = await fetch(apiUrl, { headers: { 'Referer': AUTOEMBED_DOMAIN, 'User-Agent': USER_AGENT } });
|
| 331 |
+
if (!response.ok) {
|
| 332 |
+
let errorBody = ""; try { errorBody = await response.text(); } catch (_) { }
|
| 333 |
+
return createProviderErrorObject(providerName, `API request failed: HTTP ${response.status}. ${errorBody}`);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 334 |
}
|
| 335 |
+
const data = await response.json();
|
| 336 |
+
if (data.error || !data.videoSource) return createProviderErrorObject(providerName, data.error || "No videoSource found in API response");
|
| 337 |
+
|
| 338 |
+
const m3u8Url = data.videoSource;
|
| 339 |
+
const m3u8Response = await fetch(m3u8Url, { headers: { 'Referer': AUTOEMBED_DOMAIN, 'User-Agent': USER_AGENT } });
|
| 340 |
+
if (!m3u8Response.ok) return createProviderErrorObject(providerName, `Failed to fetch m3u8 from ${m3u8Url}: HTTP ${m3u8Response.status}`);
|
| 341 |
+
|
| 342 |
+
const m3u8Content = await m3u8Response.text();
|
| 343 |
+
// Pass s and e to the M3U8 parser
|
| 344 |
+
const files = parseAutoEmbedM3U8(m3u8Content, s, e, m3u8Url);
|
| 345 |
+
if (files.length === 0) return createProviderErrorObject(providerName, "No valid streams found after parsing m3u8");
|
| 346 |
+
|
| 347 |
+
const subtitles = data.subtitles ? mapAutoEmbedSubtitles(data.subtitles) : [];
|
| 348 |
+
return { source: { provider: providerName, files: files, subtitles: subtitles, headers: { "Referer": AUTOEMBED_DOMAIN, "User-Agent": USER_AGENT, "Origin": AUTOEMBED_DOMAIN } } };
|
| 349 |
+
} catch (error) {
|
| 350 |
+
console.error(`${providerName}: Unexpected error - `, error);
|
| 351 |
+
return createProviderErrorObject(providerName, `Network or processing error: ${error.message}`);
|
| 352 |
+
}
|
| 353 |
}
|
| 354 |
|
| 355 |
// --- Provider Configuration ---
|