parse-torrent
Version:
Parse a torrent and return an object of keys/values
137 lines (112 loc) • 3.56 kB
JavaScript
module.exports = parseTorrent
module.exports.toBuffer = toBuffer
var bencode = require('bencode')
var path = require('path')
var Rusha = require('rusha-browserify') // Fast SHA1 (works in browser)
/**
* Parse a torrent. Throws an exception if the torrent is missing required fields.
* @param {Buffer|Object} torrent
* @return {Object} parsed torrent
*/
function parseTorrent (torrent) {
if (Buffer.isBuffer(torrent)) {
torrent = bencode.decode(torrent)
}
// sanity check
ensure(torrent.info, 'info')
ensure(torrent.info.name, 'info.name')
ensure(torrent.info['piece length'], 'info[\'piece length\']')
ensure(torrent.info.pieces, 'info.pieces')
if (torrent.info.files) {
torrent.info.files.forEach(function (file) {
ensure(typeof file.length === 'number', 'info.files[0].length')
ensure(file.path, 'info.files[0].path')
})
} else {
ensure(torrent.info.length, 'info.length')
}
var result = {}
result.info = torrent.info
result.infoBuffer = bencode.encode(torrent.info)
result.infoHash = sha1(result.infoBuffer)
result.name = torrent.info.name.toString()
result.private = !!torrent.info.private
if (torrent['creation date'])
result.created = new Date(torrent['creation date'] * 1000)
// announce/announce-list may be missing if metadata fetched via ut_metadata extension
var announce = torrent['announce-list']
if (!announce) {
if (torrent.announce) {
announce = [[torrent.announce]]
} else {
announce = []
}
}
result.announceList = announce.map(function (urls) {
return urls.map(function (url) {
return url.toString()
})
})
result.announce = [].concat.apply([], result.announceList)
var files = torrent.info.files || [torrent.info]
result.files = files.map(function (file, i) {
var parts = [].concat(file.name || result.name, file.path || []).map(function (p) {
return p.toString()
})
return {
path: path.join.apply(null, [path.sep].concat(parts)).slice(1),
name: parts[parts.length - 1],
length: file.length,
offset: files.slice(0, i).reduce(sumLength, 0)
}
})
result.length = files.reduce(sumLength, 0)
var lastFile = result.files[result.files.length - 1]
result.pieceLength = torrent.info['piece length']
result.lastPieceLength = ((lastFile.offset + lastFile.length) % result.pieceLength) || result.pieceLength
result.pieces = splitPieces(torrent.info.pieces)
return result
}
/**
* Convert a parsed torrent object back into a .torrent file buffer.
* @param {Object} parsed parsed torrent
* @return {Buffer}
*/
function toBuffer (parsed) {
var torrent = {
info: parsed.info
}
if (parsed.announceList) {
torrent['announce-list'] = parsed.announceList.map(function (urls) {
return urls.map(function (url) {
url = new Buffer(url, 'utf8')
if (!torrent.announce) {
torrent.announce = url
}
return url
})
})
}
if (parsed.created) {
torrent['creation date'] = (parsed.created.getTime() / 1000) | 0
}
return bencode.encode(torrent)
}
function sumLength (sum, file) {
return sum + file.length
}
function splitPieces (buf) {
var pieces = []
for (var i = 0; i < buf.length; i += 20) {
pieces.push(buf.slice(i, i + 20).toString('hex'))
}
return pieces
}
function sha1 (buf) {
return (new Rusha()).digestFromBuffer(buf)
}
function ensure (bool, fieldName) {
if (!bool) {
throw new Error('Torrent is missing required field: ' + fieldName)
}
}