|
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 || 7000;
|
|
app.listen(PORT, () => {
|
|
console.log(`TV Club Stremio Addon running on port ${PORT}`);
|
|
});
|
|
|