|
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 readJSONFile(filename) { |
|
const filePath = path.join(__dirname, filename); |
|
return JSON.parse(fs.readFileSync(filePath, 'utf8')); |
|
} |
|
|
|
|
|
const seriesDataFiles = fs.readdirSync(__dirname) |
|
.filter(file => file.endsWith('_series_data.json')); |
|
|
|
console.log("Found series data files:", seriesDataFiles); |
|
|
|
|
|
const catalogs = []; |
|
const metaData = {}; |
|
|
|
|
|
seriesDataFiles.forEach(filename => { |
|
const data = readJSONFile(filename); |
|
const catalogType = 'series'; |
|
|
|
|
|
Object.keys(data).forEach(category => { |
|
if (!manifest.catalogs[0].extra[0].options.includes(category)) { |
|
manifest.catalogs[0].extra[0].options.push(category); |
|
} |
|
}); |
|
|
|
|
|
Object.entries(data).forEach(([category, seriesList]) => { |
|
|
|
seriesList.forEach(series => { |
|
const seriesMeta = { |
|
id: series.id, |
|
type: catalogType, |
|
name: series.name, |
|
genres: [category], |
|
poster: series.poster || null |
|
}; |
|
|
|
|
|
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 |
|
}); |
|
}); |
|
}); |
|
}); |
|
|
|
|
|
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) |
|
.map(catalog => catalog.meta); |
|
|
|
|
|
if (searchQuery) { |
|
seriesCatalog = seriesCatalog.filter(meta => |
|
meta.name.toLowerCase().includes(searchQuery) |
|
); |
|
} |
|
|
|
resolve({ metas: seriesCatalog }); |
|
} else { |
|
resolve({ metas: [] }); |
|
} |
|
}); |
|
}); |
|
|
|
|
|
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({}); |
|
} |
|
}); |
|
}); |
|
|
|
|
|
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 || 3000; |
|
app.listen(PORT, () => { |
|
console.log(`TV Club Stremio Addon running on port ${PORT}`); |
|
}); |
|
|