├── .npmignore ├── test ├── package.json ├── torrents │ ├── bitlove-intro.torrent │ ├── leaves-url-list.torrent │ ├── leaves-empty-url-list.torrent │ ├── leaves-duplicate-tracker.torrent │ └── leaves-empty-announce-list.torrent ├── corrupt.js ├── empty-url-list.js ├── empty-announce-list.js ├── dedupe-announce.js ├── encode.js ├── webseed.js ├── no-announce-list.js └── basic.js ├── .airtap.yml ├── .travis.yml ├── LICENSE ├── package.json ├── README.md └── index.js /.npmignore: -------------------------------------------------------------------------------- 1 | test/torrents 2 | -------------------------------------------------------------------------------- /test/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "test", 3 | "version": "0.0.0", 4 | "browserify": { 5 | "transform": ["brfs"] 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /test/torrents/bitlove-intro.torrent: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/webtorrent/parse-torrent-file/HEAD/test/torrents/bitlove-intro.torrent -------------------------------------------------------------------------------- /test/torrents/leaves-url-list.torrent: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/webtorrent/parse-torrent-file/HEAD/test/torrents/leaves-url-list.torrent -------------------------------------------------------------------------------- /test/torrents/leaves-empty-url-list.torrent: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/webtorrent/parse-torrent-file/HEAD/test/torrents/leaves-empty-url-list.torrent -------------------------------------------------------------------------------- /test/torrents/leaves-duplicate-tracker.torrent: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/webtorrent/parse-torrent-file/HEAD/test/torrents/leaves-duplicate-tracker.torrent -------------------------------------------------------------------------------- /test/torrents/leaves-empty-announce-list.torrent: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/webtorrent/parse-torrent-file/HEAD/test/torrents/leaves-empty-announce-list.torrent -------------------------------------------------------------------------------- /.airtap.yml: -------------------------------------------------------------------------------- 1 | sauce_connect: true 2 | loopback: airtap.local 3 | browsers: 4 | - name: chrome 5 | version: latest 6 | - name: firefox 7 | version: latest 8 | - name: safari 9 | version: latest 10 | - name: microsoftedge 11 | version: latest 12 | - name: iphone 13 | version: latest 14 | -------------------------------------------------------------------------------- /test/corrupt.js: -------------------------------------------------------------------------------- 1 | var fixtures = require('webtorrent-fixtures') 2 | var parseTorrentFile = require('../') 3 | var test = require('tape') 4 | 5 | test('exception thrown when torrent file is missing `name` field', function (t) { 6 | t.throws(function () { 7 | parseTorrentFile(fixtures.corrupt.torrent) 8 | }) 9 | t.end() 10 | }) 11 | -------------------------------------------------------------------------------- /test/empty-url-list.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs') 2 | var parseTorrentFile = require('../') 3 | var path = require('path') 4 | var test = require('tape') 5 | 6 | var leavesUrlList = fs.readFileSync(path.join(__dirname, 'torrents/leaves-empty-url-list.torrent')) 7 | 8 | test('parse empty url-list', function (t) { 9 | var torrent = parseTorrentFile(leavesUrlList) 10 | t.deepEqual(torrent.urlList, []) 11 | t.end() 12 | }) 13 | -------------------------------------------------------------------------------- /test/empty-announce-list.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs') 2 | var parseTorrentFile = require('../') 3 | var path = require('path') 4 | var test = require('tape') 5 | 6 | var leavesAnnounceList = fs.readFileSync(path.join(__dirname, 'torrents/leaves-empty-announce-list.torrent')) 7 | 8 | test('parse torrent with empty announce-list', function (t) { 9 | t.deepEquals(parseTorrentFile(leavesAnnounceList).announce, ['udp://tracker.publicbt.com:80/announce']) 10 | t.end() 11 | }) 12 | -------------------------------------------------------------------------------- /test/dedupe-announce.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs') 2 | var parseTorrentFile = require('../') 3 | var path = require('path') 4 | var test = require('tape') 5 | 6 | var leavesDuplicateTracker = fs.readFileSync(path.join(__dirname, 'torrents/leaves-duplicate-tracker.torrent')) 7 | 8 | var expectedAnnounce = [ 9 | 'http://tracker.example.com/announce' 10 | ] 11 | 12 | test('dedupe announce list', function (t) { 13 | t.deepEqual(parseTorrentFile(leavesDuplicateTracker).announce, expectedAnnounce) 14 | t.end() 15 | }) 16 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | sudo: false 3 | node_js: 4 | - lts/* 5 | addons: 6 | sauce_connect: true 7 | hosts: 8 | - airtap.local 9 | env: 10 | global: 11 | - secure: eVC0YNr2CsyESygd7DagpW3RW4nyngxYFLnucpJMyinMjQ4JRHoC5uNpWJyRFB7RqdIvnk61kub5fNL/EsAG9Qa1A6HRTXB6hQ0H+Dj/vy1jcCK4vC/6kuw7jPe613oNq7AoJCymHRZVr/gnBkwxFedt2hdQGZ+6NO9qK0+m8ao= 12 | - secure: AqmBy4fZMfDSrSpfshUQ7Y+DX6BpE5/fcWc/y6HLzKKm41L1N7DBnyjSup64FjUSN50WPy1TNuVG+IbBeY8NAe1HRan3G7SdeELqYr7zteGXV6DoL1CvYdWlg3ckBrTcfv3Jo+zscrWo4v9gMqSdrxdm66Ys/EBt0l7ta3qMNDg= 13 | -------------------------------------------------------------------------------- /test/encode.js: -------------------------------------------------------------------------------- 1 | var fixtures = require('webtorrent-fixtures') 2 | var parseTorrentFile = require('../') 3 | var test = require('tape') 4 | 5 | test('encode', function (t) { 6 | var parsedTorrent = parseTorrentFile(fixtures.leaves.torrent) 7 | var buf = parseTorrentFile.encode(parsedTorrent) 8 | var doubleParsedTorrent = parseTorrentFile(buf) 9 | 10 | t.deepEqual(doubleParsedTorrent, parsedTorrent) 11 | t.end() 12 | }) 13 | 14 | test('encode w/ comment field', function (t) { 15 | var parsedTorrent = parseTorrentFile(fixtures.leaves.torrent) 16 | parsedTorrent.comment = 'hi there!' 17 | var buf = parseTorrentFile.encode(parsedTorrent) 18 | var doubleParsedTorrent = parseTorrentFile(buf) 19 | 20 | t.deepEqual(doubleParsedTorrent, parsedTorrent) 21 | t.end() 22 | }) 23 | -------------------------------------------------------------------------------- /test/webseed.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs') 2 | var parseTorrentFile = require('../') 3 | var path = require('path') 4 | var test = require('tape') 5 | 6 | var leavesUrlList = fs.readFileSync(path.join(__dirname, 'torrents/leaves-url-list.torrent')) 7 | 8 | test('parse url-list for webseed support', function (t) { 9 | var torrent = parseTorrentFile(leavesUrlList) 10 | t.deepEqual(torrent.urlList, [ 'http://www2.hn.psu.edu/faculty/jmanis/whitman/leaves-of-grass6x9.pdf' ]) 11 | t.end() 12 | }) 13 | 14 | test('encode url-list for webseed support', function (t) { 15 | var parsedTorrent = parseTorrentFile(leavesUrlList) 16 | var buf = parseTorrentFile.encode(parsedTorrent) 17 | var doubleParsedTorrent = parseTorrentFile(buf) 18 | t.deepEqual(doubleParsedTorrent.urlList, [ 'http://www2.hn.psu.edu/faculty/jmanis/whitman/leaves-of-grass6x9.pdf' ]) 19 | t.end() 20 | }) 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) Feross Aboukhadijeh and WebTorrent, LLC 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | 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, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "parse-torrent-file", 3 | "description": "Parse a .torrent file and return an object of keys/values", 4 | "version": "4.2.2", 5 | "author": { 6 | "name": "WebTorrent, LLC", 7 | "email": "feross@webtorrent.io", 8 | "url": "https://webtorrent.io" 9 | }, 10 | "bugs": { 11 | "url": "https://github.com/webtorrent/parse-torrent-file/issues" 12 | }, 13 | "dependencies": { 14 | "bencode": "^2.0.0", 15 | "simple-sha1": "^2.0.0", 16 | "uniq": "^1.0.1" 17 | }, 18 | "devDependencies": { 19 | "airtap": "0.0.4", 20 | "brfs": "^1.0.0", 21 | "standard": "*", 22 | "tape": "^4.0.0", 23 | "webtorrent-fixtures": "^1.4.0" 24 | }, 25 | "keywords": [ 26 | ".torrent", 27 | "bittorrent", 28 | "bittorrent", 29 | "parse torrent", 30 | "peer-to-peer", 31 | "read torrent", 32 | "torrent", 33 | "webtorrent" 34 | ], 35 | "license": "MIT", 36 | "main": "index.js", 37 | "repository": { 38 | "type": "git", 39 | "url": "git://github.com/webtorrent/parse-torrent-file.git" 40 | }, 41 | "scripts": { 42 | "test": "standard && npm run test-node && npm run test-browser", 43 | "test-browser": "airtap -- test/*.js", 44 | "test-browser-local": "airtap --local -- test/*.js", 45 | "test-node": "tape test/*.js" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /test/no-announce-list.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs') 2 | var parseTorrentFile = require('../') 3 | var path = require('path') 4 | var test = require('tape') 5 | 6 | var bitloveIntro = fs.readFileSync(path.join(__dirname, 'torrents/bitlove-intro.torrent')) 7 | 8 | var bitloveParsed = { 9 | infoHash: '4cb67059ed6bd08362da625b3ae77f6f4a075705', 10 | infoHashBuffer: Buffer.from('4cb67059ed6bd08362da625b3ae77f6f4a075705', 'hex'), 11 | name: 'bl001-introduction.webm', 12 | announce: [ 13 | 'http://t.bitlove.org/announce' 14 | ], 15 | urlList: [ 16 | 'http://spaceboyz.net/~astro/bitlove-show/bl001-introduction.webm' 17 | ], 18 | files: [ 19 | { 20 | 'path': 'bl001-introduction.webm', 21 | 'name': 'bl001-introduction.webm', 22 | 'length': 19211729, 23 | 'offset': 0 24 | } 25 | ], 26 | length: 19211729, 27 | pieceLength: 1048576, 28 | lastPieceLength: 337361, 29 | pieces: [ 30 | '90a75dcd4e88d287c7ac5599c108f6036c13c4ce', 31 | '1ef5468bdff9a4466ad4e446477981cb67d07933', 32 | '1fa911a663451280953edb723e67611957dc0fe1', 33 | '2abad6066e29c723f01b0908ec30e0e737514a88', 34 | '55afda8e14a45e7f797eb47b82b2d0a3b2ca5f36', 35 | '7e1f49593515ca1b93ad01c3ee050e35f04f5c2e', 36 | '15b9abb123228002cca6a7d88fc9fc99d24583e1', 37 | '32704a020d2f121bfc612b7627cd92e2b39ad43c', 38 | '35bebb2888f7143c2966bb4d5f74e0b875825856', 39 | '6875f4bb1a9fa631ee35bcd7469b1e8ff37d65a2', 40 | 'cbbeeeadc148ed681b699e88a940f796f51c0915', 41 | 'c69121c81d85055678bf198bb29fc9e504ed8c7f', 42 | '7e3863c6e1c6a8c824569f1cc0950498dceb03c4', 43 | 'ab4e77dade5f54246559c40915b700a4f734cee0', 44 | '92c47be2d397afbf06a9e9a573a63a3c683d2aa5', 45 | '01ad212a1495208b7ffbb173ce5782291695652b', 46 | '3f6233bf4ea3649c7799a1848f06cade97987525', 47 | 'db37c799e45bd02fc25eacc12e18c6c11b4da3fb', 48 | '4c73df9307b3939fec3cd5f0df179c50a49c6ca3' 49 | ], 50 | info: { 51 | length: 19211729, 52 | name: Buffer.from('YmwwMDEtaW50cm9kdWN0aW9uLndlYm0=', 'base64'), 53 | 'piece length': 1048576, 54 | pieces: Buffer.from('kKddzU6I0ofHrFWZwQj2A2wTxM4e9UaL3/mkRmrU5EZHeYHLZ9B5Mx+pEaZjRRKAlT7bcj5nYRlX3A/hKrrWBm4pxyPwGwkI7DDg5zdRSohVr9qOFKRef3l+tHuCstCjsspfNn4fSVk1Fcobk60Bw+4FDjXwT1wuFbmrsSMigALMpqfYj8n8mdJFg+EycEoCDS8SG/xhK3YnzZLis5rUPDW+uyiI9xQ8KWa7TV904Lh1glhWaHX0uxqfpjHuNbzXRpsej/N9ZaLLvu6twUjtaBtpnoipQPeW9RwJFcaRIcgdhQVWeL8Zi7KfyeUE7Yx/fjhjxuHGqMgkVp8cwJUEmNzrA8SrTnfa3l9UJGVZxAkVtwCk9zTO4JLEe+LTl6+/BqnppXOmOjxoPSqlAa0hKhSVIIt/+7FzzleCKRaVZSs/YjO/TqNknHeZoYSPBsrel5h1Jds3x5nkW9Avwl6swS4YxsEbTaP7THPfkwezk5/sPNXw3xecUKScbKM=', 'base64') 55 | }, 56 | infoBuffer: Buffer.from('ZDY6bGVuZ3RoaTE5MjExNzI5ZTQ6bmFtZTIzOmJsMDAxLWludHJvZHVjdGlvbi53ZWJtMTI6cGllY2UgbGVuZ3RoaTEwNDg1NzZlNjpwaWVjZXMzODA6kKddzU6I0ofHrFWZwQj2A2wTxM4e9UaL3/mkRmrU5EZHeYHLZ9B5Mx+pEaZjRRKAlT7bcj5nYRlX3A/hKrrWBm4pxyPwGwkI7DDg5zdRSohVr9qOFKRef3l+tHuCstCjsspfNn4fSVk1Fcobk60Bw+4FDjXwT1wuFbmrsSMigALMpqfYj8n8mdJFg+EycEoCDS8SG/xhK3YnzZLis5rUPDW+uyiI9xQ8KWa7TV904Lh1glhWaHX0uxqfpjHuNbzXRpsej/N9ZaLLvu6twUjtaBtpnoipQPeW9RwJFcaRIcgdhQVWeL8Zi7KfyeUE7Yx/fjhjxuHGqMgkVp8cwJUEmNzrA8SrTnfa3l9UJGVZxAkVtwCk9zTO4JLEe+LTl6+/BqnppXOmOjxoPSqlAa0hKhSVIIt/+7FzzleCKRaVZSs/YjO/TqNknHeZoYSPBsrel5h1Jds3x5nkW9Avwl6swS4YxsEbTaP7THPfkwezk5/sPNXw3xecUKScbKNl', 'base64') 57 | } 58 | 59 | test('parse torrent with no announce-list', function (t) { 60 | t.deepEquals(parseTorrentFile(bitloveIntro), bitloveParsed) 61 | t.end() 62 | }) 63 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # parse-torrent-file [![travis][travis-image]][travis-url] [![npm][npm-image]][npm-url] [![downloads][downloads-image]][downloads-url] [![javascript style guide][standard-image]][standard-url] 2 | 3 | [travis-image]: https://img.shields.io/travis/webtorrent/parse-torrent-file/master.svg 4 | [travis-url]: https://travis-ci.org/webtorrent/parse-torrent-file 5 | [npm-image]: https://img.shields.io/npm/v/parse-torrent-file.svg 6 | [npm-url]: https://npmjs.org/package/parse-torrent-file 7 | [downloads-image]: https://img.shields.io/npm/dm/parse-torrent-file.svg 8 | [downloads-url]: https://npmjs.org/package/parse-torrent-file 9 | [standard-image]: https://img.shields.io/badge/code_style-standard-brightgreen.svg 10 | [standard-url]: https://standardjs.com 11 | 12 | ## DEPRECATED: Use the [`parse-torrent`](https://github.com/webtorrent/parse-torrent) package instead. 13 | 14 | #### Parse a .torrent file and return an object of keys/values 15 | 16 | [![Sauce Test Status](https://saucelabs.com/browser-matrix/parse-torrent-file.svg)](https://saucelabs.com/u/parse-torrent-file) 17 | 18 | Works in node and the browser (with [browserify](http://browserify.org/)). This module is 19 | used by [WebTorrent](http://webtorrent.io)! 20 | 21 | ## install 22 | 23 | ``` 24 | npm install parse-torrent-file 25 | ``` 26 | 27 | ## usage 28 | 29 | ```js 30 | var parseTorrentFile = require('parse-torrent-file') 31 | var path = require('path') 32 | 33 | var torrent = fs.readFileSync(path.join(__dirname, 'torrents/leaves.torrent')) 34 | var parsed 35 | try { 36 | parsed = parseTorrentFile(torrent) 37 | } catch (e) { 38 | // the torrent file was corrupt 39 | console.error(e) 40 | } 41 | 42 | console.log(parsed.name) // Prints "Leaves of Grass by Walt Whitman.epub" 43 | ``` 44 | 45 | The `parsed` torrent object looks like this: 46 | 47 | ```js 48 | { 49 | "infoHash": "d2474e86c95b19b8bcfdb92bc12c9d44667cfa36", 50 | "infoHashBuffer": ..., 51 | "name": "Leaves of Grass by Walt Whitman.epub", 52 | "private": false, 53 | "created": "2013-08-01T13:27:46.000Z", 54 | "announce": [ 55 | "http://tracker.example.com/announce" 56 | ], 57 | "urlList": [], 58 | "files": [ 59 | { 60 | "path": "Leaves of Grass by Walt Whitman.epub", 61 | "name": "Leaves of Grass by Walt Whitman.epub", 62 | "length": 362017, 63 | "offset": 0 64 | } 65 | ], 66 | "length": 362017, 67 | "pieceLength": 16384, 68 | "lastPieceLength": 1569, 69 | "pieces": [ 70 | "1f9c3f59beec079715ec53324bde8569e4a0b4eb", 71 | "ec42307d4ce5557b5d3964c5ef55d354cf4a6ecc", 72 | "7bf1bcaf79d11fa5e0be06593c8faafc0c2ba2cf", 73 | "76d71c5b01526b23007f9e9929beafc5151e6511", 74 | "0931a1b44c21bf1e68b9138f90495e690dbc55f5", 75 | "72e4c2944cbacf26e6b3ae8a7229d88aafa05f61", 76 | "eaae6abf3f07cb6db9677cc6aded4dd3985e4586", 77 | "27567fa7639f065f71b18954304aca6366729e0b", 78 | "4773d77ae80caa96a524804dfe4b9bd3deaef999", 79 | "c9dd51027467519d5eb2561ae2cc01467de5f643", 80 | "0a60bcba24797692efa8770d23df0a830d91cb35", 81 | "b3407a88baa0590dc8c9aa6a120f274367dcd867", 82 | "e88e8338c572a06e3c801b29f519df532b3e76f6", 83 | "70cf6aee53107f3d39378483f69cf80fa568b1ea", 84 | "c53b506159e988d8bc16922d125d77d803d652c3", 85 | "ca3070c16eed9172ab506d20e522ea3f1ab674b3", 86 | "f923d76fe8f44ff32e372c3b376564c6fb5f0dbe", 87 | "52164f03629fd1322636babb2c014b7dae582da4", 88 | "1363965261e6ce12b43701f0a8c9ed1520a70eba", 89 | "004400a267765f6d3dd5c7beb5bd3c75f3df2a54", 90 | "560a61801147fa4ec7cf568e703acb04e5610a4d", 91 | "56dcc242d03293e9446cf5e457d8eb3d9588fd90", 92 | "c698de9b0dad92980906c026d8c1408fa08fe4ec" 93 | ] 94 | } 95 | ``` 96 | 97 | To convert a parsed torrent back into a .torrent file buffer, call `parseTorrentFile.encode`. 98 | 99 | ```js 100 | var parseTorrentFile = require('parse-torrent-file') 101 | 102 | // parse a torrent 103 | var parsed = parseTorrentFile(/* some buffer */) 104 | 105 | // convert parsed torrent back to a buffer 106 | var buf = parseTorrentFile.encode(parsed) 107 | ``` 108 | 109 | ## credit 110 | 111 | This was originally based on [read-torrent](https://www.npmjs.org/package/read-torrent) by [mafintosh](https://twitter.com/mafintosh). It's basically a pared-down version of that, but it works in the browser (so [WebTorrent](http://webtorrent.io) can use it), doesn't have huge npm dependencies like `request` (saving on file size), and it has tests. Thanks for publishing good modules, mafintosh! 112 | 113 | ## license 114 | 115 | MIT. Copyright (c) [Feross Aboukhadijeh](https://feross.org) and [WebTorrent, LLC](https://webtorrent.io). 116 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = decodeTorrentFile 2 | module.exports.decode = decodeTorrentFile 3 | module.exports.encode = encodeTorrentFile 4 | 5 | var bencode = require('bencode') 6 | var path = require('path') 7 | var sha1 = require('simple-sha1') 8 | var uniq = require('uniq') 9 | 10 | /** 11 | * Parse a torrent. Throws an exception if the torrent is missing required fields. 12 | * @param {Buffer|Object} torrent 13 | * @return {Object} parsed torrent 14 | */ 15 | function decodeTorrentFile (torrent) { 16 | if (Buffer.isBuffer(torrent)) { 17 | torrent = bencode.decode(torrent) 18 | } 19 | 20 | // sanity check 21 | ensure(torrent.info, 'info') 22 | ensure(torrent.info['name.utf-8'] || torrent.info.name, 'info.name') 23 | ensure(torrent.info['piece length'], 'info[\'piece length\']') 24 | ensure(torrent.info.pieces, 'info.pieces') 25 | 26 | if (torrent.info.files) { 27 | torrent.info.files.forEach(function (file) { 28 | ensure(typeof file.length === 'number', 'info.files[0].length') 29 | ensure(file['path.utf-8'] || file.path, 'info.files[0].path') 30 | }) 31 | } else { 32 | ensure(typeof torrent.info.length === 'number', 'info.length') 33 | } 34 | 35 | var result = {} 36 | result.info = torrent.info 37 | result.infoBuffer = bencode.encode(torrent.info) 38 | result.infoHash = sha1.sync(result.infoBuffer) 39 | result.infoHashBuffer = Buffer.from(result.infoHash, 'hex') 40 | 41 | result.name = (torrent.info['name.utf-8'] || torrent.info.name).toString() 42 | 43 | if (torrent.info.private !== undefined) result.private = !!torrent.info.private 44 | 45 | if (torrent['creation date']) result.created = new Date(torrent['creation date'] * 1000) 46 | if (torrent['created by']) result.createdBy = torrent['created by'].toString() 47 | 48 | if (Buffer.isBuffer(torrent.comment)) result.comment = torrent.comment.toString() 49 | 50 | // announce and announce-list will be missing if metadata fetched via ut_metadata 51 | result.announce = [] 52 | if (Array.isArray(torrent['announce-list']) && torrent['announce-list'].length > 0) { 53 | torrent['announce-list'].forEach(function (urls) { 54 | urls.forEach(function (url) { 55 | result.announce.push(url.toString()) 56 | }) 57 | }) 58 | } else if (torrent.announce) { 59 | result.announce.push(torrent.announce.toString()) 60 | } 61 | 62 | // handle url-list (BEP19 / web seeding) 63 | if (Buffer.isBuffer(torrent['url-list'])) { 64 | // some clients set url-list to empty string 65 | torrent['url-list'] = torrent['url-list'].length > 0 66 | ? [ torrent['url-list'] ] 67 | : [] 68 | } 69 | result.urlList = (torrent['url-list'] || []).map(function (url) { 70 | return url.toString() 71 | }) 72 | 73 | uniq(result.announce) 74 | uniq(result.urlList) 75 | 76 | var files = torrent.info.files || [ torrent.info ] 77 | result.files = files.map(function (file, i) { 78 | var parts = [].concat(result.name, file['path.utf-8'] || file.path || []).map(function (p) { 79 | return p.toString() 80 | }) 81 | return { 82 | path: path.join.apply(null, [path.sep].concat(parts)).slice(1), 83 | name: parts[parts.length - 1], 84 | length: file.length, 85 | offset: files.slice(0, i).reduce(sumLength, 0) 86 | } 87 | }) 88 | 89 | result.length = files.reduce(sumLength, 0) 90 | 91 | var lastFile = result.files[result.files.length - 1] 92 | 93 | result.pieceLength = torrent.info['piece length'] 94 | result.lastPieceLength = ((lastFile.offset + lastFile.length) % result.pieceLength) || result.pieceLength 95 | result.pieces = splitPieces(torrent.info.pieces) 96 | 97 | return result 98 | } 99 | 100 | /** 101 | * Convert a parsed torrent object back into a .torrent file buffer. 102 | * @param {Object} parsed parsed torrent 103 | * @return {Buffer} 104 | */ 105 | function encodeTorrentFile (parsed) { 106 | var torrent = { 107 | info: parsed.info 108 | } 109 | 110 | torrent['announce-list'] = (parsed.announce || []).map(function (url) { 111 | if (!torrent.announce) torrent.announce = url 112 | url = Buffer.from(url, 'utf8') 113 | return [ url ] 114 | }) 115 | 116 | torrent['url-list'] = parsed.urlList || [] 117 | 118 | if (parsed.private === true || parsed.private === false) { 119 | torrent['private'] = parsed.private 120 | } 121 | 122 | if (parsed.created) { 123 | torrent['creation date'] = (parsed.created.getTime() / 1000) | 0 124 | } 125 | 126 | if (parsed.createdBy) { 127 | torrent['created by'] = parsed.createdBy 128 | } 129 | 130 | if (parsed.comment) { 131 | torrent.comment = parsed.comment 132 | } 133 | 134 | return bencode.encode(torrent) 135 | } 136 | 137 | function sumLength (sum, file) { 138 | return sum + file.length 139 | } 140 | 141 | function splitPieces (buf) { 142 | var pieces = [] 143 | for (var i = 0; i < buf.length; i += 20) { 144 | pieces.push(buf.slice(i, i + 20).toString('hex')) 145 | } 146 | return pieces 147 | } 148 | 149 | function ensure (bool, fieldName) { 150 | if (!bool) throw new Error('Torrent is missing required field: ' + fieldName) 151 | } 152 | -------------------------------------------------------------------------------- /test/basic.js: -------------------------------------------------------------------------------- 1 | var bencode = require('bencode') 2 | var fixtures = require('webtorrent-fixtures') 3 | var parseTorrentFile = require('../') 4 | var test = require('tape') 5 | 6 | var leavesParsed = { 7 | infoHash: 'd2474e86c95b19b8bcfdb92bc12c9d44667cfa36', 8 | infoHashBuffer: Buffer.from('d2474e86c95b19b8bcfdb92bc12c9d44667cfa36', 'hex'), 9 | name: 'Leaves of Grass by Walt Whitman.epub', 10 | created: new Date('Thu Aug 01 2013 06:27:46 GMT-0700 (PDT)'), 11 | createdBy: 'uTorrent/3300', 12 | announce: [], 13 | urlList: [], 14 | files: [ 15 | { 16 | path: 'Leaves of Grass by Walt Whitman.epub', 17 | name: 'Leaves of Grass by Walt Whitman.epub', 18 | length: 362017, 19 | offset: 0 20 | } 21 | ], 22 | length: 362017, 23 | pieceLength: 16384, 24 | lastPieceLength: 1569, 25 | pieces: [ 26 | '1f9c3f59beec079715ec53324bde8569e4a0b4eb', 27 | 'ec42307d4ce5557b5d3964c5ef55d354cf4a6ecc', 28 | '7bf1bcaf79d11fa5e0be06593c8faafc0c2ba2cf', 29 | '76d71c5b01526b23007f9e9929beafc5151e6511', 30 | '0931a1b44c21bf1e68b9138f90495e690dbc55f5', 31 | '72e4c2944cbacf26e6b3ae8a7229d88aafa05f61', 32 | 'eaae6abf3f07cb6db9677cc6aded4dd3985e4586', 33 | '27567fa7639f065f71b18954304aca6366729e0b', 34 | '4773d77ae80caa96a524804dfe4b9bd3deaef999', 35 | 'c9dd51027467519d5eb2561ae2cc01467de5f643', 36 | '0a60bcba24797692efa8770d23df0a830d91cb35', 37 | 'b3407a88baa0590dc8c9aa6a120f274367dcd867', 38 | 'e88e8338c572a06e3c801b29f519df532b3e76f6', 39 | '70cf6aee53107f3d39378483f69cf80fa568b1ea', 40 | 'c53b506159e988d8bc16922d125d77d803d652c3', 41 | 'ca3070c16eed9172ab506d20e522ea3f1ab674b3', 42 | 'f923d76fe8f44ff32e372c3b376564c6fb5f0dbe', 43 | '52164f03629fd1322636babb2c014b7dae582da4', 44 | '1363965261e6ce12b43701f0a8c9ed1520a70eba', 45 | '004400a267765f6d3dd5c7beb5bd3c75f3df2a54', 46 | '560a61801147fa4ec7cf568e703acb04e5610a4d', 47 | '56dcc242d03293e9446cf5e457d8eb3d9588fd90', 48 | 'c698de9b0dad92980906c026d8c1408fa08fe4ec' 49 | ], 50 | info: { 51 | length: 362017, 52 | name: Buffer.from('TGVhdmVzIG9mIEdyYXNzIGJ5IFdhbHQgV2hpdG1hbi5lcHVi', 'base64'), 53 | 'piece length': 16384, 54 | pieces: Buffer.from('H5w/Wb7sB5cV7FMyS96FaeSgtOvsQjB9TOVVe105ZMXvVdNUz0puzHvxvK950R+l4L4GWTyPqvwMK6LPdtccWwFSayMAf56ZKb6vxRUeZREJMaG0TCG/Hmi5E4+QSV5pDbxV9XLkwpRMus8m5rOuinIp2IqvoF9h6q5qvz8Hy225Z3zGre1N05heRYYnVn+nY58GX3GxiVQwSspjZnKeC0dz13roDKqWpSSATf5Lm9PervmZyd1RAnRnUZ1eslYa4swBRn3l9kMKYLy6JHl2ku+odw0j3wqDDZHLNbNAeoi6oFkNyMmqahIPJ0Nn3Nhn6I6DOMVyoG48gBsp9RnfUys+dvZwz2ruUxB/PTk3hIP2nPgPpWix6sU7UGFZ6YjYvBaSLRJdd9gD1lLDyjBwwW7tkXKrUG0g5SLqPxq2dLP5I9dv6PRP8y43LDs3ZWTG+18NvlIWTwNin9EyJja6uywBS32uWC2kE2OWUmHmzhK0NwHwqMntFSCnDroARACiZ3ZfbT3Vx761vTx1898qVFYKYYARR/pOx89WjnA6ywTlYQpNVtzCQtAyk+lEbPXkV9jrPZWI/ZDGmN6bDa2SmAkGwCbYwUCPoI/k7A==', 'base64') 55 | }, 56 | infoBuffer: Buffer.from('ZDY6bGVuZ3RoaTM2MjAxN2U0Om5hbWUzNjpMZWF2ZXMgb2YgR3Jhc3MgYnkgV2FsdCBXaGl0bWFuLmVwdWIxMjpwaWVjZSBsZW5ndGhpMTYzODRlNjpwaWVjZXM0NjA6H5w/Wb7sB5cV7FMyS96FaeSgtOvsQjB9TOVVe105ZMXvVdNUz0puzHvxvK950R+l4L4GWTyPqvwMK6LPdtccWwFSayMAf56ZKb6vxRUeZREJMaG0TCG/Hmi5E4+QSV5pDbxV9XLkwpRMus8m5rOuinIp2IqvoF9h6q5qvz8Hy225Z3zGre1N05heRYYnVn+nY58GX3GxiVQwSspjZnKeC0dz13roDKqWpSSATf5Lm9PervmZyd1RAnRnUZ1eslYa4swBRn3l9kMKYLy6JHl2ku+odw0j3wqDDZHLNbNAeoi6oFkNyMmqahIPJ0Nn3Nhn6I6DOMVyoG48gBsp9RnfUys+dvZwz2ruUxB/PTk3hIP2nPgPpWix6sU7UGFZ6YjYvBaSLRJdd9gD1lLDyjBwwW7tkXKrUG0g5SLqPxq2dLP5I9dv6PRP8y43LDs3ZWTG+18NvlIWTwNin9EyJja6uywBS32uWC2kE2OWUmHmzhK0NwHwqMntFSCnDroARACiZ3ZfbT3Vx761vTx1898qVFYKYYARR/pOx89WjnA6ywTlYQpNVtzCQtAyk+lEbPXkV9jrPZWI/ZDGmN6bDa2SmAkGwCbYwUCPoI/k7GU=', 'base64') 57 | } 58 | 59 | var leavesMagnetParsed = { 60 | infoHash: 'd2474e86c95b19b8bcfdb92bc12c9d44667cfa36', 61 | infoHashBuffer: Buffer.from('d2474e86c95b19b8bcfdb92bc12c9d44667cfa36', 'hex'), 62 | name: 'Leaves of Grass by Walt Whitman.epub', 63 | announce: [], 64 | urlList: [], 65 | files: [ 66 | { 67 | path: 'Leaves of Grass by Walt Whitman.epub', 68 | name: 'Leaves of Grass by Walt Whitman.epub', 69 | length: 362017, 70 | offset: 0 71 | } 72 | ], 73 | length: 362017, 74 | pieceLength: 16384, 75 | lastPieceLength: 1569, 76 | pieces: [ 77 | '1f9c3f59beec079715ec53324bde8569e4a0b4eb', 78 | 'ec42307d4ce5557b5d3964c5ef55d354cf4a6ecc', 79 | '7bf1bcaf79d11fa5e0be06593c8faafc0c2ba2cf', 80 | '76d71c5b01526b23007f9e9929beafc5151e6511', 81 | '0931a1b44c21bf1e68b9138f90495e690dbc55f5', 82 | '72e4c2944cbacf26e6b3ae8a7229d88aafa05f61', 83 | 'eaae6abf3f07cb6db9677cc6aded4dd3985e4586', 84 | '27567fa7639f065f71b18954304aca6366729e0b', 85 | '4773d77ae80caa96a524804dfe4b9bd3deaef999', 86 | 'c9dd51027467519d5eb2561ae2cc01467de5f643', 87 | '0a60bcba24797692efa8770d23df0a830d91cb35', 88 | 'b3407a88baa0590dc8c9aa6a120f274367dcd867', 89 | 'e88e8338c572a06e3c801b29f519df532b3e76f6', 90 | '70cf6aee53107f3d39378483f69cf80fa568b1ea', 91 | 'c53b506159e988d8bc16922d125d77d803d652c3', 92 | 'ca3070c16eed9172ab506d20e522ea3f1ab674b3', 93 | 'f923d76fe8f44ff32e372c3b376564c6fb5f0dbe', 94 | '52164f03629fd1322636babb2c014b7dae582da4', 95 | '1363965261e6ce12b43701f0a8c9ed1520a70eba', 96 | '004400a267765f6d3dd5c7beb5bd3c75f3df2a54', 97 | '560a61801147fa4ec7cf568e703acb04e5610a4d', 98 | '56dcc242d03293e9446cf5e457d8eb3d9588fd90', 99 | 'c698de9b0dad92980906c026d8c1408fa08fe4ec' 100 | ], 101 | info: { 102 | length: 362017, 103 | name: Buffer.from('TGVhdmVzIG9mIEdyYXNzIGJ5IFdhbHQgV2hpdG1hbi5lcHVi', 'base64'), 104 | 'piece length': 16384, 105 | pieces: Buffer.from('H5w/Wb7sB5cV7FMyS96FaeSgtOvsQjB9TOVVe105ZMXvVdNUz0puzHvxvK950R+l4L4GWTyPqvwMK6LPdtccWwFSayMAf56ZKb6vxRUeZREJMaG0TCG/Hmi5E4+QSV5pDbxV9XLkwpRMus8m5rOuinIp2IqvoF9h6q5qvz8Hy225Z3zGre1N05heRYYnVn+nY58GX3GxiVQwSspjZnKeC0dz13roDKqWpSSATf5Lm9PervmZyd1RAnRnUZ1eslYa4swBRn3l9kMKYLy6JHl2ku+odw0j3wqDDZHLNbNAeoi6oFkNyMmqahIPJ0Nn3Nhn6I6DOMVyoG48gBsp9RnfUys+dvZwz2ruUxB/PTk3hIP2nPgPpWix6sU7UGFZ6YjYvBaSLRJdd9gD1lLDyjBwwW7tkXKrUG0g5SLqPxq2dLP5I9dv6PRP8y43LDs3ZWTG+18NvlIWTwNin9EyJja6uywBS32uWC2kE2OWUmHmzhK0NwHwqMntFSCnDroARACiZ3ZfbT3Vx761vTx1898qVFYKYYARR/pOx89WjnA6ywTlYQpNVtzCQtAyk+lEbPXkV9jrPZWI/ZDGmN6bDa2SmAkGwCbYwUCPoI/k7A==', 'base64') 106 | }, 107 | infoBuffer: Buffer.from('ZDY6bGVuZ3RoaTM2MjAxN2U0Om5hbWUzNjpMZWF2ZXMgb2YgR3Jhc3MgYnkgV2FsdCBXaGl0bWFuLmVwdWIxMjpwaWVjZSBsZW5ndGhpMTYzODRlNjpwaWVjZXM0NjA6H5w/Wb7sB5cV7FMyS96FaeSgtOvsQjB9TOVVe105ZMXvVdNUz0puzHvxvK950R+l4L4GWTyPqvwMK6LPdtccWwFSayMAf56ZKb6vxRUeZREJMaG0TCG/Hmi5E4+QSV5pDbxV9XLkwpRMus8m5rOuinIp2IqvoF9h6q5qvz8Hy225Z3zGre1N05heRYYnVn+nY58GX3GxiVQwSspjZnKeC0dz13roDKqWpSSATf5Lm9PervmZyd1RAnRnUZ1eslYa4swBRn3l9kMKYLy6JHl2ku+odw0j3wqDDZHLNbNAeoi6oFkNyMmqahIPJ0Nn3Nhn6I6DOMVyoG48gBsp9RnfUys+dvZwz2ruUxB/PTk3hIP2nPgPpWix6sU7UGFZ6YjYvBaSLRJdd9gD1lLDyjBwwW7tkXKrUG0g5SLqPxq2dLP5I9dv6PRP8y43LDs3ZWTG+18NvlIWTwNin9EyJja6uywBS32uWC2kE2OWUmHmzhK0NwHwqMntFSCnDroARACiZ3ZfbT3Vx761vTx1898qVFYKYYARR/pOx89WjnA6ywTlYQpNVtzCQtAyk+lEbPXkV9jrPZWI/ZDGmN6bDa2SmAkGwCbYwUCPoI/k7GU=', 'base64') 108 | } 109 | 110 | var numbersParsed = { 111 | infoHash: '89d97c2261a21b040cf11caa661a3ba7233bb7e6', 112 | infoHashBuffer: Buffer.from('89d97c2261a21b040cf11caa661a3ba7233bb7e6', 'hex'), 113 | name: 'numbers', 114 | created: new Date('+047910-02-15T17:44:02.000Z'), 115 | announce: [], 116 | urlList: [], 117 | files: [ 118 | { 119 | path: 'numbers/1.txt', 120 | name: '1.txt', 121 | length: 1, 122 | offset: 0 123 | }, 124 | { 125 | path: 'numbers/2.txt', 126 | name: '2.txt', 127 | length: 2, 128 | offset: 1 129 | }, 130 | { 131 | path: 'numbers/3.txt', 132 | name: '3.txt', 133 | length: 3, 134 | offset: 3 135 | } 136 | ], 137 | length: 6, 138 | pieceLength: 16384, 139 | lastPieceLength: 6, 140 | pieces: [ 141 | '1f74648e50a6a6708ec54ab327a163d5536b7ced' 142 | ], 143 | info: { 144 | files: [ 145 | { 146 | length: 1, 147 | path: [ Buffer.from('1.txt') ] 148 | }, 149 | { 150 | length: 2, 151 | path: [ Buffer.from('2.txt') ] 152 | }, 153 | { 154 | length: 3, 155 | path: [ Buffer.from('3.txt') ] 156 | } 157 | ], 158 | name: Buffer.from('bnVtYmVycw==', 'base64'), 159 | 'piece length': 16384, 160 | pieces: Buffer.from('H3RkjlCmpnCOxUqzJ6Fj1VNrfO0=', 'base64') 161 | }, 162 | infoBuffer: Buffer.from('ZDU6ZmlsZXNsZDY6bGVuZ3RoaTFlNDpwYXRobDU6MS50eHRlZWQ2Omxlbmd0aGkyZTQ6cGF0aGw1OjIudHh0ZWVkNjpsZW5ndGhpM2U0OnBhdGhsNTozLnR4dGVlZTQ6bmFtZTc6bnVtYmVyczEyOnBpZWNlIGxlbmd0aGkxNjM4NGU2OnBpZWNlczIwOh90ZI5QpqZwjsVKsyehY9VTa3ztZQ==', 'base64') 163 | } 164 | 165 | test('parse single file torrent', function (t) { 166 | t.deepEquals(parseTorrentFile(fixtures.leaves.torrent), leavesParsed) 167 | t.end() 168 | }) 169 | 170 | test('parse "torrent" from magnet metadata protocol', function (t) { 171 | t.deepEquals(parseTorrentFile(fixtures.leavesMetadata.torrent), leavesMagnetParsed) 172 | t.end() 173 | }) 174 | 175 | test('parse multiple file torrent', function (t) { 176 | t.deepEquals(parseTorrentFile(fixtures.numbers.torrent), numbersParsed) 177 | t.end() 178 | }) 179 | 180 | test('parse torrent from object', function (t) { 181 | var torrent = bencode.decode(fixtures.numbers.torrent) 182 | t.deepEquals(parseTorrentFile(torrent), numbersParsed) 183 | t.end() 184 | }) 185 | --------------------------------------------------------------------------------