File size: 5,755 Bytes
ea5d04f
 
 
8740315
ea5d04f
 
 
 
 
 
8740315
 
 
 
 
 
 
 
ea5d04f
 
 
 
 
 
 
8740315
 
 
 
 
 
 
 
 
 
ea5d04f
 
 
8740315
 
 
 
 
 
 
 
 
ea5d04f
 
 
8740315
ea5d04f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8740315
ea5d04f
8740315
 
 
 
 
ea5d04f
 
 
 
 
 
 
 
 
 
 
 
 
 
8740315
 
 
 
 
 
 
 
 
 
 
 
 
 
ea5d04f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8740315
ea5d04f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8740315
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
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
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 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() || '');
        
        // Skip if not a video file
        if (!filename.match(/\.(mp4|mkv|avi|mov|wmv|m4v|ts)$/i)) {
            return { file, score: -100, filename };
        }

        // 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}"`);
        }

        // Quality bonus
        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;

        // Source quality bonus
        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;

        // Encoding bonus
        if (filename.match(/x265|hevc|h\.?265/i)) score += 15;
        else if (filename.match(/x264|h\.?264/i)) score += 10;

        // 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 || file.length || 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 || 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;
}