├── .npmignore ├── LICENSE ├── package.json ├── .gitignore ├── README.md └── index.js /.npmignore: -------------------------------------------------------------------------------- 1 | .travis.yml 2 | test 3 | tmp 4 | .gitignore 5 | .idea 6 | .DS_Store 7 | *.log 8 | tmp -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Layton Whiteley 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "magnet-to-torrent", 3 | "version": "1.0.8", 4 | "description": "convert magnet link to torrent download link", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "publish": "git push origin --tags && git push origin", 9 | "release:patch": "npm version patch && npm publish --access public", 10 | "release:minor": "npm version minor && npm publish --access public", 11 | "release:major": "npm version major && npm publish --access public" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "https://github.com/lwhiteley/magnet-to-torrent.git" 16 | }, 17 | "keywords": [ 18 | "magnet", 19 | "torrent", 20 | "magnettotorrent", 21 | "magnet2torrent", 22 | "torcache", 23 | "torrage", 24 | "bt.box" 25 | ], 26 | "author": "Layton Whiteley", 27 | "license": "MIT", 28 | "bugs": { 29 | "url": "https://github.com/lwhiteley/magnet-to-torrent/issues" 30 | }, 31 | "homepage": "https://github.com/lwhiteley/magnet-to-torrent", 32 | "dependencies": { 33 | "bluebird": "^3.5.1", 34 | "debug": "^3.1.0", 35 | "lodash": "^4.17.15", 36 | "magnet-uri": "^5.1.7", 37 | "needle": "^2.1.1", 38 | "validator": "^9.3.0" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .project 3 | data/ 4 | x/ 5 | tmp 6 | 7 | # Created by https://www.gitignore.io 8 | 9 | ### Node ### 10 | # Logs 11 | logs 12 | *.log 13 | 14 | # Runtime data 15 | pids 16 | *.pid 17 | *.seed 18 | 19 | # Directory for instrumented libs generated by jscoverage/JSCover 20 | lib-cov 21 | 22 | # Coverage directory used by tools like istanbul 23 | coverage 24 | 25 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 26 | .grunt 27 | 28 | # node-waf configuration 29 | .lock-wscript 30 | 31 | # Compiled binary addons (http://nodejs.org/api/addons.html) 32 | build/Release 33 | 34 | # Dependency directory 35 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 36 | node_modules 37 | 38 | ### grunt ### 39 | # Grunt usually compiles files inside this directory 40 | dist/ 41 | 42 | # Grunt usually preprocesses files such as coffeescript, compass... inside the .tmp directory 43 | .tmp/ 44 | 45 | 46 | ### Intellij ### 47 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm 48 | 49 | *.iml 50 | 51 | ## Directory-based project format: 52 | .idea/ 53 | # if you remove the above rule, at least ignore the following: 54 | 55 | # User-specific stuff: 56 | # .idea/workspace.xml 57 | # .idea/tasks.xml 58 | # .idea/dictionaries 59 | 60 | # Sensitive or high-churn files: 61 | # .idea/dataSources.ids 62 | # .idea/dataSources.xml 63 | # .idea/sqlDataSources.xml 64 | # .idea/dynamic.xml 65 | # .idea/uiDesigner.xml 66 | 67 | # Gradle: 68 | # .idea/gradle.xml 69 | # .idea/libraries 70 | 71 | # Mongo Explorer plugin: 72 | # .idea/mongoSettings.xml 73 | 74 | ## File-based project format: 75 | *.ipr 76 | *.iws 77 | 78 | ## Plugin-specific files: 79 | 80 | # IntelliJ 81 | /out/ 82 | 83 | # mpeltonen/sbt-idea plugin 84 | .idea_modules/ 85 | 86 | # JIRA plugin 87 | atlassian-ide-plugin.xml 88 | 89 | # Crashlytics plugin (for Android Studio and IntelliJ) 90 | com_crashlytics_export_strings.xml 91 | crashlytics.properties 92 | crashlytics-build.properties 93 | 94 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # magnet-to-torrent 2 | convert a magnet uri to a torrent download link 3 | 4 | ### Install 5 | 6 | ```shell 7 | npm install --save magnet-to-torrent 8 | ``` 9 | ### How to use 10 | 11 | ```javascript 12 | var magnetToTorrent = require('magnet-to-torrent'); 13 | 14 | var magnet = '< a valid magnet uri >'; 15 | magnetToTorrent.getLink(magnet) 16 | .then( function(torrentLink){ 17 | console.log(torrentLink); // torrent url as string 18 | }) 19 | .catch(function(error){ 20 | console.error(error); // couldn't get a valid link 21 | }); 22 | ``` 23 | 24 | ### Check if URI is a Magnet URI 25 | 26 | The following verifies if the magnet uri provided is formatted correctly. 27 | 28 | ```javascript 29 | var bool = magnetToTorrent.isMagnet(magnet); // returns boolean 30 | ``` 31 | 32 | ### Default Services Used 33 | 34 | The library will attempt to retrieve a working torrent link from the following services, respectively: 35 | 36 | - http://bt.box.n0808.com 37 | - http://reflektor.karmorra.info 38 | - http://torcache.net 39 | - https://torrage.com 40 | 41 | ### Adding a Conversion Service 42 | 43 | Each service takes in `hash` as a parameter and uses it to build the download link in the 44 | format of how the said service allows a user to download torrents. 45 | The library tests if the torrent is cached by the service and responds 46 | with the first url that has the torrent available. 47 | 48 | You can add your own service before attempting to convert magnets. 49 | 50 | See snippet below. 51 | 52 | eg. 53 | ```javascript 54 | var service = function(hash){ 55 | return `http://reflektor.karmorra.info/torrent/${hash}.torrent`; 56 | }; 57 | 58 | magnetToTorrent.addService(service); 59 | 60 | /** 61 | OR: 62 | Optionally, use a second parameter to push the service to the top of the stack 63 | This will ensure your service is called first 64 | **/ 65 | magnetToTorrent.addService(service, true); 66 | ``` 67 | 68 | ### Notes: 69 | 70 | - `hash` is the torrent hash extracted from the magnet uri 71 | - This is an experiment but should work fine. 72 | 73 | Report any issues. 74 | 75 | 76 | ### Credits 77 | 78 | - to the author of [magnet2torrent](https://www.npmjs.com/package/magnet2torrent) 79 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const logger = { 4 | debug: require('debug')('magnet-to-torrent:main'), 5 | error: require('debug')('magnet-to-torrent:error'), 6 | }; 7 | 8 | const mguri = require('magnet-uri'); 9 | const needle = require('needle'); 10 | const Promise = require('bluebird'); 11 | const isFunction = require('lodash/isFunction'); 12 | const validator = require('validator'); 13 | 14 | const service = {}; 15 | 16 | const servUrl = [ 17 | function(hash) { 18 | return `http://btcache.me/torrent/${hash}`; 19 | }, 20 | function(hash) { 21 | return `http://bt.box.n0808.com/${hash.slice(0, 2)}/${hash.slice(-2)}/${hash}.torrent`; 22 | }, 23 | function(hash){ 24 | return `http://reflektor.karmorra.info/torrent/${hash}.torrent`; 25 | }, 26 | function(hash) { 27 | return `http://torcache.net/torrent/${hash}.torrent`; 28 | }, 29 | function(hash) { 30 | return `https://torrage.com/torrent/${hash}.torrent`; 31 | } 32 | ]; 33 | 34 | var parseInfoHash = function(uri) { 35 | if(uri){ 36 | const uriObj = mguri.decode(uri); 37 | const hash = uriObj.infoHash || uri; 38 | if (/^[A-Za-z0-9]{40}$/.test(hash)) { 39 | return hash.toUpperCase(); 40 | } 41 | } 42 | }; 43 | service.isMagnet = function(uri) { 44 | return !!parseInfoHash(uri); 45 | }; 46 | service.addService = function(serv, pushToFront) { 47 | if(isFunction(serv)){ 48 | !pushToFront ? servUrl.push(serv) : servUrl.unshift(serv); 49 | logger.debug('Magnet conversion service added to stack!'); 50 | }else{ 51 | logger.debug('Magnet conversion service not added!'); 52 | } 53 | }; 54 | 55 | var verifyTorrent = function(url) { 56 | const options = { follow_max: 5 }; 57 | const result = needle('head', url, options).then((response) => { 58 | if (!(response.statusCode >= 200 && response.statusCode < 300)) { 59 | const err = new Error(`Error response: ${response.statusCode}`); 60 | logger.error(err); 61 | return Promise.reject(err); 62 | } 63 | 64 | if (response.headers['content-type'] === 'application/octet-stream' || 65 | response.headers['content-type'] === 'application/x-bittorrent') { 66 | return url; 67 | } else { 68 | const err = new Error(`Invalid content type: ${response.headers['content-type']}`); 69 | logger.error(err); 70 | return Promise.reject(err); 71 | } 72 | }); 73 | return Promise.resolve(result); 74 | }; 75 | 76 | service.getLink = function(uri) { 77 | const hash = parseInfoHash(uri); 78 | return new Promise((resolve, reject) => { 79 | if (!hash) { 80 | const err = new Error('Invalid magnet uri or info hash.'); 81 | logger.error(err); 82 | return reject(err); 83 | }else{ 84 | var getNext = function(x) { 85 | const attemptCount = x+1; 86 | logger.debug(`Magnet conversion attempt ${attemptCount}`); 87 | if (x < servUrl.length ) { 88 | var torrentUrl = servUrl[x](hash); 89 | if(validator.isURL(torrentUrl)){ 90 | logger.debug(`Attempting to check url: ${torrentUrl}`); 91 | verifyTorrent(torrentUrl) 92 | .then((url) => { 93 | logger.debug(`Magnet conversion completed; result: ${url}`); 94 | resolve(url); 95 | }) 96 | .catch((err) => { 97 | logger.error(err); 98 | getNext(x+1); 99 | }); 100 | }else{ 101 | getNext(x+1); 102 | } 103 | } else { 104 | logger.debug(`Magnet conversion failed for ${attemptCount} attempts`); 105 | reject(new Error('Could not convert magnet link. All services tried.')); 106 | } 107 | }; 108 | getNext(0); 109 | } 110 | }); 111 | }; 112 | 113 | module.exports = service; 114 | --------------------------------------------------------------------------------