calculus / src /aither.js
no1b4me's picture
Upload 12 files
8740315 verified
import https from 'https';
const API_CONFIG = {
baseUrl: 'https://aither.cc',
apiKey: 'dRkYjNbT0S4ObFArTH1FXUTLPGsFRV4SqpPwwwEIo0IAtzsKhfF3zkPpTpCOcyBzab5q068680zVeHPk644q9sshtqpMv5mglfou'
};
const agent = new https.Agent({
rejectUnauthorized: true,
timeout: 30000,
keepAlive: true
});
async function fetchWithTimeout(url, options = {}, timeout = 30000) {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeout);
try {
const response = await fetch(url, {
...options,
signal: controller.signal,
agent
});
clearTimeout(timeoutId);
return response;
} catch (error) {
clearTimeout(timeoutId);
throw error;
}
}
function isCorrectEpisode(title, season, episode) {
if (!title) return false;
const seasonStr = season.toString().padStart(2, '0');
const episodeStr = episode.toString().padStart(2, '0');
// Common episode patterns
const patterns = [
new RegExp(`S${seasonStr}E${episodeStr}`, 'i'), // S01E01
new RegExp(`${season}x${episodeStr}`, 'i'), // 1x01
new RegExp(`[. ]${season}${episodeStr}[. ]`), // .101. or ' 101 '
new RegExp(`Season ${season} Episode ${episode}`, 'i'), // Season 1 Episode 1
new RegExp(`Season.?${season}.?Episode.?${episode}`, 'i') // Season1Episode1 or Season.1.Episode.1
];
return patterns.some(pattern => pattern.test(title));
}
function extractSeasonEpisode(query) {
const match = query.match(/S(\d{1,2})E(\d{1,2})/i);
if (match) {
return {
season: parseInt(match[1]),
episode: parseInt(match[2])
};
}
return null;
}
async function fetchTorrents(searchQuery, type = 'movie') {
console.log('\n🔄 Fetching from Aither API for:', searchQuery);
try {
let params;
let episodeInfo = null;
if (type === 'series') {
// Extract season and episode info
episodeInfo = extractSeasonEpisode(searchQuery);
if (!episodeInfo) {
console.log('No valid season/episode info found in query');
return [];
}
// Extract show name for search
const showName = searchQuery.split(/S\d{2}E\d{2}/i)[0].trim();
console.log('Series search:', { showName, season: episodeInfo.season, episode: episodeInfo.episode });
params = new URLSearchParams({
name: showName,
sortField: 'created_at',
sortDirection: 'desc',
perPage: '100'
});
} else {
// For movies, use IMDB ID if available, otherwise use title
const isImdbId = searchQuery.startsWith('tt');
params = new URLSearchParams({
[isImdbId ? 'imdbId' : 'name']: isImdbId ? searchQuery.replace('tt', '') : searchQuery,
sortField: 'created_at',
sortDirection: 'desc',
perPage: '100'
});
}
const url = `${API_CONFIG.baseUrl}/api/torrents/filter?${params}`;
console.log('Request URL:', url);
const response = await fetchWithTimeout(url, {
headers: {
'Host': 'aither.cc',
'Authorization': `Bearer ${API_CONFIG.apiKey}`,
'Accept': '*/*',
'Accept-Language': '*',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
'Accept-Encoding': 'gzip, deflate'
},
method: 'GET',
compress: true
});
if (!response.ok) {
throw new Error(`API request failed: ${response.status}`);
}
const data = await response.json();
if (!data.data || !Array.isArray(data.data)) {
console.log('No torrents found');
return [];
}
console.log(`Found ${data.data.length} initial results`);
const streams = await Promise.all(data.data.map(async (item, index) => {
try {
console.log(`\nProcessing item ${index + 1}/${data.data.length}:`, item.attributes.name);
// Skip if no video files
const hasVideoFiles = item.attributes.files.some(file =>
/\.(mp4|mkv|avi|mov|wmv)$/i.test(file.name)
);
if (!hasVideoFiles) {
console.log('No video files found in:', item.attributes.name);
return null;
}
// For series, check if it matches the requested episode
if (type === 'series' && episodeInfo) {
if (!isCorrectEpisode(item.attributes.name, episodeInfo.season, episodeInfo.episode)) {
console.log('Episode mismatch:', item.attributes.name);
return null;
}
}
// Find the main video file (largest video file)
const videoFiles = item.attributes.files
.filter(file => /\.(mp4|mkv|avi|mov|wmv)$/i.test(file.name))
.sort((a, b) => b.size - a.size);
const mainFile = videoFiles[0];
// Extract quality from name
const qualityMatch = item.attributes.name.match(/\b(2160p|1080p|720p|4k|uhd)\b/i);
const quality = qualityMatch ? qualityMatch[1].toLowerCase() : '';
return {
magnetLink: `magnet:?xt=urn:btih:${item.attributes.info_hash}`,
filename: mainFile.name,
websiteTitle: item.attributes.name,
quality: quality,
size: formatSize(item.attributes.size),
source: 'Aither',
infoHash: item.attributes.info_hash,
mainFileSize: mainFile.size,
seeders: item.attributes.seeders || 0,
leechers: item.attributes.leechers || 0
};
} catch (error) {
console.error(`Error processing item ${index + 1}:`, error);
return null;
}
}));
const validStreams = streams.filter(Boolean);
console.log(`✅ Processed ${validStreams.length} valid streams`);
// Sort by quality and seeders
validStreams.sort((a, b) => {
const qualityOrder = { '2160p': 4, '4k': 4, 'uhd': 4, '1080p': 3, '720p': 2 };
const qualityDiff = (qualityOrder[b.quality] || 0) - (qualityOrder[a.quality] || 0);
return qualityDiff || b.seeders - a.seeders;
});
if (validStreams.length > 0) {
console.log('Sample stream:', {
name: validStreams[0].websiteTitle,
quality: validStreams[0].quality,
size: validStreams[0].size
});
}
return validStreams;
} catch (error) {
console.error('❌ Error fetching from Aither:', error);
console.error('Error details:', {
message: error.message,
cause: error.cause,
code: error.code,
syscall: error.syscall
});
return [];
}
}
function formatSize(bytes) {
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
if (bytes === 0) return '0 Bytes';
const i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024)));
return Math.round(bytes / Math.pow(1024, i) * 100) / 100 + ' ' + sizes[i];
}
export { fetchTorrents };