diff --git a/addon/lib/landingTemplate.js b/addon/lib/landingTemplate.js index 59206d7..39a0e1d 100644 --- a/addon/lib/landingTemplate.js +++ b/addon/lib/landingTemplate.js @@ -205,6 +205,7 @@ export default function landingTemplate(manifest, config = {}) { const premiumizeApiKey = config[MochOptions.premiumize.key] || ''; const allDebridApiKey = config[MochOptions.alldebrid.key] || ''; const debridLinkApiKey = config[MochOptions.debridlink.key] || ''; + const easyDebridApiKey = config[MochOptions.easydebrid.key] || ''; const offcloudApiKey = config[MochOptions.offcloud.key] || ''; const torboxApiKey = config[MochOptions.torbox.key] || ''; const putioKey = config[MochOptions.putio.key] || ''; @@ -326,6 +327,11 @@ export default function landingTemplate(manifest, config = {}) { +
+ + +
+
@@ -401,6 +407,7 @@ export default function landingTemplate(manifest, config = {}) { $('#iPremiumize').val("${premiumizeApiKey}"); $('#iAllDebrid').val("${allDebridApiKey}"); $('#iDebridLink').val("${debridLinkApiKey}"); + $('#iEasyDebrid').val("${easyDebridApiKey}"); $('#iOffcloud').val("${offcloudApiKey}"); $('#iTorbox').val("${torboxApiKey}"); $('#iPutioClientId').val("${putioClientId}"); @@ -428,6 +435,7 @@ export default function landingTemplate(manifest, config = {}) { $('#dPremiumize').toggle(provider === '${MochOptions.premiumize.key}'); $('#dAllDebrid').toggle(provider === '${MochOptions.alldebrid.key}'); $('#dDebridLink').toggle(provider === '${MochOptions.debridlink.key}'); + $('#dEasyDebrid').toggle(provider === '${MochOptions.easydebrid.key}'); $('#dOffcloud').toggle(provider === '${MochOptions.offcloud.key}'); $('#dTorbox').toggle(provider === '${MochOptions.torbox.key}'); $('#dPutio').toggle(provider === '${MochOptions.putio.key}'); @@ -447,6 +455,7 @@ export default function landingTemplate(manifest, config = {}) { const allDebridValue = $('#iAllDebrid').val() || ''; const debridLinkValue = $('#iDebridLink').val() || '' const premiumizeValue = $('#iPremiumize').val() || ''; + const easyDebridValue = $('#iEasyDebrid').val() || ''; const offcloudValue = $('#iOffcloud').val() || ''; const torboxValue = $('#iTorbox').val() || ''; const putioClientIdValue = $('#iPutioClientId').val() || ''; @@ -465,6 +474,7 @@ export default function landingTemplate(manifest, config = {}) { const premiumize = premiumizeValue.length && premiumizeValue.trim(); const allDebrid = allDebridValue.length && allDebridValue.trim(); const debridLink = debridLinkValue.length && debridLinkValue.trim(); + const easyDebrid = easyDebridValue.length && easyDebridValue.trim(); const offcloud = offcloudValue.length && offcloudValue.trim(); const torbox = torboxValue.length && torboxValue.trim(); const putio = putioClientIdValue.length && putioTokenValue.length && putioClientIdValue.trim() + '@' + putioTokenValue.trim(); @@ -484,6 +494,7 @@ export default function landingTemplate(manifest, config = {}) { ['${MochOptions.premiumize.key}', premiumize], ['${MochOptions.alldebrid.key}', allDebrid], ['${MochOptions.debridlink.key}', debridLink], + ['${MochOptions.easydebrid.key}', easyDebrid], ['${MochOptions.offcloud.key}', offcloud], ['${MochOptions.torbox.key}', torbox], ['${MochOptions.putio.key}', putio] diff --git a/addon/lib/manifest.js b/addon/lib/manifest.js index 4036e01..5972ba0 100644 --- a/addon/lib/manifest.js +++ b/addon/lib/manifest.js @@ -74,7 +74,7 @@ function getCatalogs(config) { function getResources(config) { const streamResource = { name: 'stream', - types: [Type.MOVIE, Type.SERIES], + types: [Type.MOVIE, Type.SERIES, Type.ANIME], idPrefixes: ['tt', 'kitsu'] }; const metaResource = { diff --git a/addon/moch/easydebrid.js b/addon/moch/easydebrid.js new file mode 100644 index 0000000..e8c1f8b --- /dev/null +++ b/addon/moch/easydebrid.js @@ -0,0 +1,83 @@ +import { EasyDebridClient } from '@paradise-cloud/easy-debrid'; +import { isVideo, isArchive } from '../lib/extension.js'; +import StaticResponse from './static.js'; +import { BadTokenError, sameFilename, streamFilename } from './mochHelper.js'; +import magnet from "magnet-uri"; + +const KEY = 'easydebrid'; + +export async function getCachedStreams(streams, apiKey) { + const options = await getDefaultOptions(apiKey); + const ED = new EasyDebridClient(options); + const hashes = streams.map(stream => stream.infoHash); + return ED.linkLookup(hashes) + .catch(error => { + if (toCommonError(error)) { + return Promise.reject(error); + } + console.warn('Failed EasyDebrid cached torrent availability request:', error); + return undefined; + }) + .then(response => streams + .reduce((mochStreams, stream, index) => { + const filename = streamFilename(stream); + mochStreams[`${stream.infoHash}@${stream.fileIdx}`] = { + url: `${apiKey}/${stream.infoHash}/${filename}/${stream.fileIdx}`, + cached: response?.cached?.[index] + }; + return mochStreams; + }, {})); +} + +export async function resolve({ ip, isBrowser, apiKey, infoHash, cachedEntryInfo, fileIndex }) { + console.log(`Unrestricting EasyDebrid ${infoHash} [${fileIndex}]`); + const options = await getDefaultOptions(apiKey); + const ED = new EasyDebridClient(options); + return _getCachedLink(ED, infoHash, cachedEntryInfo, fileIndex, ip, isBrowser) + .catch(error => { + if (isAccessDeniedError(error)) { + console.log(`Access denied to EasyDebrid ${infoHash} [${fileIndex}]`); + return StaticResponse.FAILED_ACCESS; + } + return Promise.reject(`Failed EasyDebrid adding torrent ${JSON.stringify(error)}`); + }); +} + +async function _getCachedLink(ED, infoHash, encodedFileName, fileIndex, ip, isBrowser) { + const magnetLink = magnet.encode({ infoHash }) + const cachedTorrent = await ED.generateDebridLink(magnetLink); + if (cachedTorrent?.files?.length) { + const files = cachedTorrent.files.map(file => ({ + ...file, + path: file.directory.join("/") + `/${file.filename}`, + })) + const targetFileName = decodeURIComponent(encodedFileName); + const videos = files.filter(file => isVideo(file.path)).sort((a, b) => b.size - a.size); + const targetVideo = Number.isInteger(fileIndex) + && videos.find(video => sameFilename(video.path, targetFileName)) + || videos[0]; + if (!targetVideo && videos.every(video => isArchive(video.path))) { + console.log(`Only EasyDebrid archive is available for [${infoHash}] ${fileIndex}`) + return StaticResponse.FAILED_RAR; + } + const unrestrictedLink = targetVideo.url; + console.log(`Unrestricted EasyDebrid ${infoHash} [${fileIndex}] to ${unrestrictedLink}`); + return unrestrictedLink; + } + return Promise.reject('No cached entry found'); +} + +export function toCommonError(error) { + if (error && error.message === 'Not logged in.') { + return BadTokenError; + } + return undefined; +} + +function isAccessDeniedError(error) { + return ['Account not premium.'].some(value => error?.message?.includes(value)); +} + +async function getDefaultOptions(apiKey) { + return { accessToken: apiKey }; +} diff --git a/addon/moch/moch.js b/addon/moch/moch.js index 75ac805..e3af154 100644 --- a/addon/moch/moch.js +++ b/addon/moch/moch.js @@ -3,6 +3,7 @@ import * as realdebrid from './realdebrid.js'; import * as premiumize from './premiumize.js'; import * as alldebrid from './alldebrid.js'; import * as debridlink from './debridlink.js'; +import * as easydebrid from './easydebrid.js'; import * as offcloud from './offcloud.js'; import * as torbox from './torbox.js'; import * as putio from './putio.js'; @@ -44,6 +45,14 @@ export const MochOptions = { shortName: 'DL', catalogs: [''] }, + easydebrid: { + key: 'easydebrid', + instance: easydebrid, + name: 'EasyDebrid', + shortName: 'ED', + catalogs: [], + noDownloads: true + }, offcloud: { key: 'offcloud', instance: offcloud, @@ -192,9 +201,10 @@ function populateDownloadLinks(streams, results, config) { const torrentStreams = streams.filter(stream => stream.infoHash); const seededStreams = streams.filter(stream => !stream.title.includes('👤 0')); torrentStreams.forEach(stream => mochResults.forEach(mochResult => { + const supportDownloads = !mochResult.moch.noDownloads; const cachedEntry = mochResult.mochStreams[`${stream.infoHash}@${stream.fileIdx}`]; const isCached = cachedEntry?.cached; - if (!isCached && isHealthyStreamForDebrid(seededStreams, stream)) { + if (supportDownloads && !isCached && isHealthyStreamForDebrid(seededStreams, stream)) { streams.push({ name: `[${mochResult.moch.shortName} download] ${stream.name}`, title: stream.title, diff --git a/addon/package-lock.json b/addon/package-lock.json index 58edcdb..49c5651 100644 --- a/addon/package-lock.json +++ b/addon/package-lock.json @@ -10,6 +10,7 @@ "license": "MIT", "dependencies": { "@keyv/mongo": "^3.0.1", + "@paradise-cloud/easy-debrid": "^3.0.0", "@putdotio/api-client": "^8.42.0", "all-debrid-api": "^1.2.0", "axios": "^1.7.7", @@ -118,6 +119,18 @@ "sparse-bitfield": "^3.0.3" } }, + "node_modules/@paradise-cloud/easy-debrid": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@paradise-cloud/easy-debrid/-/easy-debrid-3.0.0.tgz", + "integrity": "sha512-UrkvWQgnapw2nZKc5ZgNU29B52RrVe+fIDidJZWx2MZBowar7CBDSEc/E54q/Am9OKnsttMw9/gh2o3IuXwOHA==", + "license": "MIT", + "dependencies": { + "axios": "^1.6.8" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/@putdotio/api-client": { "version": "8.42.0", "resolved": "https://registry.npmjs.org/@putdotio/api-client/-/api-client-8.42.0.tgz", diff --git a/addon/package.json b/addon/package.json index 31491ef..1270af7 100644 --- a/addon/package.json +++ b/addon/package.json @@ -10,6 +10,7 @@ "license": "MIT", "dependencies": { "@keyv/mongo": "^3.0.1", + "@paradise-cloud/easy-debrid": "^3.0.0", "@putdotio/api-client": "^8.42.0", "all-debrid-api": "^1.2.0", "axios": "^1.7.7",