diff --git a/addon/index.js b/addon/index.js index af791a9..4adc7f5 100644 --- a/addon/index.js +++ b/addon/index.js @@ -8,5 +8,6 @@ app.use(express.static('static', { maxAge: '1y' })); app.use((req, res, next) => serverless(req, res, next)); app.listen(process.env.PORT || 7000, () => { initBestTrackers() - .then(() => console.log(`Started addon at: http://localhost:${process.env.PORT || 7000}`)); + .then(() => console.log(`Started addon at: http://localhost:${process.env.PORT || 7000}`)) + .catch(error => console.error('Failed init trackers', error)); }); diff --git a/addon/lib/magnetHelper.js b/addon/lib/magnetHelper.js index 306243e..d22dfe6 100644 --- a/addon/lib/magnetHelper.js +++ b/addon/lib/magnetHelper.js @@ -1,7 +1,6 @@ const needle = require('needle'); const magnet = require('magnet-uri'); -const { getRandomProxy, getProxyAgent, getRandomUserAgent } = require('../lib/requestHelper'); -const { cacheWrapProxy } = require('../lib/cache'); +const { getRandomUserAgent } = require('../lib/requestHelper'); const { getTorrent } = require('../lib/repository'); const { Type } = require('../lib/types'); @@ -33,10 +32,7 @@ async function getMagnetLink(infoHash) { } async function initBestTrackers() { - const userAgent = getRandomUserAgent(); - const proxy = await cacheWrapProxy('moch', () => getRandomProxy()).catch(() => getRandomProxy()); - const agent = getProxyAgent(proxy); - const options = { timeout: 30000, agent: agent, headers: { 'User-Agent': userAgent } }; + const options = { timeout: 30000, headers: { 'User-Agent': getRandomUserAgent() } }; BEST_TRACKERS = await needle('get', TRACKERS_URL, options) .then(response => response.body && response.body.trim()) @@ -46,6 +42,7 @@ async function initBestTrackers() { return []; }); ALL_TRACKERS = BEST_TRACKERS.concat(ANIME_TRACKERS); + console.log('Retrieved best trackers: ', BEST_TRACKERS); } module.exports = { initBestTrackers, getAllTrackers, getMagnetLink }; \ No newline at end of file diff --git a/addon/lib/manifest.js b/addon/lib/manifest.js index e4f0351..abc6f15 100644 --- a/addon/lib/manifest.js +++ b/addon/lib/manifest.js @@ -25,7 +25,7 @@ function manifest(config = {}) { .map(moch => moch.name) .join(' & '); const possibleMochs = Object.values(MochOptions).map(moch => moch.name).join('/') - const mochsDesc = enabledMochs ? ` and ${enabledMochs} enabled ` : ''; + const mochsDesc = enabledMochs ? ` and ${enabledMochs} enabled` : ''; return { id: 'com.stremio.torrentio.addon', version: '0.0.7', diff --git a/addon/lib/requestHelper.js b/addon/lib/requestHelper.js index a3dcc3e..1642a54 100644 --- a/addon/lib/requestHelper.js +++ b/addon/lib/requestHelper.js @@ -23,7 +23,7 @@ function getRandomProxy() { } function getProxyAgent(proxy) { - return new HttpsProxyAgent(proxy); + return proxy && new HttpsProxyAgent(proxy); } function blacklistProxy(proxy) { diff --git a/addon/moch/moch.js b/addon/moch/moch.js index f729955..b2d6fe1 100644 --- a/addon/moch/moch.js +++ b/addon/moch/moch.js @@ -7,7 +7,7 @@ const putio = require('./putio'); const StaticResponse = require('./static'); const { cacheWrapResolvedUrl } = require('../lib/cache'); -const MIN_API_KEY_SYMBOLS = 20; +const MIN_API_KEY_SYMBOLS = 15; const RESOLVER_HOST = process.env.RESOLVER_HOST || 'http://localhost:7050'; const MOCHS = { realdebrid: { @@ -16,13 +16,12 @@ const MOCHS = { name: "RealDebrid", shortName: 'RD' }, - // @TODO disabled until it is possible to resolve stream url for specific ip - // premiumize: { - // key: 'premiumize', - // instance: premiumize, - // name: 'Premiumize', - // shortName: 'PM' - // }, + premiumize: { + key: 'premiumize', + instance: premiumize, + name: 'Premiumize', + shortName: 'PM' + }, alldebrid: { key: 'alldebrid', instance: alldebrid, @@ -113,6 +112,7 @@ async function getMochItemMeta(mochKey, itemId, config) { meta.videos .map(video => video.streams) .reduce((a, b) => a.concat(b), []) + .filter(stream => !stream.url.startsWith('http')) .forEach(stream => stream.url = `${RESOLVER_HOST}/${moch.key}/${stream.url}`) return meta; }); diff --git a/addon/moch/premiumize.js b/addon/moch/premiumize.js index d5fe4db..612fa96 100644 --- a/addon/moch/premiumize.js +++ b/addon/moch/premiumize.js @@ -1,10 +1,14 @@ const PremiumizeClient = require('premiumize-api'); +const magnet = require('magnet-uri'); +const { Type } = require('../lib/types'); const { isVideo } = require('../lib/extension'); const StaticResponse = require('./static'); const { getRandomProxy, getProxyAgent, getRandomUserAgent } = require('../lib/requestHelper'); const { cacheWrapProxy, cacheUserAgent } = require('../lib/cache'); const { getMagnetLink } = require('../lib/magnetHelper'); +const KEY = 'premiumize'; + async function getCachedStreams(streams, apiKey) { const options = await getDefaultOptions(apiKey); const PM = new PremiumizeClient(apiKey, options); @@ -28,19 +32,70 @@ async function getCachedStreams(streams, apiKey) { }, {}) } +async function getCatalog(apiKey, offset = 0) { + if (offset > 0) { + return []; + } + const options = await getDefaultOptions(apiKey); + const PM = new PremiumizeClient(apiKey, options); + return PM.folder.list() + .then(response => response.content) + .then(torrents => (torrents || []) + .filter(torrent => torrent && torrent.type === 'folder') + .map(torrent => ({ + id: `${KEY}:${torrent.id}`, + type: Type.OTHER, + name: torrent.name + }))); +} + +async function getItemMeta(itemId, apiKey) { + const options = await getDefaultOptions(apiKey); + const PM = new PremiumizeClient(apiKey, options); + const rootFolder = await PM.folder.list(itemId, null); + return getFolderContents(PM, itemId) + .then(contents => ({ + id: `${KEY}:${itemId}`, + type: Type.OTHER, + name: rootFolder.name, + videos: contents + .map((file, index) => ({ + id: `${KEY}:${file.id}:${index}`, + title: file.name, + released: new Date(file.created_at * 1000 - index).toISOString(), + streams: [ + { url: file.stream_link || file.link } + ] + })) + })) +} + +async function getFolderContents(PM, itemId, ip, folderPrefix = '') { + return PM.folder.list(itemId, null, ip) + .then(response => response.content) + .then(contents => Promise.all(contents + .filter(content => content.type === 'folder') + .map(content => getFolderContents(PM, content.id, ip, [folderPrefix, content.name].join('/')))) + .then(otherContents => otherContents.reduce((a, b) => a.concat(b), [])) + .then(otherContents => contents + .filter(content => content.type === 'file' && isVideo(content.name)) + .map(content => ({ ...content, name: [folderPrefix, content.name].join('/') })) + .concat(otherContents))); +} + async function resolve({ ip, apiKey, infoHash, cachedEntryInfo, fileIndex }) { - console.log(`Unrestricting ${infoHash} [${fileIndex}]`); + console.log(`Unrestricting ${infoHash} [${fileIndex}] for IP ${ip}`); const options = await getDefaultOptions(apiKey, ip); const PM = new PremiumizeClient(apiKey, options); - const cachedLink = await _getCachedLink(PM, infoHash, cachedEntryInfo, fileIndex).catch(() => undefined); + const cachedLink = await _getCachedLink(PM, infoHash, cachedEntryInfo, fileIndex, ip).catch(() => undefined); if (cachedLink) { return cachedLink; } - const torrent = await _createOrFindTorrent(PM, infoHash, cachedEntryInfo, fileIndex); + const torrent = await _createOrFindTorrent(PM, infoHash); if (torrent && statusReady(torrent.status)) { - return _getCachedLink(PM, infoHash, cachedEntryInfo, fileIndex); + return _getCachedLink(PM, infoHash, cachedEntryInfo, fileIndex, ip); } else if (torrent && statusDownloading(torrent.status)) { return StaticResponse.DOWNLOADING; } else if (torrent && statusError(torrent.status)) { @@ -49,8 +104,8 @@ async function resolve({ ip, apiKey, infoHash, cachedEntryInfo, fileIndex }) { return Promise.reject("Failed Premiumize adding torrent"); } -async function _getCachedLink(PM, infoHash, encodedFileName, fileIndex) { - const cachedTorrent = await PM.transfer.directDownload(encode({ infoHash })); +async function _getCachedLink(PM, infoHash, encodedFileName, fileIndex, ip) { + const cachedTorrent = await PM.transfer.directDownload(magnet.encode({ infoHash }), ip); if (cachedTorrent && cachedTorrent.content && cachedTorrent.content.length) { const targetFileName = decodeURIComponent(encodedFileName); const videos = cachedTorrent.content.filter(file => isVideo(file.path)); @@ -113,4 +168,4 @@ async function getDefaultOptions(id, ip) { return { timeout: 30000, agent: agent, headers: { 'User-Agent': userAgent } }; } -module.exports = { getCachedStreams, resolve }; \ No newline at end of file +module.exports = { getCachedStreams, resolve, getCatalog, getItemMeta }; \ No newline at end of file diff --git a/addon/package-lock.json b/addon/package-lock.json index fd78dfa..6a84d2d 100644 --- a/addon/package-lock.json +++ b/addon/package-lock.json @@ -1426,6 +1426,11 @@ "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=" }, + "is_js": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/is_js/-/is_js-0.9.0.tgz", + "integrity": "sha1-CrlFQFArp6+iTIVqqYVWFmnpxS0=" + }, "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", @@ -1913,9 +1918,9 @@ "dev": true }, "premiumize-api": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/premiumize-api/-/premiumize-api-1.0.1.tgz", - "integrity": "sha512-2lhZ7x2C6ESaQ0WraYyDCD7sy6JuV5c2KmoypmJpD17CGzyPjQT7/eDqLzAqnDSK0LeICEaRy+rB9KZMBX5+SQ==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/premiumize-api/-/premiumize-api-1.0.2.tgz", + "integrity": "sha512-lp5AJ+0dP3wWH/UhciNvUW6OVXKJAj3uNXjN5TWlKIS62aEynlPaYWms7DnacwwZ1cFfJXY8RblBsUUja6p97w==", "requires": { "request": "^2.83.0" } @@ -2044,6 +2049,14 @@ } } }, + "request-ip": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/request-ip/-/request-ip-2.1.3.tgz", + "integrity": "sha512-J3qdE/IhVM3BXkwMIVO4yFrvhJlU3H7JH16+6yHucadT4fePnR8dyh+vEs6FIx0S2x5TCt2ptiPfHcn0sqhbYQ==", + "requires": { + "is_js": "^0.9.0" + } + }, "require_optional": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/require_optional/-/require_optional-1.0.1.tgz", diff --git a/addon/package.json b/addon/package.json index 2b29547..5b33129 100644 --- a/addon/package.json +++ b/addon/package.json @@ -21,8 +21,9 @@ "parse-torrent-title": "git://github.com/TheBeastLT/parse-torrent-title.git#3e3932737604f4a9215a6800f6c263bb2f92f1ce", "pg": "^7.8.2", "pg-hstore": "^2.3.2", - "premiumize-api": "^1.0.1", + "premiumize-api": "^1.0.2", "real-debrid-api": "git://github.com/TheBeastLT/node-real-debrid.git#935a5c23ae809edbcd2a111526a7f74d6767c50d", + "request-ip": "^2.1.3", "sequelize": "^4.43.0", "stremio-addon-sdk": "^1.6.1", "user-agents": "^1.0.559" diff --git a/addon/serverless.js b/addon/serverless.js index a3f5328..b29b79f 100644 --- a/addon/serverless.js +++ b/addon/serverless.js @@ -1,5 +1,6 @@ const rateLimit = require('express-rate-limit'); const { getRouter } = require('stremio-addon-sdk'); +const requestIp = require('request-ip'); const addonInterface = require('./addon'); const qs = require('querystring') const { manifest } = require('./lib/manifest'); @@ -77,7 +78,7 @@ router.get('/:moch/:apiKey/:infoHash/:cachedEntryInfo/:fileIndex?', (req, res) = infoHash: req.params.infoHash, fileIndex: isNaN(req.params.fileIndex) ? undefined : parseInt(req.params.fileIndex), cachedEntryInfo: req.params.cachedEntryInfo, - ip: req.ip + ip: requestIp.getClientIp(req) } moch.resolve(parameters) .then(url => {