208 lines
8.2 KiB
TypeScript
208 lines
8.2 KiB
TypeScript
import { ISource, SearchResult, SelectionData, ContentLinks, MediaType, VideoLink } from '../../src/types/source.js';
|
|
import { sourceRegistry } from '../../src/core/registry.js';
|
|
import { MovixAPI } from './api.js';
|
|
import { CONFIG } from '../../src/utils/config.js';
|
|
|
|
class MovixSource implements ISource {
|
|
public readonly name = 'movix';
|
|
public readonly displayName = 'Movix';
|
|
|
|
public async healthCheck(): Promise<boolean> {
|
|
if (!CONFIG.MOVIX_URL) return false;
|
|
try {
|
|
// A quick check to see if the search endpoint is reachable
|
|
await MovixAPI.search('test');
|
|
return true;
|
|
} catch (e) {
|
|
console.error(`[MOVIX] Health check failed:`, e);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public async search(query: string, mediaType?: MediaType): Promise<SearchResult[]> {
|
|
try {
|
|
const response = await MovixAPI.search(query);
|
|
if (!response || !response.results) return [];
|
|
|
|
const results: SearchResult[] = [];
|
|
const searchLower = query.toLowerCase().trim();
|
|
|
|
for (const item of response.results) {
|
|
if (!item.name) continue;
|
|
|
|
// Filtre optionnel pour aligner les résultats avec la recherche
|
|
const nameLower = item.name.toLowerCase();
|
|
const originalLower = item.original_title ? item.original_title.toLowerCase() : '';
|
|
|
|
// On vérifie si la requête est incluse dans le titre ou le titre original
|
|
if (!nameLower.includes(searchLower) && !originalLower.includes(searchLower)) {
|
|
// Pour être un peu plus permissif, on vérifie si tous les mots clés y sont
|
|
const words = searchLower.split(' ');
|
|
const allWordsMatch = words.every(w => nameLower.includes(w) || originalLower.includes(w));
|
|
if (!allWordsMatch) continue;
|
|
}
|
|
|
|
// Filtrage basique par mediaType si fourni
|
|
if (mediaType) {
|
|
if (mediaType === 'movie' && item.type !== 'movie') continue;
|
|
if (mediaType === 'series' && item.type !== 'serie') continue; // Verify if it's 'serie' or 'series'
|
|
}
|
|
|
|
const hrefPath = `movix:${item.id}:${item.tmdb_id || 0}:${item.type}`;
|
|
|
|
let image = null;
|
|
if (item.poster) {
|
|
image = item.poster.startsWith('http') ? item.poster : `https://image.tmdb.org/t/p/w300/${item.poster}`;
|
|
}
|
|
|
|
results.push({
|
|
title: item.name,
|
|
year: item.year ? item.year.toString() : null,
|
|
image,
|
|
hrefPath,
|
|
type: item.type === 'movie' ? 'movie' : (item.type === 'serie' || item.type === 'series' ? 'series' : 'other'),
|
|
source: this.name
|
|
});
|
|
}
|
|
|
|
return results;
|
|
} catch (e) {
|
|
console.error(`[MOVIX] Search error:`, e);
|
|
return [];
|
|
}
|
|
}
|
|
|
|
public async getTrending(mediaType: MediaType): Promise<SearchResult[]> {
|
|
const typeStr = mediaType === 'series' ? 'tv' : 'movie';
|
|
const url = `https://api.themoviedb.org/3/trending/${typeStr}/day?api_key=f3d757824f08ea2cff45eb8f47ca3a1e&language=fr-FR`;
|
|
|
|
try {
|
|
const res = await fetch(url);
|
|
const data = await res.json();
|
|
|
|
if (!data || !data.results) return [];
|
|
|
|
return data.results.map((item: any) => {
|
|
const title = item.title || item.name;
|
|
const year = item.release_date ? item.release_date.split('-')[0] : (item.first_air_date ? item.first_air_date.split('-')[0] : null);
|
|
const image = item.poster_path ? `https://image.tmdb.org/t/p/w300${item.poster_path}` : null;
|
|
const tmdbId = item.id;
|
|
const type = mediaType === 'series' ? 'series' : 'movie';
|
|
|
|
// Identifiant spécial pour faire la recherche au moment du clic
|
|
const hrefPath = `movix:tmdb:${tmdbId}:${type}:${encodeURIComponent(title)}`;
|
|
|
|
return {
|
|
title,
|
|
year,
|
|
image,
|
|
hrefPath,
|
|
type,
|
|
source: this.name
|
|
};
|
|
});
|
|
} catch(e) {
|
|
console.error(`[MOVIX] TMDB Trending error:`, e);
|
|
return [];
|
|
}
|
|
}
|
|
|
|
public async getRecent(): Promise<SearchResult[]> {
|
|
// Fallback on movies trending as recent if no specific endpoint
|
|
return this.getTrending('movie');
|
|
}
|
|
|
|
public async getContentLinks(identifier: string, season?: number): Promise<ContentLinks> {
|
|
try {
|
|
const parts = identifier.split(':');
|
|
if (parts.length < 4) return { links: [] };
|
|
|
|
let id: string, tmdbId: string, type: string;
|
|
|
|
if (parts[1] === 'tmdb') {
|
|
tmdbId = parts[2];
|
|
type = parts[3];
|
|
const title = decodeURIComponent(parts.slice(4).join(':'));
|
|
|
|
// Recherche sur Movix pour récupérer l'ID interne
|
|
const searchRes = await MovixAPI.search(title);
|
|
const item = searchRes.results?.find((r: any) => String(r.tmdb_id) === String(tmdbId) || r.name === title);
|
|
if (!item) {
|
|
console.log(`[MOVIX] TMDB item not found on Movix: ${title}`);
|
|
return { links: [] };
|
|
}
|
|
id = item.id;
|
|
// Update type depending on what Movix returned
|
|
type = item.type === 'serie' || item.type === 'series' ? 'series' : 'movie';
|
|
} else {
|
|
id = parts[1];
|
|
tmdbId = parts[2];
|
|
type = parts[3];
|
|
}
|
|
|
|
const data = await MovixAPI.getDownloadLinks(type, id, tmdbId);
|
|
|
|
const links: VideoLink[] = [];
|
|
|
|
if (data && data.data) {
|
|
// Pour chaque host (1fichier, etc)
|
|
for (const item of data.data) {
|
|
if (!item.links || !Array.isArray(item.links)) continue;
|
|
|
|
const host = item.host || 'unknown';
|
|
const quality = item.qualite || 'Unknown';
|
|
const lang = item.langue || 'Unknown';
|
|
const size = item.size || '';
|
|
|
|
for (const linkObj of item.links) {
|
|
const linkId = linkObj.id;
|
|
if (!linkId) continue;
|
|
|
|
links.push({
|
|
id: `${linkId}|${id}`, // Store both linkId and titleId
|
|
host: host,
|
|
label: `${quality} - ${lang}`,
|
|
url: null, // Resolves later
|
|
size: size,
|
|
quality: quality,
|
|
langs: [lang]
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
return { links };
|
|
} catch (e) {
|
|
console.error(`[MOVIX] Error in getContentLinks:`, e);
|
|
return { links: [] };
|
|
}
|
|
}
|
|
|
|
public async getSelection(identifier: string, type?: string, seasonValue?: string | number): Promise<SelectionData> {
|
|
const content = await this.getContentLinks(identifier);
|
|
return {
|
|
links: content.links,
|
|
seasons: [],
|
|
isSeries: type === 'series'
|
|
};
|
|
}
|
|
|
|
public async resolveLink(combinedId: string): Promise<string | null> {
|
|
try {
|
|
const [linkId, titleId] = combinedId.split('|');
|
|
if (!linkId || !titleId) return null;
|
|
|
|
const res = await MovixAPI.decodeLink(linkId, titleId);
|
|
if (res && res.url) {
|
|
return res.url;
|
|
}
|
|
return null;
|
|
} catch (e) {
|
|
console.error(`[MOVIX] ResolveLink error for ${combinedId}:`, e);
|
|
return null;
|
|
}
|
|
}
|
|
}
|
|
|
|
sourceRegistry.register(new MovixSource());
|