Skip to content

Commit

Permalink
add magnet support to patbrid
Browse files Browse the repository at this point in the history
this commit adds magnet-file support to patbrid
  • Loading branch information
DatPat authored May 9, 2020
0 parents commit ab9ccee
Show file tree
Hide file tree
Showing 9 changed files with 1,433 additions and 0 deletions.
12 changes: 12 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
FROM node:12-alpine

WORKDIR /workspace

RUN mkdir -p /watch && mkdir -p /download

COPY package.json yarn.lock /workspace/
RUN yarn install --frozen-lockfile

ADD . /workspace

CMD yarn start
31 changes: 31 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
patbrid
===

A real-debrid blackhole downloader using aria2 RPC.

### Environment Variables

Value | Description | Default
--- | --- | ---
REAL_DEBRID_API_KEY | Real Debrid API Key |
ARIA2_URL | Aria2 JSON-RPC URL |
ARIA2_SECRET | Aria2 JSON-RPC Secret |
WATCH_DIR | Directory to watch | /watch
WATCH_RATE | Rate to check for updates | 5000

### Development

#### Requirements

* Docker
* aria2 with JSON-RPC enabled

#### Setup

Copy `.env.example` to `.env`

#### Run

`$ docker-compose build`

`$ docker-compose run --rm downloader`
13 changes: 13 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
version: '3.2'

services:
downloader:
build: .
restart: on-failure
command: yarn watch
env_file: .env
volumes:
- /workspace/node_modules
- .:/workspace
- ./watch:/watch
- ./download:/download
44 changes: 44 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
const chokidar = require('chokidar')
const RealDebridWatcher = require('./lib/watchers/real-debrid')
const Aria2Downloader = require('./lib/downloaders/aria2')

const {
REAL_DEBRID_API_KEY,
ARIA2_URL,
ARIA2_SECRET,
WATCH_DIR = '/watch',
WATCH_RATE = 5000
} = process.env

if (!REAL_DEBRID_API_KEY) {
console.log('[!] REAL_DEBRID_API_KEY env var is not set')

process.exit(-1)
}

if (!ARIA2_URL) {
console.log('[!] ARIA2_URL env var is not set')

process.exit(-1)
}

if (!ARIA2_SECRET) {
console.log('[!] ARIA2_SECRET env var is not set')

process.exit(-1)
}

// Create a downloader instance
const downloader = new Aria2Downloader(ARIA2_URL, ARIA2_SECRET)

// Create a watcher instance
const watcher = new RealDebridWatcher(REAL_DEBRID_API_KEY, downloader.download)

// Watch for new torrent files
console.log(`[+] Watching '${WATCH_DIR}' for new torrents`)

chokidar.watch(`${WATCH_DIR}/*.torrent`)
.on('add', path => watcher.addTorrent(path))

// Check the torrent watch list every "WATCH_RATE" ms
setInterval(() => watcher.checkWatchList(), WATCH_RATE)
40 changes: 40 additions & 0 deletions lib/downloaders/aria2/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
const debug = require('debug')('patbrid:downloaders:aria2')
const rpc = require('node-json-rpc')
const { URL } = require('url')

class Aria2 {
constructor (url, secret) {
debug('ctor', url, secret)

this.url = url
this.secret = secret
this.id = 0

const rpcUrl = new URL(this.url)

this.rpcClient = new rpc.Client({
host: rpcUrl.hostname,
port: rpcUrl.port,
path: rpcUrl.pathname
})

this.download = this._download.bind(this)
}

_download (links) {
debug('_download', links)

return new Promise((resolve, reject) => {
this.rpcClient.call({
jsonrpc: '2.0',
method: 'aria2.addUri',
params: [`token:${this.secret}`, links],
id: this.id++
}, (err, res) => {
!err ? resolve(res) : reject(err)
})
})
}
}

module.exports = Aria2
94 changes: 94 additions & 0 deletions lib/watchers/real-debrid/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
const debug = require('debug')('patbrid:watchers:real-debrid')
const RealDebridClient = require('real-debrid-api')
const RealDebridTorrent = require('./torrent')
const fs = require('fs')

