commit 0e5d0e7596f3037ecbd88587ade4069568c69752 Author: Kegan Myers Date: Sun Apr 12 16:33:32 2020 -0500 initial commit diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..919b0b7 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,6 @@ +.dockerignore +.gitignore +Dockerfile +node_modules +*.log +README.md \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ab05030 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +node_modules +*.log \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..3d6bb33 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,9 @@ +FROM node:12-alpine as base +WORKDIR /app +FROM base as packages +ADD package.json yarn.lock /app/ +RUN yarn --production --frozen-lockfile +FROM base as final +COPY --from="packages" /app/node_modules /app/node_modules +ADD . /app +CMD node index.js diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..33e7489 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 Kegan Myers + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..15b6749 --- /dev/null +++ b/README.md @@ -0,0 +1,22 @@ +# fetch-dht + +fetch-dht is a server that fetches peers from the [DHT](https://www.bittorrent.org/beps/bep_0005.html) swarm and serves them, likely to a bittorrent client performing an "announce". + +# fetch-dht server list + +- [dht.terrible.network](https://dht.terrible.network) - hosted by author + +# License + +[MIT](./LICENSE) + +# Contributing + +This gitea instance is currently not open for self registration. If you [email me](mailto:kegan@keganmyers.com) I will create you an account, after which you can use your account to make a pull request. + +Please note that by contributing to this repository you are: + +- Agreeing to publish your code under the MIT license +- Confirming that you have the right (copyright, patent, etc.) to release your contribution under an MIT license + +No PRs with modified licenses will be accepted or reviewed. diff --git a/index.js b/index.js new file mode 100644 index 0000000..c0bc192 --- /dev/null +++ b/index.js @@ -0,0 +1,40 @@ +const express = require('express'); +const path = require('path'); +const serveStatic = require('serve-static'); + +require('dotenv').config(); + +const DHT = require('./lib/dht'); +const log = require('./lib/log'); + +const config = process.env; + +const dht = new DHT(config.DHT_PORT); +const app = express(); + +app.use(log.middleware); +app.use(require('./routes')({ dht })); +app.use( + serveStatic(path.resolve(__dirname, 'static'), { + acceptRanges: false, + dotfiles: 'ignore', + etag: true, + extensions: false, + fallthrough: true, + immutable: false, + index: 'index.html', + lastModified: false, + maxAge: '1d', + redirect: false, + }), +); +app.use(({ log }, res) => { + log({ status: 'noRoute' }); + res.status(404).end(); +}); +app.use((e, { log }, res, next) => { + log({ status: 'routerError', error: e.message, stack: e.stack }); + res.status(500).end(); +}); + +app.listen(config.PORT || 3000); diff --git a/lib/binaryQuerystring/index.js b/lib/binaryQuerystring/index.js new file mode 100644 index 0000000..6a2c3ef --- /dev/null +++ b/lib/binaryQuerystring/index.js @@ -0,0 +1,59 @@ +const qs = require('querystring'); + +const spliceString = (string, index, removals = 0, insert = '') => { + return ( + string.slice(0, index) + insert + string.slice(index + Math.abs(removals)) + ); +}; + +const DBUC_REGEX = /^[0-9a-fA-F]{2}$/; +const decodeComponent = (string) => { + let percentIndex; + while ((percentIndex = string.indexOf('%')) !== -1) { + const encodedByte = string.substr(percentIndex + 1, 2); + if (!DBUC_REGEX.test(encodedByte)) { + throw new Error('Invalid input (not URI encoded?)'); + } + string = spliceString( + string, + percentIndex, + 3, + String.fromCharCode(parseInt(encodedByte, 16)), + ); + } + return string; +}; + +const parse = (query, maxKeys = 12) => { + return qs.parse(query, '&', '=', { + decodeURIComponent: decodeComponent, + maxKeys, + }); +}; + +const fromUrl = (url, max) => { + const splitUrl = url.split('?'); + if (splitUrl.length > 2) { + throw new Error('Invalid URL'); + } + if (splitUrl.length === 1) { + return {}; + } + return parse(splitUrl[1]); +}; + +module.exports = { + decodeURIComponent: decodeComponent, + decodeUriComponent: decodeComponent, + parse, + fromUrl, + __test: (cb) => { + cb({ + spliceString, + DBUC_REGEX, + decodeComponent, + parse, + fromUrl, + }); + }, +}; diff --git a/lib/binaryQuerystring/index.test.js b/lib/binaryQuerystring/index.test.js new file mode 100644 index 0000000..651940b --- /dev/null +++ b/lib/binaryQuerystring/index.test.js @@ -0,0 +1,46 @@ +const assert = require('assert'); + +require('./index.js').__test( + ({ spliceString, DBUC_REGEX, decodeComponent, parse, fromUrl }) => { + assert.strictEqual( + spliceString('abcdefg', 2, 3, 'HELLO'), + 'abHELLOfg', + 'splice string not working as intended (0)', + ); + assert.strictEqual( + spliceString('abcd', 2, 0, 'HELLO'), + 'abHELLOcd', + 'splice string not working as intended (1)', + ); + assert.strictEqual( + spliceString('abcd', 2, -2, 'HELLO'), + 'abHELLO', + 'splice string not working as intended (2)', + ); + + for (const [input, expected] of [ + [ + '%28m.%5BO%83i%85S%283j%C1%26%3A%E0%2Az%60%D5', + '(m.[O\u0083i\u0085S(3j\u00C1&\u003A\u00E0\u002Az`\u00D5', + ], + [ + '(m.%5bO%83i%85S(3j%c1%26%3a%e0*z%60%d5', + '(m.[O\u0083i\u0085S(3j\u00C1&\u003A\u00E0\u002Az`\u00D5', + ], + ]) { + const actual = decodeComponent(input); + assert.strictEqual( + actual, + expected, + `decodeComponent failed on input '${input}'`, + ); + } + + // expect other tests to validate deeper functionality + assert.deepEqual( + fromUrl('http://example.com/foo?bar=hi'), + { bar: 'hi' }, + `parseBinaryQuerystring failed`, + ); + }, +); diff --git a/lib/dht/index.js b/lib/dht/index.js new file mode 100644 index 0000000..701a536 --- /dev/null +++ b/lib/dht/index.js @@ -0,0 +1,36 @@ +const DHT = require('bittorrent-dht'); + +class DhtWrapper { + constructor(DHT_PORT = 20000) { + this._peerMap = new Map(); + this._port = DHT_PORT; + + this._dht = new DHT(); + this._dht.listen(this._port); + this._dht.on('peer', ({ host, port }, infoHash) => { + const hexHash = Buffer.from(infoHash).toString('hex'); + if (!this._peerMap.has(hexHash)) { + return; + } + this._peerMap.get(hexHash).add(`${host}:${port}`); + }); + } + async lookup(infoHash) { + const peers = new Set(); + this._peerMap.set(infoHash, peers); + return await new Promise((resolve, reject) => { + this._dht.lookup(infoHash, (e) => { + if (!!e) { + reject(e); + return; + } + if (this._peerMap.get(infoHash) === peers) { + this._peerMap.delete(infoHash); + } + resolve(Array.from(peers)); + }); + }); + } +} + +module.exports = DhtWrapper; diff --git a/lib/fakePeerId/index.js b/lib/fakePeerId/index.js new file mode 100644 index 0000000..968eb72 --- /dev/null +++ b/lib/fakePeerId/index.js @@ -0,0 +1,20 @@ +const crypto = require('crypto'); + +const memo = new Map(); + +const fakePeerId = (addr) => { + const hash = crypto.createHash('sha1'); + hash.update(addr); + return hash.digest('hex').slice(0, 20); +}; + +// not super expensive, but probably worth memoizing +module.exports = (addr) => { + if (memo.size >= 300000) { + memo.clear(); + } + if (!memo.has(addr)) { + memo.set(addr, fakePeerId(addr)); + } + return memo.get(addr); +}; diff --git a/lib/log/index.js b/lib/log/index.js new file mode 100644 index 0000000..a81c31d --- /dev/null +++ b/lib/log/index.js @@ -0,0 +1,23 @@ +const uuid = require('uuid'); + +module.exports = {}; + +const log = ({ time, ...args }) => { + console.log( + JSON.stringify({ time: new Date().toISOString(), requestId, ...args }), + ); +}; + +const traced = (requestId) => ({ requestId: _, ...args }) => + log({ requestId, ...args }); + +const middleware = (req, res, next) => { + // generate (or use existing) requestId + req.requestId = req.requestId || uuid.v4(); + // set a tracing logger use elsewhere + req.log = traced(req.requestId); + req.log({ status: 'new' }); + next(); +}; + +module.exports = { log, traced, middleware }; diff --git a/lib/peerListFormatters/bencoded.js b/lib/peerListFormatters/bencoded.js new file mode 100644 index 0000000..ad57472 --- /dev/null +++ b/lib/peerListFormatters/bencoded.js @@ -0,0 +1,23 @@ +const addrToIPPort = require('addr-to-ip-port'); +const bencode = require('bencode'); + +const fakePeerId = require('../fakePeerId'); + +module.exports = (peers, res) => { + const response = bencode.encode({ + interval: 30 * 60, // 30 minutes between requests + peers: peers.map((peer) => { + const [ip, port] = addrToIPPort(peer); + return { + ip, + 'peer id': fakePeerId(peer), + port, + }; + }), + }); + res + .set({ + 'Content-Length': response.length, + }) + .end(response); +}; diff --git a/lib/peerListFormatters/compact.js b/lib/peerListFormatters/compact.js new file mode 100644 index 0000000..7dee9c8 --- /dev/null +++ b/lib/peerListFormatters/compact.js @@ -0,0 +1,14 @@ +const bencode = require('bencode'); +const string2compact = require('string2compact'); + +module.exports = (peers, res) => { + const response = bencode.encode({ + interval: 30 * 60, // 30 minutes between requests + peers: string2compact(peers), + }); + res + .set({ + 'Content-Length': response.length, + }) + .end(response); +}; diff --git a/lib/peerListFormatters/index.js b/lib/peerListFormatters/index.js new file mode 100644 index 0000000..c00dbd0 --- /dev/null +++ b/lib/peerListFormatters/index.js @@ -0,0 +1,14 @@ +const formatters = new Map(); +const DEFAULT = 'bencoded'; + +formatters.set('bencoded', require('./bencoded')); +formatters.set('json', require('./json')); +formatters.set('compact', require('./compact')); + +module.exports = (peers, res, format = DEFAULT) => { + if (!formatters.has(format)) { + format = DEFAULT; + } + formatters.get(format)(peers, res); + return format; +}; diff --git a/lib/peerListFormatters/json.js b/lib/peerListFormatters/json.js new file mode 100644 index 0000000..2524aa4 --- /dev/null +++ b/lib/peerListFormatters/json.js @@ -0,0 +1,3 @@ +module.exports = (peers, res) => { + const response = res.json(peers); +}; diff --git a/package.json b/package.json new file mode 100644 index 0000000..4fab199 --- /dev/null +++ b/package.json @@ -0,0 +1,23 @@ +{ + "name": "@terribleplan/dht-peers", + "version": "0.0.1", + "description": "A gateway for querying the DHT", + "main": "index.js", + "scripts": { + "start": "node ." + }, + "author": "Kegan Myers ", + "license": "MIT", + "private": true, + "dependencies": { + "addr-to-ip-port": "^1.5.1", + "bencode": "^2.0.1", + "bittorrent-dht": "^9.0.3", + "dotenv": "^8.2.0", + "express": "^4.17.1", + "express-promise-router": "^3.0.3", + "serve-static": "^1.14.1", + "string2compact": "^1.3.0", + "uuid": "^7.0.3" + } +} diff --git a/routes/fetch.js b/routes/fetch.js new file mode 100644 index 0000000..9706843 --- /dev/null +++ b/routes/fetch.js @@ -0,0 +1,87 @@ +const Router = require('express-promise-router'); +const bencode = require('bencode'); +const qs = require('querystring'); + +const peerListFormatters = require('../lib/peerListFormatters'); +const bqs = require('../lib/binaryQuerystring'); + +const INFO_HASH_REGEX = new RegExp('^[0-9a-f]{40}$', 'i'); + +module.exports = ({ dht }) => { + const app = Router(); + app.get( + '/', + async ( + { + log, + originalUrl, + query: { info_hash: infoHash, compact, json, event = '' }, + }, + res, + ) => { + // worst case if every character is percent encoded, or best case if every _is_ decoded + if (!infoHash || infoHash.length > 60 || infoHash < 20) { + res + .status(400) + .end(bencode.encode({ 'failure reason': 'invalid arguments' })); + return; + } + + if (infoHash.length !== 20) { + infoHash = bqs.fromUrl(originalUrl).info_hash; + + if (!infoHash || infoHash.length !== 20) { + res + .status(400) + .end(bencode.encode({ 'failure reason': 'invalid arguments' })); + log({ status: 'refused', type: 'dhtGateway' }); + return; + } + } + + infoHash = Buffer.from(infoHash, 'latin1').toString('hex'); + if (!infoHash.match(INFO_HASH_REGEX)) { + res + .status(400) + .end(bencode.encode({ 'failure reason': 'invalid arguments' })); + log({ status: 'refused', type: 'dhtGateway' }); + return; + } + + let format = 'bencoded'; + if (compact === '1') { + format = 'compact'; + } else if (json === '1') { + format = 'json'; + } + + try { + let peers = []; + // a completed or stopped torrent does not need peers + if (event !== 'completed' && event !== 'stopped') { + peers = await dht.lookup(infoHash); + } + // this sends the response as well as formatting it + peerListFormatters(peers, res, format); + log({ + status: 'served', + type: 'dhtGateway', + format, + count: peers.length, + }); + } catch (e) { + res + .status(500) + .end(bencode.encode({ 'failure reason': 'internal error' })); + log({ + status: 'failed', + type: 'dhtGateway', + format, + error: e.message, + stack: e.stack, + }); + } + }, + ); + return app; +}; diff --git a/routes/index.js b/routes/index.js new file mode 100644 index 0000000..7b20d17 --- /dev/null +++ b/routes/index.js @@ -0,0 +1,9 @@ +const Router = require('express-promise-router'); + +module.exports = (params) => { + const app = Router(); + + app.use('/fetch', require('./fetch')(params)); + + return app; +}; diff --git a/static/favicon.ico b/static/favicon.ico new file mode 100644 index 0000000..2257b57 Binary files /dev/null and b/static/favicon.ico differ diff --git a/static/index.css b/static/index.css new file mode 100644 index 0000000..6dd4a0f --- /dev/null +++ b/static/index.css @@ -0,0 +1,400 @@ +/* github-markdown-css | Copyright (c) Sindre Sorhus (https://sindresorhus.com) | MIT | https://raw.githubusercontent.com/sindresorhus/github-markdown-css/1485dd78f5e744ef36e946e5ae44838e3906f9d8/license */ +body { + -ms-text-size-adjust: 100%; + -webkit-text-size-adjust: 100%; + line-height: 1.5; + color: #24292e; + font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Helvetica, Arial, + sans-serif, Apple Color Emoji, Segoe UI Emoji; + font-size: 16px; + line-height: 1.5; + word-wrap: break-word; +} +body details { + display: block; +} +body summary { + display: list-item; +} +body a { + background-color: initial; +} +body a:active, +body a:hover { + outline-width: 0; +} +body strong { + font-weight: inherit; + font-weight: bolder; +} +body h1 { + font-size: 2em; + margin: 0.67em 0; +} +body img { + border-style: none; +} +body code, +body kbd, +body pre { + font-family: monospace, monospace; + font-size: 1em; +} +body hr { + box-sizing: initial; + height: 0; + overflow: visible; +} +body input { + font: inherit; + margin: 0; +} +body input { + overflow: visible; +} +body [type='checkbox'] { + box-sizing: border-box; + padding: 0; +} +body * { + box-sizing: border-box; +} +body input { + font-family: inherit; + font-size: inherit; + line-height: inherit; +} +body a { + color: #0366d6; + text-decoration: none; +} +body a:hover { + text-decoration: underline; +} +body strong { + font-weight: 600; +} +body hr { + height: 0; + margin: 15px 0; + overflow: hidden; + background: 0 0; + border: 0; + border-bottom: 1px solid #dfe2e5; +} +body hr:after, +body hr:before { + display: table; + content: ''; +} +body hr:after { + clear: both; +} +body table { + border-spacing: 0; + border-collapse: collapse; +} +body td, +body th { + padding: 0; +} +body details summary { + cursor: pointer; +} +body kbd { + display: inline-block; + padding: 3px 5px; + font: 11px SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace; + line-height: 10px; + color: #444d56; + vertical-align: middle; + background-color: #fafbfc; + border: 1px solid #d1d5da; + border-radius: 3px; + box-shadow: inset 0 -1px 0 #d1d5da; +} +body h1, +body h2, +body h3, +body h4, +body h5, +body h6 { + margin-top: 0; + margin-bottom: 0; +} +body h1 { + font-size: 32px; +} +body h1, +body h2 { + font-weight: 600; +} +body h2 { + font-size: 24px; +} +body h3 { + font-size: 20px; +} +body h3, +body h4 { + font-weight: 600; +} +body h4 { + font-size: 16px; +} +body h5 { + font-size: 14px; +} +body h5, +body h6 { + font-weight: 600; +} +body h6 { + font-size: 12px; +} +body p { + margin-top: 0; + margin-bottom: 10px; +} +body blockquote { + margin: 0; +} +body ol, +body ul { + padding-left: 0; + margin-top: 0; + margin-bottom: 0; +} +body ol ol, +body ul ol { + list-style-type: lower-roman; +} +body ol ol ol, +body ol ul ol, +body ul ol ol, +body ul ul ol { + list-style-type: lower-alpha; +} +body dd { + margin-left: 0; +} +body code, +body pre { + font-family: SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace; + font-size: 12px; +} +body pre { + margin-top: 0; + margin-bottom: 0; +} +body input::-webkit-inner-spin-button, +body input::-webkit-outer-spin-button { + margin: 0; + -webkit-appearance: none; + appearance: none; +} +body :checked + .radio-label { + position: relative; + z-index: 1; + border-color: #0366d6; +} +body hr { + border-bottom-color: #eee; +} +body kbd { + display: inline-block; + padding: 3px 5px; + font: 11px SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace; + line-height: 10px; + color: #444d56; + vertical-align: middle; + background-color: #fafbfc; + border: 1px solid #d1d5da; + border-radius: 3px; + box-shadow: inset 0 -1px 0 #d1d5da; +} +body:after, +body:before { + display: table; + content: ''; +} +body:after { + clear: both; +} +body > :first-child { + margin-top: 0 !important; +} +body > :last-child { + margin-bottom: 0 !important; +} +body a:not([href]) { + color: inherit; + text-decoration: none; +} +body blockquote, +body details, +body dl, +body ol, +body p, +body pre, +body table, +body ul { + margin-top: 0; + margin-bottom: 16px; +} +body hr { + height: 0.25em; + padding: 0; + margin: 24px 0; + background-color: #e1e4e8; + border: 0; +} +body blockquote { + padding: 0 1em; + color: #6a737d; + border-left: 0.25em solid #dfe2e5; +} +body blockquote > :first-child { + margin-top: 0; +} +body blockquote > :last-child { + margin-bottom: 0; +} +body h1, +body h2, +body h3, +body h4, +body h5, +body h6 { + margin-top: 24px; + margin-bottom: 16px; + font-weight: 600; + line-height: 1.25; +} +body h1 { + font-size: 2em; +} +body h1, +body h2 { + padding-bottom: 0.3em; + border-bottom: 1px solid #eaecef; +} +body h2 { + font-size: 1.5em; +} +body h3 { + font-size: 1.25em; +} +body h4 { + font-size: 1em; +} +body h5 { + font-size: 0.875em; +} +body h6 { + font-size: 0.85em; + color: #6a737d; +} +body ol, +body ul { + padding-left: 2em; +} +body ol ol, +body ol ul, +body ul ol, +body ul ul { + margin-top: 0; + margin-bottom: 0; +} +body li { + word-wrap: break-all; +} +body li > p { + margin-top: 16px; +} +body li + li { + margin-top: 0.25em; +} +body dl { + padding: 0; +} +body dl dt { + padding: 0; + margin-top: 16px; + font-size: 1em; + font-style: italic; + font-weight: 600; +} +body dl dd { + padding: 0 16px; + margin-bottom: 16px; +} +body table { + display: block; + width: 100%; + overflow: auto; +} +body table th { + font-weight: 600; +} +body table td, +body table th { + padding: 6px 13px; + border: 1px solid #dfe2e5; +} +body table tr { + background-color: #fff; + border-top: 1px solid #c6cbd1; +} +body table tr:nth-child(2n) { + background-color: #f6f8fa; +} +body img { + max-width: 100%; + box-sizing: initial; + background-color: #fff; +} +body img[align='right'] { + padding-left: 20px; +} +body img[align='left'] { + padding-right: 20px; +} +body code { + padding: 0.2em 0.4em; + margin: 0; + font-size: 85%; + background-color: rgba(27, 31, 35, 0.05); + border-radius: 3px; +} +body pre { + word-wrap: normal; +} +body pre > code { + padding: 0; + margin: 0; + font-size: 100%; + word-break: normal; + white-space: pre; + background: 0 0; + border: 0; +} +body pre { + padding: 16px; + overflow: auto; + font-size: 85%; + line-height: 1.45; + background-color: #f6f8fa; + border-radius: 3px; +} +body pre code { + display: inline; + max-width: auto; + padding: 0; + margin: 0; + overflow: visible; + line-height: inherit; + word-wrap: normal; + background-color: initial; + border: 0; +} diff --git a/static/index.html b/static/index.html new file mode 100644 index 0000000..b6f1def --- /dev/null +++ b/static/index.html @@ -0,0 +1,93 @@ + + + + Fetch Magnet + + + + + +

