File size: 4,455 Bytes
ea5d04f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
import { VIDEO_EXTENSIONS } from './const.js';

export function isVideo(filename) {
    return VIDEO_EXTENSIONS.some(ext => 
        filename.toLowerCase().endsWith(ext)
    );
}

export function extractInfoHash(magnetLink) {
    const match = magnetLink.match(/xt=urn:btih:([^&]+)/i);
    return match ? match[1].toLowerCase() : null;
}

export async function wait(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
}

export function base64Encode(str) {
    return Buffer.from(str).toString('base64')
        .replace(/\+/g, '-')
        .replace(/\//g, '_')
        .replace(/=/g, '');
}

export function base64Decode(str) {
    str = str.replace(/-/g, '+').replace(/_/g, '/');
    while (str.length % 4) str += '=';
    return Buffer.from(str, 'base64').toString('ascii');
}

export function findBestFile(files, metadata) {
    if (!files?.length) return null;
    if (files.length === 1) return files[0];

    // If no metadata provided, return largest file
    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();

    // Get key parts of the title (for Harry Potter and the Prisoner of Azkaban -> ["harry", "potter", "prisoner", "azkaban"])
    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})`);
    
    // Score each video file
    const scoredFiles = files.map(file => {
        let score = 0;
        const filename = file.name?.toLowerCase() || file.path?.toLowerCase() || '';
        
        // Count matching title parts
        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}"`);
        }

        // Year matching
        if (filename.includes(yearStr)) {
            score += 50;
            console.log(`Year match found in "${filename}"`);
        }

        // Penalize likely pack/collection files
        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}"`);
        }

        // Penalize extras/bonus content
        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 || 0,
            filename,
            matchingParts
        };
    });

    // Log all scores for debugging
    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(', ')}`);
    });

    // Sort first by score, then by size
    scoredFiles.sort((a, b) => {
        // If scores are significantly different, use score
        if (Math.abs(b.score - a.score) > 20) {
            return b.score - a.score;
        }
        // If scores are close, use file size
        return b.size - a.size;
    });

    const bestMatch = scoredFiles[0];
    
    // If no good matches found (low score), fall back to largest file
    if (bestMatch.score < 30) {
        console.log('No confident match found, falling back to largest file');
        const largestFile = files.sort((a, b) => (b.size || 0) - (a.size || 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;
}