nfp_sites/base/file/torrent.mjs

106 lines
3.4 KiB
JavaScript

import bencode from 'bencode'
/*
Taken from parse-torrent
*/
/**
* Parse a torrent. Throws an exception if the torrent is missing required fields.
* @param {Buffer|Object} torrent
* @return {Object} parsed torrent
*/
export function decodeTorrentFile (torrent) {
if (Buffer.isBuffer(torrent)) {
torrent = bencode.decode(torrent)
}
// sanity check
ensure(torrent.info, 'info')
ensure(torrent.info['name.utf-8'] || 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(file => {
ensure(typeof file.length === 'number', 'info.files[0].length')
ensure(file['path.utf-8'] || file.path, 'info.files[0].path')
})
} else {
ensure(typeof torrent.info.length === 'number', 'info.length')
}
const result = {
info: torrent.info,
infoBuffer: bencode.encode(torrent.info),
name: (torrent.info['name.utf-8'] || torrent.info.name).toString(),
announce: []
}
result.infoHash = sha1.sync(result.infoBuffer)
result.infoHashBuffer = Buffer.from(result.infoHash, 'hex')
if (torrent.info.private !== undefined) result.private = !!torrent.info.private
if (torrent['creation date']) result.created = new Date(torrent['creation date'] * 1000)
if (torrent['created by']) result.createdBy = torrent['created by'].toString()
if (Buffer.isBuffer(torrent.comment)) result.comment = torrent.comment.toString()
// announce and announce-list will be missing if metadata fetched via ut_metadata
if (Array.isArray(torrent['announce-list']) && torrent['announce-list'].length > 0) {
torrent['announce-list'].forEach(urls => {
urls.forEach(url => {
result.announce.push(url.toString())
})
})
} else if (torrent.announce) {
result.announce.push(torrent.announce.toString())
}
// handle url-list (BEP19 / web seeding)
if (Buffer.isBuffer(torrent['url-list'])) {
// some clients set url-list to empty string
torrent['url-list'] = torrent['url-list'].length > 0
? [torrent['url-list']]
: []
}
result.urlList = (torrent['url-list'] || []).map(url => url.toString())
// remove duplicates by converting to Set and back
result.announce = Array.from(new Set(result.announce))
result.urlList = Array.from(new Set(result.urlList))
const files = torrent.info.files || [torrent.info]
result.files = files.map((file, i) => {
const parts = [].concat(result.name, file['path.utf-8'] || file.path || []).map(p => 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)
const 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
}
function splitPieces (buf) {
const pieces = []
for (let i = 0; i < buf.length; i += 20) {
pieces.push(buf.slice(i, i + 20).toString('hex'))
}
return pieces
}
function ensure (bool, fieldName) {
if (!bool) throw new Error(`Torrent is missing required field: ${fieldName}`)
}