From 94ae74b32df5c0758f4b92d840ca641d5f63394d Mon Sep 17 00:00:00 2001 From: Feross Aboukhadijeh Date: Sun, 11 May 2014 16:41:47 -0700 Subject: [PATCH 1/4] remove `new` trick for non-exported function --- index.js | 1 - 1 file changed, 1 deletion(-) diff --git a/index.js b/index.js index fe3da18..e85f41d 100644 --- a/index.js +++ b/index.js @@ -34,7 +34,6 @@ inherits(Tracker, EventEmitter) */ function Tracker (client, announceUrl, interval, opts) { var self = this - if (!(self instanceof Tracker)) return new Tracker(client, announceUrl, interval, opts) EventEmitter.call(self) self._opts = opts || {} From fec4e0dc3d0cbf89af37ba833aaf2ea7cd5f4376 Mon Sep 17 00:00:00 2001 From: Feross Aboukhadijeh Date: Sun, 11 May 2014 17:29:12 -0700 Subject: [PATCH 2/4] add readme for scrape feature --- README.md | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 341ba29..5d07f7f 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,7 @@ by [WebTorrent](http://webtorrent.io). - Includes client & server implementations - Supports HTTP & UDP trackers ([BEP 15](http://www.bittorrent.org/beps/bep_0015.html)) +- Supports tracker scrape ## install @@ -53,9 +54,9 @@ client.on('error', function (err) { client.start() client.on('update', function (data) { - console.log('got a response from tracker: ' + data.announce) - console.log('number of seeders on this tracker: ' + data.complete) - console.log('number of leechers on this tracker: ' + data.incomplete) + console.log('got an announce response from tracker: ' + data.announce) + console.log('number of seeders in the swarm: ' + data.complete) + console.log('number of leechers in the swarm: ' + data.incomplete) }) client.once('peer', function (addr) { @@ -70,6 +71,16 @@ client.update() // stop getting peers from the tracker, gracefully leave the swarm client.stop() + +// scrape +client.scape() + +client.on('scrape', function (data) { + console.log('got a scrape response from tracker: ' + data.announce) + console.log('number of seeders in the swarm: ' + data.complete) + console.log('number of leechers in the swarm: ' + data.incomplete) + console.log('number of total downloads of this torrent: ' + data.incomplete) +}) ``` ### server From e5e0fa9e27500c2f89b00198de171eb4e9f045c1 Mon Sep 17 00:00:00 2001 From: Feross Aboukhadijeh Date: Sun, 11 May 2014 17:32:57 -0700 Subject: [PATCH 3/4] minor style cleanup --- index.js | 93 ++++++++++++++++++++++++-------------------------- test/client.js | 2 +- test/server.js | 5 +-- 3 files changed, 48 insertions(+), 52 deletions(-) diff --git a/index.js b/index.js index e85f41d..87eca89 100644 --- a/index.js +++ b/index.js @@ -28,25 +28,24 @@ inherits(Tracker, EventEmitter) * An individual torrent tracker * * @param {Client} client parent bittorrent tracker client - * @param {string} announceUrl announce url of tracker - * @param {Number} interval interval in ms to send announce requests to the tracker + * @param {string} announceUrl announce url of tracker * @param {Object} opts optional options */ -function Tracker (client, announceUrl, interval, opts) { +function Tracker (client, announceUrl, opts) { var self = this EventEmitter.call(self) self._opts = opts || {} - + self.client = client - + self._announceUrl = announceUrl - self._intervalMs = interval + self._intervalMs = self.client._intervalMs // use client interval initially self._interval = null - + if (self._announceUrl.indexOf('udp:') === 0) { - self._requestImpl = self._requestUdp.bind(self) + self._requestImpl = self._requestUdp } else { - self._requestImpl = self._requestHttp.bind(self) + self._requestImpl = self._requestHttp } } @@ -55,7 +54,7 @@ Tracker.prototype.start = function (opts) { opts = opts || {} opts.event = 'started' self._request(opts) - + self.setInterval(self._intervalMs) // start announcing on intervals } @@ -64,7 +63,7 @@ Tracker.prototype.stop = function (opts) { opts = opts || {} opts.event = 'stopped' self._request(opts) - + self.setInterval(0) // stop announcing on intervals } @@ -84,25 +83,25 @@ Tracker.prototype.update = function (opts) { Tracker.prototype.scrape = function (opts) { var self = this - + if (!self._scrapeUrl) { var announce = 'announce' - var i = self._announceUrl.lastIndexOf('\/') + 1 - + var i = self._announceUrl.lastIndexOf('/') + 1 + if (i >= 1 && self._announceUrl.slice(i, i + announce.length) === announce) { self._scrapeUrl = self._announceUrl.slice(0, i) + 'scrape' + self._announceUrl.slice(i + announce.length) } } - + if (!self._scrapeUrl) { self.client.emit('error', new Error('scrape not supported for announceUrl ' + self._announceUrl)) return } - + opts = extend({ info_hash: bytewiseEncodeURIComponent(self.client._infoHash) }, opts) - + self._requestImpl(self._scrapeUrl, opts) } @@ -133,11 +132,11 @@ Tracker.prototype._request = function (opts) { uploaded: 0, // default, user should provide real value downloaded: 0 // default, user should provide real value }, opts) - + if (self._trackerId) { opts.trackerid = self._trackerId } - + self._requestImpl(self._announceUrl, opts) } @@ -229,7 +228,7 @@ Tracker.prototype._requestUdp = function (requestUrl, opts) { clearTimeout(timeout) socket.close() return - + case 2: // scrape if (message.length < 20) { return error('invalid scrape message') @@ -258,7 +257,7 @@ Tracker.prototype._requestUdp = function (requestUrl, opts) { function genTransactionId () { transactionId = new Buffer(hat(32), 'hex') } - + function announce (connectionId, opts) { opts = opts || {} genTransactionId() @@ -279,7 +278,7 @@ Tracker.prototype._requestUdp = function (requestUrl, opts) { toUInt16(self.client._port || 0) ])) } - + function scrape (connectionId, opts) { genTransactionId() @@ -315,7 +314,7 @@ Tracker.prototype._handleResponse = function (requestUrl, data) { if (warning) { self.client.emit('warning', warning); } - + if (requestUrl === self._announceUrl) { var interval = data.interval || data['min interval'] if (interval && !self._opts.interval && self._intervalMs !== 0) { @@ -349,10 +348,10 @@ Tracker.prototype._handleResponse = function (requestUrl, data) { }) } } else if (requestUrl === self._scrapeUrl) { - // note: the unofficial spec says to use the 'files' key but i've seen 'host' in practice + // NOTE: the unofficial spec says to use the 'files' key but i've seen 'host' in practice data = data.files || data.host || {} data = data[bytewiseEncodeURIComponent(self.client._infoHash)] - + if (!data) { self.client.emit('error', new Error('invalid scrape response')) } else { @@ -401,7 +400,7 @@ function Client (peerId, port, torrent, opts) { self._intervalMs = self._opts.interval || (30 * 60 * 1000) // default: 30 minutes self._trackers = torrent.announce.map(function (announceUrl) { - return Tracker(self, announceUrl, self._intervalMs, self._opts) + return new Tracker(self, announceUrl, self._opts) }) } @@ -443,7 +442,7 @@ Client.prototype.scrape = function (opts) { Client.prototype.setInterval = function (intervalMs) { var self = this self._intervalMs = intervalMs - + self._trackers.forEach(function (tracker) { tracker.setInterval(intervalMs) }) @@ -522,27 +521,27 @@ Server.prototype._onHttpRequest = function (req, res) { var warning var s = req.url.split('?') + var params = querystring.parse(s[1]) + + // TODO: detect when required params are missing + // TODO: support multiple info_hash parameters as a concatenation of individual requests + var infoHash = bytewiseDecodeURIComponent(params.info_hash).toString('hex') + + if (!infoHash) { + return error('bittorrent-tracker server only supports announcing one torrent at a time') + } if (s[0] === '/announce') { - var params = querystring.parse(s[1]) - var ip = self._trustProxy ? req.headers['x-forwarded-for'] || req.connection.remoteAddress : req.connection.remoteAddress var port = Number(params.port) var addr = ip + ':' + port - - // TODO: support multiple info_hash parameters as a concatenation of individual requests - var infoHash = bytewiseDecodeURIComponent(params.info_hash).toString('hex') var peerId = bytewiseDecodeURIComponent(params.peer_id).toString('utf8') - - if (!infoHash) { - return error('bittorrent-tracker server only supports announcing one torrent at a time') - } - + var swarm = self._getSwarm(infoHash) var peer = swarm.peers[addr] - + switch (params.event) { case 'started': if (peer) { @@ -626,17 +625,11 @@ Server.prototype._onHttpRequest = function (req, res) { } res.end(bncode.encode(response)) + } else if (s[0] === '/scrape') { // unofficial scrape message - var params = querystring.parse(s[1]) - var infoHash = bytewiseDecodeURIComponent(params.info_hash).toString('hex') - - if (!infoHash) { - return error('bittorrent-tracker server only supports scraping one torrent at a time') - } - var swarm = self._getSwarm(infoHash) var response = { files : { } } - + response.files[params.info_hash] = { complete: swarm.complete, incomplete: swarm.incomplete, @@ -645,7 +638,7 @@ Server.prototype._onHttpRequest = function (req, res) { min_request_interval: self._interval } } - + res.end(bncode.encode(response)) } } @@ -660,7 +653,7 @@ Server.prototype._getSwarm = function (infoHash) { peers: {} } } - + return swarm } @@ -710,7 +703,9 @@ function toUInt32 (n) { function toUInt64 (n) { if (n > MAX_UINT || typeof n === 'string') { var bytes = bn(n).toArray() - while (bytes.length < 8) bytes.unshift(0) + while (bytes.length < 8) { + bytes.unshift(0) + } return new Buffer(bytes) } return Buffer.concat([toUInt32(0), toUInt32(n)]) diff --git a/test/client.js b/test/client.js index f38431d..43b910e 100644 --- a/test/client.js +++ b/test/client.js @@ -98,7 +98,7 @@ test('client.scrape()', function (t) { t.equal(typeof data.incomplete, 'number') t.equal(typeof data.downloaded, 'number') }) - + client.scrape() }) diff --git a/test/server.js b/test/server.js index 75938e4..5270ca8 100644 --- a/test/server.js +++ b/test/server.js @@ -67,12 +67,15 @@ test('server', function (t) { t.equal(data.incomplete, 0) client.scrape() + client.once('scrape', function (data) { t.equal(data.announce, announceUrl) t.equal(typeof data.complete, 'number') t.equal(typeof data.incomplete, 'number') t.equal(typeof data.downloaded, 'number') + client.stop() + client.once('update', function (data) { t.equal(data.announce, announceUrl) t.equal(data.complete, 0) @@ -80,8 +83,6 @@ test('server', function (t) { server.close() }) - - client.stop() }) }) }) From d0c1caf83b52ce6acce1576b5890cfc8d6e7e519 Mon Sep 17 00:00:00 2001 From: Feross Aboukhadijeh Date: Sun, 11 May 2014 17:33:28 -0700 Subject: [PATCH 4/4] 0.6.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 3605b71..549fcfe 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "bittorrent-tracker", "description": "Simple, robust, BitTorrent tracker (client & server) implementation", - "version": "0.5.1", + "version": "0.6.0", "author": { "name": "Feross Aboukhadijeh", "email": "feross@feross.org",