From 70c279d7d773d5758a6230ffa7263d801c2a2e26 Mon Sep 17 00:00:00 2001 From: TheBeastLT Date: Sun, 15 Mar 2020 23:46:06 +0100 Subject: [PATCH] [addon] adds filtering based on provider --- .gitignore | 3 ++- addon/addon.js | 51 ++++++++++++++++++++---------------- addon/index.js | 11 +++++--- addon/lib/landingTemplate.js | 4 +-- addon/lib/manifest.js | 4 +-- addon/lib/streamInfo.js | 23 +++++++++++++++- addon/serverless.js | 39 ++++++++++++++++++++++++--- now.json | 4 --- package-lock.json | 4 +-- package.json | 2 +- scraper/lib/cache.js | 2 +- 11 files changed, 102 insertions(+), 45 deletions(-) diff --git a/.gitignore b/.gitignore index ba60ba0..c285558 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /.idea **/node_modules -**.env \ No newline at end of file +**.env +.now \ No newline at end of file diff --git a/addon/addon.js b/addon/addon.js index 4e136ae..4c38dd9 100644 --- a/addon/addon.js +++ b/addon/addon.js @@ -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) { diff --git a/addon/index.js b/addon/index.js index 9e43810..31357aa 100644 --- a/addon/index.js +++ b/addon/index.js @@ -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}`) +}); diff --git a/addon/lib/landingTemplate.js b/addon/lib/landingTemplate.js index 5e044fb..1534020 100644 --- a/addon/lib/landingTemplate.js +++ b/addon/lib/landingTemplate.js @@ -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'; diff --git a/addon/lib/manifest.js b/addon/lib/manifest.js index f49ce6e..c85c2e9 100644 --- a/addon/lib/manifest.js +++ b/addon/lib/manifest.js @@ -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', diff --git a/addon/lib/streamInfo.js b/addon/lib/streamInfo.js index 94497f8..effef04 100644 --- a/addon/lib/streamInfo.js +++ b/addon/lib/streamInfo.js @@ -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 }; diff --git a/addon/serverless.js b/addon/serverless.js index 10018a0..651ef80 100644 --- a/addon/serverless.js +++ b/addon/serverless.js @@ -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; diff --git a/now.json b/now.json index a3a055a..6ca4fa6 100644 --- a/now.json +++ b/now.json @@ -1,10 +1,6 @@ { "version": 2, "builds": [ - { - "src": "/addon/static/**/*", - "use": "@now/static" - }, { "src": "/addon/**/*.js", "use": "@now/node" diff --git a/package-lock.json b/package-lock.json index 5296bd5..07c51bb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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" } diff --git a/package.json b/package.json index db3a42f..932d1d7 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/scraper/lib/cache.js b/scraper/lib/cache.js index 309ca2c..b6601a6 100644 --- a/scraper/lib/cache.js +++ b/scraper/lib/cache.js @@ -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 };