Initial commit (v1.5.9)
This commit is contained in:
@@ -0,0 +1,207 @@
|
||||
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());
|
||||
Reference in New Issue
Block a user