CoolFace
Apppublic

mikmc5/documents

sourceHugging Faceupdated 2y agoView on Hugging Face
0likes
index.js261 linesDownload Raw Back to root
1require('dotenv').config();2const { addonBuilder, serveHTTP } = require('stremio-addon-sdk');3const axios = require('axios');4const fs = require('fs').promises;5const path = require('path');6const winston = require('winston');7const schedule = require('node-schedule');8 9const logger = winston.createLogger({10    level: 'debug',11    format: winston.format.combine(12        winston.format.timestamp(),13        winston.format.printf(({ level, message, timestamp }) => `${timestamp} ${level}: ${message}`)14    ),15    transports: [16        new winston.transports.Console(),17        new winston.transports.File({ filename: 'addon.log' })18    ]19});20 21const TMDB_API_KEY = process.env.TMDB_API_KEY;22const TMDB_BASE_URL = 'https://api.themoviedb.org/3';23const CACHE_DIR = path.join(__dirname, 'cache');24const CATALOG_CACHE_FILE = path.join(CACHE_DIR, 'catalog_cache.json');25const META_CACHE_FILE = path.join(CACHE_DIR, 'meta_cache.json');26 27const YEAR_START = process.env.YEAR_START || 2000;28const YEAR_END = process.env.YEAR_END || new Date().getFullYear();29const MIN_SCORE = parseFloat(process.env.MIN_SCORE) || 5;30const MAX_PAGES = parseInt(process.env.MAX_PAGES, 10) || 10;31const PAGE_SIZE = parseInt(process.env.PAGE_SIZE, 10) || 25;32 33const GENRES = `with_genres=99`; // Target documentaries34 35let catalogCache = {};36let metaCache = {};37 38const manifest = {39    id: 'org.stremio.custom-docs',40    version: '1.0.0',41    name: 'Hems Docs',42    description: 'Stremio Add-on showcasing the best documentaries',43    catalogs: [44        {45            id: 'custom-docs-catalog',46            name: 'Documentaries',47            type: 'movie',48            pageSize: PAGE_SIZE,49            extra: [{ name: 'search', isRequired: false }]50        }51    ],52    resources: ['catalog', 'meta'],53    types: ['movie'],54    idPrefixes: ['tmdb']55};56 57const builder = new addonBuilder(manifest);58 59async function ensureCacheDirectory() {60    try {61        await fs.mkdir(CACHE_DIR, { recursive: true });62        logger.debug('Cache directory ensured');63    } catch (error) {64        logger.error('Error creating cache directory:', error);65    }66}67 68async function loadCache() {69    try {70        const catalogData = await fs.readFile(CATALOG_CACHE_FILE, 'utf8');71        catalogCache = JSON.parse(catalogData);72        logger.debug(`Catalog cache loaded. Items: ${catalogCache.data ? catalogCache.data.length : 0}`);73    } catch (error) {74        logger.info('No existing catalog cache found. Will create a new one.');75    }76 77    try {78        const metaData = await fs.readFile(META_CACHE_FILE, 'utf8');79        metaCache = JSON.parse(metaData);80        logger.debug(`Meta cache loaded. Items: ${Object.keys(metaCache).length}`);81    } catch (error) {82        logger.info('No existing meta cache found. Will create a new one.');83    }84}85 86async function saveCache() {87    try {88        await fs.writeFile(CATALOG_CACHE_FILE, JSON.stringify(catalogCache));89        await fs.writeFile(META_CACHE_FILE, JSON.stringify(metaCache));90        logger.debug('Cache saved successfully');91    } catch (error) {92        logger.error('Error saving cache:', error);93    }94}95 96async function fetchMovies() {97    let page = 1;98    let totalPages = 1;99    const allResults = [];100 101    try {102        do {103            const url = `${TMDB_BASE_URL}/discover/movie?api_key=${TMDB_API_KEY}&include_adult=false&include_video=false&language=en&page=${page}&sort_by=primary_release_date.desc&primary_release_date.gte=${YEAR_START}-01-01&primary_release_date.lte=${YEAR_END}-12-31&vote_average.gte=${MIN_SCORE}&${GENRES}`;104            logger.debug(`Requesting TMDB: ${url}`);105 106            const response = await axios.get(url);107            const { results, total_pages } = response.data;108            totalPages = total_pages;109 110            logger.debug(`Page ${page}/${totalPages} fetched. Results: ${results.length}`);111 112            const validResults = results.filter(movie => movie.release_date && !isNaN(new Date(movie.release_date)));113            allResults.push(...validResults);114 115            page++;116        } while (page <= totalPages && page <= MAX_PAGES);117 118        allResults.sort((a, b) => new Date(b.release_date) - new Date(a.release_date));119        logger.info(`Total documentaries fetched: ${allResults.length}`);120        return allResults;121    } catch (error) {122        logger.error('Error fetching documentaries:', error);123        return [];124    }125}126 127async function fetchMovieMetadata(movieId) {128    try {129        const url = `${TMDB_BASE_URL}/movie/${movieId}?api_key=${TMDB_API_KEY}`;130        const response = await axios.get(url);131        return response.data;132    } catch (error) {133        logger.error(`Error fetching metadata for movie ID ${movieId}:`, error.message);134        return { error: 'Failed to fetch movie data' };135    }136}137 138async function updateCatalogAndMetaCache() {139    logger.info('Updating catalog and meta cache...');140    try {141        const results = await fetchMovies();142        catalogCache = {143            timestamp: Date.now(),144            data: results145        };146 147        for (const movie of results) {148            const movieId = movie.id;149            logger.debug(`Fetching meta for movie ID: ${movieId}`);150            const movieData = await fetchMovieMetadata(movieId);151            metaCache[movieId] = {152                timestamp: Date.now(),153                data: movieData154            };155        }156 157        await saveCache();158        logger.info(`Catalog and meta cache updated. Items: ${results.length}`);159    } catch (error) {160        logger.error('Error updating catalog and meta cache:', error);161    }162}163 164builder.defineCatalogHandler(async (args) => {165    const searchQuery = args.extra && args.extra.search ? args.extra.search : '';166    logger.debug(`Catalog request received. Query: "${searchQuery}"`);167 168    let results = catalogCache.data || [];169    logger.debug(`Catalog cache has ${results.length} items`);170 171    if (searchQuery) {172        results = results.filter(movie =>173            movie.title.toLowerCase().includes(searchQuery.toLowerCase())174        );175        logger.debug(`Filtered results for "${searchQuery}": ${results.length}`);176    }177 178    const metas = results.map(item => ({179        id: `tmdb-${item.id}`,180        type: 'movie',181        name: item.title,182        poster: item.poster_path ? `https://image.tmdb.org/t/p/w500${item.poster_path}` : null,183        description: item.overview || 'No description available',184        releaseInfo: item.release_date ? item.release_date.split('-')[0] : 'Unknown'185    }));186 187    logger.info(`Catalog request processed. Query: "${searchQuery}", Results: ${metas.length}`);188    return { metas };189});190 191builder.defineMetaHandler(async (args) => {192    const id = args.id.split('-')[1];193    logger.debug(`Meta request received for movie ID: ${id}`);194 195    if (!metaCache[id]) {196        logger.warn(`Meta not found for movie ID: ${id}`);197        return { meta: null };198    }199 200    const movieData = metaCache[id].data;201 202    if (movieData.error) {203        logger.warn(`Meta request failed for movie ID: ${id}`);204        return { meta: null };205    }206 207    const { 208        title, 209        overview, 210        poster_path, 211        release_date, 212        genres, 213        runtime, 214        vote_average, 215        backdrop_path 216    } = movieData;217 218    logger.info(`Meta request processed for movie ID: ${id}`);219    return {220        meta: {221            id: args.id,222            type: 'movie',223            name: title || 'Unknown Title',224            poster: poster_path ? `https://image.tmdb.org/t/p/w500${poster_path}` : null,225            background: backdrop_path ? `https://image.tmdb.org/t/p/w1280${backdrop_path}` : null,226            description: overview || 'No description available',227            releaseInfo: release_date ? release_date.split('-')[0] : 'Unknown',228            genre: genres && Array.isArray(genres) ? genres.map(g => g.name) : [],229            runtime: runtime ? `${runtime} min` : 'Unknown',230            rating: vote_average ? vote_average.toFixed(1) : 'N/A',231        }232    };233});234 235async function startServer() {236    await ensureCacheDirectory();237    await loadCache();238 239    async function refreshCache() {240        logger.info('Scheduled update: Refreshing catalog and meta cache');241        await updateCatalogAndMetaCache();242    }243 244    schedule.scheduleJob('0 0 * * *', refreshCache);245 246    if (!catalogCache.timestamp || Date.now() - catalogCache.timestamp > 24 * 60 * 60 * 1000) {247        logger.info('Initial cache refresh needed');248        await refreshCache();249    } else {250        logger.info('Cache is up to date. Next refresh scheduled for midnight');251    }252 253    const interface = builder.getInterface();254    const port = process.env.PORT || 8055;255 256    logger.info(`Starting Stremio Add-on server on port ${port}`);257    serveHTTP(interface, { port });258}259 260startServer();261