|
import { VIDEO_EXTENSIONS } from './const.js'; |
|
|
|
export function isVideo(filename) { |
|
if (!filename) return false; |
|
return VIDEO_EXTENSIONS.some(ext => |
|
filename.toLowerCase().endsWith(ext) |
|
); |
|
} |
|
|
|
export function extractInfoHash(magnetLink) { |
|
if (!magnetLink) return null; |
|
try { |
|
const match = magnetLink.match(/xt=urn:btih:([^&]+)/i); |
|
return match ? match[1].toLowerCase() : null; |
|
} catch (error) { |
|
console.error('Error extracting info hash:', error); |
|
return null; |
|
} |
|
} |
|
|
|
export async function wait(ms) { |
|
return new Promise(resolve => setTimeout(resolve, ms)); |
|
} |
|
|
|
export function base64Encode(str) { |
|
if (!str) return ''; |
|
try { |
|
return Buffer.from(str).toString('base64') |
|
.replace(/\+/g, '-') |
|
.replace(/\//g, '_') |
|
.replace(/=/g, ''); |
|
} catch (error) { |
|
console.error('Error encoding to base64:', error); |
|
return ''; |
|
} |
|
} |
|
|
|
export function base64Decode(str) { |
|
if (!str) return ''; |
|
try { |
|
str = str.replace(/-/g, '+').replace(/_/g, '/'); |
|
while (str.length % 4) str += '='; |
|
return Buffer.from(str, 'base64').toString('ascii'); |
|
} catch (error) { |
|
console.error('Error decoding from base64:', error); |
|
return ''; |
|
} |
|
} |
|
|
|
export function findBestFile(files, metadata) { |
|
if (!Array.isArray(files) || !files?.length) return null; |
|
if (files.length === 1) return files[0]; |
|
|
|
|
|
if (!metadata?.meta?.name) { |
|
const largestFile = files.sort((a, b) => (b.size || 0) - (a.size || 0))[0]; |
|
console.log('Selected largest file (no metadata available):', largestFile.name || largestFile.path); |
|
return largestFile; |
|
} |
|
|
|
const title = metadata.meta.name.toLowerCase(); |
|
const year = new Date(metadata.meta.released).getFullYear(); |
|
const yearStr = year.toString(); |
|
|
|
|
|
const titleParts = title |
|
.replace(/[^a-z0-9\s]/g, '') |
|
.split(/\s+/) |
|
.filter(part => !['and', 'the', 'of'].includes(part)); |
|
|
|
console.log(`Looking for match with title parts:`, titleParts, `(${year})`); |
|
|
|
|
|
const scoredFiles = files.map(file => { |
|
let score = 0; |
|
const filename = (file.name?.toLowerCase() || file.path?.toLowerCase() || ''); |
|
|
|
|
|
if (!filename.match(/\.(mp4|mkv|avi|mov|wmv|m4v|ts)$/i)) { |
|
return { file, score: -100, filename }; |
|
} |
|
|
|
|
|
const matchingParts = titleParts.filter(part => filename.includes(part)); |
|
score += (matchingParts.length / titleParts.length) * 100; |
|
|
|
if (matchingParts.length > 0) { |
|
console.log(`Found ${matchingParts.length}/${titleParts.length} title parts in "${filename}"`); |
|
} |
|
|
|
|
|
if (filename.includes(yearStr)) { |
|
score += 50; |
|
console.log(`Year match found in "${filename}"`); |
|
} |
|
|
|
|
|
if (filename.match(/2160p|4k|uhd/i)) score += 40; |
|
else if (filename.match(/1080p/i)) score += 30; |
|
else if (filename.match(/720p/i)) score += 20; |
|
|
|
|
|
if (filename.match(/blu-?ray|remux/i)) score += 25; |
|
else if (filename.match(/web-?dl|webrip/i)) score += 15; |
|
else if (filename.match(/hdtv/i)) score += 10; |
|
|
|
|
|
if (filename.match(/x265|hevc|h\.?265/i)) score += 15; |
|
else if (filename.match(/x264|h\.?264/i)) score += 10; |
|
|
|
|
|
if (filename.match(/\b(pack|collection|complete|season|episode|[es]\d{2,3})\b/i)) { |
|
score -= 30; |
|
console.log(`Pack/collection penalty applied to "${filename}"`); |
|
} |
|
|
|
|
|
if (filename.match(/\b(extra|bonus|behind|feature|trailer|sample)\b/i)) { |
|
score -= 40; |
|
console.log(`Extras/bonus penalty applied to "${filename}"`); |
|
} |
|
|
|
return { |
|
file, |
|
score, |
|
size: file.size || file.length || 0, |
|
filename, |
|
matchingParts |
|
}; |
|
}); |
|
|
|
|
|
console.log('\nFile scores:'); |
|
scoredFiles.forEach(({filename, score, size, matchingParts}) => { |
|
console.log(`"${filename}": score=${score}, size=${(size / (1024 * 1024)).toFixed(2)}MB, matched=${matchingParts.join(', ')}`); |
|
}); |
|
|
|
|
|
scoredFiles.sort((a, b) => { |
|
|
|
if (Math.abs(b.score - a.score) > 20) { |
|
return b.score - a.score; |
|
} |
|
|
|
return b.size - a.size; |
|
}); |
|
|
|
const bestMatch = scoredFiles[0]; |
|
|
|
|
|
if (bestMatch.score < 30) { |
|
console.log('No confident match found, falling back to largest file'); |
|
const largestFile = files.sort((a, b) => (b.size || b.length || 0) - (a.size || a.length || 0))[0]; |
|
console.log(`Selected largest file: ${largestFile.name || largestFile.path}`); |
|
return largestFile; |
|
} |
|
|
|
console.log(`\nSelected "${bestMatch.file.name || bestMatch.file.path}"`); |
|
console.log(`Score: ${bestMatch.score}`); |
|
console.log(`Size: ${(bestMatch.size / (1024 * 1024)).toFixed(2)}MB`); |
|
console.log(`Matched parts: ${bestMatch.matchingParts.join(', ')}`); |
|
return bestMatch.file; |
|
} |
|
|