initial commit

This commit is contained in:
Kegan Myers 2020-04-12 16:33:32 -05:00
commit 0e5d0e7596
22 changed files with 1417 additions and 0 deletions

6
.dockerignore Normal file
View File

@ -0,0 +1,6 @@
.dockerignore
.gitignore
Dockerfile
node_modules
*.log
README.md

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
node_modules
*.log

9
Dockerfile Normal file
View File

@ -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

21
LICENSE Normal file
View File

@ -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.

22
README.md Normal file
View File

@ -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.

40
index.js Normal file
View File

@ -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);

View File

@ -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,
});
},
};

View File

@ -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`,
);
},
);

36
lib/dht/index.js Normal file
View File

@ -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;

20
lib/fakePeerId/index.js Normal file
View File

@ -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);
};

23
lib/log/index.js Normal file
View File

@ -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 };

View File

@ -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);
};

View File

@ -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);
};

View File

@ -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;
};

View File

@ -0,0 +1,3 @@
module.exports = (peers, res) => {
const response = res.json(peers);
};

23
package.json Normal file
View File

@ -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 <kegan@keganmyers.com>",
"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"
}
}

87
routes/fetch.js Normal file
View File

@ -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;
};

9
routes/index.js Normal file
View File

@ -0,0 +1,9 @@
const Router = require('express-promise-router');
module.exports = (params) => {
const app = Router();
app.use('/fetch', require('./fetch')(params));
return app;
};

BIN
static/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 318 B

400
static/index.css Normal file
View File

@ -0,0 +1,400 @@
/* github-markdown-css | Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (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;
}

93
static/index.html Normal file
View File

@ -0,0 +1,93 @@
<!DOCTYPE html>
<html>
<head>
<title>Fetch Magnet</title>
<link rel="shortcut icon" type="image/x-icon" href="/favicon.ico" />
<link rel="stylesheet" type="text/css" href="/index.css" />
<style type="text/css">
body {
max-width: 76rem;
margin: 0 auto;
padding: 1rem;
}
body input {
width: 100%;
margin: 0.5rem 0;
padding-left: 0.3rem;
padding-right: 0.3rem;
}
</style>
</head>
<body>
<h1>About</h1>
<p>
<a href="https://www.bittorrent.org/beps/bep_0005.html">DHT</a> 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.
</p>
<h1>Usage</h1>
<p>
You will most likely want to add
<code>https://dht.terrible.network/fetch</code> to the list of trackers in
your torrent client.
</p>
<p>
In addition to supporting "compact" representation
(<code>?compact=1</code>) this service also supports "json" representation
(<code>?json=1</code>) for ease of use in code.
</p>
<h1>FAQ</h1>
<h2>So is this just a BitTorrent tracker?</h2>
<p>
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.
</p>
<h2>Will this announce me to the DHT?</h2>
<p>
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.
</p>
<h1>Support</h1>
<p>
This service is provided for free by the autor of this software,
<a href="https://terribleplan.com/">Kegan Myers</a>, you can support him
via <a href="https://www.patreon.com/terribleplan">Patreon</a>.
</p>
<h1>Open Source</h1>
<p>
The source that runs this service is available on
<a href="https://git.keganmyers.com/terribleplan/fetch-dht">
my gitea server
</a>
and is open source under an MIT license.
</p>
<h1>Legal</h1>
<ul>
<li>
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.
</li>
<li>
This service respects your privacy, no identifying information is
collected, shared, or stored.
</li>
<li>
Access to this service may be revoked at any time for any reason without
warning.
</li>
<li>
Piracy is illegal. The end user of this service bears all responsibility
for what is done with the data returned by this service.
</li>
<li>
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.
</li>
</ul>
</body>
</html>

467
yarn.lock Normal file
View File

@ -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"