mikmc5/bitsa
0
1// Update your jac-server.js to use the new Jacred adapter2import express from 'express';3import { fetchRSSFeeds } from './rutor.js'; // Your updated Jacred adapter4 5const app = express();6const port = process.env.PORT || 7860;7const TMDB_API_KEY = 'f051e7366c6105ad4f9aafe4733d9dae';8const TMDB_BASE_URL = 'https://api.themoviedb.org/3';9 10app.use(express.json());11 12function standardizeEpisodeFormat(query) {13 // Handle 1x01 format14 const altMatch = query.match(/^(.*?)(\d{1,2})x(\d{1,2})/i);15 if (altMatch) {16 const [_, title, season, episode] = altMatch;17 const paddedSeason = String(season).padStart(2, '0');18 const paddedEpisode = String(episode).padStart(2, '0');19 return `${title.trim()} S${paddedSeason}E${paddedEpisode}`;20 }21 return query;22}23 24function isExactEpisodeMatch(torrentTitle, showTitle, seasonNum, episodeNum) {25 if (!torrentTitle || !showTitle) return false;26 27 // Clean up title28 torrentTitle = torrentTitle.replace(/\[.*?\]/g, '')29 .replace(/\(.*?\)/g, '')30 .replace(/\s+/g, ' ')31 .trim();32 33 // Normalize the titles34 const normalizedTorrentTitle = torrentTitle.toLowerCase();35 const normalizedShowTitle = showTitle.toLowerCase().trim();36 37 // Split show title into significant words38 const showWords = normalizedShowTitle.split(' ')39 .filter(word => word.length > 2)40 .filter(word => !['the', 'and', 'or', 'in', 'on', 'at', 'to'].includes(word));41 42 const hasAllShowWords = showWords.every(word => 43 normalizedTorrentTitle.includes(word)44 );45 46 if (!hasAllShowWords) return false;47 48 // Format season and episode numbers49 const seasonStr = String(seasonNum).padStart(2, '0');50 const episodeStr = String(episodeNum).padStart(2, '0');51 52 // Common episode patterns53 const patterns = [54 new RegExp(`s${seasonStr}e${episodeStr}`, 'i'),55 new RegExp(`${seasonNum}x${episodeStr}`, 'i'),56 new RegExp(`[^0-9]${seasonNum}${episodeStr}[^0-9]`, 'i'),57 new RegExp(`season\\s*${seasonNum}\\s*episode\\s*${episodeNum}`, 'i'),58 new RegExp(`s${seasonStr}\\.?e${episodeStr}`, 'i'),59 new RegExp(`${seasonStr}${episodeStr}`, 'i')60 ];61 62 return patterns.some(pattern => pattern.test(normalizedTorrentTitle));63}64 65function isExactMovieMatch(torrentTitle, movieTitle, year) {66 // Clean title67 torrentTitle = torrentTitle.replace(/\[.*?\]/g, '')68 .replace(/\(.*?\)/g, '')69 .replace(/\s+/g, ' ')70 .trim();71 72 const normalizedTorrentTitle = torrentTitle.toLowerCase();73 const normalizedMovieTitle = movieTitle.toLowerCase();74 75 const movieWords = normalizedMovieTitle.split(' ')76 .filter(word => word.length > 2)77 .filter(word => !['the', 'and', 'or', 'in', 'on', 'at', 'to'].includes(word));78 79 const hasAllMovieWords = movieWords.every(word => 80 normalizedTorrentTitle.includes(word)81 );82 83 if (!hasAllMovieWords) return false;84 85 const yearMatch = torrentTitle.match(/(?:19|20)\d{2}/);86 return yearMatch && yearMatch[0] === year.toString();87}88 89async function getTMDBDetails(tmdbId, type = 'movie') {90 try {91 const response = await fetch(`${TMDB_BASE_URL}/${type}/${tmdbId}?api_key=${TMDB_API_KEY}&append_to_response=external_ids`);92 if (!response.ok) throw new Error(`TMDB API error: ${response.status}`);93 return await response.json();94 } catch (error) {95 console.error('TMDB fetch error:', error);96 return null;97 }98}99 100async function getTVShowDetails(tmdbId, seasonNum, episodeNum) {101 try {102 const [showResponse, episodeResponse] = await Promise.all([103 fetch(`${TMDB_BASE_URL}/tv/${tmdbId}?api_key=${TMDB_API_KEY}&append_to_response=external_ids`),104 fetch(`${TMDB_BASE_URL}/tv/${tmdbId}/season/${seasonNum}/episode/${episodeNum}?api_key=${TMDB_API_KEY}`)105 ]);106 107 if (!showResponse.ok || !episodeResponse.ok) {108 throw new Error('TMDB API error');109 }110 111 const [showData, episodeData] = await Promise.all([112 showResponse.json(),113 episodeResponse.json()114 ]);115 116 return {117 showTitle: showData.name,118 episodeTitle: episodeData.name,119 seasonNumber: seasonNum,120 episodeNumber: episodeNum,121 airDate: episodeData.air_date,122 imdbId: showData.external_ids?.imdb_id123 };124 } catch (error) {125 console.error('TMDB fetch error:', error);126 return null;127 }128}129 130async function handleSearch({ query, type }) {131 if (!query) throw new Error('Missing required parameter: query');132 if (!['movie', 'series'].includes(type)) throw new Error('Invalid type. Must be either "movie" or "series"');133 134 let searchQuery = query;135 let exactMatchParams = {};136 let tmdbData = null;137 138 if (type === 'series') {139 // Handle TMDB ID format: "tmdb:12345:1:1"140 const tmdbMatch = query.match(/^tmdb:(\d+):(\d+):(\d+)$/);141 if (tmdbMatch) {142 const [_, tmdbId, seasonNum, episodeNum] = tmdbMatch;143 tmdbData = await getTVShowDetails(tmdbId, parseInt(seasonNum), parseInt(episodeNum));144 if (tmdbData) {145 exactMatchParams = {146 title: tmdbData.showTitle,147 season: parseInt(seasonNum),148 episode: parseInt(episodeNum)149 };150 searchQuery = `${tmdbData.showTitle} S${String(seasonNum).padStart(2, '0')}E${String(episodeNum).padStart(2, '0')}`;151 }152 }153 // Handle IMDB ID format: "tt1234567:1:1"154 else if (query.startsWith('tt')) {155 const imdbMatch = query.match(/^(tt\d+):(\d+):(\d+)$/);156 if (imdbMatch) {157 const [_, imdbId, seasonNum, episodeNum] = imdbMatch;158 const findResponse = await fetch(159 `${TMDB_BASE_URL}/find/${imdbId}?api_key=${TMDB_API_KEY}&external_source=imdb_id`160 );161 if (findResponse.ok) {162 const findData = await findResponse.json();163 if (findData.tv_results?.[0]) {164 const tmdbId = findData.tv_results[0].id;165 tmdbData = await getTVShowDetails(tmdbId, parseInt(seasonNum), parseInt(episodeNum));166 if (tmdbData) {167 exactMatchParams = {168 title: tmdbData.showTitle,169 season: parseInt(seasonNum),170 episode: parseInt(episodeNum)171 };172 searchQuery = `${tmdbData.showTitle} S${String(seasonNum).padStart(2, '0')}E${String(episodeNum).padStart(2, '0')}`;173 }174 }175 }176 }177 }178 else {179 searchQuery = standardizeEpisodeFormat(query);180 const seasonEpisodeMatch = searchQuery.match(/^(.*?)S(\d{1,2})E(\d{1,2})/i);181 if (seasonEpisodeMatch) {182 exactMatchParams = {183 title: seasonEpisodeMatch[1].trim(),184 season: parseInt(seasonEpisodeMatch[2]),185 episode: parseInt(seasonEpisodeMatch[3])186 };187 }188 }189 } else if (type === 'movie') {190 // Handle TMDB ID format191 if (query.startsWith('tmdb:')) {192 const tmdbId = query.replace('tmdb:', '');193 tmdbData = await getTMDBDetails(tmdbId, 'movie');194 if (tmdbData) {195 exactMatchParams = {196 title: tmdbData.title,197 year: new Date(tmdbData.release_date).getFullYear()198 };199 searchQuery = `${tmdbData.title} (${exactMatchParams.year})`;200 }201 }202 // Handle IMDB ID format203 else if (query.startsWith('tt')) {204 const findResponse = await fetch(205 `${TMDB_BASE_URL}/find/${query}?api_key=${TMDB_API_KEY}&external_source=imdb_id`206 );207 if (findResponse.ok) {208 const findData = await findResponse.json();209 if (findData.movie_results?.[0]) {210 tmdbData = findData.movie_results[0];211 exactMatchParams = {212 title: tmdbData.title,213 year: new Date(tmdbData.release_date).getFullYear()214 };215 searchQuery = `${tmdbData.title} (${exactMatchParams.year})`;216 }217 }218 }219 else {220 const movieYearMatch = query.match(/^(.+?)(?:\s*\(?(\d{4})\)?)?$/);221 if (movieYearMatch) {222 exactMatchParams = {223 title: movieYearMatch[1].trim(),224 year: movieYearMatch[2] ? parseInt(movieYearMatch[2]) : null225 };226 }227 }228 }229 230 // ✅ Fetch results using the updated Jacred adapter231 let results = await fetchRSSFeeds(searchQuery, type);232 233 // Filter results based on exact match parameters234 if (type === 'series' && exactMatchParams.season !== undefined) {235 results = results.filter(result => 236 isExactEpisodeMatch(237 result.websiteTitle,238 exactMatchParams.title,239 exactMatchParams.season,240 exactMatchParams.episode241 )242 );243 } else if (type === 'movie' && exactMatchParams.year) {244 results = results.filter(result =>245 isExactMovieMatch(246 result.websiteTitle,247 exactMatchParams.title,248 exactMatchParams.year249 )250 );251 }252 253 return {254 query: searchQuery,255 originalQuery: query,256 type,257 tmdbData,258 results: results.map(result => ({259 title: result.websiteTitle,260 quality: result.quality,261 size: result.size,262 seeders: result.seeders || 0, // ✅ Now using actual seeder data from Jacred263 leechers: result.peers || 0, // ✅ Now using actual peer data from Jacred 264 magnetLink: result.magnetLink265 }))266 };267}268 269app.get('/api/search', async (req, res) => {270 try {271 const result = await handleSearch({ query: req.query.query, type: req.query.type });272 res.json(result);273 } catch (error) {274 res.status(500).json({ error: 'Internal server error', message: error.message });275 }276});277 278app.post('/api/search', async (req, res) => {279 try {280 const result = await handleSearch(req.body);281 res.json(result);282 } catch (error) {283 res.status(500).json({ error: 'Internal server error', message: error.message });284 }285});286 287app.listen(port, () => {288 console.log(`Server running at http://localhost:${port}`);289 console.log(`🚀 Using Jacred API for Rutor torrents`);290 if (!TMDB_API_KEY) {291 console.warn('Warning: TMDB_API_KEY not set. TMDB features will not work.');292 }293});