const axios = require('axios'); function createXtreamModule(xtreamBaseUrl, username, password) { const xtreamApiUrl = `${xtreamBaseUrl}/player_api.php`; async function getDetailsFromCinemeta(ttnumber, type = 'movie') { const url = `https://v3-cinemeta.strem.io/meta/${type}/${ttnumber}.json`; try { const response = await axios.get(url); const { name: title, season, episode } = response.data.meta; return { title, season, episode }; } catch (error) { console.error('Error fetching data from Cinemeta:', error); throw error; } } async function getVodStreams() { const params = { username, password, action: 'get_vod_streams' }; try { const response = await axios.get(xtreamApiUrl, { params }); return response.data; } catch (error) { console.error('Error fetching VOD streams from Xtream Codes:', error); throw error; } } async function getSeries() { const params = { username, password, action: 'get_series' }; try { const response = await axios.get(xtreamApiUrl, { params }); return response.data; } catch (error) { console.error('Error fetching series from Xtream Codes:', error); throw error; } } async function getSeriesInfo(seriesId) { const params = { username, password, action: 'get_series_info', series_id: seriesId }; try { const response = await axios.get(xtreamApiUrl, { params }); return response.data; } catch (error) { console.error('Error fetching series info from Xtream Codes:', error); throw error; } } function normalizeTitle(title) { // Remove the year and any surrounding parentheses or brackets let normalized = title.replace(/\s*\([^)]*\)|\s*\[[^\]]*\]|\s*\d{4}$/g, ''); // Convert to lowercase and remove all non-alphanumeric characters normalized = normalized.toLowerCase().replace(/[^a-z0-9]/g, ''); return normalized; } function findMatchingContent(contentList, title) { const normalizedTitle = normalizeTitle(title); console.log(`Searching for normalized title: ${normalizedTitle}`); return contentList.find(item => { const itemNormalizedTitle = normalizeTitle(item.name); console.log(`Comparing with: ${itemNormalizedTitle}`); return itemNormalizedTitle === normalizedTitle; }); } function findBestMatchingContent(contentList, title) { const normalizedTitle = normalizeTitle(title); console.log(`Searching for best match for normalized title: ${normalizedTitle}`); let bestMatch = null; let highestSimilarity = 0; contentList.forEach(item => { const itemNormalizedTitle = normalizeTitle(item.name); const similarity = calculateSimilarity(normalizedTitle, itemNormalizedTitle); console.log(`Comparing with: ${itemNormalizedTitle}, Similarity: ${similarity}`); if (similarity > highestSimilarity) { highestSimilarity = similarity; bestMatch = item; } }); console.log(`Best match found: ${bestMatch ? bestMatch.name : 'None'} with similarity: ${highestSimilarity}`); return highestSimilarity > 0.8 ? bestMatch : null; // Adjust threshold as needed } function calculateSimilarity(str1, str2) { const len = Math.max(str1.length, str2.length); const editDistance = levenshteinDistance(str1, str2); return 1 - editDistance / len; } function levenshteinDistance(str1, str2) { const m = str1.length; const n = str2.length; const dp = Array(m + 1).fill(null).map(() => Array(n + 1).fill(null)); for (let i = 0; i <= m; i++) { dp[i][0] = i; } for (let j = 0; j <= n; j++) { dp[0][j] = j; } for (let i = 1; i <= m; i++) { for (let j = 1; j <= n; j++) { if (str1[i - 1] === str2[j - 1]) { dp[i][j] = dp[i - 1][j - 1]; } else { dp[i][j] = Math.min( dp[i - 1][j - 1] + 1, dp[i][j - 1] + 1, dp[i - 1][j] + 1 ); } } } return dp[m][n]; } function buildStreamUrl(content) { let url; if (content.stream_type) { // For movies url = `${xtreamBaseUrl}/${content.stream_type}/${username}/${password}/${content.stream_id}.${content.container_extension}`; } else if (content.id) { // For series episodes url = `${xtreamBaseUrl}/series/${username}/${password}/${content.id}.${content.container_extension}`; } console.log('Constructed stream URL:', url); return url; } return async function(input) { try { const [ttNumber, season, episode] = input.split(':'); const type = ttNumber.startsWith('tt') ? (season && episode ? 'series' : 'movie') : 'series'; const { title } = await getDetailsFromCinemeta(ttNumber, type); console.log(`Title from Cinemeta: ${title} (${type})`); let contentList, matchingContent, contentInfo, specificEpisode; if (type === 'movie') { contentList = await getVodStreams(); matchingContent = findMatchingContent(contentList, title); if (!matchingContent) { console.log('Exact match not found, trying best match...'); matchingContent = findBestMatchingContent(contentList, title); } if (matchingContent) { const contentUrl = buildStreamUrl(matchingContent); return { title: matchingContent.name, contentUrl, type }; } else { console.log(`No matching content found for movie: ${title}`); return null; } } else { contentList = await getSeries(); matchingContent = findMatchingContent(contentList, title); if (!matchingContent) { console.log('Exact match not found, trying best match...'); matchingContent = findBestMatchingContent(contentList, title); } if (matchingContent) { contentInfo = await getSeriesInfo(matchingContent.series_id); if (season && episode) { specificEpisode = contentInfo.episodes[season]?.find(ep => ep.episode_num == episode); if (specificEpisode) { const episodeUrl = buildStreamUrl(specificEpisode); return { title: matchingContent.name, type, seriesId: matchingContent.series_id, season, episode, episodeTitle: specificEpisode.title, contentUrl: episodeUrl }; } else { console.log(`Episode not found: ${title} S${season}E${episode}`); return null; } } else { return { title: matchingContent.name, type, seriesId: matchingContent.series_id, seasons: contentInfo.seasons, episodes: contentInfo.episodes }; } } else { console.log(`No matching content found for series: ${title}`); return null; } } } catch (error) { console.error('Error:', error.message); throw error; } }; } module.exports = { createXtreamModule };