About

+

+ DHT is a + powerful technology that enables "trackerless" torrents; however, there + are a number of reasons why a client may be unable to join the DHT swarm + such as local or ISP blocking. This server acts as a gateway to discover + clients that have announced to the DHT network, and provides a + tracker-compatible interface for ease of use. +

+

Usage

+

+ You will most likely want to add + https://dht.terrible.network/fetch to the list of trackers in + your torrent client. +

+

+ In addition to supporting "compact" representation + (?compact=1) this service also supports "json" representation + (?json=1) for ease of use in code. +

+

FAQ

+

So is this just a BitTorrent tracker?

+

+ No. This server does not collect/store the information necessary to act as + a tracker (IP address, "port" parameter), it does not serve anything other + than information queried from the DHT. +

+

Will this announce me to the DHT?

+

+ No. This server does not collect the information necessary to do so (IP + address), and it is technologically impossible to announce an IP other + than one owned by the machine speaking to the DHT swarm. +

+

Support

+

+ This service is provided for free by the autor of this software, + Kegan Myers, you can support him + via Patreon. +

+

Open Source

+

+ The source that runs this service is available on + + my gitea server + + and is open source under an MIT license. +

+

Legal

+
    +
  • + This service is provided as-is on a best-effort basis. No warranty is or + shall be issued for the use of or reliance upon this service. +
  • +
  • + This service respects your privacy, no identifying information is + collected, shared, or stored. +
  • +
  • + Access to this service may be revoked at any time for any reason without + warning. +
  • +
  • + Piracy is illegal. The end user of this service bears all responsibility + for what is done with the data returned by this service. +
  • +
  • + This service does not host any content, it is a gateway that finds peers + in the distributed hash table and serves a list of them. +
  • +
+ + diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 0000000..39a76c0 --- /dev/null +++ b/yarn.lock @@ -0,0 +1,467 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +accepts@~1.3.7: + version "1.3.7" + resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.7.tgz#531bc726517a3b2b41f850021c6cc15eaab507cd" + dependencies: + mime-types "~2.1.24" + negotiator "0.6.2" + +addr-to-ip-port@^1.0.1, addr-to-ip-port@^1.5.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/addr-to-ip-port/-/addr-to-ip-port-1.5.1.tgz#bfada13fd6aeeeac19f1e9f7d84b4bbab45e5208" + +array-flatten@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" + +bencode@^2.0.0, bencode@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/bencode/-/bencode-2.0.1.tgz#667a6a31c5e038d558608333da6b7c94e836c85b" + dependencies: + safe-buffer "^5.1.1" + +bittorrent-dht@^9.0.3: + version "9.0.3" + resolved "https://registry.yarnpkg.com/bittorrent-dht/-/bittorrent-dht-9.0.3.tgz#bdcac9383bdc5e2a459eef6418332f3ae182d63f" + dependencies: + bencode "^2.0.0" + debug "^4.1.1" + inherits "^2.0.1" + k-bucket "^5.0.0" + k-rpc "^5.0.0" + last-one-wins "^1.0.4" + lru "^3.1.0" + randombytes "^2.0.5" + record-cache "^1.0.2" + simple-sha1 "^3.0.0" + +body-parser@1.19.0: + version "1.19.0" + resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.0.tgz#96b2709e57c9c4e09a6fd66a8fd979844f69f08a" + dependencies: + bytes "3.1.0" + content-type "~1.0.4" + debug "2.6.9" + depd "~1.1.2" + http-errors "1.7.2" + iconv-lite "0.4.24" + on-finished "~2.3.0" + qs "6.7.0" + raw-body "2.4.0" + type-is "~1.6.17" + +bytes@3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6" + +chrome-dgram@^3.0.2: + version "3.0.4" + resolved "https://registry.yarnpkg.com/chrome-dgram/-/chrome-dgram-3.0.4.tgz#aa785f23d1fc71c8619e8af166db7b9dc21a4f3e" + dependencies: + inherits "^2.0.1" + run-series "^1.1.2" + +chrome-dns@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/chrome-dns/-/chrome-dns-1.0.1.tgz#6870af680a40d2c4b2efc2154a378793f5a4ce4b" + dependencies: + chrome-net "^3.3.2" + +chrome-net@^3.3.2: + version "3.3.3" + resolved "https://registry.yarnpkg.com/chrome-net/-/chrome-net-3.3.3.tgz#09b40337d97fa857ac44ee9a2d82a66e43863401" + dependencies: + inherits "^2.0.1" + +content-disposition@0.5.3: + version "0.5.3" + resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.3.tgz#e130caf7e7279087c5616c2007d0485698984fbd" + dependencies: + safe-buffer "5.1.2" + +content-type@~1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" + +cookie-signature@1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" + +cookie@0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.0.tgz#beb437e7022b3b6d49019d088665303ebe9c14ba" + +debug@2.6.9: + version "2.6.9" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" + dependencies: + ms "2.0.0" + +debug@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791" + dependencies: + ms "^2.1.1" + +depd@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" + +destroy@~1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80" + +dotenv@^8.2.0: + version "8.2.0" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-8.2.0.tgz#97e619259ada750eea3e4ea3e26bceea5424b16a" + +ee-first@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" + +encodeurl@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" + +escape-html@~1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" + +etag@~1.8.1: + version "1.8.1" + resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" + +express-promise-router@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/express-promise-router/-/express-promise-router-3.0.3.tgz#5e6d22a5a3f013d71833172fe8d7ab780c3f6b70" + dependencies: + is-promise "^2.1.0" + lodash.flattendeep "^4.0.0" + methods "^1.0.0" + +express@^4.17.1: + version "4.17.1" + resolved "https://registry.yarnpkg.com/express/-/express-4.17.1.tgz#4491fc38605cf51f8629d39c2b5d026f98a4c134" + dependencies: + accepts "~1.3.7" + array-flatten "1.1.1" + body-parser "1.19.0" + content-disposition "0.5.3" + content-type "~1.0.4" + cookie "0.4.0" + cookie-signature "1.0.6" + debug "2.6.9" + depd "~1.1.2" + encodeurl "~1.0.2" + escape-html "~1.0.3" + etag "~1.8.1" + finalhandler "~1.1.2" + fresh "0.5.2" + merge-descriptors "1.0.1" + methods "~1.1.2" + on-finished "~2.3.0" + parseurl "~1.3.3" + path-to-regexp "0.1.7" + proxy-addr "~2.0.5" + qs "6.7.0" + range-parser "~1.2.1" + safe-buffer "5.1.2" + send "0.17.1" + serve-static "1.14.1" + setprototypeof "1.1.1" + statuses "~1.5.0" + type-is "~1.6.18" + utils-merge "1.0.1" + vary "~1.1.2" + +finalhandler@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.2.tgz#b7e7d000ffd11938d0fdb053506f6ebabe9f587d" + dependencies: + debug "2.6.9" + encodeurl "~1.0.2" + escape-html "~1.0.3" + on-finished "~2.3.0" + parseurl "~1.3.3" + statuses "~1.5.0" + unpipe "~1.0.0" + +forwarded@~0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84" + +fresh@0.5.2: + version "0.5.2" + resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" + +http-errors@1.7.2: + version "1.7.2" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.2.tgz#4f5029cf13239f31036e5b2e55292bcfbcc85c8f" + dependencies: + depd "~1.1.2" + inherits "2.0.3" + setprototypeof "1.1.1" + statuses ">= 1.5.0 < 2" + toidentifier "1.0.0" + +http-errors@~1.7.2: + version "1.7.3" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.3.tgz#6c619e4f9c60308c38519498c14fbb10aacebb06" + dependencies: + depd "~1.1.2" + inherits "2.0.4" + setprototypeof "1.1.1" + statuses ">= 1.5.0 < 2" + toidentifier "1.0.0" + +iconv-lite@0.4.24: + version "0.4.24" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" + dependencies: + safer-buffer ">= 2.1.2 < 3" + +inherits@2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" + +inherits@2.0.4, inherits@^2.0.1: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + +ipaddr.js@1.9.1, ipaddr.js@^1.0.1: + version "1.9.1" + resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" + +is-promise@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.1.0.tgz#79a2a9ece7f096e80f36d2b2f3bc16c1ff4bf3fa" + +k-bucket@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/k-bucket/-/k-bucket-5.0.0.tgz#ef7a401fcd4c37cd31dceaa6ae4440ca91055e01" + dependencies: + randombytes "^2.0.3" + +k-rpc-socket@^1.7.2: + version "1.11.1" + resolved "https://registry.yarnpkg.com/k-rpc-socket/-/k-rpc-socket-1.11.1.tgz#f14b4b240a716c6cad7b6434b21716dbd7c7b0e8" + dependencies: + bencode "^2.0.0" + chrome-dgram "^3.0.2" + chrome-dns "^1.0.0" + chrome-net "^3.3.2" + +k-rpc@^5.0.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/k-rpc/-/k-rpc-5.1.0.tgz#af2052de2e84994d55da3032175da5dad8640174" + dependencies: + k-bucket "^5.0.0" + k-rpc-socket "^1.7.2" + randombytes "^2.0.5" + +last-one-wins@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/last-one-wins/-/last-one-wins-1.0.4.tgz#c1bfd0cbcb46790ec9156b8d1aee8fcb86cda22a" + +lodash.flattendeep@^4.0.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz#fb030917f86a3134e5bc9bec0d69e0013ddfedb2" + +lru@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/lru/-/lru-3.1.0.tgz#ea7fb8546d83733396a13091d76cfeb4c06837d5" + dependencies: + inherits "^2.0.1" + +media-typer@0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" + +merge-descriptors@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" + +methods@^1.0.0, methods@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" + +mime-db@1.43.0: + version "1.43.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.43.0.tgz#0a12e0502650e473d735535050e7c8f4eb4fae58" + +mime-types@~2.1.24: + version "2.1.26" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.26.tgz#9c921fc09b7e149a65dfdc0da4d20997200b0a06" + dependencies: + mime-db "1.43.0" + +mime@1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" + +ms@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" + +ms@2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" + +ms@^2.1.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + +negotiator@0.6.2: + version "0.6.2" + resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb" + +on-finished@~2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" + dependencies: + ee-first "1.1.1" + +parseurl@~1.3.3: + version "1.3.3" + resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" + +path-to-regexp@0.1.7: + version "0.1.7" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" + +proxy-addr@~2.0.5: + version "2.0.6" + resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.6.tgz#fdc2336505447d3f2f2c638ed272caf614bbb2bf" + dependencies: + forwarded "~0.1.2" + ipaddr.js "1.9.1" + +qs@6.7.0: + version "6.7.0" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc" + +queue-microtask@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.1.2.tgz#139bf8186db0c545017ec66c2664ac646d5c571e" + +randombytes@^2.0.3, randombytes@^2.0.5: + version "2.1.0" + resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" + dependencies: + safe-buffer "^5.1.0" + +range-parser@~1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" + +raw-body@2.4.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.4.0.tgz#a1ce6fb9c9bc356ca52e89256ab59059e13d0332" + dependencies: + bytes "3.1.0" + http-errors "1.7.2" + iconv-lite "0.4.24" + unpipe "1.0.0" + +record-cache@^1.0.2: + version "1.1.0" + resolved "https://registry.yarnpkg.com/record-cache/-/record-cache-1.1.0.tgz#f8a467a691a469584b26e88d36b18afdb3932037" + +run-series@^1.1.2: + version "1.1.8" + resolved "https://registry.yarnpkg.com/run-series/-/run-series-1.1.8.tgz#2c4558f49221e01cd6371ff4e0a1e203e460fc36" + +rusha@^0.8.1: + version "0.8.13" + resolved "https://registry.yarnpkg.com/rusha/-/rusha-0.8.13.tgz#9a084e7b860b17bff3015b92c67a6a336191513a" + +safe-buffer@5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" + +safe-buffer@^5.1.0, safe-buffer@^5.1.1: + version "5.2.0" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.0.tgz#b74daec49b1148f88c64b68d49b1e815c1f2f519" + +"safer-buffer@>= 2.1.2 < 3": + version "2.1.2" + resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + +send@0.17.1: + version "0.17.1" + resolved "https://registry.yarnpkg.com/send/-/send-0.17.1.tgz#c1d8b059f7900f7466dd4938bdc44e11ddb376c8" + dependencies: + debug "2.6.9" + depd "~1.1.2" + destroy "~1.0.4" + encodeurl "~1.0.2" + escape-html "~1.0.3" + etag "~1.8.1" + fresh "0.5.2" + http-errors "~1.7.2" + mime "1.6.0" + ms "2.1.1" + on-finished "~2.3.0" + range-parser "~1.2.1" + statuses "~1.5.0" + +serve-static@1.14.1, serve-static@^1.14.1: + version "1.14.1" + resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.14.1.tgz#666e636dc4f010f7ef29970a88a674320898b2f9" + dependencies: + encodeurl "~1.0.2" + escape-html "~1.0.3" + parseurl "~1.3.3" + send "0.17.1" + +setprototypeof@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.1.tgz#7e95acb24aa92f5885e0abef5ba131330d4ae683" + +simple-sha1@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/simple-sha1/-/simple-sha1-3.0.1.tgz#b34c3c978d74ac4baf99b6555c1e6736e0d6e700" + dependencies: + queue-microtask "^1.1.2" + rusha "^0.8.1" + +"statuses@>= 1.5.0 < 2", statuses@~1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" + +string2compact@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/string2compact/-/string2compact-1.3.0.tgz#22d946127b082d1203c51316af60117a337423c3" + dependencies: + addr-to-ip-port "^1.0.1" + ipaddr.js "^1.0.1" + +toidentifier@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553" + +type-is@~1.6.17, type-is@~1.6.18: + version "1.6.18" + resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" + dependencies: + media-typer "0.3.0" + mime-types "~2.1.24" + +unpipe@1.0.0, unpipe@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" + +utils-merge@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" + +uuid@^7.0.3: + version "7.0.3" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-7.0.3.tgz#c5c9f2c8cf25dc0a372c4df1441c41f5bd0c680b" + +vary@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"