mirror of
https://github.com/TheBeastLT/torrentio-scraper.git
synced 2026-01-11 22:40:22 +00:00
add torbox integration, closes #184
This commit is contained in:
parent
655f17901c
commit
94f492cd59
13 changed files with 307 additions and 40 deletions
|
|
@ -38,9 +38,9 @@ builder.defineStreamHandler((args) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
builder.defineCatalogHandler((args) => {
|
builder.defineCatalogHandler((args) => {
|
||||||
const mochKey = args.id.replace("torrentio-", '');
|
const [_, mochKey, catalogId] = args.id.split('-');
|
||||||
console.log(`Incoming catalog ${args.id} request with skip=${args.extra.skip || 0}`)
|
console.log(`Incoming catalog ${args.id} request with skip=${args.extra.skip || 0}`)
|
||||||
return getMochCatalog(mochKey, args.extra)
|
return getMochCatalog(mochKey, catalogId, args.extra)
|
||||||
.then(metas => ({
|
.then(metas => ({
|
||||||
metas: metas,
|
metas: metas,
|
||||||
cacheMaxAge: CATALOG_CACHE_MAX_AGE
|
cacheMaxAge: CATALOG_CACHE_MAX_AGE
|
||||||
|
|
|
||||||
|
|
@ -206,6 +206,7 @@ export default function landingTemplate(manifest, config = {}) {
|
||||||
const allDebridApiKey = config[MochOptions.alldebrid.key] || '';
|
const allDebridApiKey = config[MochOptions.alldebrid.key] || '';
|
||||||
const debridLinkApiKey = config[MochOptions.debridlink.key] || '';
|
const debridLinkApiKey = config[MochOptions.debridlink.key] || '';
|
||||||
const offcloudApiKey = config[MochOptions.offcloud.key] || '';
|
const offcloudApiKey = config[MochOptions.offcloud.key] || '';
|
||||||
|
const torboxApiKey = config[MochOptions.torbox.key] || '';
|
||||||
const putioKey = config[MochOptions.putio.key] || '';
|
const putioKey = config[MochOptions.putio.key] || '';
|
||||||
const putioClientId = putioKey.replace(/@.*/, '');
|
const putioClientId = putioKey.replace(/@.*/, '');
|
||||||
const putioToken = putioKey.replace(/.*@/, '');
|
const putioToken = putioKey.replace(/.*@/, '');
|
||||||
|
|
@ -330,6 +331,11 @@ export default function landingTemplate(manifest, config = {}) {
|
||||||
<input type="text" id="iOffcloud" onchange="generateInstallLink()" class="input">
|
<input type="text" id="iOffcloud" onchange="generateInstallLink()" class="input">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div id="dTorbox">
|
||||||
|
<label class="label" for="iTorbox">TorBox API Key (Find it <a href='https://torbox.app/settings' target="_blank">here</a>):</label>
|
||||||
|
<input type="text" id="iTorbox" onchange="generateInstallLink()" class="input">
|
||||||
|
</div>
|
||||||
|
|
||||||
<div id="dPutio">
|
<div id="dPutio">
|
||||||
<label class="label" for="iPutio">Put.io ClientId and Token (Create new OAuth App <a href='https://app.put.io/oauth' target="_blank">here</a>):</label>
|
<label class="label" for="iPutio">Put.io ClientId and Token (Create new OAuth App <a href='https://app.put.io/oauth' target="_blank">here</a>):</label>
|
||||||
<input type="text" id="iPutioClientId" placeholder="ClientId" onchange="generateInstallLink()" class="input">
|
<input type="text" id="iPutioClientId" placeholder="ClientId" onchange="generateInstallLink()" class="input">
|
||||||
|
|
@ -396,6 +402,7 @@ export default function landingTemplate(manifest, config = {}) {
|
||||||
$('#iAllDebrid').val("${allDebridApiKey}");
|
$('#iAllDebrid').val("${allDebridApiKey}");
|
||||||
$('#iDebridLink').val("${debridLinkApiKey}");
|
$('#iDebridLink').val("${debridLinkApiKey}");
|
||||||
$('#iOffcloud').val("${offcloudApiKey}");
|
$('#iOffcloud').val("${offcloudApiKey}");
|
||||||
|
$('#iTorbox').val("${torboxApiKey}");
|
||||||
$('#iPutioClientId').val("${putioClientId}");
|
$('#iPutioClientId').val("${putioClientId}");
|
||||||
$('#iPutioToken').val("${putioToken}");
|
$('#iPutioToken').val("${putioToken}");
|
||||||
$('#iSort').val("${sort}");
|
$('#iSort').val("${sort}");
|
||||||
|
|
@ -422,6 +429,7 @@ export default function landingTemplate(manifest, config = {}) {
|
||||||
$('#dAllDebrid').toggle(provider === '${MochOptions.alldebrid.key}');
|
$('#dAllDebrid').toggle(provider === '${MochOptions.alldebrid.key}');
|
||||||
$('#dDebridLink').toggle(provider === '${MochOptions.debridlink.key}');
|
$('#dDebridLink').toggle(provider === '${MochOptions.debridlink.key}');
|
||||||
$('#dOffcloud').toggle(provider === '${MochOptions.offcloud.key}');
|
$('#dOffcloud').toggle(provider === '${MochOptions.offcloud.key}');
|
||||||
|
$('#dTorbox').toggle(provider === '${MochOptions.torbox.key}');
|
||||||
$('#dPutio').toggle(provider === '${MochOptions.putio.key}');
|
$('#dPutio').toggle(provider === '${MochOptions.putio.key}');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -439,7 +447,8 @@ export default function landingTemplate(manifest, config = {}) {
|
||||||
const allDebridValue = $('#iAllDebrid').val() || '';
|
const allDebridValue = $('#iAllDebrid').val() || '';
|
||||||
const debridLinkValue = $('#iDebridLink').val() || ''
|
const debridLinkValue = $('#iDebridLink').val() || ''
|
||||||
const premiumizeValue = $('#iPremiumize').val() || '';
|
const premiumizeValue = $('#iPremiumize').val() || '';
|
||||||
const offcloudValue = $('#iOffcloud').val() || ''
|
const offcloudValue = $('#iOffcloud').val() || '';
|
||||||
|
const torboxValue = $('#iTorbox').val() || '';
|
||||||
const putioClientIdValue = $('#iPutioClientId').val() || '';
|
const putioClientIdValue = $('#iPutioClientId').val() || '';
|
||||||
const putioTokenValue = $('#iPutioToken').val() || '';
|
const putioTokenValue = $('#iPutioToken').val() || '';
|
||||||
|
|
||||||
|
|
@ -457,6 +466,7 @@ export default function landingTemplate(manifest, config = {}) {
|
||||||
const allDebrid = allDebridValue.length && allDebridValue.trim();
|
const allDebrid = allDebridValue.length && allDebridValue.trim();
|
||||||
const debridLink = debridLinkValue.length && debridLinkValue.trim();
|
const debridLink = debridLinkValue.length && debridLinkValue.trim();
|
||||||
const offcloud = offcloudValue.length && offcloudValue.trim();
|
const offcloud = offcloudValue.length && offcloudValue.trim();
|
||||||
|
const torbox = torboxValue.length && torboxValue.trim();
|
||||||
const putio = putioClientIdValue.length && putioTokenValue.length && putioClientIdValue.trim() + '@' + putioTokenValue.trim();
|
const putio = putioClientIdValue.length && putioTokenValue.length && putioClientIdValue.trim() + '@' + putioTokenValue.trim();
|
||||||
|
|
||||||
const preConfigurations = {
|
const preConfigurations = {
|
||||||
|
|
@ -475,6 +485,7 @@ export default function landingTemplate(manifest, config = {}) {
|
||||||
['${MochOptions.alldebrid.key}', allDebrid],
|
['${MochOptions.alldebrid.key}', allDebrid],
|
||||||
['${MochOptions.debridlink.key}', debridLink],
|
['${MochOptions.debridlink.key}', debridLink],
|
||||||
['${MochOptions.offcloud.key}', offcloud],
|
['${MochOptions.offcloud.key}', offcloud],
|
||||||
|
['${MochOptions.torbox.key}', torbox],
|
||||||
['${MochOptions.putio.key}', putio]
|
['${MochOptions.putio.key}', putio]
|
||||||
].filter(([_, value]) => value.length).map(([key, value]) => key + '=' + value).join('|');
|
].filter(([_, value]) => value.length).map(([key, value]) => key + '=' + value).join('|');
|
||||||
configurationValue = Object.entries(preConfigurations)
|
configurationValue = Object.entries(preConfigurations)
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import { getManifestOverride } from './configuration.js';
|
||||||
import { Type } from './types.js';
|
import { Type } from './types.js';
|
||||||
|
|
||||||
const DefaultProviders = Providers.options.map(provider => provider.key);
|
const DefaultProviders = Providers.options.map(provider => provider.key);
|
||||||
const CatalogMochs = Object.values(MochOptions).filter(moch => moch.catalog);
|
const MochProviders = Object.values(MochOptions);
|
||||||
|
|
||||||
export function manifest(config = {}) {
|
export function manifest(config = {}) {
|
||||||
const overrideManifest = getManifestOverride(config);
|
const overrideManifest = getManifestOverride(config);
|
||||||
|
|
@ -36,7 +36,7 @@ export function dummyManifest() {
|
||||||
|
|
||||||
function getName(manifest, config) {
|
function getName(manifest, config) {
|
||||||
const rootName = manifest?.name || 'Torrentio';
|
const rootName = manifest?.name || 'Torrentio';
|
||||||
const mochSuffix = Object.values(MochOptions)
|
const mochSuffix = MochProviders
|
||||||
.filter(moch => config[moch.key])
|
.filter(moch => config[moch.key])
|
||||||
.map(moch => moch.shortName)
|
.map(moch => moch.shortName)
|
||||||
.join('/');
|
.join('/');
|
||||||
|
|
@ -48,11 +48,11 @@ function getDescription(config) {
|
||||||
const enabledProvidersDesc = Providers.options
|
const enabledProvidersDesc = Providers.options
|
||||||
.map(provider => `${provider.label}${providersList.includes(provider.key) ? '(+)' : '(-)'}`)
|
.map(provider => `${provider.label}${providersList.includes(provider.key) ? '(+)' : '(-)'}`)
|
||||||
.join(', ')
|
.join(', ')
|
||||||
const enabledMochs = Object.values(MochOptions)
|
const enabledMochs = MochProviders
|
||||||
.filter(moch => config[moch.key])
|
.filter(moch => config[moch.key])
|
||||||
.map(moch => moch.name)
|
.map(moch => moch.name)
|
||||||
.join(' & ');
|
.join(' & ');
|
||||||
const possibleMochs = Object.values(MochOptions).map(moch => moch.name).join('/')
|
const possibleMochs = MochProviders.map(moch => moch.name).join('/')
|
||||||
const mochsDesc = enabledMochs ? ` and ${enabledMochs} enabled` : '';
|
const mochsDesc = enabledMochs ? ` and ${enabledMochs} enabled` : '';
|
||||||
return 'Provides torrent streams from scraped torrent providers.'
|
return 'Provides torrent streams from scraped torrent providers.'
|
||||||
+ ` Currently supports ${enabledProvidersDesc}${mochsDesc}.`
|
+ ` Currently supports ${enabledProvidersDesc}${mochsDesc}.`
|
||||||
|
|
@ -60,14 +60,15 @@ function getDescription(config) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function getCatalogs(config) {
|
function getCatalogs(config) {
|
||||||
return CatalogMochs
|
return MochProviders
|
||||||
.filter(moch => showDebridCatalog(config) && config[moch.key])
|
.filter(moch => showDebridCatalog(config) && config[moch.key])
|
||||||
.map(moch => ({
|
.map(moch => moch.catalogs.map(catalogName => ({
|
||||||
id: `torrentio-${moch.key}`,
|
id: catalogName ? `torrentio-${moch.key}-${catalogName.toLowerCase()}` : `torrentio-${moch.key}`,
|
||||||
name: `${moch.name}`,
|
name: catalogName ? `${moch.name} ${catalogName}` : `${moch.name}`,
|
||||||
type: 'other',
|
type: 'other',
|
||||||
extra: [{ name: 'skip' }],
|
extra: [{ name: 'skip' }],
|
||||||
}));
|
})))
|
||||||
|
.reduce((a, b) => a.concat(b), []);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getResources(config) {
|
function getResources(config) {
|
||||||
|
|
@ -79,9 +80,9 @@ function getResources(config) {
|
||||||
const metaResource = {
|
const metaResource = {
|
||||||
name: 'meta',
|
name: 'meta',
|
||||||
types: [Type.OTHER],
|
types: [Type.OTHER],
|
||||||
idPrefixes: CatalogMochs.filter(moch => config[moch.key]).map(moch => moch.key)
|
idPrefixes: MochProviders.filter(moch => config[moch.key]).map(moch => moch.key)
|
||||||
};
|
};
|
||||||
if (showDebridCatalog(config) && CatalogMochs.filter(moch => config[moch.key]).length) {
|
if (showDebridCatalog(config) && MochProviders.filter(moch => config[moch.key]).length) {
|
||||||
return [streamResource, metaResource];
|
return [streamResource, metaResource];
|
||||||
}
|
}
|
||||||
return [streamResource];
|
return [streamResource];
|
||||||
|
|
|
||||||
|
|
@ -20,11 +20,11 @@ export async function getCachedStreams(streams, apiKey, ip) {
|
||||||
}, {})
|
}, {})
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getCatalog(apiKey, offset = 0, ip) {
|
export async function getCatalog(apiKey, catalogId, config) {
|
||||||
if (offset > 0) {
|
if (config.skip > 0) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
const options = await getDefaultOptions(ip);
|
const options = await getDefaultOptions(config.ip);
|
||||||
const AD = new AllDebridClient(apiKey, options);
|
const AD = new AllDebridClient(apiKey, options);
|
||||||
return AD.magnet.status()
|
return AD.magnet.status()
|
||||||
.then(response => response.data.magnets)
|
.then(response => response.data.magnets)
|
||||||
|
|
|
||||||
|
|
@ -18,8 +18,8 @@ export async function getCachedStreams(streams, apiKey) {
|
||||||
}, {})
|
}, {})
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getCatalog(apiKey, offset = 0) {
|
export async function getCatalog(apiKey, catalogId, config) {
|
||||||
if (offset > 0) {
|
if (config.skip > 0) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
const options = await getDefaultOptions();
|
const options = await getDefaultOptions();
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
import namedQueue from 'named-queue';
|
|
||||||
import * as options from './options.js';
|
import * as options from './options.js';
|
||||||
import * as realdebrid from './realdebrid.js';
|
import * as realdebrid from './realdebrid.js';
|
||||||
import * as premiumize from './premiumize.js';
|
import * as premiumize from './premiumize.js';
|
||||||
import * as alldebrid from './alldebrid.js';
|
import * as alldebrid from './alldebrid.js';
|
||||||
import * as debridlink from './debridlink.js';
|
import * as debridlink from './debridlink.js';
|
||||||
import * as offcloud from './offcloud.js';
|
import * as offcloud from './offcloud.js';
|
||||||
|
import * as torbox from './torbox.js';
|
||||||
import * as putio from './putio.js';
|
import * as putio from './putio.js';
|
||||||
import StaticResponse, { isStaticUrl } from './static.js';
|
import StaticResponse, { isStaticUrl } from './static.js';
|
||||||
import { cacheWrapResolvedUrl } from '../lib/cache.js';
|
import { cacheWrapResolvedUrl } from '../lib/cache.js';
|
||||||
|
|
@ -21,42 +21,49 @@ export const MochOptions = {
|
||||||
instance: realdebrid,
|
instance: realdebrid,
|
||||||
name: "RealDebrid",
|
name: "RealDebrid",
|
||||||
shortName: 'RD',
|
shortName: 'RD',
|
||||||
catalog: true
|
catalogs: ['']
|
||||||
},
|
},
|
||||||
premiumize: {
|
premiumize: {
|
||||||
key: 'premiumize',
|
key: 'premiumize',
|
||||||
instance: premiumize,
|
instance: premiumize,
|
||||||
name: 'Premiumize',
|
name: 'Premiumize',
|
||||||
shortName: 'PM',
|
shortName: 'PM',
|
||||||
catalog: true
|
catalogs: ['']
|
||||||
},
|
},
|
||||||
alldebrid: {
|
alldebrid: {
|
||||||
key: 'alldebrid',
|
key: 'alldebrid',
|
||||||
instance: alldebrid,
|
instance: alldebrid,
|
||||||
name: 'AllDebrid',
|
name: 'AllDebrid',
|
||||||
shortName: 'AD',
|
shortName: 'AD',
|
||||||
catalog: true
|
catalogs: ['']
|
||||||
},
|
},
|
||||||
debridlink: {
|
debridlink: {
|
||||||
key: 'debridlink',
|
key: 'debridlink',
|
||||||
instance: debridlink,
|
instance: debridlink,
|
||||||
name: 'DebridLink',
|
name: 'DebridLink',
|
||||||
shortName: 'DL',
|
shortName: 'DL',
|
||||||
catalog: true
|
catalogs: ['']
|
||||||
},
|
},
|
||||||
offcloud: {
|
offcloud: {
|
||||||
key: 'offcloud',
|
key: 'offcloud',
|
||||||
instance: offcloud,
|
instance: offcloud,
|
||||||
name: 'Offcloud',
|
name: 'Offcloud',
|
||||||
shortName: 'OC',
|
shortName: 'OC',
|
||||||
catalog: true
|
catalogs: ['']
|
||||||
|
},
|
||||||
|
torbox: {
|
||||||
|
key: 'torbox',
|
||||||
|
instance: torbox,
|
||||||
|
name: 'TorBox',
|
||||||
|
shortName: 'TB',
|
||||||
|
catalogs: [`Torrents`, `Usenet`, `WebDL`]
|
||||||
},
|
},
|
||||||
putio: {
|
putio: {
|
||||||
key: 'putio',
|
key: 'putio',
|
||||||
instance: putio,
|
instance: putio,
|
||||||
name: 'Put.io',
|
name: 'Put.io',
|
||||||
shortName: 'Putio',
|
shortName: 'Putio',
|
||||||
catalog: true
|
catalogs: ['']
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -112,7 +119,7 @@ export async function resolve(parameters) {
|
||||||
return unrestrictQueues[moch.key].wrap(id, method);
|
return unrestrictQueues[moch.key].wrap(id, method);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getMochCatalog(mochKey, config) {
|
export async function getMochCatalog(mochKey, catalogId, config, ) {
|
||||||
const moch = MochOptions[mochKey];
|
const moch = MochOptions[mochKey];
|
||||||
if (!moch) {
|
if (!moch) {
|
||||||
return Promise.reject(new Error(`Not a valid moch provider: ${mochKey}`));
|
return Promise.reject(new Error(`Not a valid moch provider: ${mochKey}`));
|
||||||
|
|
@ -120,7 +127,7 @@ export async function getMochCatalog(mochKey, config) {
|
||||||
if (isInvalidToken(config[mochKey], mochKey)) {
|
if (isInvalidToken(config[mochKey], mochKey)) {
|
||||||
return Promise.reject(new Error(`Invalid API key for moch provider: ${mochKey}`));
|
return Promise.reject(new Error(`Invalid API key for moch provider: ${mochKey}`));
|
||||||
}
|
}
|
||||||
return moch.instance.getCatalog(config[moch.key], config.skip, config.ip)
|
return moch.instance.getCatalog(config[moch.key], catalogId, config)
|
||||||
.catch(rawError => {
|
.catch(rawError => {
|
||||||
const commonError = moch.instance.toCommonError(rawError);
|
const commonError = moch.instance.toCommonError(rawError);
|
||||||
if (commonError === BadTokenError) {
|
if (commonError === BadTokenError) {
|
||||||
|
|
|
||||||
|
|
@ -34,8 +34,8 @@ export async function getCachedStreams(streams, apiKey) {
|
||||||
}, {})
|
}, {})
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getCatalog(apiKey, offset = 0) {
|
export async function getCatalog(apiKey, catalogId, config) {
|
||||||
if (offset > 0) {
|
if (config.skip > 0) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
const options = await getDefaultOptions();
|
const options = await getDefaultOptions();
|
||||||
|
|
|
||||||
|
|
@ -37,8 +37,8 @@ async function _getCachedStreams(PM, apiKey, streams) {
|
||||||
}, {}));
|
}, {}));
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getCatalog(apiKey, offset = 0) {
|
export async function getCatalog(apiKey, catalogId, config) {
|
||||||
if (offset > 0) {
|
if (config.skip > 0) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
const options = await getDefaultOptions();
|
const options = await getDefaultOptions();
|
||||||
|
|
|
||||||
|
|
@ -22,8 +22,8 @@ export async function getCachedStreams(streams, apiKey) {
|
||||||
}, {});
|
}, {});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getCatalog(apiKey, offset = 0) {
|
export async function getCatalog(apiKey, catalogId, config) {
|
||||||
if (offset > 0) {
|
if (config.skip > 0) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
const Putio = createPutioAPI(apiKey)
|
const Putio = createPutioAPI(apiKey)
|
||||||
|
|
|
||||||
|
|
@ -39,11 +39,8 @@ function _getCachedFileIds(fileIndex, cachedResults) {
|
||||||
return cachedIds || [];
|
return cachedIds || [];
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getCatalog(apiKey, offset, ip) {
|
export async function getCatalog(apiKey, catalogId, config) {
|
||||||
if (offset > 0) {
|
const options = await getDefaultOptions(config.ip);
|
||||||
return [];
|
|
||||||
}
|
|
||||||
const options = await getDefaultOptions(ip);
|
|
||||||
const RD = new RealDebridClient(apiKey, options);
|
const RD = new RealDebridClient(apiKey, options);
|
||||||
const downloadsMeta = {
|
const downloadsMeta = {
|
||||||
id: `${KEY}:${DEBRID_DOWNLOADS}`,
|
id: `${KEY}:${DEBRID_DOWNLOADS}`,
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,9 @@ const staticVideoUrls = {
|
||||||
FAILED_RAR: `videos/failed_rar_v2.mp4`,
|
FAILED_RAR: `videos/failed_rar_v2.mp4`,
|
||||||
FAILED_OPENING: `videos/failed_opening_v2.mp4`,
|
FAILED_OPENING: `videos/failed_opening_v2.mp4`,
|
||||||
FAILED_UNEXPECTED: `videos/failed_unexpected_v2.mp4`,
|
FAILED_UNEXPECTED: `videos/failed_unexpected_v2.mp4`,
|
||||||
FAILED_INFRINGEMENT: `videos/failed_infringement_v2.mp4`
|
FAILED_INFRINGEMENT: `videos/failed_infringement_v2.mp4`,
|
||||||
|
LIMITS_EXCEEDED: `videos/limits_exceeded_v1.mp4`,
|
||||||
|
BLOCKED_ACCESS: `videos/blocked_access_v1.mp4`,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
249
addon/moch/torbox.js
Normal file
249
addon/moch/torbox.js
Normal file
|
|
@ -0,0 +1,249 @@
|
||||||
|
import axios from 'axios';
|
||||||
|
import { Type } from '../lib/types.js';
|
||||||
|
import { isVideo } from '../lib/extension.js';
|
||||||
|
import StaticResponse from './static.js';
|
||||||
|
import { getMagnetLink } from '../lib/magnetHelper.js';
|
||||||
|
import { chunkArray, BadTokenError, sameFilename, streamFilename } from './mochHelper.js';
|
||||||
|
|
||||||
|
const KEY = 'torbox';
|
||||||
|
const timeout = 30000;
|
||||||
|
const baseUrl = 'https://api.torbox.app/v1'
|
||||||
|
|
||||||
|
export async function getCachedStreams(streams, apiKey, ip) {
|
||||||
|
const hashBatches = chunkArray(streams.map(stream => stream.infoHash), 150)
|
||||||
|
.map(hashes => getAvailabilityResponse(apiKey, hashes));
|
||||||
|
const available = await Promise.all(hashBatches)
|
||||||
|
.then(results => results
|
||||||
|
.map(data => data.map(entry => entry.hash))
|
||||||
|
.reduce((all, result) => all.concat(result), []))
|
||||||
|
.catch(error => {
|
||||||
|
if (toCommonError(error)) {
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
|
const message = error.message || error;
|
||||||
|
console.warn('Failed TorBox cached torrent availability request:', message);
|
||||||
|
return undefined;
|
||||||
|
});
|
||||||
|
return available && streams
|
||||||
|
.reduce((mochStreams, stream) => {
|
||||||
|
const isCached = available.includes(stream.infoHash);
|
||||||
|
const fileName = streamFilename(stream);
|
||||||
|
mochStreams[`${stream.infoHash}@${stream.fileIdx}`] = {
|
||||||
|
url: `${apiKey}/${stream.infoHash}/${fileName}/${stream.fileIdx}`,
|
||||||
|
cached: isCached
|
||||||
|
};
|
||||||
|
return mochStreams;
|
||||||
|
}, {})
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getCatalog(apiKey, type, config) {
|
||||||
|
if (config.skip > 0) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return getItemList(apiKey, type, null, config.skip)
|
||||||
|
.then(items => (items || [])
|
||||||
|
.filter(item => statusReady(item))
|
||||||
|
.map(item => ({
|
||||||
|
id: `${KEY}:${type}-${item.id}`,
|
||||||
|
type: Type.OTHER,
|
||||||
|
name: item.name
|
||||||
|
})));
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getItemMeta(itemId, apiKey) {
|
||||||
|
const [type, id] = itemId.split('-');
|
||||||
|
const item = await getItemList(apiKey, type, id);
|
||||||
|
const createDate = item ? new Date(item.created_at) : new Date();
|
||||||
|
return {
|
||||||
|
id: `${KEY}:${itemId}`,
|
||||||
|
type: Type.OTHER,
|
||||||
|
name: item.name,
|
||||||
|
infoHash: item.hash,
|
||||||
|
videos: item.files
|
||||||
|
.filter(file => isVideo(file.short_name))
|
||||||
|
.map((file, index) => ({
|
||||||
|
id: `${KEY}:${itemId}:${file.id}`,
|
||||||
|
title: file.name,
|
||||||
|
released: new Date(createDate.getTime() - index).toISOString(),
|
||||||
|
streams: [{ url: `${apiKey}/null/${itemId}-${file.id}/null` }]
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function resolve({ ip, apiKey, infoHash, cachedEntryInfo, fileIndex }) {
|
||||||
|
console.log(`Unrestricting TorBox ${infoHash} [${fileIndex}]`);
|
||||||
|
return _resolve(apiKey, infoHash, cachedEntryInfo, fileIndex, ip)
|
||||||
|
.catch(error => {
|
||||||
|
if (isAccessDeniedError(error)) {
|
||||||
|
console.log(`Access denied to TorBox ${infoHash} [${fileIndex}]`);
|
||||||
|
return StaticResponse.FAILED_ACCESS;
|
||||||
|
}
|
||||||
|
if (isLimitExceededError(error)) {
|
||||||
|
console.log(`Limits exceeded to TorBox ${infoHash} [${fileIndex}]`);
|
||||||
|
return StaticResponse.LIMITS_EXCEEDED;
|
||||||
|
}
|
||||||
|
return Promise.reject(`Failed TorBox adding torrent: ${JSON.stringify(error.message || error)}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function _resolve(apiKey, infoHash, cachedEntryInfo, fileIndex, ip) {
|
||||||
|
if (infoHash === 'null') {
|
||||||
|
const [type, rootId, fileId] = cachedEntryInfo.split('-');
|
||||||
|
return getDownloadLink(apiKey, type, rootId, fileId, ip);
|
||||||
|
}
|
||||||
|
const torrent = await _createOrFindTorrent(apiKey, infoHash);
|
||||||
|
if (torrent && statusReady(torrent)) {
|
||||||
|
return _unrestrictLink(apiKey, infoHash, torrent, cachedEntryInfo, fileIndex);
|
||||||
|
} else if (torrent && statusDownloading(torrent)) {
|
||||||
|
console.log(`Downloading to TorBox ${infoHash} [${fileIndex}]...`);
|
||||||
|
return StaticResponse.DOWNLOADING;
|
||||||
|
} else if (torrent && statusError(torrent)) {
|
||||||
|
console.log(`Retry failed download in TorBox ${infoHash} [${fileIndex}]...`);
|
||||||
|
return _retryCreateTorrent(apiKey, infoHash, cachedEntryInfo, fileIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.reject(`Failed TorBox adding torrent ${JSON.stringify(torrent)}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function _createOrFindTorrent(apiKey, infoHash) {
|
||||||
|
return _findTorrent(apiKey, infoHash)
|
||||||
|
.catch(() => _createTorrent(apiKey, infoHash));
|
||||||
|
}
|
||||||
|
|
||||||
|
async function _findTorrent(apiKey, infoHash) {
|
||||||
|
const torrents = await getTorrentList(apiKey);
|
||||||
|
const foundTorrents = torrents.filter(torrent => torrent.hash === infoHash);
|
||||||
|
const nonFailedTorrent = foundTorrents.find(torrent => !statusError(torrent));
|
||||||
|
const foundTorrent = nonFailedTorrent || foundTorrents[0];
|
||||||
|
return foundTorrent || Promise.reject('No recent torrent found');
|
||||||
|
}
|
||||||
|
|
||||||
|
async function _createTorrent(apiKey, infoHash) {
|
||||||
|
const magnetLink = await getMagnetLink(infoHash);
|
||||||
|
return createTorrent(apiKey, magnetLink)
|
||||||
|
.then(data => {
|
||||||
|
if (data.torrent_id) {
|
||||||
|
return getTorrentList(apiKey, data.torrent_id);
|
||||||
|
}
|
||||||
|
if (data.queued_id) {
|
||||||
|
return Promise.resolve({ ...data, download_state: 'metaDL' })
|
||||||
|
}
|
||||||
|
return Promise.reject(`Unexpected create data: ${JSON.stringify(data)}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function _retryCreateTorrent(apiKey, infoHash, cachedEntryInfo, fileIndex) {
|
||||||
|
const newTorrent = await _createTorrent(apiKey, infoHash);
|
||||||
|
return newTorrent && statusReady(newTorrent)
|
||||||
|
? _unrestrictLink(apiKey, infoHash, newTorrent, cachedEntryInfo, fileIndex)
|
||||||
|
: StaticResponse.FAILED_DOWNLOAD;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function _unrestrictLink(apiKey, infoHash, torrent, cachedEntryInfo, fileIndex) {
|
||||||
|
const targetFileName = decodeURIComponent(cachedEntryInfo);
|
||||||
|
const videos = torrent.files
|
||||||
|
.filter(file => isVideo(file.short_name))
|
||||||
|
.sort((a, b) => b.size - a.size);
|
||||||
|
const targetVideo = Number.isInteger(fileIndex)
|
||||||
|
&& videos.find(video => sameFilename(video.name, targetFileName))
|
||||||
|
|| videos[0];
|
||||||
|
|
||||||
|
if (!targetVideo) {
|
||||||
|
return Promise.reject(`No TorBox file found for index ${fileIndex} in: ${JSON.stringify(torrent)}`);
|
||||||
|
}
|
||||||
|
return getDownloadLink(apiKey, 'torrents', torrent.id, targetVideo.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getAvailabilityResponse(apiKey, hashes) {
|
||||||
|
const url = `${baseUrl}/api/torrents/checkcached`;
|
||||||
|
const headers = getHeaders(apiKey);
|
||||||
|
const params = { hash: hashes.join(','), format: 'list' };
|
||||||
|
return axios.get(url, { params, headers, timeout })
|
||||||
|
.then(response => {
|
||||||
|
if (response.data?.success) {
|
||||||
|
return Promise.resolve(response.data.data || []);
|
||||||
|
}
|
||||||
|
return Promise.reject(response.data);
|
||||||
|
})
|
||||||
|
.catch(error => Promise.reject(error.response?.data || error));
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createTorrent(apiKey, magnetLink){
|
||||||
|
const url = `${baseUrl}/api/torrents/createtorrent`
|
||||||
|
const headers = getHeaders(apiKey);
|
||||||
|
const data = new URLSearchParams();
|
||||||
|
data.append('magnet', magnetLink);
|
||||||
|
data.append('allow_zip', 'false');
|
||||||
|
return axios.post(url, data, { headers, timeout })
|
||||||
|
.then(response => {
|
||||||
|
if (response.data?.success) {
|
||||||
|
return Promise.resolve(response.data.data);
|
||||||
|
}
|
||||||
|
return Promise.reject(response.data);
|
||||||
|
})
|
||||||
|
.catch(error => Promise.reject(error.response?.data || error));
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getTorrentList(apiKey, id = undefined, offset = 0) {
|
||||||
|
return getItemList(apiKey, 'torrents', id, offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getItemList(apiKey, type, id = undefined, offset = 0) {
|
||||||
|
const url = `${baseUrl}/api/${type}/mylist`;
|
||||||
|
const headers = getHeaders(apiKey);
|
||||||
|
const params = { id, offset };
|
||||||
|
return axios.get(url, { params, headers, timeout })
|
||||||
|
.then(response => {
|
||||||
|
if (response.data?.success) {
|
||||||
|
return Promise.resolve(response.data.data);
|
||||||
|
}
|
||||||
|
return Promise.reject(response.data);
|
||||||
|
})
|
||||||
|
.catch(error => Promise.reject(error.response?.data || error));
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getDownloadLink(token, type, rootId, file_id, user_ip) {
|
||||||
|
const url = `${baseUrl}/api/${type}/requestdl`;
|
||||||
|
const params = { token, torrent_id: rootId, usenet_id: rootId, web_id: rootId, file_id, user_ip };
|
||||||
|
return axios.get(url, { params, timeout })
|
||||||
|
.then(response => {
|
||||||
|
if (response.data?.success) {
|
||||||
|
console.log(`Unrestricted TorBox ${type} [${rootId}] to ${response.data.data}`);
|
||||||
|
return Promise.resolve(response.data.data);
|
||||||
|
}
|
||||||
|
return Promise.reject(response.data);
|
||||||
|
})
|
||||||
|
.catch(error => Promise.reject(error.response?.data || error));
|
||||||
|
}
|
||||||
|
|
||||||
|
function getHeaders(apiKey) {
|
||||||
|
return { Authorization: `Bearer ${apiKey}` };
|
||||||
|
}
|
||||||
|
|
||||||
|
export function toCommonError(data) {
|
||||||
|
const error = data?.response?.data || data;
|
||||||
|
if (['AUTH_ERROR', 'BAD_TOKEN'].includes(error?.error)) {
|
||||||
|
return BadTokenError;
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
function statusDownloading(torrent) {
|
||||||
|
return ['metaDL', 'downloading', 'stalled', 'processing', 'completed'].includes(torrent?.download_state);
|
||||||
|
}
|
||||||
|
|
||||||
|
function statusError(torrent) {
|
||||||
|
return !torrent?.active && !torrent?.download_finished;
|
||||||
|
}
|
||||||
|
|
||||||
|
function statusReady(torrent) {
|
||||||
|
return torrent?.download_present;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isAccessDeniedError(error) {
|
||||||
|
return ['AUTH_ERROR', 'BAD_TOKEN', 'PLAN_RESTRICTED_FEATURE'].includes(error?.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isLimitExceededError(error) {
|
||||||
|
return ['DOWNLOAD_TOO_LARGE', 'MONTHLY_LIMIT', 'COOLDOWN_LIMIT', 'ACTIVE_LIMIT'].includes(error?.error);
|
||||||
|
}
|
||||||
BIN
addon/static/videos/limits_exceeded_v1.mp4
Normal file
BIN
addon/static/videos/limits_exceeded_v1.mp4
Normal file
Binary file not shown.
Loading…
Reference in a new issue