diff --git a/addon/addon.js b/addon/addon.js index 36b0f9c..ddfa3a1 100644 --- a/addon/addon.js +++ b/addon/addon.js @@ -32,7 +32,7 @@ builder.defineStreamHandler((args) => { .sort((a, b) => b.torrent.seeders - a.torrent.seeders || b.torrent.uploadDate - a.torrent.uploadDate) .map(record => toStreamInfo(record))))) .then(streams => applyFilters(streams, args.extra)) - .then(streams => applySorting(streams, args.extra)) + .then(streams => applySorting(streams, args.extra, args.type)) .then(streams => applyStaticInfo(streams)) .then(streams => applyMochs(streams, args.extra)) .then(streams => enrichCacheParams(streams)) diff --git a/addon/lib/landingTemplate.js b/addon/lib/landingTemplate.js index 253c245..250e8e9 100644 --- a/addon/lib/landingTemplate.js +++ b/addon/lib/landingTemplate.js @@ -437,7 +437,7 @@ function landingTemplate(manifest, config = {}) { const qualityFilters = qualityFilterValue.length && qualityFilterValue; const sort = sortValue !== '${SortOptions.options.qualitySeeders.key}' && sortValue; const language = languageValue.length && languageValue !== 'none' && languageValue; - const limit = /^[1-9][0-9]?$/.test(limitValue) && limitValue; + const limit = /^[1-9][0-9]{0,2}$/.test(limitValue) && limitValue; const debridOptions = debridOptionsValue.length && debridOptionsValue.trim(); const realDebrid = realDebridValue.length && realDebridValue.trim(); diff --git a/addon/lib/sort.js b/addon/lib/sort.js index ad0ca3a..3a996bd 100644 --- a/addon/lib/sort.js +++ b/addon/lib/sort.js @@ -1,5 +1,7 @@ const { QualityFilter } = require('./filter'); const { languages, containsLanguage } = require('./languages'); +const { Type } = require("./types"); +const { hasMochConfigured } = require("../moch/moch"); const OTHER_QUALITIES = QualityFilter.options.find(option => option.key === 'other'); const CAM_QUALITIES = QualityFilter.options.find(option => option.key === 'cam'); @@ -37,39 +39,42 @@ const LanguageOptions = { })) } -function sortStreams(streams, config) { +function sortStreams(streams, config, type) { const language = config[LanguageOptions.key]; if (language && language !== languages[0]) { // No need to filter english since it's hard to predict which entries are english const streamsWithLanguage = streams.filter(stream => containsLanguage(stream, language)); const streamsNoLanguage = streams.filter(stream => !streamsWithLanguage.includes(stream)); - return _sortStreams(streamsWithLanguage, config).concat(_sortStreams(streamsNoLanguage, config)); + return _sortStreams(streamsWithLanguage, config, type).concat(_sortStreams(streamsNoLanguage, config, type)); } - return _sortStreams(streams, config); + return _sortStreams(streams, config, type); } -function _sortStreams(streams, config) { +function _sortStreams(streams, config, type) { const sort = config.sort && config.sort.toLowerCase() || undefined; const limit = /^[1-9][0-9]*$/.test(config.limit) && parseInt(config.limit) || undefined; + const sortedStreams = sortBySeeders(streams, config, type, limit); if (sort === SortOptions.options.seeders.key) { - return sortBySeeders(streams, limit); + return sortedStreams } else if (sort === SortOptions.options.size.key) { - return sortBySize(streams, limit); + return sortBySize(sortedStreams, limit); } const nestedSort = sort === SortOptions.options.qualitySize.key ? sortBySize : noopSort; - return sortByVideoQuality(streams, nestedSort, limit) + return sortByVideoQuality(sortedStreams, nestedSort, limit) } function noopSort(streams) { return streams; } -function sortBySeeders(streams, limit) { +function sortBySeeders(streams, config, type, limit) { // streams are already presorted by seeders and upload date const healthy = streams.filter(stream => extractSeeders(stream.title) >= HEALTHY_SEEDERS); const seeded = streams.filter(stream => extractSeeders(stream.title) >= SEEDED_SEEDERS); - if (healthy.length >= MIN_HEALTHY_COUNT) { + if (type === Type.SERIES && hasMochConfigured(config)) { + return streams.slice(0, limit); + } else if (healthy.length >= MIN_HEALTHY_COUNT) { return healthy.slice(0, limit); } else if (seeded.length >= MAX_UNHEALTHY_COUNT) { return seeded.slice(0, MIN_HEALTHY_COUNT).slice(0, limit); @@ -78,7 +83,7 @@ function sortBySeeders(streams, limit) { } function sortBySize(streams, limit) { - return sortBySeeders(streams) + return streams .sort((a, b) => { const aSize = extractSize(a.title); const bSize = extractSize(b.title); @@ -87,7 +92,7 @@ function sortBySize(streams, limit) { } function sortByVideoQuality(streams, nestedSort, limit) { - const qualityMap = sortBySeeders(streams) + const qualityMap = streams .reduce((map, stream) => { const quality = extractQuality(stream.name); map[quality] = (map[quality] || []).concat(stream); diff --git a/addon/moch/moch.js b/addon/moch/moch.js index 0bfb93b..6cb6066 100644 --- a/addon/moch/moch.js +++ b/addon/moch/moch.js @@ -64,8 +64,12 @@ const unrestrictQueue = new namedQueue((task, callback) => task.method() .then(result => callback(false, result)) .catch((error => callback(error))), 20); +function hasMochConfigured(config) { + return Object.keys(MOCHS).find(moch => config && config[moch]) +} + async function applyMochs(streams, config) { - if (!streams || !streams.length || !Object.keys(MOCHS).find(moch => config[moch])) { + if (!streams || !streams.length || !hasMochConfigured(config)) { return streams; } return Promise.all(Object.keys(config) @@ -170,23 +174,29 @@ function populateCachedLinks(streams, mochResult) { } function populateDownloadLinks(streams, mochResults) { - streams - .filter(stream => stream.infoHash) - .forEach(stream => mochResults - .forEach(mochResult => { - const cachedEntry = mochResult.mochStreams[stream.infoHash]; - if (!cachedEntry || !cachedEntry.cached) { - streams.push({ - name: `[${mochResult.moch.shortName} download] ${stream.name}`, - title: stream.title, - url: `${RESOLVER_HOST}/${mochResult.moch.key}/${cachedEntry.url}/${streamFilename(stream)}`, - behaviorHints: stream.behaviorHints - }) - } - })); + const torrentStreams = streams.filter(stream => stream.infoHash); + torrentStreams.forEach(stream => mochResults.forEach(mochResult => { + const cachedEntry = mochResult.mochStreams[stream.infoHash]; + const isCached = cachedEntry && cachedEntry.cached; + if (!isCached && isHealthyStreamForDebrid(torrentStreams, stream)) { + streams.push({ + name: `[${mochResult.moch.shortName} download] ${stream.name}`, + title: stream.title, + url: `${RESOLVER_HOST}/${mochResult.moch.key}/${cachedEntry.url}/${streamFilename(stream)}`, + behaviorHints: stream.behaviorHints + }) + } + })); return streams; } +function isHealthyStreamForDebrid(streams, stream) { + const isZeroSeeders = /👤 0\b/.test(stream.title); + const is4kStream = /\b4k\b/.test(stream.name); + const isNotEnoughOptions = streams.length <= 5; + return !isZeroSeeders || is4kStream || isNotEnoughOptions; +} + function isInvalidToken(token, mochKey) { return token.length < MIN_API_KEY_SYMBOLS || TOKEN_BLACKLIST.includes(`${mochKey}|${token}`); } @@ -215,4 +225,4 @@ function errorStreamResponse(mochKey, error) { return undefined; } -module.exports = { applyMochs, getMochCatalog, getMochItemMeta, resolve, MochOptions: MOCHS } +module.exports = { applyMochs, getMochCatalog, getMochItemMeta, resolve, hasMochConfigured, MochOptions: MOCHS }