-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
this commit adds magnet-file support to patbrid
- Loading branch information
0 parents
commit ab9ccee
Showing
9 changed files
with
1,433 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" | ||
} | ||
} |
Oops, something went wrong.