const express = require('express'); const { addonBuilder, getRouter } = require('stremio-addon-sdk'); const cors = require('cors'); const fs = require('fs'); const path = require('path'); const manifest = { id: 'hy.yourtvshows.org', version: '1.0.0', name: 'TV Club', description: 'Stream TV series categorized by genre with search capability', resources: ['catalog', 'stream', 'meta'], types: ['series'], idPrefixes: ['tt'], catalogs: [ { id: 'main-catalog', type: 'series', name: 'All Series', extra: [ { name: 'genre', options: [] }, { name: 'search', isRequired: false } ] } ] }; const builder = new addonBuilder(manifest); // Function to read JSON files function readJSONFile(filename) { const filePath = path.join(__dirname, filename); return JSON.parse(fs.readFileSync(filePath, 'utf8')); } // Load and parse series data files const seriesDataFiles = fs.readdirSync(__dirname) .filter(file => file.endsWith('_series_data.json')); console.log("Found series data files:", seriesDataFiles); // Store catalogs and metadata const catalogs = []; const metaData = {}; // Process each series data file dynamically seriesDataFiles.forEach(filename => { const data = readJSONFile(filename); const catalogType = 'series'; // Assuming 'series' for all catalogs // Add categories to genre options in manifest Object.keys(data).forEach(category => { if (!manifest.catalogs[0].extra[0].options.includes(category)) { manifest.catalogs[0].extra[0].options.push(category); } }); // Process each category in the file Object.entries(data).forEach(([category, seriesList]) => { // Add series metadata for each series in the category seriesList.forEach(series => { const seriesMeta = { id: series.id, type: catalogType, name: series.name, genres: [category], poster: series.poster || null }; // Store metadata and catalog information metaData[series.id] = { ...seriesMeta, seasons: series.seasons.map(season => ({ season: season.season, episodes: season.episodes.map(ep => ({ episode: ep.episode, stream_url: ep.stream_url })) })) }; catalogs.push({ category, meta: seriesMeta }); }); }); }); // Catalog handler to list series by genre and enable search builder.defineCatalogHandler(({ type, extra }) => { return new Promise((resolve) => { if (type === 'series') { const genre = extra.genre; const searchQuery = extra.search ? extra.search.toLowerCase() : null; let seriesCatalog = catalogs .filter(catalog => !genre || catalog.category === genre) // Filter by genre if provided .map(catalog => catalog.meta); // Filter by search query if provided if (searchQuery) { seriesCatalog = seriesCatalog.filter(meta => meta.name.toLowerCase().includes(searchQuery) ); } resolve({ metas: seriesCatalog }); } else { resolve({ metas: [] }); } }); }); // Meta handler to get details of a specific series builder.defineMetaHandler(({ id }) => { return new Promise((resolve) => { const seriesMeta = metaData[id]; if (seriesMeta) { resolve({ meta: { id: seriesMeta.id, type: 'series', name: seriesMeta.name, poster: seriesMeta.poster, genres: seriesMeta.genres, seasons: seriesMeta.seasons.map(season => ({ number: season.season, episodes: season.episodes.map(ep => ({ id: `${seriesMeta.id}:${season.season}:${ep.episode}`, title: `Episode ${ep.episode}`, season: season.season, number: ep.episode })) })) } }); } else { resolve({}); } }); }); // Stream handler to fetch specific episode streams builder.defineStreamHandler(({ type, id }) => { return new Promise((resolve) => { if (type === 'series') { const [seriesId, seasonNumber, episodeNumber] = id.split(':'); const seriesMeta = metaData[seriesId]; if (seriesMeta) { const season = seriesMeta.seasons.find(s => s.season == seasonNumber); if (season) { const episode = season.episodes.find(e => e.episode == episodeNumber); if (episode) { resolve({ streams: [{ title: `${seriesMeta.name} S${seasonNumber}E${episodeNumber}`, url: episode.stream_url }] }); return; } } } } resolve({ streams: [] }); }); }); const addonInterface = builder.getInterface(); const app = express(); app.use(cors()); app.use('/', getRouter(addonInterface)); app.use((err, req, res, next) => { console.error(err.stack); res.status(500).send('Something went wrong!'); }); const PORT = process.env.PORT || 7000; app.listen(PORT, () => { console.log(`TV Club Stremio Addon running on port ${PORT}`); });