diff --git a/addon/addon.js b/addon/addon.js index cc75050..7d63c76 100644 --- a/addon/addon.js +++ b/addon/addon.js @@ -3,17 +3,14 @@ const { manifest } = require('./lib/manifest'); const { cacheWrapStream } = require('./lib/cache'); const { toStreamInfo, sanitizeStreamInfo } = require('./lib/streamInfo'); const repository = require('./lib/repository'); -const realdebrid = require('./moch/realdebrid'); +const applyStreamSorting = require('./lib/sort'); +const applyMochs = require('./moch/moch'); const CACHE_MAX_AGE = process.env.CACHE_MAX_AGE || 4 * 60 * 60; // 4 hours in seconds const CACHE_MAX_AGE_EMPTY = 30 * 60; // 30 minutes const STALE_REVALIDATE_AGE = 4 * 60 * 60; // 4 hours const STALE_ERROR_AGE = 7 * 24 * 60 * 60; // 7 days -const MOCHS = { - 'realdebrid': realdebrid -}; - const builder = new addonBuilder(manifest()); builder.defineStreamHandler((args) => { @@ -29,8 +26,7 @@ builder.defineStreamHandler((args) => { 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 => applyStreamSorting(streams, args.extra)) .then(streams => applyMochs(streams, args.extra)) .then(streams => streams.map(stream => sanitizeStreamInfo(stream))) .then(streams => ({ @@ -77,66 +73,4 @@ function filterStreamByProvider(streams, providers) { 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 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 sortedStreams.slice(0, MAX_UNHEALTHY_COUNT); -} - -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; - 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) { - return bResolution - aResolution; // higher resolution first; - } else if (aResolution) { - return -1; - } else if (bResolution) { - return 1; - } - return a < b ? -1 : b < a ? 1 : 0; - }); - return sortedQualities - .map(quality => qualityMap[quality]) - .reduce((a, b) => a.concat(b), []); -} - -function applyMochs(streams, config) { - if (!streams || !streams.length) { - return streams; - } - - return Object.keys(config) - .filter(configKey => MOCHS[configKey]) - .reduce(async (streams, moch) => { - return await MOCHS[moch].applyMoch(streams, config[moch]) - .catch(error => { - console.warn(error); - return streams; - }); - }, streams); -} - module.exports = builder.getInterface(); diff --git a/addon/lib/landingTemplate.js b/addon/lib/landingTemplate.js index 0465f4d..3f12742 100644 --- a/addon/lib/landingTemplate.js +++ b/addon/lib/landingTemplate.js @@ -154,6 +154,16 @@ button:active { width: 100%; } +.btn { + text-align: left; +} + +.caret { + position: absolute; + left: 97%; + top: 45%; +} + .multiselect-container { border: 0; border-radius: 0; @@ -168,10 +178,12 @@ button:active { border-radius: 0; outline: 0; color: #333; + background-color: rgb(255, 255, 255); box-shadow: 0 0.5vh 1vh rgba(0, 0, 0, 0.2); } `; const { Providers } = require('./manifest'); +const { SortType } = require('./sort'); function landingTemplate(manifest, providers = [], realDebridApiKey = '') { const background = manifest.background || 'https://dl.strem.io/addon-background.jpg'; @@ -227,6 +239,15 @@ function landingTemplate(manifest, providers = [], realDebridApiKey = '') { ${providersHTML} + + + + + + @@ -248,12 +269,27 @@ function landingTemplate(manifest, providers = [], realDebridApiKey = '') { generateInstallLink(); }); + function sortModeChange() { + if ($('#iSort').val() === '${SortType.SEEDERS}') { + $("#iLimitLabel").text("Max results:"); + } else { + $("#iLimitLabel").text("Max results per quality:"); + } + generateInstallLink(); + } + function generateInstallLink() { const providersValue = $('#iProviders').val().join(','); const realDebridValue = $('#iRealDebrid').val(); + const sortValue = $('#iSort').val(); + const limitValue = $('#iLimit').val(); + const providers = providersValue && providersValue.length ? 'providers=' + providersValue : ''; const realDebrid = realDebridValue && realDebridValue.length ? 'realdebrid=' + realDebridValue : ''; - const configurationValue = [providers, realDebrid].filter(value => value.length).join('|'); + const sort = sortValue === '${SortType.SEEDERS}' ? 'sort=' + sortValue : ''; + const limit = /^[1-9][0-9]*$/.test(limitValue) ? 'limit=' + limitValue : ''; + + const configurationValue = [providers, sort, limit, 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/sort.js b/addon/lib/sort.js new file mode 100644 index 0000000..d7bff59 --- /dev/null +++ b/addon/lib/sort.js @@ -0,0 +1,62 @@ +const HEALTHY_SEEDERS = 5; +const SEEDED_SEEDERS = 1; +const MIN_HEALTHY_COUNT = 10; +const MAX_UNHEALTHY_COUNT = 5; + +const SortType = { + QUALITY: 'quality', + SEEDERS: 'seeders', +}; + +function sortStreams(streams, config) { + const sort = config.sort && config.sort.toLowerCase() || undefined; + const limit = /^[1-9][0-9]*$/.test(config.limit) && parseInt(config.limit) || undefined; + if (sort === SortType.SEEDERS) { + return sortBySeeders(streams).slice(0, limit) + } + return sortByVideoQuality(streams, limit) +} + +function sortBySeeders(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 sortedStreams.slice(0, MAX_UNHEALTHY_COUNT); +} + +function sortByVideoQuality(streams, limit) { + const qualityMap = sortBySeeders(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; + 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) { + return bResolution - aResolution; // higher resolution first; + } else if (aResolution) { + return -1; + } else if (bResolution) { + return 1; + } + return a < b ? -1 : b < a ? 1 : 0; + }); + return sortedQualities + .map(quality => qualityMap[quality].slice(0, limit)) + .reduce((a, b) => a.concat(b), []); +} + +module.exports = sortStreams; +module.exports.SortType = SortType; \ No newline at end of file diff --git a/addon/moch/moch.js b/addon/moch/moch.js new file mode 100644 index 0000000..4328661 --- /dev/null +++ b/addon/moch/moch.js @@ -0,0 +1,23 @@ +const realdebrid = require('./realdebrid'); + +const MOCHS = { + 'realdebrid': realdebrid +}; + +async function applyMochs(streams, config) { + if (!streams || !streams.length) { + return streams; + } + + return Object.keys(config) + .filter(configKey => MOCHS[configKey]) + .reduce(async (streams, moch) => { + return await MOCHS[moch].applyMoch(streams, config[moch]) + .catch(error => { + console.warn(error); + return streams; + }); + }, streams); +} + +module.exports = applyMochs; \ No newline at end of file