[addon] adds filtering based on provider
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1,3 +1,4 @@
|
||||
/.idea
|
||||
**/node_modules
|
||||
**.env
|
||||
**.env
|
||||
.now
|
||||
@@ -1,8 +1,7 @@
|
||||
const { addonBuilder } = require('stremio-addon-sdk');
|
||||
const titleParser = require('parse-torrent-title');
|
||||
const { manifest } = require('./lib/manifest');
|
||||
const { toStreamInfo } = require('./lib/streamInfo');
|
||||
const { cacheWrapStream } = require('./lib/cache');
|
||||
const { toStreamInfo, sanitizeStreamInfo } = require('./lib/streamInfo');
|
||||
const repository = require('./lib/repository');
|
||||
|
||||
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 = {
|
||||
series: () => seriesRecordsHandler(args),
|
||||
movie: () => movieRecordsHandler(args),
|
||||
series: () => seriesRecordsHandler(args).then(records => records.map(record => toStreamInfo(record))),
|
||||
movie: () => movieRecordsHandler(args).then(records => records.map(record => toStreamInfo(record))),
|
||||
fallback: () => Promise.reject('not supported type')
|
||||
};
|
||||
|
||||
return cacheWrapStream(args.id, handlers[args.type] || handlers.fallback)
|
||||
.then(records => filterRecordsBySeeders(records))
|
||||
.then(records => sortRecordsByVideoQuality(records))
|
||||
.then(records => records.map(record => toStreamInfo(record)))
|
||||
return cacheWrapStream(args.id, (handlers[args.type] || handlers.fallback))
|
||||
.then(streams => filterStreamByProvider(streams, args.extra.providers))
|
||||
.then(streams => filterStreamsBySeeders(streams))
|
||||
.then(streams => sortStreamsByVideoQuality(streams))
|
||||
.then(streams => streams.map(stream => sanitizeStreamInfo(stream)))
|
||||
.then(streams => ({
|
||||
streams: streams,
|
||||
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}`);
|
||||
}
|
||||
|
||||
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 SEEDED_SEEDERS = 1;
|
||||
const MIN_HEALTHY_COUNT = 10;
|
||||
const MAX_UNHEALTHY_COUNT = 5;
|
||||
|
||||
function filterRecordsBySeeders(records) {
|
||||
const sortedRecords = records
|
||||
.sort((a, b) => b.torrent.seeders - a.torrent.seeders || b.torrent.uploadDate - a.torrent.uploadDate);
|
||||
const healthy = sortedRecords.filter(record => record.torrent.seeders >= HEALTHY_SEEDERS);
|
||||
const seeded = sortedRecords.filter(record => record.torrent.seeders >= SEEDED_SEEDERS);
|
||||
function filterStreamsBySeeders(streams) {
|
||||
const sortedStreams = streams
|
||||
.sort((a, b) => b.filters.seeders - a.filters.seeders || b.filters.uploadDate - a.filters.uploadDate);
|
||||
const healthy = sortedStreams.filter(stream => stream.filters.seeders >= HEALTHY_SEEDERS);
|
||||
const seeded = sortedStreams.filter(stream => stream.filters.seeders >= SEEDED_SEEDERS);
|
||||
|
||||
if (healthy.length >= MIN_HEALTHY_COUNT) {
|
||||
return healthy;
|
||||
} else if (seeded.length >= MAX_UNHEALTHY_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) {
|
||||
const qualityMap = records
|
||||
.reduce((map, record) => {
|
||||
const parsedFile = titleParser.parse(record.title);
|
||||
const parsedTorrent = titleParser.parse(record.torrent.title);
|
||||
const quality = parsedFile.resolution || parsedTorrent.resolution || parsedFile.source || parsedTorrent.source;
|
||||
map[quality] = (map[quality] || []).concat(record);
|
||||
function sortStreamsByVideoQuality(streams) {
|
||||
const qualityMap = streams
|
||||
.reduce((map, stream) => {
|
||||
const quality = stream.filters.quality;
|
||||
map[quality] = (map[quality] || []).concat(stream);
|
||||
return map;
|
||||
}, {});
|
||||
const sortedQualities = Object.keys(qualityMap)
|
||||
.sort((a, b) => {
|
||||
const aQuality = a === '4k' ? '2160p' : a === 'undefined' ? undefined : a;
|
||||
const bQuality = b === '4k' ? '2160p' : b === 'undefined' ? undefined : b;
|
||||
const aQuality = a === '4k' ? '2160p' : a;
|
||||
const bQuality = b === '4k' ? '2160p' : b;
|
||||
const aResolution = aQuality && aQuality.match(/\d+p/) && parseInt(aQuality, 10);
|
||||
const bResolution = bQuality && bQuality.match(/\d+p/) && parseInt(bQuality, 10);
|
||||
if (aResolution && bResolution) {
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
const { serveHTTP } = require('stremio-addon-sdk');
|
||||
const addonInterface = require('./addon');
|
||||
const express = require('express');
|
||||
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}`)
|
||||
});
|
||||
|
||||
@@ -174,8 +174,6 @@ button:active {
|
||||
const { Providers } = require('./manifest');
|
||||
|
||||
function landingTemplate(manifest, providers = [], realDebridApiKey = '') {
|
||||
console.log(providers);
|
||||
console.log(realDebridApiKey);
|
||||
const background = manifest.background || 'https://dl.strem.io/addon-background.jpg';
|
||||
const logo = manifest.logo || 'https://dl.strem.io/addon-logo.png';
|
||||
const contactHTML = manifest.contactEmail ?
|
||||
@@ -254,7 +252,7 @@ function landingTemplate(manifest, providers = [], realDebridApiKey = '') {
|
||||
const providersValue = $('#iProviders').val().join(',');
|
||||
const realDebridValue = $('#iRealDebrid').val();
|
||||
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 configuration = configurationValue && configurationValue.length ? '/' + configurationValue : '';
|
||||
installLink.href = 'stremio://' + window.location.host + configuration + '/manifest.json';
|
||||
|
||||
@@ -6,10 +6,10 @@ const Providers = [
|
||||
'HorribleSubs'
|
||||
];
|
||||
|
||||
function manifest(providers, realDebridApiKey) {
|
||||
function manifest({ providers, realdebrid } = {}) {
|
||||
const providersList = Array.isArray(providers) && providers.map(provider => getProvider(provider)) || Providers;
|
||||
const providersDesc = providers && providers.length ? 'Enabled providers -' : 'Currently supports';
|
||||
const realDebridDesc = realDebridApiKey ? ' and RealdDebrid enabled' : '';
|
||||
const realDebridDesc = realdebrid ? ' and RealDebrid enabled' : '';
|
||||
return {
|
||||
id: 'com.stremio.torrentio.addon',
|
||||
version: '0.0.1-beta',
|
||||
|
||||
@@ -10,6 +10,16 @@ function toStreamInfo(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) {
|
||||
const titleInfo = titleParser.parse(record.title);
|
||||
const sameInfo = record.title === record.torrent.title;
|
||||
@@ -28,6 +38,12 @@ function movieStream(record) {
|
||||
name: `${ADDON_NAME}\n${record.torrent.provider}`,
|
||||
title: title,
|
||||
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,
|
||||
infoHash: record.infoHash,
|
||||
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;
|
||||
}
|
||||
|
||||
module.exports = { toStreamInfo };
|
||||
module.exports = { toStreamInfo, sanitizeStreamInfo };
|
||||
|
||||
@@ -22,19 +22,52 @@ router.get('/', (_, res) => {
|
||||
|
||||
router.get('/:configuration', (req, res) => {
|
||||
const configValues = parseConfiguration(req.params.configuration);
|
||||
console.log(configValues);
|
||||
const landingHTML = landingTemplate(manifest(), configValues.providers, configValues.realdebrid);
|
||||
const landingHTML = landingTemplate(manifest(configValues), configValues.providers, configValues.realdebrid);
|
||||
res.setHeader('content-type', 'text/html');
|
||||
res.end(landingHTML);
|
||||
});
|
||||
|
||||
router.get('/:configuration/manifest.json', (req, res) => {
|
||||
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.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) {
|
||||
router(req, res, function () {
|
||||
res.statusCode = 404;
|
||||
|
||||
4
now.json
4
now.json
@@ -1,10 +1,6 @@
|
||||
{
|
||||
"version": 2,
|
||||
"builds": [
|
||||
{
|
||||
"src": "/addon/static/**/*",
|
||||
"use": "@now/static"
|
||||
},
|
||||
{
|
||||
"src": "/addon/**/*.js",
|
||||
"use": "@now/node"
|
||||
|
||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1385,8 +1385,8 @@
|
||||
}
|
||||
},
|
||||
"parse-torrent-title": {
|
||||
"version": "git://github.com/TheBeastLT/parse-torrent-title.git#494145d07b1ac842d3edf0ed9c69ca65caf30eef",
|
||||
"from": "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#7c00602bc1c405f5574758eeabb72b133fea81d5",
|
||||
"requires": {
|
||||
"moment": "^2.24.0"
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
"node-schedule": "^1.3.2",
|
||||
"nodejs-bing": "^0.1.0",
|
||||
"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",
|
||||
"pg": "^7.8.2",
|
||||
"pg-hstore": "^2.3.2",
|
||||
|
||||
@@ -79,7 +79,7 @@ function cacheWrapKitsuId(key, 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 };
|
||||
|
||||
Reference in New Issue
Block a user