class RealDebridWatcher {
constructor (apiKey, downloadFn) {
debug('ctor', apiKey)

this.client = new RealDebridClient(apiKey)
this.downloadFn = downloadFn
this.watchList = []
}

addTorrent (file) {
debug('addTorrent', file)

// Create a torrent instance
const torrent = new RealDebridTorrent(this.client, this.downloadFn, file)

// Add the torrent to the queue
return torrent.addToQueue()
// Save to the watch list
.then(() => this.addToWatchList(torrent))
// Log errors
.catch(err => console.error('[!] addTorrent failed', err))
}

addMagnet (file) {
debug('addMagnet', file)

var self = this

fs.readFile(file, 'utf8', function(err, data) {
if (err)
debug( 'read magnet error', err)

// Create a torrent instance
const torrent = new RealDebridTorrent(self.client, self.downloadFn, file, data)

// Add the torrent to the queue
return torrent.addToQueue()
// Save to the watch list
.then(() => self.addToWatchList(torrent))
// Log errors
.catch(err => console.error('[!] addTorrent failed', err))
});
}

checkWatchList () {
debug('checkWatchList', this.watchList.length)

// Remove invalid torrents
this.removeInvalidTorrents()

// Go through each torrent and update it
const promises = this.watchList.map(torrent => torrent.update())

// Wait for all torrents to update
return Promise.all(promises)
.catch(err => console.error('[!] checkWatchList failed', err))
}

addToWatchList (torrent) {
debug('addToWatchList', torrent.file)

// Add the torrent to the watch list
this.watchList.push(torrent)
}

removeFromWatchList (torrent) {
debug('removeFromWatchList', torrent.file)

// Remove the torrent from the watch list
const index = this.watchList.indexOf(torrent)

if (~index) {
this.watchList.splice(index, 1)
}
}

removeInvalidTorrents () {
debug('removeInvalidTorrents')

// Remove any invalid torrents from the watch list
this.watchList.forEach(torrent => {
if (torrent.status === 'invalid') {
this.removeFromWatchList(torrent)
}
})
}
}

module.exports = RealDebridWatcher
138 changes: 138 additions & 0 deletions lib/watchers/real-debrid/torrent.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
const debug = require('debug')('patbrid:watchers:real-debrid:torrent')
const fs = require('fs')

class RealDebridTorrent {
constructor (client, downloadFn, file, magnetlink = null) {
debug('ctor', file)

this.client = client
this.downloadFn = downloadFn
this.file = file
this.status = 'pending'
this.magnetlink = magnetlink
}

addToQueue () {
debug('addToQueue', this.file)
// Add the torrent file
if(this.magnetlink != null )
{
return this.client.torrents.addMagnet(this.magnetlink)
.then(result => {
// Set the id and mark as queued
this.id = result.id
this.status = 'queued'

console.log(`[+] '${this.file}' added to queue (${this.id})`)

return this._beginDownload()
})
}
else
{
return this.client.torrents.addTorrent(this.file)
.then(result => {
// Set the id and mark as queued
this.id = result.id
this.status = 'queued'

console.log(`[+] '${this.file}' added to queue (${this.id})`)

return this._beginDownload()
})
}
}

update () {
debug('update', this.file)

// Get the info for the torrent
return this.client.torrents.info(this.id)
.then(info => this._handleUpdate(info))
.catch(err => {
debug('update failed', err)

this.status = 'invalid'

console.log(`[+] '${this.file}' is invalid`)

Promise.resolve(null)
})
}

_beginDownload () {
debug('_beginDownload', this.file)

// Select all the files for download
return this.client.torrents.selectFiles(this.id, 'all')
.then(() => {
this.status = 'downloading'

console.log(`[+] '${this.file}' downloading remotely`)
})
}

_handleUpdate (info) {
debug('_handleUpdate', this.file, info)

// Show torrent status
console.log(`[+] '${this.file}' id: ${this.id} local: ${this.status} remote: ${info.status} progress: ${info.progress}%`)

// Has the remote status finished downloading
if (info.status === 'downloaded' && this.status === 'downloading') {
// Mark torrent as downloaded
this.status = 'downloaded'

// Get the download links
this._getDownloadLinks(info.links)
// Add all download links to downloader
.then(results => {
console.log(`[+] '${this.file}' downloading locally`)

const urls = results.map(result => result.download)

return this.downloadFn(urls)
})
// Delete the torrent
.then(() => this._delete())
// Catch any errors
.catch(err => console.error('[!] _handleUpdate failed', err))
}
}

_getDownloadLinks (links) {
debug('_getDownloadLinks', this.file)

// Go through each link in the info json
const promises = links.map(link =>
// Unrestrict the link
this.client.unrestrict.link(link)
.catch(err => {
console.log(`[!] _getDownloadLink(${link}) failed`, err)

return Promise.resolve(null)
})
)

// Wait for all links to resolve and remove bad links
return Promise.all(promises)
.then(results => results.filter(x => !!x))
}

_delete () {
debug('_delete', this.file)

// Delete the torrent
return this.client.torrents.delete(this.id)
.then(() => {
this.status = 'invalid'

fs.unlinkSync(this.file)

console.log(`[+] '${this.file}' deleted`)
})
.catch(err => console.error('[!] delete failed', err))
}
}

module.exports = RealDebridTorrent
16 changes: 16 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"private": true,
"scripts": {
"watch": "nodemon",
"start": "node index.js"
},
"dependencies": {
"chokidar": "^3.3.0",
"debug": "^4.1.1",
"node-json-rpc": "^0.0.1",
"real-debrid-api": "^1.0.1"
},
"devDependencies": {
"nodemon": "^2.0.1"
}
}
Loading

0 comments on commit ab9ccee

Please sign in to comment.