[addon] adds filtering based on provider

This commit is contained in:
TheBeastLT
2020-03-15 23:46:06 +01:00
parent 6c56d76f98
commit 70c279d7d7
11 changed files with 102 additions and 45 deletions

3
.gitignore vendored
View File

@@ -1,3 +1,4 @@
/.idea /.idea
**/node_modules **/node_modules
**.env **.env
.now

View File

@@ -1,8 +1,7 @@
const { addonBuilder } = require('stremio-addon-sdk'); const { addonBuilder } = require('stremio-addon-sdk');
const titleParser = require('parse-torrent-title');
const { manifest } = require('./lib/manifest'); const { manifest } = require('./lib/manifest');
const { toStreamInfo } = require('./lib/streamInfo');
const { cacheWrapStream } = require('./lib/cache'); const { cacheWrapStream } = require('./lib/cache');
const { toStreamInfo, sanitizeStreamInfo } = require('./lib/streamInfo');
const repository = require('./lib/repository'); const repository = require('./lib/repository');
const CACHE_MAX_AGE = process.env.CACHE_MAX_AGE || 4 * 60 * 60; // 4 hours in seconds const CACHE_MAX_AGE = process.env.CACHE_MAX_AGE || 4 * 60 * 60; // 4 hours in seconds
@@ -18,15 +17,16 @@ builder.defineStreamHandler((args) => {
} }
const handlers = { const handlers = {
series: () => seriesRecordsHandler(args), series: () => seriesRecordsHandler(args).then(records => records.map(record => toStreamInfo(record))),
movie: () => movieRecordsHandler(args), movie: () => movieRecordsHandler(args).then(records => records.map(record => toStreamInfo(record))),
fallback: () => Promise.reject('not supported type') fallback: () => Promise.reject('not supported type')
}; };
return cacheWrapStream(args.id, handlers[args.type] || handlers.fallback) return cacheWrapStream(args.id, (handlers[args.type] || handlers.fallback))
.then(records => filterRecordsBySeeders(records)) .then(streams => filterStreamByProvider(streams, args.extra.providers))
.then(records => sortRecordsByVideoQuality(records)) .then(streams => filterStreamsBySeeders(streams))
.then(records => records.map(record => toStreamInfo(record))) .then(streams => sortStreamsByVideoQuality(streams))
.then(streams => streams.map(stream => sanitizeStreamInfo(stream)))
.then(streams => ({ .then(streams => ({
streams: streams, streams: streams,
cacheMaxAge: streams.length ? CACHE_MAX_AGE : CACHE_MAX_AGE_EMPTY, cacheMaxAge: streams.length ? CACHE_MAX_AGE : CACHE_MAX_AGE_EMPTY,
@@ -64,38 +64,43 @@ async function movieRecordsHandler(args) {
return Promise.reject(`Unsupported id type: ${args.id}`); return Promise.reject(`Unsupported id type: ${args.id}`);
} }
function filterStreamByProvider(streams, providers) {
if (!providers || !providers.length) {
return streams;
}
return streams.filter(stream => providers.includes(stream.name.split('\n')[1].toLowerCase()))
}
const HEALTHY_SEEDERS = 5; const HEALTHY_SEEDERS = 5;
const SEEDED_SEEDERS = 1; const SEEDED_SEEDERS = 1;
const MIN_HEALTHY_COUNT = 10; const MIN_HEALTHY_COUNT = 10;
const MAX_UNHEALTHY_COUNT = 5; const MAX_UNHEALTHY_COUNT = 5;
function filterRecordsBySeeders(records) { function filterStreamsBySeeders(streams) {
const sortedRecords = records const sortedStreams = streams
.sort((a, b) => b.torrent.seeders - a.torrent.seeders || b.torrent.uploadDate - a.torrent.uploadDate); .sort((a, b) => b.filters.seeders - a.filters.seeders || b.filters.uploadDate - a.filters.uploadDate);
const healthy = sortedRecords.filter(record => record.torrent.seeders >= HEALTHY_SEEDERS); const healthy = sortedStreams.filter(stream => stream.filters.seeders >= HEALTHY_SEEDERS);
const seeded = sortedRecords.filter(record => record.torrent.seeders >= SEEDED_SEEDERS); const seeded = sortedStreams.filter(stream => stream.filters.seeders >= SEEDED_SEEDERS);
if (healthy.length >= MIN_HEALTHY_COUNT) { if (healthy.length >= MIN_HEALTHY_COUNT) {
return healthy; return healthy;
} else if (seeded.length >= MAX_UNHEALTHY_COUNT) { } else if (seeded.length >= MAX_UNHEALTHY_COUNT) {
return seeded.slice(0, MIN_HEALTHY_COUNT); return seeded.slice(0, MIN_HEALTHY_COUNT);
} }
return sortedRecords.slice(0, MAX_UNHEALTHY_COUNT); return sortedStreams.slice(0, MAX_UNHEALTHY_COUNT);
} }
function sortRecordsByVideoQuality(records) { function sortStreamsByVideoQuality(streams) {
const qualityMap = records const qualityMap = streams
.reduce((map, record) => { .reduce((map, stream) => {
const parsedFile = titleParser.parse(record.title); const quality = stream.filters.quality;
const parsedTorrent = titleParser.parse(record.torrent.title); map[quality] = (map[quality] || []).concat(stream);
const quality = parsedFile.resolution || parsedTorrent.resolution || parsedFile.source || parsedTorrent.source;
map[quality] = (map[quality] || []).concat(record);
return map; return map;
}, {}); }, {});
const sortedQualities = Object.keys(qualityMap) const sortedQualities = Object.keys(qualityMap)
.sort((a, b) => { .sort((a, b) => {
const aQuality = a === '4k' ? '2160p' : a === 'undefined' ? undefined : a; const aQuality = a === '4k' ? '2160p' : a;
const bQuality = b === '4k' ? '2160p' : b === 'undefined' ? undefined : b; const bQuality = b === '4k' ? '2160p' : b;
const aResolution = aQuality && aQuality.match(/\d+p/) && parseInt(aQuality, 10); const aResolution = aQuality && aQuality.match(/\d+p/) && parseInt(aQuality, 10);
const bResolution = bQuality && bQuality.match(/\d+p/) && parseInt(bQuality, 10); const bResolution = bQuality && bQuality.match(/\d+p/) && parseInt(bQuality, 10);
if (aResolution && bResolution) { if (aResolution && bResolution) {

View File

@@ -1,6 +1,9 @@
const { serveHTTP } = require('stremio-addon-sdk'); const express = require('express');
const addonInterface = require('./addon'); const serverless = require('./serverless');
const PORT = process.env.PORT || 7000; const app = express();
serveHTTP(addonInterface, { port: PORT, cacheMaxAge: 86400 }); app.use((req, res, next) => serverless(req, res, next));
app.listen(process.env.PORT || 7000, () => {
console.log(`Started addon at: http://localhost:${process.env.PORT || 7000}`)
});

View File

@@ -174,8 +174,6 @@ button:active {
const { Providers } = require('./manifest'); const { Providers } = require('./manifest');
function landingTemplate(manifest, providers = [], realDebridApiKey = '') { function landingTemplate(manifest, providers = [], realDebridApiKey = '') {
console.log(providers);
console.log(realDebridApiKey);
const background = manifest.background || 'https://dl.strem.io/addon-background.jpg'; const background = manifest.background || 'https://dl.strem.io/addon-background.jpg';
const logo = manifest.logo || 'https://dl.strem.io/addon-logo.png'; const logo = manifest.logo || 'https://dl.strem.io/addon-logo.png';
const contactHTML = manifest.contactEmail ? const contactHTML = manifest.contactEmail ?
@@ -254,7 +252,7 @@ function landingTemplate(manifest, providers = [], realDebridApiKey = '') {
const providersValue = $('#iProviders').val().join(','); const providersValue = $('#iProviders').val().join(',');
const realDebridValue = $('#iRealDebrid').val(); const realDebridValue = $('#iRealDebrid').val();
const providers = providersValue && providersValue.length ? 'providers=' + providersValue : ''; const providers = providersValue && providersValue.length ? 'providers=' + providersValue : '';
const realDebrid = realDebridValue && realDebridValue.length ? 'realrebrid='+realDebridValue : ''; const realDebrid = realDebridValue && realDebridValue.length ? 'realdebrid=' + realDebridValue : '';
const configurationValue = [providers, realDebrid].filter(value => value.length).join('|'); const configurationValue = [providers, realDebrid].filter(value => value.length).join('|');
const configuration = configurationValue && configurationValue.length ? '/' + configurationValue : ''; const configuration = configurationValue && configurationValue.length ? '/' + configurationValue : '';
installLink.href = 'stremio://' + window.location.host + configuration + '/manifest.json'; installLink.href = 'stremio://' + window.location.host + configuration + '/manifest.json';

View File

@@ -6,10 +6,10 @@ const Providers = [
'HorribleSubs' 'HorribleSubs'
]; ];
function manifest(providers, realDebridApiKey) { function manifest({ providers, realdebrid } = {}) {
const providersList = Array.isArray(providers) && providers.map(provider => getProvider(provider)) || Providers; const providersList = Array.isArray(providers) && providers.map(provider => getProvider(provider)) || Providers;
const providersDesc = providers && providers.length ? 'Enabled providers -' : 'Currently supports'; const providersDesc = providers && providers.length ? 'Enabled providers -' : 'Currently supports';
const realDebridDesc = realDebridApiKey ? ' and RealdDebrid enabled' : ''; const realDebridDesc = realdebrid ? ' and RealDebrid enabled' : '';
return { return {
id: 'com.stremio.torrentio.addon', id: 'com.stremio.torrentio.addon',
version: '0.0.1-beta', version: '0.0.1-beta',

View File

@@ -10,6 +10,16 @@ function toStreamInfo(record) {
return seriesStream(record); return seriesStream(record);
} }
function sanitizeStreamInfo(stream) {
if (stream.filters) {
delete stream.filters;
}
if (stream.fileIdx === undefined || stream.fileIdx === null) {
delete stream.fileIdx;
}
return stream;
}
function movieStream(record) { function movieStream(record) {
const titleInfo = titleParser.parse(record.title); const titleInfo = titleParser.parse(record.title);
const sameInfo = record.title === record.torrent.title; const sameInfo = record.title === record.torrent.title;
@@ -28,6 +38,12 @@ function movieStream(record) {
name: `${ADDON_NAME}\n${record.torrent.provider}`, name: `${ADDON_NAME}\n${record.torrent.provider}`,
title: title, title: title,
infoHash: record.infoHash, infoHash: record.infoHash,
fileIdx: record.fileIndex,
filters: {
quality: titleInfo.resolution || record.torrent.resolution || titleInfo.source,
seeders: record.torrent.seeders,
uploadDate: new Date(record.torrent.uploadDate)
}
}; };
} }
@@ -55,6 +71,11 @@ function seriesStream(record) {
title: title, title: title,
infoHash: record.infoHash, infoHash: record.infoHash,
fileIdx: record.fileIndex, fileIdx: record.fileIndex,
filters: {
quality: tInfo.resolution || eInfo.resolution || record.torrent.resolution || tInfo.source || eInfo.source,
seeders: record.torrent.seeders,
uploadDate: new Date(record.torrent.uploadDate)
}
}; };
} }
@@ -64,4 +85,4 @@ function joinDetailParts(parts, prefix = '', delimiter = ' ') {
return filtered.length > 0 ? `${prefix}${filtered}` : undefined; return filtered.length > 0 ? `${prefix}${filtered}` : undefined;
} }
module.exports = { toStreamInfo }; module.exports = { toStreamInfo, sanitizeStreamInfo };

View File

@@ -22,19 +22,52 @@ router.get('/', (_, res) => {
router.get('/:configuration', (req, res) => { router.get('/:configuration', (req, res) => {
const configValues = parseConfiguration(req.params.configuration); const configValues = parseConfiguration(req.params.configuration);
console.log(configValues); const landingHTML = landingTemplate(manifest(configValues), configValues.providers, configValues.realdebrid);
const landingHTML = landingTemplate(manifest(), configValues.providers, configValues.realdebrid);
res.setHeader('content-type', 'text/html'); res.setHeader('content-type', 'text/html');
res.end(landingHTML); res.end(landingHTML);
}); });
router.get('/:configuration/manifest.json', (req, res) => { router.get('/:configuration/manifest.json', (req, res) => {
const configValues = parseConfiguration(req.params.configuration); const configValues = parseConfiguration(req.params.configuration);
const manifestBuf = JSON.stringify(manifest(configValues.providers, configValues.realdebrid)); const manifestBuf = JSON.stringify(manifest(configValues));
res.setHeader('Content-Type', 'application/json; charset=utf-8'); res.setHeader('Content-Type', 'application/json; charset=utf-8');
res.end(manifestBuf) res.end(manifestBuf)
}); });
router.get('/:configuration/:resource/:type/:id.json', (req, res, next) => {
const { configuration, resource, type, id } = req.params;
const configValues = parseConfiguration(configuration);
addonInterface.get(resource, type, id, configValues)
.then(resp => {
const cacheHeaders = {
cacheMaxAge: 'max-age',
staleRevalidate: 'stale-while-revalidate',
staleError: 'stale-if-error'
};
const cacheControl = Object.keys(cacheHeaders)
.map(prop => resp[prop] && cacheHeaders[prop] + '=' + resp[prop])
.filter(val => !!val).join(', ');
res.setHeader('Cache-Control', `${cacheControl}, public`);
res.setHeader('Content-Type', 'application/json; charset=utf-8');
res.end(JSON.stringify(resp));
})
.catch(err => {
if (err.noHandler) {
if (next) {
next()
} else {
res.writeHead(404);
res.end(JSON.stringify({ err: 'not found' }));
}
} else {
console.error(err);
res.writeHead(500);
res.end(JSON.stringify({ err: 'handler error' }));
}
});
});
module.exports = function (req, res) { module.exports = function (req, res) {
router(req, res, function () { router(req, res, function () {
res.statusCode = 404; res.statusCode = 404;

View File

@@ -1,10 +1,6 @@
{ {
"version": 2, "version": 2,
"builds": [ "builds": [
{
"src": "/addon/static/**/*",
"use": "@now/static"
},
{ {
"src": "/addon/**/*.js", "src": "/addon/**/*.js",
"use": "@now/node" "use": "@now/node"

4
package-lock.json generated
View File

@@ -1385,8 +1385,8 @@
} }
}, },
"parse-torrent-title": { "parse-torrent-title": {
"version": "git://github.com/TheBeastLT/parse-torrent-title.git#494145d07b1ac842d3edf0ed9c69ca65caf30eef", "version": "git://github.com/TheBeastLT/parse-torrent-title.git#7c00602bc1c405f5574758eeabb72b133fea81d5",
"from": "git://github.com/TheBeastLT/parse-torrent-title.git#494145d07b1ac842d3edf0ed9c69ca65caf30eef", "from": "git://github.com/TheBeastLT/parse-torrent-title.git#7c00602bc1c405f5574758eeabb72b133fea81d5",
"requires": { "requires": {
"moment": "^2.24.0" "moment": "^2.24.0"
} }

View File

@@ -29,7 +29,7 @@
"node-schedule": "^1.3.2", "node-schedule": "^1.3.2",
"nodejs-bing": "^0.1.0", "nodejs-bing": "^0.1.0",
"parse-torrent": "^6.1.2", "parse-torrent": "^6.1.2",
"parse-torrent-title": "git://github.com/TheBeastLT/parse-torrent-title.git#494145d07b1ac842d3edf0ed9c69ca65caf30eef", "parse-torrent-title": "git://github.com/TheBeastLT/parse-torrent-title.git#7c00602bc1c405f5574758eeabb72b133fea81d5",
"peer-search": "^0.6.x", "peer-search": "^0.6.x",
"pg": "^7.8.2", "pg": "^7.8.2",
"pg-hstore": "^2.3.2", "pg-hstore": "^2.3.2",

View File

@@ -79,7 +79,7 @@ function cacheWrapKitsuId(key, method) {
} }
function cacheWrapMetadata(id, method) { function cacheWrapMetadata(id, method) {
return cacheWrap(memoryCache, `${METADATA_PREFIX}:${id}`, method, { ttl: GLOBAL_TTL }); return cacheWrap(memoryCache, `${METADATA_PREFIX}:${id}`, method, { ttl: MEMORY_TTL });
} }
module.exports = { cacheWrapImdbId, cacheWrapKitsuId, cacheWrapMetadata, retrieveTorrentFiles }; module.exports = { cacheWrapImdbId, cacheWrapKitsuId, cacheWrapMetadata, retrieveTorrentFiles };