//Thanks ibro for the TMDB search! async function searchResults(keyword) { try { let transformedResults = []; const keywordGroups = { trending: ["!trending", "!hot", "!tr", "!!"], topRatedMovie: ["!top-rated-movie", "!topmovie", "!tm", "??"], topRatedTV: ["!top-rated-tv", "!toptv", "!tt", "::"], popularMovie: ["!popular-movie", "!popmovie", "!pm", ";;"], popularTV: ["!popular-tv", "!poptv", "!pt", "++"], }; const skipTitleFilter = Object.values(keywordGroups).flat(); const shouldFilter = !matchesKeyword(keyword, skipTitleFilter); // --- TMDB Section --- const encodedKeyword = encodeURIComponent(keyword); let baseUrl = null; if (matchesKeyword(keyword, keywordGroups.trending)) { baseUrl = `https://api.themoviedb.org/3/trending/all/week?api_key=9801b6b0548ad57581d111ea690c85c8&include_adult=false&page=`; } else if (matchesKeyword(keyword, keywordGroups.topRatedMovie)) { baseUrl = `https://api.themoviedb.org/3/movie/top_rated?api_key=9801b6b0548ad57581d111ea690c85c8&include_adult=false&page=`; } else if (matchesKeyword(keyword, keywordGroups.topRatedTV)) { baseUrl = `https://api.themoviedb.org/3/tv/top_rated?api_key=9801b6b0548ad57581d111ea690c85c8&include_adult=false&page=`; } else if (matchesKeyword(keyword, keywordGroups.popularMovie)) { baseUrl = `https://api.themoviedb.org/3/movie/popular?api_key=9801b6b0548ad57581d111ea690c85c8&include_adult=false&page=`; } else if (matchesKeyword(keyword, keywordGroups.popularTV)) { baseUrl = `https://api.themoviedb.org/3/tv/popular?api_key=9801b6b0548ad57581d111ea690c85c8&include_adult=false&page=`; } else { baseUrl = `https://api.themoviedb.org/3/search/multi?api_key=9801b6b0548ad57581d111ea690c85c8&query=${encodedKeyword}&include_adult=false&page=`; } let dataResults = []; if (baseUrl) { const pagePromises = Array.from({ length: 5 }, (_, i) => soraFetch(baseUrl + (i + 1)).then(r => r.json()) ); const pages = await Promise.all(pagePromises); dataResults = pages.flatMap(p => p.results || []); } if (dataResults.length > 0) { function slugify(str) { return (str || "") .toLowerCase() .replace(/[^a-z0-9\s-]/g, "") .replace(/\s+/g, "-") .replace(/-+/g, "-"); } transformedResults = transformedResults.concat( dataResults .map(result => { if (result.media_type === "movie" || result.title) { const title = result.title || result.name || result.original_title || result.original_name || "Untitled"; const slug = slugify(title); return { title, image: result.poster_path ? `https://image.tmdb.org/t/p/w500${result.poster_path}` : "", href: `movie/${result.id}-${slug}`, }; } else if (result.media_type === "tv" || result.name) { const title = result.name || result.title || result.original_name || result.original_title || "Untitled"; const slug = slugify(title); return { title, image: result.poster_path ? `https://image.tmdb.org/t/p/w500${result.poster_path}` : "", href: `tv/${result.id}-${slug}`, }; } }) .filter(Boolean) .filter(result => result.title !== "Overflow") .filter(result => result.title !== "My Marriage Partner Is My Student, a Cocky Troublemaker") .filter(r => !shouldFilter || r.title.toLowerCase().includes(keyword.toLowerCase())) ); } console.log("Transformed Results: " + JSON.stringify(transformedResults)); return JSON.stringify(transformedResults); } catch (error) { console.log("Fetch error in searchResults: " + error); return JSON.stringify([{ title: "Error", image: "", href: "" }]); } } function matchesKeyword(keyword, commands) { const lower = keyword.toLowerCase(); return commands.some(cmd => lower.startsWith(cmd.toLowerCase())); } async function extractDetails(url) { try { if(url.includes('movie')) { const match = url.match(/movie\/([^\/]+)/); if (!match) throw new Error("Invalid URL format"); const movieId = match[1]; const responseText = await soraFetch(`https://api.themoviedb.org/3/movie/${movieId}?api_key=ad301b7cc82ffe19273e55e4d4206885`); const data = await responseText.json(); const transformedResults = [{ description: data.overview || 'No description available', aliases: `If stream fails try again!`, airdate: `If stream fails try again!` }]; return JSON.stringify(transformedResults); } else if(url.includes('tv')) { const match = url.match(/tv\/([^\/]+)/); if (!match) throw new Error("Invalid URL format"); const showId = match[1]; const responseText = await soraFetch(`https://api.themoviedb.org/3/tv/${showId}?api_key=ad301b7cc82ffe19273e55e4d4206885`); const data = await responseText.json(); const transformedResults = [{ description: data.overview || 'No description available', aliases: `If stream fails try again!`, airdate: `If stream fails try again!` }]; console.log(JSON.stringify(transformedResults)); return JSON.stringify(transformedResults); } else { throw new Error("Invalid URL format"); } } catch (error) { console.log('Details error: ' + error); return JSON.stringify([{ description: 'Error loading description', aliases: 'Duration: Unknown', airdate: 'Aired/Released: Unknown' }]); } } async function extractEpisodes(url) { try { function slugify(str) { return (str || "") .toLowerCase() .replace(/[^a-z0-9\s-]/g, "") .replace(/\s+/g, "-") .replace(/-+/g, "-"); } if(url.startsWith('movie/')) { const match = url.match(/movie\/(\d+)-(.+)/); if (!match) throw new Error("Invalid movie URL format"); const movieId = match[1]; const titleSlug = match[2]; const movie = [ { href: `movie/${movieId}-${titleSlug}`, number: 1, title: "Full Movie" } ]; return JSON.stringify(movie); } else if(url.startsWith('tv/')) { const match = url.match(/tv\/(\d+)-(.+)/); if (!match) throw new Error("Invalid TV URL format"); const showId = match[1]; const titleSlug = match[2]; const showResponseText = await soraFetch(`https://api.themoviedb.org/3/tv/${showId}?api_key=ad301b7cc82ffe19273e55e4d4206885`); const showData = await showResponseText.json(); let allEpisodes = []; for (const season of showData.seasons) { const seasonNumber = season.season_number; if(seasonNumber === 0) continue; const seasonResponseText = await soraFetch(`https://api.themoviedb.org/3/tv/${showId}/season/${seasonNumber}?api_key=ad301b7cc82ffe19273e55e4d4206885`); const seasonData = await seasonResponseText.json(); if (seasonData.episodes && seasonData.episodes.length) { const episodes = seasonData.episodes.map(episode => ({ href: `tv/${showId}-${seasonNumber}-${episode.episode_number}-${titleSlug}`, number: episode.episode_number, title: episode.name || "" })); allEpisodes = allEpisodes.concat(episodes); } } return JSON.stringify(allEpisodes); } else { throw new Error("Invalid URL format"); } } catch (error) { console.log('Fetch error in extractEpisodes: ' + error); return JSON.stringify([]); } } async function extractNextActionIdentifier(watchUrl) { const htmlResponse = await soraFetch(watchUrl); const htmlText = await htmlResponse.text(); const layoutMatch = htmlText.match(/]*src="([^"]*app\/watch\/movie\/[^"]*layout-[^"]*\.js)"[^>]*><\/script>]*src="([^"]*\.js)"[^>]*><\/script>]*src="[^"]*app\/watch\/(movie|tv)\/[^"]*layout-[^"]*\.js"/); if (!beforeLayoutMatch || !beforeLayoutMatch[1]) { throw new Error("error 2"); } let targetUrl = beforeLayoutMatch[1]; if (targetUrl.startsWith('/_next/')) { targetUrl = 'https://mapple.uk' + targetUrl; } else if (!targetUrl.startsWith('http')) { targetUrl = 'https://mapple.uk/' + targetUrl; } try { const response = await soraFetch(targetUrl); const text = await response.text(); let actionMatch = text.match(/createServerReference\)\("([a-f0-9]{40,})"[^"]*"getStreamUrl/); if (!actionMatch) { actionMatch = text.match(/createServerReference\)\("([a-f0-9]{40,})"/); } if (!actionMatch) { actionMatch = text.match(/"([a-f0-9]{40,})"[^"]*"getStreamUrl/); } if (actionMatch && actionMatch[1]) { return actionMatch[1]; } } catch (e) { throw new Error("error 3: " + e); } throw new Error("error 4"); } async function extractStreamUrl(ID) { const actionIdentifier = await extractNextActionIdentifier("https://mapple.uk/watch/movie/1061474-superman"); console.log("Action Identifier: " + actionIdentifier); const sessionResponse = await soraFetch("https://enc-dec.app/api/enc-mapple"); const sessionData = await sessionResponse.json(); console.log("Session Data: " + JSON.stringify(sessionData)); const sessionID = sessionData.result.sessionId || "N/A"; if (ID.startsWith('/movie/') || ID.startsWith('movie/')) { const match = ID.match(/movie\/(\d+)/); const tmdbID = match ? match[1] : ''; console.log("Extracted TMDB ID: " + tmdbID); const headers = { "Content-Type": "text/plain", "next-action": actionIdentifier }; const postData = `[{"mediaId":${tmdbID},"mediaType":"movie","tv_slug":"","source":"mapple","useFallbackVideo":false,"sessionId":"${sessionID}"}]`; const response = await fetchv2("https://mapple.uk/watch/"+ ID, headers, "POST", postData); const data = await response.text(); console.log("Stream data: " + data); const lines = data.split('\n'); let streamData = null; for (const line of lines) { if (line.includes('"success":true')) { streamData = JSON.parse(line.substring(line.indexOf('{'))); break; } } if (streamData && streamData.data && streamData.data.stream_url) { const m3u8Response = await soraFetch(streamData.data.stream_url, { headers: { "referer": "https://mapple.uk/", "origin": "https://mapple.uk" } }); const m3u8Text = await m3u8Response.text(); console.log("M3U8 Playlist: " + m3u8Text); const streams = []; const lines = m3u8Text.split('\n'); const resolutionNames = { "3840x2160": "4K", "1920x1080": "1080p", "1280x720": "720p", "640x360": "360p" }; for (let i = 0; i < lines.length; i++) { if (lines[i].startsWith('#EXT-X-STREAM-INF')) { const resolutionMatch = lines[i].match(/RESOLUTION=(\d+x\d+)/); const streamUrl = lines[i + 1]; if (resolutionMatch && streamUrl && streamUrl.trim()) { const resolution = resolutionMatch[1]; const friendlyName = resolutionNames[resolution] || resolution; streams.push({ title: friendlyName, streamUrl: streamUrl.trim(), headers: { "referer": "https://mapple.uk/", "origin": "https://mapple.uk" } }); } } } let englishSubtitleUrl = ""; try { const subResponse = await soraFetch(`https://mapple.uk/api/subtitles?id=${tmdbID}&mediaType=movie`); const subData = await subResponse.json(); const englishSub = subData.find(sub => sub.language === "en"); if (englishSub) { englishSubtitleUrl = englishSub.url; } } catch (e) { englishSubtitleUrl = ""; } console.log("English Subtitle URL: " + englishSubtitleUrl); console.log("Extracted streams: " + JSON.stringify(streams)); return JSON.stringify({ streams: streams.length > 0 ? streams : [ { title: "Mapple Server", streamUrl: streamData.data.stream_url, headers: { "referer": "https://mapple.uk/", "origin": "https://mapple.uk" } } ], subtitle: englishSubtitleUrl || "" }); } else { throw new Error("Failed to extract stream URL"); } } else if (ID.startsWith('/tv/') || ID.startsWith('tv/')) { const match = ID.match(/tv\/(\d+)-(\d+)-(\d+)/); if (!match) throw new Error("Invalid TV URL format"); const tmdbID = match[1]; const seasonNumber = match[2]; const episodeNumber = match[3]; const headers = { "Content-Type": "text/plain", "next-action": actionIdentifier }; const postData = `[{"mediaId":${tmdbID},"mediaType":"tv","tv_slug":"${seasonNumber}-${episodeNumber}","source":"mapple","useFallbackVideo":false,"sessionId":"${sessionID}"}]`; const response = await fetchv2("https://mapple.uk/watch/"+ ID, headers, "POST", postData); const data = await response.text(); console.log("Stream data: " + data); const lines = data.split('\n'); let streamData = null; for (const line of lines) { if (line.includes('"success":true')) { streamData = JSON.parse(line.substring(line.indexOf('{'))); break; } } if (streamData && streamData.data && streamData.data.stream_url) { const m3u8Response = await soraFetch(streamData.data.stream_url, { headers: { "referer": "https://mapple.uk/", "origin": "https://mapple.uk" } }); const m3u8Text = await m3u8Response.text(); console.log("M3U8 Playlist: " + m3u8Text); const streams = []; const lines = m3u8Text.split('\n'); const resolutionNames = { "3840x2160": "4K", "1920x1080": "1080p", "1280x720": "720p", "640x360": "360p" }; for (let i = 0; i < lines.length; i++) { if (lines[i].startsWith('#EXT-X-STREAM-INF')) { const resolutionMatch = lines[i].match(/RESOLUTION=(\d+x\d+)/); const streamUrl = lines[i + 1]; if (resolutionMatch && streamUrl && streamUrl.trim()) { const resolution = resolutionMatch[1]; const friendlyName = resolutionNames[resolution] || resolution; streams.push({ title: friendlyName, streamUrl: streamUrl.trim(), headers: { "referer": "https://mapple.uk/", "origin": "https://mapple.uk" } }); } } } let englishSubtitleUrl = ""; try { const subResponse = await soraFetch(`https://mapple.uk/api/subtitles?id=${tmdbID}&mediaType=tv&season=${seasonNumber}&episode=${episodeNumber}`); const subData = await subResponse.json(); const englishSub = subData.find(sub => sub.language === "en"); if (englishSub) { englishSubtitleUrl = englishSub.url; } } catch (e) { englishSubtitleUrl = ""; } console.log("English Subtitle URL: " + englishSubtitleUrl); console.log("Extracted streams: " + JSON.stringify(streams)); return JSON.stringify({ streams: streams.length > 0 ? streams : [ { title: "Mapple Server", streamUrl: streamData.data.stream_url, headers: { "referer": "https://mapple.uk/", "origin": "https://mapple.uk" } } ], subtitle: englishSubtitleUrl || "" }); } else { throw new Error("Failed to extract stream URL"); } } } async function soraFetch(url, options = { headers: {}, method: 'GET', body: null, encoding: 'utf-8' }) { try { return await fetchv2( url, options.headers ?? {}, options.method ?? 'GET', options.body ?? null, true, options.encoding ?? 'utf-8' ); } catch(e) { try { return await fetch(url, options); } catch(error) { return null; } } }