diff --git a/.env.sample b/.env.sample index 15c83e6..c4f17c7 100644 --- a/.env.sample +++ b/.env.sample @@ -3,8 +3,15 @@ STATIC_SERVE_FOLDER="data/static" LOG_FILE_REQUESTS="data/static/static_log.txt" DB_PATH="./data/db/db.db" RECENT_SEARCHES_FOLDER="./data/recent_searches" +RECENT_SEARCHES_LIMIT=20 VIDEO_FILES_FOLDERS='[ "/home/user/videos", "/mnt/hdd/videos" ]' -VIDEO_FILES_EXT='["*.mkv", "*.mp4", "*.avi"]' +VIDEO_FILES_EXT='[".mkv", ".mp4", ".avi"]' +PATTERNS_TO_IGNORE='["AOE1/"]' #file path patterns to ignore from video files scan +LINKOMANIJA_USERNAME=usern +LINKOMANIJA_PASSWORD=password +LINKOMANIJA_PASSKEY=some123long321passkey +TORRENT_DOWNLOAD_DIR='/mnt/hdd/videos' +TRANSMISSION_CLIENT_HOST=192.168.1.133 diff --git a/migrations/1672729641013-CreateFilesTable.ts b/migrations/1672729641013-CreateFilesTable.ts index 25803df..5406034 100644 --- a/migrations/1672729641013-CreateFilesTable.ts +++ b/migrations/1672729641013-CreateFilesTable.ts @@ -5,36 +5,44 @@ export class CreateFilesTable1672729641013 implements MigrationInterface { await queryRunner.query( `create table files ( - id INTEGER + id INTEGER primary key autoincrement, - path VARCHAR(512), - deleted TINYINT, - size INTEGER, - file_name VARCHAR(512), - infos TEXT, - info TEXT, - title_id INTEGER, - season INTEGER default NULL, - duration REAL, - transmission INTEGER default NULL, - lm INTEGER default NULL + path VARCHAR(512), + relative_path TEXT, + deleted TINYINT, + size INTEGER, + file_name VARCHAR(512), + infos TEXT, + info TEXT, + title_id INTEGER, + season INTEGER default NULL, + duration REAL, + transmission_id INTEGER default NULL, + linkomanija INTEGER default NULL, + progress REAL, + stream_provider TEXT default 'fs' not null );`, ); await queryRunner.query( - `create unique index path + `create unique index files_path on files (path);`, ); await queryRunner.query( - `create index season + `create index files_season on files (season);`, ); await queryRunner.query( - `create index title_id + `create index files_title_id on files (title_id);`, ); + + await queryRunner.query( + `create index files_relative_path + on files (relative_path);`, + ); } public async down(queryRunner: QueryRunner): Promise { diff --git a/migrations/1672730402752-CreateLrtCategoriesTable.ts b/migrations/1672730402752-CreateLrtCategoriesTable.ts index 1940853..680fd20 100644 --- a/migrations/1672730402752-CreateLrtCategoriesTable.ts +++ b/migrations/1672730402752-CreateLrtCategoriesTable.ts @@ -7,8 +7,11 @@ export class CreateLrtCategoriesTable1672730402752 await queryRunner.query( `create table lrt_categories ( - category_url TEXT, - category_id TEXT + category_url VARCHAR(256), + category_id INTEGER, + title VARCHAR(128), + last_access TIMESTAMP, + thumb VARCHAR(256) );`, ); @@ -16,6 +19,11 @@ export class CreateLrtCategoriesTable1672730402752 `create index lrt_categories_category_url_index on lrt_categories (category_url);`, ); + + await queryRunner.query( + `create index lrt_categories_category_id + on lrt_categories (category_id);`, + ); } public async down(queryRunner: QueryRunner): Promise { diff --git a/migrations/1672820632908-AddStreamProviderColumnToFilesTable.ts b/migrations/1672820632908-AddStreamProviderColumnToFilesTable.ts deleted file mode 100644 index d35e173..0000000 --- a/migrations/1672820632908-AddStreamProviderColumnToFilesTable.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { MigrationInterface, QueryRunner } from 'typeorm'; - -export class AddStreamProviderColumnToFilesTable1672820632908 - implements MigrationInterface -{ - public async up(queryRunner: QueryRunner): Promise { - await queryRunner.query( - `alter table files - add stream_provider TEXT default 'fs' not null;`, - ); - } - - public async down(queryRunner: QueryRunner): Promise { - await queryRunner.query( - `alter table files - drop column stream_provider;`, - ); - } -} diff --git a/migrations/1672990196772-AddRelativePathColumnToFilesTable.ts b/migrations/1672990196772-AddRelativePathColumnToFilesTable.ts deleted file mode 100644 index 9435aeb..0000000 --- a/migrations/1672990196772-AddRelativePathColumnToFilesTable.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { MigrationInterface, QueryRunner } from 'typeorm'; - -export class AddRelativePathColumnToFilesTable1672990196772 - implements MigrationInterface -{ - public async up(queryRunner: QueryRunner): Promise { - await queryRunner.query( - `alter table files - add relative_path TEXT;`, - ); - } - - public async down(queryRunner: QueryRunner): Promise { - await queryRunner.query( - `alter table files - drop column relative_path;`, - ); - } -} diff --git a/migrations/1673010469305-RenameLinkomanijaColumnInFilesTable.ts b/migrations/1673010469305-RenameLinkomanijaColumnInFilesTable.ts deleted file mode 100644 index 179c141..0000000 --- a/migrations/1673010469305-RenameLinkomanijaColumnInFilesTable.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { MigrationInterface, QueryRunner } from 'typeorm'; - -export class RenameLinkomanijaColumnInFilesTable1673010469305 - implements MigrationInterface -{ - public async up(queryRunner: QueryRunner): Promise { - await queryRunner.query( - `ALTER TABLE files RENAME COLUMN lm to linkomanija;`, - ); - } - - public async down(queryRunner: QueryRunner): Promise { - await queryRunner.query( - `ALTER TABLE files RENAME COLUMN linkomanija to lm;`, - ); - } -} diff --git a/migrations/1673010885172-AddWebtorrentColumnsToFilesTable.ts b/migrations/1673010885172-AddWebtorrentColumnsToFilesTable.ts deleted file mode 100644 index 467d005..0000000 --- a/migrations/1673010885172-AddWebtorrentColumnsToFilesTable.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { MigrationInterface, QueryRunner } from 'typeorm'; - -export class AddWebtorrentColumnsToFilesTable1673010885172 - implements MigrationInterface -{ - public async up(queryRunner: QueryRunner): Promise { - await queryRunner.query( - `alter table files - add webtorrent INTEGER;`, - ); - } - - public async down(queryRunner: QueryRunner): Promise { - await queryRunner.query( - `alter table files - drop column webtorrent;`, - ); - } -} diff --git a/migrations/1673603746273-CreateTorrentsTableAndRelateToFiles.ts b/migrations/1673603746273-CreateTorrentsTableAndRelateToFiles.ts new file mode 100644 index 0000000..c55a9d1 --- /dev/null +++ b/migrations/1673603746273-CreateTorrentsTableAndRelateToFiles.ts @@ -0,0 +1,51 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class CreateTorrentsTableAndRelateToFiles1673603746273 + implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `alter table files + add torrent_id INTEGER;`, + ); + + await queryRunner.query( + `create unique index file_torrent_id + on files (torrent_id);`, + ); + + await queryRunner.query( + `create table torrents + ( + id INTEGER primary key, + magnet VARCHAR(512), + name VARCHAR(256), + path VARCHAR(512), + size INTEGER, + stopped TINYINT, + info_hash VARCHAR(64), + progress REAL, + transmission_id INTEGER, + info TEXT + );`, + ); + + await queryRunner.query( + `create unique index torrent_magnet + on torrents (magnet);`, + ); + + await queryRunner.query( + `create unique index torrent_transmission_id + on torrents (transmission_id);`, + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `alter table files + drop column torrent_id;`, + ); + + await queryRunner.query(`DROP TABLE torrents`); + } +} diff --git a/package-lock.json b/package-lock.json index 7209867..bdb2c09 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,7 +15,7 @@ "@nestjs/platform-express": "^9.0.0", "@nestjs/serve-static": "^3.0.0", "@nestjs/typeorm": "^9.0.1", - "@types/fluent-ffmpeg": "^2.1.20", + "cheerio": "^1.0.0-rc.12", "dotenv": "^16.0.3", "fluent-ffmpeg": "^2.1.2", "reflect-metadata": "^0.1.13", @@ -24,16 +24,21 @@ "sqlite": "^4.1.2", "sqlite3": "^5.1.2", "torrent-name-parser": "^0.6.5", - "typeorm": "^0.3.11" + "transmission": "^0.4.10", + "typeorm": "^0.3.11", + "webtorrent": "^1.9.7" }, "devDependencies": { "@nestjs/cli": "^9.0.0", "@nestjs/schematics": "^9.0.0", "@nestjs/testing": "^9.0.0", + "@types/cheerio": "^0.22.31", "@types/express": "^4.17.13", + "@types/fluent-ffmpeg": "^2.1.20", "@types/jest": "28.1.8", "@types/node": "^16.0.0", "@types/supertest": "^2.0.11", + "@types/webtorrent": "^0.109.3", "@typescript-eslint/eslint-plugin": "^5.0.0", "@typescript-eslint/parser": "^5.0.0", "copyfiles": "^2.4.1", @@ -2026,6 +2031,15 @@ "@babel/types": "^7.3.0" } }, + "node_modules/@types/bittorrent-protocol": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@types/bittorrent-protocol/-/bittorrent-protocol-3.1.2.tgz", + "integrity": "sha512-7k9nivNeG7Sc8wVuBs+XjBp2u7pH8tqW3BB93/SAg3xht/cZEK+Rqkj79xSyJqyj86eA0F6n85EKkkyGki8afg==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/body-parser": { "version": "1.19.2", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz", @@ -2036,6 +2050,15 @@ "@types/node": "*" } }, + "node_modules/@types/cheerio": { + "version": "0.22.31", + "resolved": "https://registry.npmjs.org/@types/cheerio/-/cheerio-0.22.31.tgz", + "integrity": "sha512-Kt7Cdjjdi2XWSfrZ53v4Of0wG3ZcmaegFXjMmz9tfNrZSkzzo36G0AL1YqSdcIA78Etjt6E609pt5h1xnQkPUw==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/connect": { "version": "3.4.35", "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", @@ -2104,6 +2127,7 @@ "version": "2.1.20", "resolved": "https://registry.npmjs.org/@types/fluent-ffmpeg/-/fluent-ffmpeg-2.1.20.tgz", "integrity": "sha512-B+OvhCdJ3LgEq2PhvWNOiB/EfwnXLElfMCgc4Z1K5zXgSfo9I6uGKwR/lqmNPFQuebNnes7re3gqkV77SyypLg==", + "dev": true, "dependencies": { "@types/node": "*" } @@ -2157,6 +2181,15 @@ "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", "dev": true }, + "node_modules/@types/magnet-uri": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/@types/magnet-uri/-/magnet-uri-5.1.3.tgz", + "integrity": "sha512-FvJN1yYdLhvU6zWJ2YnWQ2GnpFLsA8bt+85WY0tLh6ehzGNrvBorjlcc53/zY43r/IKn+ctFs1nt7andwGnQCQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/mime": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-3.0.1.tgz", @@ -2166,7 +2199,8 @@ "node_modules/@types/node": { "version": "16.18.4", "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.4.tgz", - "integrity": "sha512-9qGjJ5GyShZjUfx2ArBIGM+xExdfLvvaCyQR0t6yRXKPcWCVYF/WemtX/uIU3r7FYECXRXkIiw2Vnhn6y8d+pw==" + "integrity": "sha512-9qGjJ5GyShZjUfx2ArBIGM+xExdfLvvaCyQR0t6yRXKPcWCVYF/WemtX/uIU3r7FYECXRXkIiw2Vnhn6y8d+pw==", + "devOptional": true }, "node_modules/@types/parse-json": { "version": "4.0.0", @@ -2174,6 +2208,26 @@ "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", "dev": true }, + "node_modules/@types/parse-torrent": { + "version": "5.8.4", + "resolved": "https://registry.npmjs.org/@types/parse-torrent/-/parse-torrent-5.8.4.tgz", + "integrity": "sha512-FdKs5yN5iYO5Cu9gVz1Zl30CbZe6HTsqloWmCf+LfbImgSzlsUkov2+npQWCQSQ3zi/a2G5C824K0UpZ2sRufA==", + "dev": true, + "dependencies": { + "@types/magnet-uri": "*", + "@types/node": "*", + "@types/parse-torrent-file": "*" + } + }, + "node_modules/@types/parse-torrent-file": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@types/parse-torrent-file/-/parse-torrent-file-4.0.3.tgz", + "integrity": "sha512-dFkPnJPKiFWiGX+HXmyTVt2js3k0d9dThmUxX8nfGC22hbyZ5BTmetsEl45sQhHLcFo43njVrIKMXM3F1ahXRw==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/prettier": { "version": "2.7.1", "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.1.tgz", @@ -2208,6 +2262,15 @@ "@types/node": "*" } }, + "node_modules/@types/simple-peer": { + "version": "9.11.5", + "resolved": "https://registry.npmjs.org/@types/simple-peer/-/simple-peer-9.11.5.tgz", + "integrity": "sha512-haXgWcAa3Y3Sn+T8lzkE4ErQUpYzhW6Cz2lh00RhQTyWt+xZ3s87wJPztUxlqSdFRqGhe2MQIBd0XsyHP3No4w==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/stack-utils": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", @@ -2233,6 +2296,18 @@ "@types/superagent": "*" } }, + "node_modules/@types/webtorrent": { + "version": "0.109.3", + "resolved": "https://registry.npmjs.org/@types/webtorrent/-/webtorrent-0.109.3.tgz", + "integrity": "sha512-EJLsxMEcEjPXHcBqL6TRAbUwIpxAul5ULrXHJ0zwig7Oe70FS6dAzCWLq4MBafX3QrQG1DzGAS0fS8iJEOjD0g==", + "dev": true, + "dependencies": { + "@types/bittorrent-protocol": "*", + "@types/node": "*", + "@types/parse-torrent": "*", + "@types/simple-peer": "*" + } + }, "node_modules/@types/yargs": { "version": "17.0.15", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.15.tgz", @@ -2581,6 +2656,15 @@ "@xtuc/long": "4.2.2" } }, + "node_modules/@webtorrent/http-node": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@webtorrent/http-node/-/http-node-1.3.0.tgz", + "integrity": "sha512-GWZQKroPES4z91Ijx6zsOsb7+USOxjy66s8AoTWg0HiBBdfnbtf9aeh3Uav0MgYn4BL8Q7tVSUpd0gGpngKGEQ==", + "dependencies": { + "freelist": "^1.0.3", + "http-parser-js": "^0.4.3" + } + }, "node_modules/@xtuc/ieee754": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", @@ -2649,6 +2733,11 @@ "node": ">=0.4.0" } }, + "node_modules/addr-to-ip-port": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/addr-to-ip-port/-/addr-to-ip-port-1.5.4.tgz", + "integrity": "sha512-ByxmJgv8vjmDcl3IDToxL2yrWFrRtFpZAToY0f46XFXl8zS081t7El5MXIodwm7RC6DhHBRoOSMLFSPKCtHukg==" + }, "node_modules/agent-base": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", @@ -2899,6 +2988,11 @@ "proxy-from-env": "^1.1.0" } }, + "node_modules/b4a": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.1.tgz", + "integrity": "sha512-AsKjNhz72yxteo/0EtQEiwkMUgk/tGmycXlbG4g3Ard2/ULtNLUykGOkeK0egmN27h0xMAhb76jYccW+XTBExA==" + }, "node_modules/babel-jest": { "version": "28.1.3", "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-28.1.3.tgz", @@ -3030,6 +3124,16 @@ } ] }, + "node_modules/bencode": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/bencode/-/bencode-2.0.3.tgz", + "integrity": "sha512-D/vrAD4dLVX23NalHwb8dSvsUsxeRPO8Y7ToKA015JQYq69MLDOMkC0uGZYA/MPpltLO8rt8eqFC2j8DxjTZ/w==" + }, + "node_modules/bep53-range": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/bep53-range/-/bep53-range-1.1.1.tgz", + "integrity": "sha512-ct6s33iiwRCUPp9KXnJ4QMWDgHIgaw36caK/5XEQ9L8dCzSQlJt1Vk6VmHh1VD4AlGCAI4C2zmtfItifBBPrhQ==" + }, "node_modules/binary-extensions": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", @@ -3039,6 +3143,207 @@ "node": ">=8" } }, + "node_modules/binary-search": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/binary-search/-/binary-search-1.3.6.tgz", + "integrity": "sha512-nbE1WxOTTrUWIfsfZ4aHGYu5DOuNkbxGokjV6Z2kxfJK3uaAb8zNK1muzOeipoLHZjInT4Br88BHpzevc681xA==" + }, + "node_modules/bitfield": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bitfield/-/bitfield-4.1.0.tgz", + "integrity": "sha512-6cEDG3K+PK9f+B7WyhWYjp09bqSa+uaAaecVA7Y5giFixyVe1s6HKGnvOqYNR4Mi4fBMjfDPLBpHkKvzzgP7kg==", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/bittorrent-dht": { + "version": "10.0.7", + "resolved": "https://registry.npmjs.org/bittorrent-dht/-/bittorrent-dht-10.0.7.tgz", + "integrity": "sha512-o6elCANGteECXz82LFqG1Ov2fG4uNzfUU7pBMx9ixxKUh99ZXNrhbiNLRNN2F2vBnqKSN7SHlUW4LJ5Z2u1eKw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "bencode": "^2.0.3", + "debug": "^4.3.4", + "k-bucket": "^5.1.0", + "k-rpc": "^5.1.0", + "last-one-wins": "^1.0.4", + "lru": "^3.1.0", + "randombytes": "^2.1.0", + "record-cache": "^1.2.0", + "simple-sha1": "^3.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/bittorrent-lsd": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/bittorrent-lsd/-/bittorrent-lsd-1.1.1.tgz", + "integrity": "sha512-dWxU2Mr2lU6jzIKgZrTsXgeXDCIcYpR1b6f2n89fn7juwPAYbNU04OgWjcQPLiNliY0filsX5CQAWntVErpk+Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "chrome-dgram": "^3.0.6", + "debug": "^4.2.0" + } + }, + "node_modules/bittorrent-peerid": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/bittorrent-peerid/-/bittorrent-peerid-1.3.6.tgz", + "integrity": "sha512-VyLcUjVMEOdSpHaCG/7odvCdLbAB1y3l9A2V6WIje24uV7FkJPrQrH/RrlFmKxP89pFVDEnE+YlHaFujlFIZsg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/bittorrent-protocol": { + "version": "3.5.5", + "resolved": "https://registry.npmjs.org/bittorrent-protocol/-/bittorrent-protocol-3.5.5.tgz", + "integrity": "sha512-cfzO//WtJGNLHXS58a4exJCSq1U0dkP2DZCQxgADInYFPdOfV1EmtpEN9toLOluVCXJRYAdwW5H6Li/hrn697A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "bencode": "^2.0.2", + "bitfield": "^4.0.0", + "debug": "^4.3.4", + "randombytes": "^2.1.0", + "rc4": "^0.1.5", + "readable-stream": "^3.6.0", + "simple-sha1": "^3.1.0", + "speedometer": "^1.1.0", + "unordered-array-remove": "^1.0.2" + } + }, + "node_modules/bittorrent-protocol/node_modules/readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/bittorrent-tracker": { + "version": "9.19.0", + "resolved": "https://registry.npmjs.org/bittorrent-tracker/-/bittorrent-tracker-9.19.0.tgz", + "integrity": "sha512-09d0aD2b+MC+zWvWajkUAKkYMynYW4tMbTKiRSthKtJZbafzEoNQSUHyND24SoCe3ZOb2fKfa6fu2INAESL9wA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "bencode": "^2.0.1", + "bittorrent-peerid": "^1.3.3", + "bn.js": "^5.2.0", + "chrome-dgram": "^3.0.6", + "clone": "^2.0.0", + "compact2string": "^1.4.1", + "debug": "^4.1.1", + "ip": "^1.1.5", + "lru": "^3.1.0", + "minimist": "^1.2.5", + "once": "^1.4.0", + "queue-microtask": "^1.2.3", + "random-iterate": "^1.0.1", + "randombytes": "^2.1.0", + "run-parallel": "^1.2.0", + "run-series": "^1.1.9", + "simple-get": "^4.0.0", + "simple-peer": "^9.11.0", + "simple-websocket": "^9.1.0", + "socks": "^2.0.0", + "string2compact": "^1.3.0", + "unordered-array-remove": "^1.0.2", + "ws": "^7.4.5" + }, + "bin": { + "bittorrent-tracker": "bin/cmd.js" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "bufferutil": "^4.0.3", + "utf-8-validate": "^5.0.5" + } + }, + "node_modules/bittorrent-tracker/node_modules/clone": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/bittorrent-tracker/node_modules/ip": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.8.tgz", + "integrity": "sha512-PuExPYUiu6qMBQb4l06ecm6T6ujzhmh+MeJcW9wa89PoAz5pvd4zPgN5WJV104mb6S2T1AwNIAaB70JNrLQWhg==" + }, "node_modules/bl": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", @@ -3064,6 +3369,56 @@ "node": ">= 6" } }, + "node_modules/blob-to-buffer": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/blob-to-buffer/-/blob-to-buffer-1.2.9.tgz", + "integrity": "sha512-BF033y5fN6OCofD3vgHmNtwZWRcq9NLyyxyILx9hfMy1sXYy4ojFl765hJ2lP0YaN2fuxPaLO2Vzzoxy0FLFFA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/block-iterator": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/block-iterator/-/block-iterator-1.1.1.tgz", + "integrity": "sha512-DrjdVWZemVO4iBf4tiOXjUrY5cNesjzy0t7sIiu2rdl8cOCHRxAgKjSJFc3vBZYYMMmshUAxajl8QQh/uxXTKQ==" + }, + "node_modules/block-stream2": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/block-stream2/-/block-stream2-2.1.0.tgz", + "integrity": "sha512-suhjmLI57Ewpmq00qaygS8UgEq2ly2PCItenIyhMqVjo4t4pGzqMvfgJuX8iWTeSDdfSSqS6j38fL4ToNL7Pfg==", + "dependencies": { + "readable-stream": "^3.4.0" + } + }, + "node_modules/block-stream2/node_modules/readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/bn.js": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", + "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==" + }, "node_modules/body-parser": { "version": "1.20.1", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", @@ -3100,6 +3455,11 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==" + }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -3121,6 +3481,11 @@ "node": ">=8" } }, + "node_modules/browserify-package-json": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/browserify-package-json/-/browserify-package-json-1.0.1.tgz", + "integrity": "sha512-CikZxJGNyNOBERbeALo0NUUeJgHs5NyEvuYChX/PcsBV91TAvEq4hYDaWSenSieT8XwAutNnS3FGvyzIMOughQ==" + }, "node_modules/browserslist": { "version": "4.21.4", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.4.tgz", @@ -3194,11 +3559,43 @@ "ieee754": "^1.1.13" } }, + "node_modules/buffer-alloc": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz", + "integrity": "sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==", + "dependencies": { + "buffer-alloc-unsafe": "^1.1.0", + "buffer-fill": "^1.0.0" + } + }, + "node_modules/buffer-alloc-unsafe": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz", + "integrity": "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==" + }, + "node_modules/buffer-fill": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz", + "integrity": "sha512-T7zexNBwiiaCOGDg9xNX9PBmjrubblRkENuptryuI64URkXDFum9il/JGL8Lm8wYfAXpredVXXZz7eMHilimiQ==" + }, "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" }, + "node_modules/bufferutil": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.7.tgz", + "integrity": "sha512-kukuqc39WOHtdxtw4UScxF/WVnMFVSQVKhtx3AjZJzhd0RGZZldcrfSEbVsWWe6KNH253574cq5F+wpv0G9pJw==", + "hasInstallScript": true, + "optional": true, + "dependencies": { + "node-gyp-build": "^4.3.0" + }, + "engines": { + "node": ">=6.14.2" + } + }, "node_modules/busboy": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", @@ -3259,6 +3656,29 @@ "node": ">=10" } }, + "node_modules/cache-chunk-store": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/cache-chunk-store/-/cache-chunk-store-3.2.2.tgz", + "integrity": "sha512-2lJdWbgHFFxcSth9s2wpId3CR3v1YC63KjP4T9WhpW7LWlY7Hiiei3QwwqzkWqlJTfR8lSy9F5kRQECeyj+yQA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "lru": "^3.1.0", + "queue-microtask": "^1.2.3" + } + }, "node_modules/call-bind": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", @@ -3333,6 +3753,65 @@ "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", "dev": true }, + "node_modules/cheerio": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.12.tgz", + "integrity": "sha512-VqR8m68vM46BNnuZ5NtnGBKIE/DfN0cRIzg9n40EIq9NOv90ayxLBXA8fXC5gquFRGJSTRqBq25Jt2ECLR431Q==", + "dependencies": { + "cheerio-select": "^2.1.0", + "dom-serializer": "^2.0.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1", + "htmlparser2": "^8.0.1", + "parse5": "^7.0.0", + "parse5-htmlparser2-tree-adapter": "^7.0.0" + }, + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/cheeriojs/cheerio?sponsor=1" + } + }, + "node_modules/cheerio-select": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz", + "integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==", + "dependencies": { + "boolbase": "^1.0.0", + "css-select": "^5.1.0", + "css-what": "^6.1.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/cheerio/node_modules/parse5": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", + "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", + "dependencies": { + "entities": "^4.4.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/cheerio/node_modules/parse5-htmlparser2-tree-adapter": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.0.0.tgz", + "integrity": "sha512-B77tOZrqqfUfnVcOrUvfdLbz4pu4RopLD/4vmu3HUPswwTA8OH0EMW9BlWR2B0RCoiZRAHEUu7IxeP1Pd1UU+g==", + "dependencies": { + "domhandler": "^5.0.2", + "parse5": "^7.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, "node_modules/chokidar": { "version": "3.5.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", @@ -3368,6 +3847,59 @@ "node": ">=10" } }, + "node_modules/chrome-dgram": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/chrome-dgram/-/chrome-dgram-3.0.6.tgz", + "integrity": "sha512-bqBsUuaOiXiqxXt/zA/jukNJJ4oaOtc7ciwqJpZVEaaXwwxqgI2/ZdG02vXYWUhHGziDlvGMQWk0qObgJwVYKA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "inherits": "^2.0.4", + "run-series": "^1.1.9" + } + }, + "node_modules/chrome-dns": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/chrome-dns/-/chrome-dns-1.0.1.tgz", + "integrity": "sha512-HqsYJgIc8ljJJOqOzLphjAs79EUuWSX3nzZi2LNkzlw3GIzAeZbaSektC8iT/tKvLqZq8yl1GJu5o6doA4TRbg==", + "dependencies": { + "chrome-net": "^3.3.2" + } + }, + "node_modules/chrome-net": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/chrome-net/-/chrome-net-3.3.4.tgz", + "integrity": "sha512-Jzy2EnzmE+ligqIZUsmWnck9RBXLuUy6CaKyuNMtowFG3ZvLt8d+WBJCTPEludV0DHpIKjAOlwjFmTaEdfdWCw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "inherits": "^2.0.1" + } + }, "node_modules/chrome-trace-event": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", @@ -3377,6 +3909,42 @@ "node": ">=6.0" } }, + "node_modules/chunk-store-stream": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/chunk-store-stream/-/chunk-store-stream-4.3.0.tgz", + "integrity": "sha512-qby+/RXoiMoTVtPiylWZt7KFF1jy6M829TzMi2hxZtBIH9ptV19wxcft6zGiXLokJgCbuZPGNGab6DWHqiSEKw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "block-stream2": "^2.0.0", + "readable-stream": "^3.6.0" + } + }, + "node_modules/chunk-store-stream/node_modules/readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/ci-info": { "version": "3.7.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.7.0.tgz", @@ -3601,6 +4169,14 @@ "node": ">= 6" } }, + "node_modules/compact2string": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/compact2string/-/compact2string-1.4.1.tgz", + "integrity": "sha512-3D+EY5nsRhqnOwDxveBv5T8wGo4DEvYxjDtPGmdOX+gfr5gE92c2RC0w2wa+xEefm07QuVqqcF3nZJUZ92l/og==", + "dependencies": { + "ipaddr.js": ">= 0.1.5" + } + }, "node_modules/component-emitter": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", @@ -3782,12 +4358,69 @@ "node": ">=10" } }, + "node_modules/cpus": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/cpus/-/cpus-1.0.3.tgz", + "integrity": "sha512-PXHBvGLuL69u55IkLa5e5838fLhIMHxmkV4ge42a8alGyn7BtawYgI0hQ849EedvtHIOLNNH3i6eQU1BiE9SUA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/create-require": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", "devOptional": true }, + "node_modules/create-torrent": { + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/create-torrent/-/create-torrent-5.0.9.tgz", + "integrity": "sha512-WQ/bMe+aCBSa5EonIkgw7CTM/1JnJDQuLJhA78omSWvuEbXDwaUy0rG3a+IYt+EiO+rdTLxdsBwrsn/wfWOMQA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "bencode": "^2.0.3", + "block-iterator": "^1.0.1", + "fast-readable-async-iterator": "^1.1.1", + "is-file": "^1.0.0", + "join-async-iterator": "^1.1.1", + "junk": "^3.1.0", + "minimist": "^1.2.7", + "piece-length": "^2.0.1", + "queue-microtask": "^1.2.3", + "run-parallel": "^1.2.0", + "simple-sha1": "^3.1.0" + }, + "bin": { + "create-torrent": "bin/cmd.js" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -3802,6 +4435,32 @@ "node": ">= 8" } }, + "node_modules/css-select": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", + "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-what": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", + "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, "node_modules/date-fns": { "version": "2.29.3", "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.29.3.tgz", @@ -3830,6 +4489,20 @@ } } }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/dedent": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", @@ -3962,6 +4635,57 @@ "node": ">=6.0.0" } }, + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ] + }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.0.1.tgz", + "integrity": "sha512-z08c1l761iKhDFtfXO04C7kTdPBLi41zwOZl00WS8b5eiaebNpY00HKbztwBq+e3vyqWNwWF3mP9YLUeqIrF+Q==", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.1" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, "node_modules/dotenv": { "version": "16.0.3", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.3.tgz", @@ -4031,7 +4755,6 @@ "version": "1.4.4", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "dev": true, "dependencies": { "once": "^1.4.0" } @@ -4049,6 +4772,17 @@ "node": ">=10.13.0" } }, + "node_modules/entities": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.4.0.tgz", + "integrity": "sha512-oYp7156SP8LkeGD0GF85ad1X9Ai79WtRsZ2gxJqtBuzH+98YUV6jkHEKlZkMbcrjJjIVJNIDP/3WL9wQkoPbWA==", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/env-paths": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", @@ -4416,7 +5150,6 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", - "dev": true, "engines": { "node": ">=0.8.x" } @@ -4542,6 +5275,15 @@ "node": ">=4" } }, + "node_modules/fast-blob-stream": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/fast-blob-stream/-/fast-blob-stream-1.1.1.tgz", + "integrity": "sha512-wdRazMMeM2pl8hq1lFG8fzix8p1VLAJunTTE2RADiFBwbUfZwybUm6IwPrmMS7qTthiayr166NoXeqWe3hfR5w==", + "dependencies": { + "fast-readable-async-iterator": "^1.1.1", + "streamx": "^2.12.4" + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -4554,6 +5296,11 @@ "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==", "dev": true }, + "node_modules/fast-fifo": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.1.0.tgz", + "integrity": "sha512-Kl29QoNbNvn4nhDsLYjyIAaIqaJB6rBx5p3sL9VjaefJ+eMFBWVZiaoguaoZfzEKr5RhAti0UgM8703akGPJ6g==" + }, "node_modules/fast-glob": { "version": "3.2.12", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", @@ -4582,6 +5329,11 @@ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "dev": true }, + "node_modules/fast-readable-async-iterator": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/fast-readable-async-iterator/-/fast-readable-async-iterator-1.1.1.tgz", + "integrity": "sha512-xEHkLUEmStETI+15zhglJLO9TjXxNkkp2ldEfYVZdcqxFhM172EfGl1irI6mVlTxXspYKH1/kjevnt/XSsPeFA==" + }, "node_modules/fast-safe-stringify": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", @@ -4846,6 +5598,11 @@ "node": ">= 0.6" } }, + "node_modules/freelist": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/freelist/-/freelist-1.0.3.tgz", + "integrity": "sha512-Ji7fEnMdZDGbS5oXElpRJsn9jPvBR8h/037D3bzreNmS8809cISq/2D9//JbA/TaZmkkN8cmecXwmQHmM+NHhg==" + }, "node_modules/fresh": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", @@ -4854,6 +5611,32 @@ "node": ">= 0.6" } }, + "node_modules/fs-chunk-store": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/fs-chunk-store/-/fs-chunk-store-3.0.1.tgz", + "integrity": "sha512-YrOFuXtUJQBkOZ2QBXBoIrjLJ/TNTpEaGnxV+TmL1qaW5J4ah6lxMh/X9pb3To+hbaoT/pRuBXLkkqoavQoQFw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2", + "random-access-file": "^2.0.1", + "randombytes": "^2.0.3", + "run-parallel": "^1.1.2", + "thunky": "^1.0.1" + } + }, "node_modules/fs-extra": { "version": "10.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", @@ -4937,6 +5720,11 @@ "node": ">=6.9.0" } }, + "node_modules/get-browser-rtc": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/get-browser-rtc/-/get-browser-rtc-1.1.0.tgz", + "integrity": "sha512-MghbMJ61EJrRsDe7w1Bvqt3ZsBuqhce5nrn/XAwgwOXhcsz53/ltdxOse1h/8eKXj5slzxdsz56g5rzOFSGwfQ==" + }, "node_modules/get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", @@ -4967,6 +5755,17 @@ "node": ">=8.0.0" } }, + "node_modules/get-stdin": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-8.0.0.tgz", + "integrity": "sha512-sY22aA6xchAzprjyqmSEQv4UbAAzRN0L2dQB0NlN5acTTK9Don6nhoc3eAbUnpZiCANAMfd/+40kVdKfFygohg==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/get-stream": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", @@ -5121,6 +5920,24 @@ "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", "dev": true }, + "node_modules/htmlparser2": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.1.tgz", + "integrity": "sha512-4lVbmc1diZC7GUJQtRQ5yBAeUCL1exyMwmForWkRLnwyzWBFxN633SALPMGYaWZvKe9j1pRZJpauvmxENSp/EA==", + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "entities": "^4.3.0" + } + }, "node_modules/http-cache-semantics": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", @@ -5142,6 +5959,11 @@ "node": ">= 0.8" } }, + "node_modules/http-parser-js": { + "version": "0.4.13", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.4.13.tgz", + "integrity": "sha512-u8u5ZaG0Tr/VvHlucK2ufMuOp4/5bvwgneXle+y228K5rMbJOlVjThONcaAw3ikAy8b2OO9RfEucdMHFz3UWMA==" + }, "node_modules/http-proxy-agent": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", @@ -5225,6 +6047,28 @@ "node": ">= 4" } }, + "node_modules/immediate-chunk-store": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/immediate-chunk-store/-/immediate-chunk-store-2.2.0.tgz", + "integrity": "sha512-1bHBna0hCa6arRXicu91IiL9RvvkbNYLVq+mzWdaLGZC3hXvX4doh8e1dLhMKez5siu63CYgO5NrGJbRX5lbPA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.3" + } + }, "node_modules/import-fresh": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", @@ -5368,8 +6212,20 @@ "node_modules/ip": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz", - "integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==", - "optional": true + "integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==" + }, + "node_modules/ip-set": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ip-set/-/ip-set-2.1.0.tgz", + "integrity": "sha512-JdHz4tSMx1IeFj8yEcQU0i58qiSkOlmZXkZ8+HJ0ROV5KcgLRDO9F703oJ1GeZCvqggrcCbmagD/V7hghY62wA==", + "dependencies": { + "ip": "^1.1.5" + } + }, + "node_modules/ip-set/node_modules/ip": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.8.tgz", + "integrity": "sha512-PuExPYUiu6qMBQb4l06ecm6T6ujzhmh+MeJcW9wa89PoAz5pvd4zPgN5WJV104mb6S2T1AwNIAaB70JNrLQWhg==" }, "node_modules/ipaddr.js": { "version": "1.9.1", @@ -5385,6 +6241,11 @@ "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", "dev": true }, + "node_modules/is-ascii": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-ascii/-/is-ascii-1.0.0.tgz", + "integrity": "sha512-CXMaB/+EWCSGlLPs7ZlXRBpaPRRSRnrOfq0N3+RGeCZfqQaHQtiDLlkPCn63+LCkRUc1iRE0AXiI+sm2/Hi3qQ==" + }, "node_modules/is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", @@ -5418,6 +6279,11 @@ "node": ">=0.10.0" } }, + "node_modules/is-file": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-file/-/is-file-1.0.0.tgz", + "integrity": "sha512-ZGMuc+xA8mRnrXtmtf2l/EkIW2zaD2LSBWlaOVEF6yH4RTndHob65V4SwWWdtGKVthQfXPVKsXqw4TDUjbVxVQ==" + }, "node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", @@ -6404,6 +7270,11 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, + "node_modules/join-async-iterator": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/join-async-iterator/-/join-async-iterator-1.1.1.tgz", + "integrity": "sha512-ATse+nuNeKZ9K1y27LKdvPe/GCe9R/u9dw9vI248e+vILeRK3IcJP4JUPAlSmKRCDK0cKhEwfmiw4Skqx7UnGQ==" + }, "node_modules/js-sdsl": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.2.0.tgz", @@ -6491,6 +7362,43 @@ "graceful-fs": "^4.1.6" } }, + "node_modules/junk": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/junk/-/junk-3.1.0.tgz", + "integrity": "sha512-pBxcB3LFc8QVgdggvZWyeys+hnrNWg4OcZIU/1X59k5jQdLBlCsYGRQaz234SqoRLTCgMH00fY0xRJH+F9METQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/k-bucket": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/k-bucket/-/k-bucket-5.1.0.tgz", + "integrity": "sha512-Fac7iINEovXIWU20GPnOMLUbjctiS+cnmyjC4zAUgvs3XPf1vo9akfCHkigftSic/jiKqKl+KA3a/vFcJbHyCg==", + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/k-rpc": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/k-rpc/-/k-rpc-5.1.0.tgz", + "integrity": "sha512-FGc+n70Hcjoa/X2JTwP+jMIOpBz+pkRffHnSl9yrYiwUxg3FIgD50+u1ePfJUOnRCnx6pbjmVk5aAeB1wIijuQ==", + "dependencies": { + "k-bucket": "^5.0.0", + "k-rpc-socket": "^1.7.2", + "randombytes": "^2.0.5" + } + }, + "node_modules/k-rpc-socket": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/k-rpc-socket/-/k-rpc-socket-1.11.1.tgz", + "integrity": "sha512-8xtA8oqbZ6v1Niryp2/g4GxW16EQh5MvrUylQoOG+zcrDff5CKttON2XUXvMwlIHq4/2zfPVFiinAccJ+WhxoA==", + "dependencies": { + "bencode": "^2.0.0", + "chrome-dgram": "^3.0.2", + "chrome-dns": "^1.0.0", + "chrome-net": "^3.3.2" + } + }, "node_modules/kleur": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", @@ -6500,6 +7408,11 @@ "node": ">=6" } }, + "node_modules/last-one-wins": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/last-one-wins/-/last-one-wins-1.0.4.tgz", + "integrity": "sha512-t+KLJFkHPQk8lfN6WBOiGkiUXoub+gnb2XTYI2P3aiISL+94xgZ1vgz1SXN/N4hthuOoLXarXfBZPUruyjQtfA==" + }, "node_modules/leven": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", @@ -6522,12 +7435,43 @@ "node": ">= 0.8.0" } }, + "node_modules/limiter": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/limiter/-/limiter-1.1.5.tgz", + "integrity": "sha512-FWWMIEOxz3GwUI4Ts/IvgVy6LPvoMPgjMdQ185nN6psJyBJ4yOpzqm695/h5umdLJg2vW3GR5iG11MAkR2AzJA==" + }, "node_modules/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", "dev": true }, + "node_modules/load-ip-set": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/load-ip-set/-/load-ip-set-2.2.1.tgz", + "integrity": "sha512-G3hQXehU2LTOp52e+lPffpK4EvidfjwbvHaGqmFcp4ptiZagR4xFdL+D08kMX906dxeqZyWhfonEjdUxrWcldg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "ip-set": "^2.1.0", + "netmask": "^2.0.1", + "once": "^1.4.0", + "simple-get": "^4.0.0", + "split": "^1.0.1" + } + }, "node_modules/loader-runner": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", @@ -6555,8 +7499,7 @@ "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, "node_modules/lodash.memoize": { "version": "4.1.2", @@ -6602,6 +7545,17 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/lru": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/lru/-/lru-3.1.0.tgz", + "integrity": "sha512-5OUtoiVIGU4VXBOshidmtOsvBIvcQR6FD/RzWSvaeHyxCGB+PCUCu+52lqMfdc0h/2CLvHhZS4TwUmMQrrMbBQ==", + "dependencies": { + "inherits": "^2.0.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, "node_modules/lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -6613,6 +7567,29 @@ "node": ">=10" } }, + "node_modules/lt_donthave": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lt_donthave/-/lt_donthave-1.0.1.tgz", + "integrity": "sha512-PfOXfDN9GnUjlNHjjxKQuMxPC8s12iSrnmg+Ff1BU1uLn7S1BFAKzpZCu6Gwg3WsCUvTZrZoDSHvy6B/j+N4/Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "debug": "^4.2.0", + "unordered-array-remove": "^1.0.2" + } + }, "node_modules/macos-release": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/macos-release/-/macos-release-2.5.0.tgz", @@ -6637,6 +7614,29 @@ "node": ">=12" } }, + "node_modules/magnet-uri": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/magnet-uri/-/magnet-uri-6.2.0.tgz", + "integrity": "sha512-O9AgdDwT771fnUj0giPYu/rACpz8173y8UXCSOdLITjOVfBenZ9H9q3FqQmveK+ORUMuD+BkKNSZP8C3+IMAKQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "bep53-range": "^1.1.0", + "thirty-two": "^1.0.2" + } + }, "node_modules/make-dir": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", @@ -6709,6 +7709,43 @@ "node": ">= 0.6" } }, + "node_modules/mediasource": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/mediasource/-/mediasource-2.4.0.tgz", + "integrity": "sha512-SKUMrbFMHgiCUZFOWZcL0aiF/KgHx9SPIKzxrl6+7nMUMDK/ZnOmJdY/9wKzYeM0g3mybt3ueg+W+/mrYfmeFQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "inherits": "^2.0.4", + "readable-stream": "^3.6.0", + "to-arraybuffer": "^1.0.1" + } + }, + "node_modules/mediasource/node_modules/readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/memfs": { "version": "3.4.12", "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.4.12.tgz", @@ -6721,6 +7758,14 @@ "node": ">= 4.0.0" } }, + "node_modules/memory-chunk-store": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/memory-chunk-store/-/memory-chunk-store-1.3.5.tgz", + "integrity": "sha512-E1Xc1U4ifk/FkC2ZsWhCaW1xg9HbE/OBmQTLe2Tr9c27YPSLbW7kw1cnb3kQWD1rDtErFJHa7mB9EVrs7aTx9g==", + "dependencies": { + "queue-microtask": "^1.2.3" + } + }, "node_modules/merge-descriptors": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", @@ -6801,6 +7846,17 @@ "node": ">=6" } }, + "node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -6919,6 +7975,43 @@ "mkdirp": "bin/cmd.js" } }, + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==" + }, + "node_modules/mp4-box-encoding": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/mp4-box-encoding/-/mp4-box-encoding-1.4.1.tgz", + "integrity": "sha512-2/PRtGGiqPc/VEhbm7xAQ+gbb7yzHjjMAv6MpAifr5pCpbh3fQUdj93uNgwPiTppAGu8HFKe3PeU+OdRyAxStA==", + "dependencies": { + "uint64be": "^2.0.2" + } + }, + "node_modules/mp4-stream": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/mp4-stream/-/mp4-stream-3.1.3.tgz", + "integrity": "sha512-DUT8f0x2jHbZjNMdqe9h6lZdt6RENWTTdGn8z3TXa4uEsoltuNY9lCCij84mdm0q7xcV0E2W25WRxlKBMo4hSw==", + "dependencies": { + "mp4-box-encoding": "^1.3.0", + "next-event": "^1.0.0", + "queue-microtask": "^1.2.2", + "readable-stream": "^3.0.6" + } + }, + "node_modules/mp4-stream/node_modules/readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -6957,6 +8050,12 @@ "thenify-all": "^1.0.0" } }, + "node_modules/napi-macros": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/napi-macros/-/napi-macros-2.2.2.tgz", + "integrity": "sha512-hmEVtAGYzVQpCKdbQea4skABsdXW4RUh5t5mJ2zzqowJS2OyXZTU1KhDVFhx+NlWZ4ap9mqR9TcDO3LTTttd+g==", + "optional": true + }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -6983,6 +8082,19 @@ "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", "dev": true }, + "node_modules/netmask": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.0.2.tgz", + "integrity": "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/next-event": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/next-event/-/next-event-1.0.0.tgz", + "integrity": "sha512-IXGPhl/yAiUU597gz+k5OYxYZkmLSWTcPPcpQjWABud9OK6m/ZNLrVdcEu4e7NgmOObFIhgZVg1jecPYT/6AoA==" + }, "node_modules/node-abort-controller": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/node-abort-controller/-/node-abort-controller-3.0.1.tgz", @@ -7046,6 +8158,17 @@ "node": ">= 10.12.0" } }, + "node_modules/node-gyp-build": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.6.0.tgz", + "integrity": "sha512-NTZVKn9IylLwUzaKjkas1e4u2DLNcV4rdYagA4PWdPwW87Bi7z+BznyKSRwS/761tV/lzCGXplWsiaMjLqP2zQ==", + "optional": true, + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, "node_modules/node-gyp/node_modules/are-we-there-yet": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz", @@ -7199,6 +8322,17 @@ "set-blocking": "^2.0.0" } }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -7392,6 +8526,14 @@ "node": ">=6" } }, + "node_modules/package-json-versionify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/package-json-versionify/-/package-json-versionify-1.0.4.tgz", + "integrity": "sha512-mtKKtCeSZMtWcc5hHJS6OlEGP7J9g7WN6vWCCZi2hCXFag/Zmjokh6WFFTQb9TuMnBcZpRjhhMQyOyglPCAahw==", + "dependencies": { + "browserify-package-json": "^1.0.0" + } + }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -7422,6 +8564,37 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/parse-torrent": { + "version": "9.1.5", + "resolved": "https://registry.npmjs.org/parse-torrent/-/parse-torrent-9.1.5.tgz", + "integrity": "sha512-K8FXRwTOaZMI0/xuv0dpng1MVHZRtMJ0jRWBJ3qZWVNTrC1MzWUxm9QwaXDz/2qPhV2XC4UIHI92IGHwseAwaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "bencode": "^2.0.2", + "blob-to-buffer": "^1.2.9", + "get-stdin": "^8.0.0", + "magnet-uri": "^6.2.0", + "queue-microtask": "^1.2.3", + "simple-get": "^4.0.1", + "simple-sha1": "^3.1.0" + }, + "bin": { + "parse-torrent": "bin/cmd.js" + } + }, "node_modules/parse5": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz", @@ -7512,6 +8685,11 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/piece-length": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/piece-length/-/piece-length-2.0.1.tgz", + "integrity": "sha512-dBILiDmm43y0JPISWEmVGKBETQjwJe6mSU9GND+P9KW0SJGUwoU/odyH1nbalOP9i8WSYuqf1lQnaj92Bhw+Ug==" + }, "node_modules/pirates": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz", @@ -7715,7 +8893,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "dev": true, "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" @@ -7748,7 +8925,6 @@ "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, "funding": [ { "type": "github", @@ -7764,11 +8940,39 @@ } ] }, + "node_modules/queue-tick": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/queue-tick/-/queue-tick-1.0.1.tgz", + "integrity": "sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==" + }, + "node_modules/random-access-file": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/random-access-file/-/random-access-file-2.2.1.tgz", + "integrity": "sha512-RGU0xmDqdOyEiynob1KYSeh8+9c9Td1MJ74GT1viMEYAn8SJ9oBtWCXLsYZukCF46yududHOdM449uRYbzBrZQ==", + "dependencies": { + "mkdirp-classic": "^0.5.2", + "random-access-storage": "^1.1.1" + } + }, + "node_modules/random-access-storage": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/random-access-storage/-/random-access-storage-1.4.3.tgz", + "integrity": "sha512-D5e2iIC5dNENWyBxsjhEnNOMCwZZ64TARK6dyMN+3g4OTC4MJxyjh9hKLjTGoNhDOPrgjI+YlFEHFnrp/cSnzQ==", + "dependencies": { + "events": "^3.3.0", + "inherits": "^2.0.3", + "queue-tick": "^1.0.0" + } + }, + "node_modules/random-iterate": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/random-iterate/-/random-iterate-1.0.1.tgz", + "integrity": "sha512-Jdsdnezu913Ot8qgKgSgs63XkAjEsnMcS1z+cC6D6TNXsUXsMxy0RpclF2pzGZTEiTXL9BiArdGTEexcv4nqcA==" + }, "node_modules/randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, "dependencies": { "safe-buffer": "^5.1.0" } @@ -7781,6 +8985,27 @@ "node": ">= 0.6" } }, + "node_modules/range-slice-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/range-slice-stream/-/range-slice-stream-2.0.0.tgz", + "integrity": "sha512-PPYLwZ63lXi6Tv2EZ8w3M4FzC0rVqvxivaOVS8pXSp5FMIHFnvi4MWHL3UdFLhwSy50aNtJsgjY0mBC6oFL26Q==", + "dependencies": { + "readable-stream": "^3.0.2" + } + }, + "node_modules/range-slice-stream/node_modules/readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/raw-body": { "version": "2.5.1", "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", @@ -7795,6 +9020,14 @@ "node": ">= 0.8" } }, + "node_modules/rc4": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/rc4/-/rc4-0.1.5.tgz", + "integrity": "sha512-xdDTNV90z5x5u25Oc871Xnvu7yAr4tV7Eluh0VSvrhUkry39q1k+zkz7xroqHbRq+8PiazySHJPArqifUvz9VA==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/react-is": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", @@ -7844,6 +9077,14 @@ "node": ">= 0.10" } }, + "node_modules/record-cache": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/record-cache/-/record-cache-1.2.0.tgz", + "integrity": "sha512-kyy3HWCez2WrotaL3O4fTn0rsIdfRKOdQQcEJ9KpvmKmbffKVvwsloX063EgRUlpJIXHiDQFhJcTbZequ2uTZw==", + "dependencies": { + "b4a": "^1.3.1" + } + }, "node_modules/reflect-metadata": { "version": "0.1.13", "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz", @@ -7861,6 +9102,32 @@ "url": "https://github.com/sponsors/mysticatea" } }, + "node_modules/render-media": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/render-media/-/render-media-4.1.0.tgz", + "integrity": "sha512-F5BMWDmgATEoyPCtKjmGNTGN1ghoZlfRQ3MJh8dS/MrvIUIxupiof/Y9uahChipXcqQ57twVbgMmyQmuO1vokw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "debug": "^4.2.0", + "is-ascii": "^1.0.0", + "mediasource": "^2.4.0", + "stream-to-blob-url": "^3.0.2", + "videostream": "^3.2.2" + } + }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -7993,7 +9260,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, "funding": [ { "type": "github", @@ -8012,6 +9278,52 @@ "queue-microtask": "^1.2.2" } }, + "node_modules/run-parallel-limit": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/run-parallel-limit/-/run-parallel-limit-1.1.0.tgz", + "integrity": "sha512-jJA7irRNM91jaKc3Hcl1npHsFLOXOoTkPCUL1JEa1R82O2miplXXRaGdjW/KM/98YQWDhJLiSs793CnXfblJUw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/run-series": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/run-series/-/run-series-1.1.9.tgz", + "integrity": "sha512-Arc4hUN896vjkqCYrUXquBFtRZdv1PfLbTYP71efP6butxyQ0kWpiNJyAgsxscmQg1cqvHY32/UCBzXedTpU2g==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/rusha": { + "version": "0.8.14", + "resolved": "https://registry.npmjs.org/rusha/-/rusha-0.8.14.tgz", + "integrity": "sha512-cLgakCUf6PedEu15t8kbsjnwIFFR2D4RfL+W3iWFJ4iac7z4B0ZI8fxy4R3J956kAI68HclCFGL8MPoUVC3qVA==" + }, "node_modules/rxjs": { "version": "7.5.7", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.5.7.tgz", @@ -8254,6 +9566,166 @@ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" }, + "node_modules/simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/simple-get": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", + "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "decompress-response": "^6.0.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, + "node_modules/simple-peer": { + "version": "9.11.1", + "resolved": "https://registry.npmjs.org/simple-peer/-/simple-peer-9.11.1.tgz", + "integrity": "sha512-D1SaWpOW8afq1CZGWB8xTfrT3FekjQmPValrqncJMX7QFl8YwhrPTZvMCANLtgBwwdS+7zURyqxDDEmY558tTw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "buffer": "^6.0.3", + "debug": "^4.3.2", + "err-code": "^3.0.1", + "get-browser-rtc": "^1.1.0", + "queue-microtask": "^1.2.3", + "randombytes": "^2.1.0", + "readable-stream": "^3.6.0" + } + }, + "node_modules/simple-peer/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/simple-peer/node_modules/err-code": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-3.0.1.tgz", + "integrity": "sha512-GiaH0KJUewYok+eeY05IIgjtAe4Yltygk9Wqp1V5yVWLdhf0hYZchRjNIT9bb0mSwRcIusT3cx7PJUf3zEIfUA==" + }, + "node_modules/simple-peer/node_modules/readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/simple-sha1": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/simple-sha1/-/simple-sha1-3.1.0.tgz", + "integrity": "sha512-ArTptMRC1v08H8ihPD6l0wesKvMfF9e8XL5rIHPanI7kGOsSsbY514MwVu6X1PITHCTB2F08zB7cyEbfc4wQjg==", + "dependencies": { + "queue-microtask": "^1.2.2", + "rusha": "^0.8.13" + } + }, + "node_modules/simple-websocket": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/simple-websocket/-/simple-websocket-9.1.0.tgz", + "integrity": "sha512-8MJPnjRN6A8UCp1I+H/dSFyjwJhp6wta4hsVRhjf8w9qBHRzxYt14RaOcjvQnhD1N4yKOddEjflwMnQM4VtXjQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "debug": "^4.3.1", + "queue-microtask": "^1.2.2", + "randombytes": "^2.1.0", + "readable-stream": "^3.6.0", + "ws": "^7.4.2" + } + }, + "node_modules/simple-websocket/node_modules/readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/sisteransi": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", @@ -8273,7 +9745,6 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", - "optional": true, "engines": { "node": ">= 6.0.0", "npm": ">= 3.0.0" @@ -8283,7 +9754,6 @@ "version": "2.7.1", "resolved": "https://registry.npmjs.org/socks/-/socks-2.7.1.tgz", "integrity": "sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ==", - "optional": true, "dependencies": { "ip": "^2.0.0", "smart-buffer": "^4.2.0" @@ -8341,6 +9811,31 @@ "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", "dev": true }, + "node_modules/speed-limiter": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/speed-limiter/-/speed-limiter-1.0.2.tgz", + "integrity": "sha512-Ax+TbUOho84bWUc3AKqWtkIvAIVws7d6QI4oJkgH4yQ5Yil+lR3vjd/7qd51dHKGzS5bFxg0++QwyNRN7s6rZA==", + "dependencies": { + "limiter": "^1.1.5", + "streamx": "^2.10.3" + } + }, + "node_modules/speedometer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/speedometer/-/speedometer-1.1.0.tgz", + "integrity": "sha512-z/wAiTESw2XVPssY2XRcme4niTc4S5FkkJ4gknudtVoc33Zil8TdTxHy5torRcgqMqksJV2Yz8HQcvtbsnw0mQ==" + }, + "node_modules/split": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz", + "integrity": "sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==", + "dependencies": { + "through": "2" + }, + "engines": { + "node": "*" + } + }, "node_modules/sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", @@ -8415,6 +9910,72 @@ "node": ">= 0.8" } }, + "node_modules/stream-to-blob": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/stream-to-blob/-/stream-to-blob-2.0.1.tgz", + "integrity": "sha512-GXlqXt3svqwIVWoICenix5Poxi4KbCF0BdXXUbpU1X4vq1V8wmjiEIU3aFJzCGNFpKxfbnG0uoowS3nKUgSPYg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "engines": { + "node": ">=8" + } + }, + "node_modules/stream-to-blob-url": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/stream-to-blob-url/-/stream-to-blob-url-3.0.2.tgz", + "integrity": "sha512-PS6wT2ZyyR38Cy+lE6PBEI1ZmO2HdzZoLeDGG0zZbYikCZd0dh8FUoSeFzgWLItpBYw1WJmPVRLpykRV+lAWLQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "stream-to-blob": "^2.0.0" + } + }, + "node_modules/stream-with-known-length-to-buffer": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/stream-with-known-length-to-buffer/-/stream-with-known-length-to-buffer-1.0.4.tgz", + "integrity": "sha512-ztP79ug6S+I7td0Nd2GBeIKCm+vA54c+e60FY87metz5n/l6ydPELd2lxsljz8OpIhsRM9HkIiAwz85+S5G5/A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "once": "^1.4.0" + } + }, "node_modules/streamsearch": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", @@ -8423,6 +9984,15 @@ "node": ">=10.0.0" } }, + "node_modules/streamx": { + "version": "2.13.2", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.13.2.tgz", + "integrity": "sha512-+TWqixPhGDXEG9L/XczSbhfkmwAtGs3BJX5QNU6cvno+pOLKeszByWcnaTu6dg8efsTYqR8ZZuXWHhZfgrxMvA==", + "dependencies": { + "fast-fifo": "^1.1.0", + "queue-tick": "^1.0.1" + } + }, "node_modules/string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", @@ -8462,6 +10032,23 @@ "node": ">=8" } }, + "node_modules/string2compact": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/string2compact/-/string2compact-1.3.2.tgz", + "integrity": "sha512-3XUxUgwhj7Eqh2djae35QHZZT4mN3fsO7kagZhSGmhhlrQagVvWSFuuFIWnpxFS0CdTB2PlQcaL16RDi14I8uw==", + "dependencies": { + "addr-to-ip-port": "^1.0.1", + "ipaddr.js": "^2.0.0" + } + }, + "node_modules/string2compact/node_modules/ipaddr.js": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.0.1.tgz", + "integrity": "sha512-1qTgH9NG+IIJ4yfKs2e6Pp1bZg8wbDbKHT21HrLIeYBTRLgMYKnMTPAuI3Lcs61nfx5h1xlXnbJtH1kX5/d/ng==", + "engines": { + "node": ">= 10" + } + }, "node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -8783,11 +10370,18 @@ "node": ">=0.8" } }, + "node_modules/thirty-two": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/thirty-two/-/thirty-two-1.0.2.tgz", + "integrity": "sha512-OEI0IWCe+Dw46019YLl6V10Us5bi574EvlJEOcAkB29IzQ/mYD1A6RyNHLjZPiHCmuodxvgF6U+vZO1L15lxVA==", + "engines": { + "node": ">=0.2.6" + } + }, "node_modules/through": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", - "dev": true + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==" }, "node_modules/through2": { "version": "2.0.5", @@ -8799,6 +10393,22 @@ "xtend": "~4.0.1" } }, + "node_modules/throughput": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/throughput/-/throughput-1.0.1.tgz", + "integrity": "sha512-4Mvv5P4xyVz6RM07wS3tGyZ/kPAiKtLeqznq3hK4pxDiTUSyQ5xeFlBiWxflCWexvSnxo2aAfedzKajJqihz4Q==" + }, + "node_modules/thunky": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", + "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==" + }, + "node_modules/timeout-refresh": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/timeout-refresh/-/timeout-refresh-1.0.3.tgz", + "integrity": "sha512-Mz0CX4vBGM5lj8ttbIFt7o4ZMxk/9rgudJRh76EvB7xXZMur7T/cjRiH2w4Fmkq0zxf2QpM8IFvOSRn8FEu3gA==", + "optional": true + }, "node_modules/tmp": { "version": "0.0.33", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", @@ -8817,6 +10427,11 @@ "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", "dev": true }, + "node_modules/to-arraybuffer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz", + "integrity": "sha512-okFlQcoGTi4LQBG/PgSYblw9VOyptsz2KJZqc6qtgGdes8VktzUQkj4BI2blit072iS8VODNcMA+tvnS9dnuMA==" + }, "node_modules/to-fast-properties": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", @@ -8846,16 +10461,86 @@ "node": ">=0.6" } }, + "node_modules/torrent-discovery": { + "version": "9.4.15", + "resolved": "https://registry.npmjs.org/torrent-discovery/-/torrent-discovery-9.4.15.tgz", + "integrity": "sha512-71nx+TpLaF27mbsSj/tZTr588Dfk7XVzx+Rf1+nrxfXqe8qn5dIlRhgA+yY4cg8Ib69vWwkKFhAzbRqg8z42aw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "bittorrent-dht": "^10.0.7", + "bittorrent-lsd": "^1.1.1", + "bittorrent-tracker": "^9.19.0", + "debug": "^4.3.4", + "run-parallel": "^1.2.0" + } + }, "node_modules/torrent-name-parser": { "version": "0.6.5", "resolved": "https://registry.npmjs.org/torrent-name-parser/-/torrent-name-parser-0.6.5.tgz", "integrity": "sha512-ao92tzD6DjYKN0dedEZxfylL4ZWYaYFgPktKxkEmlsLrF/ia41Sg+l3rJ2XLJufQUR3uTbd7bYMqxbh/kxb1pQ==" }, + "node_modules/torrent-piece": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/torrent-piece/-/torrent-piece-2.0.1.tgz", + "integrity": "sha512-JLSOyvQVLI6JTWqioY4vFL0JkEUKQcaHQsU3loxkCvPTSttw8ePs2tFwsP4XIjw99Fz8EdOzt/4faykcbnPbCQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/tr46": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" }, + "node_modules/transmission": { + "version": "0.4.10", + "resolved": "https://registry.npmjs.org/transmission/-/transmission-0.4.10.tgz", + "integrity": "sha512-GXss/sNTuHqgvy7TTE3soF8wuS8BzqZO3HV3mc61tg7vVT5u5Gr5kja82QF23GfGeZjdzFUeJGwJrEHU1DWq8Q==", + "dependencies": { + "async": "^2.1.4", + "yargs": "^1.3.3" + }, + "bin": { + "node-transmission": "bin/node-transmission" + } + }, + "node_modules/transmission/node_modules/async": { + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", + "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", + "dependencies": { + "lodash": "^4.17.14" + } + }, + "node_modules/transmission/node_modules/yargs": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-1.3.3.tgz", + "integrity": "sha512-7OGt4xXoWJQh5ulgZ78rKaqY7dNWbjfK+UKxGcIlaM2j7C4fqGchyv8CPvEWdRPrHp6Ula/YU8yGRpYGOHrI+g==" + }, "node_modules/tree-kill": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", @@ -9292,6 +10977,14 @@ "node": ">=4.2.0" } }, + "node_modules/uint64be": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/uint64be/-/uint64be-2.0.2.tgz", + "integrity": "sha512-9QqdvpGQTXgxthP+lY4e/gIBy+RuqcBaC6JVwT5I3bDLgT/btL6twZMR0pI3/Fgah9G/pdwzIprE5gL6v9UvyQ==", + "dependencies": { + "buffer-alloc": "^1.1.0" + } + }, "node_modules/unique-filename": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz", @@ -9319,6 +11012,17 @@ "node": ">= 10.0.0" } }, + "node_modules/unordered-array-remove": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unordered-array-remove/-/unordered-array-remove-1.0.2.tgz", + "integrity": "sha512-45YsfD6svkgaCBNyvD+dFHm4qFX9g3wRSIVgWVPtm2OCnphvPxzJoe20ATsiNpNJrmzHifnxm+BN5F7gFT/4gw==" + }, + "node_modules/unordered-set": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/unordered-set/-/unordered-set-2.0.1.tgz", + "integrity": "sha512-eUmNTPzdx+q/WvOHW0bgGYLWvWHNT3PTKEQLg0MAQhc0AHASHVHoP/9YytYd4RBVariqno/mEUhVZN98CmD7bg==", + "optional": true + }, "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -9371,6 +11075,68 @@ "punycode": "^2.1.0" } }, + "node_modules/ut_metadata": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ut_metadata/-/ut_metadata-3.5.2.tgz", + "integrity": "sha512-3XZZuJSeoIUyMYSuDbTbVtP4KAVGHPfU8nmHFkr8LJc+THCaUXwnu/2AV+LCSLarET/hL9IlbNfYTGrt6fOVuQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "bencode": "^2.0.1", + "bitfield": "^4.0.0", + "debug": "^4.2.0", + "simple-sha1": "^3.0.1" + } + }, + "node_modules/ut_pex": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/ut_pex/-/ut_pex-3.0.2.tgz", + "integrity": "sha512-3xM88t+AVU5GR0sIY3tmRMLUS+YKiwStc7U7+ZFQ+UHQpX7BjVJOomhmtm0Bs+8R2n812Dt2ymXm01EqDrOOpQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "bencode": "^2.0.2", + "compact2string": "^1.4.1", + "string2compact": "^1.3.2" + } + }, + "node_modules/utf-8-validate": { + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.10.tgz", + "integrity": "sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==", + "hasInstallScript": true, + "optional": true, + "dependencies": { + "node-gyp-build": "^4.3.0" + }, + "engines": { + "node": ">=6.14.2" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -9384,6 +11150,40 @@ "node": ">= 0.4.0" } }, + "node_modules/utp-native": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/utp-native/-/utp-native-2.5.3.tgz", + "integrity": "sha512-sWTrWYXPhhWJh+cS2baPzhaZc89zwlWCfwSthUjGhLkZztyPhcQllo+XVVCbNGi7dhyRlxkWxN4NKU6FbA9Y8w==", + "hasInstallScript": true, + "optional": true, + "dependencies": { + "napi-macros": "^2.0.0", + "node-gyp-build": "^4.2.0", + "readable-stream": "^3.0.2", + "timeout-refresh": "^1.0.0", + "unordered-set": "^2.0.1" + }, + "bin": { + "ucat": "ucat.js" + }, + "engines": { + "node": ">=8.12" + } + }, + "node_modules/utp-native/node_modules/readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "optional": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/uuid": { "version": "9.0.0", "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", @@ -9420,6 +11220,33 @@ "node": ">= 0.8" } }, + "node_modules/videostream": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/videostream/-/videostream-3.2.2.tgz", + "integrity": "sha512-4tz23yGGeATmbzj/ZnUm6wgQ4E1lzmMXu2mUA/c0G6adtWKxm1Di5YejdZdRsK6SdkLjKjhplFFYT7r+UUDKvA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "binary-search": "^1.3.4", + "mediasource": "^2.2.2", + "mp4-box-encoding": "^1.3.0", + "mp4-stream": "^3.0.0", + "pump": "^3.0.0", + "range-slice-stream": "^2.0.0" + } + }, "node_modules/walker": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", @@ -9522,6 +11349,88 @@ "node": ">=10.13.0" } }, + "node_modules/webtorrent": { + "version": "1.9.7", + "resolved": "https://registry.npmjs.org/webtorrent/-/webtorrent-1.9.7.tgz", + "integrity": "sha512-N+hRuVctWviTAYem/sI6tuFP2J/Rn3/ETEh++7GnJv6Oro49kDjcPuz1W6s+vfS65xKr3Eh4HMuxf3hH82LGfg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "@webtorrent/http-node": "^1.3.0", + "addr-to-ip-port": "^1.5.4", + "bitfield": "^4.1.0", + "bittorrent-dht": "^10.0.7", + "bittorrent-protocol": "^3.5.5", + "cache-chunk-store": "^3.2.2", + "chrome-net": "^3.3.4", + "chunk-store-stream": "^4.3.0", + "cpus": "^1.0.3", + "create-torrent": "^5.0.9", + "debug": "^4.3.4", + "end-of-stream": "^1.4.4", + "escape-html": "^1.0.3", + "fast-blob-stream": "^1.1.1", + "fs-chunk-store": "^3.0.1", + "immediate-chunk-store": "^2.2.0", + "join-async-iterator": "^1.1.1", + "load-ip-set": "^2.2.1", + "lt_donthave": "^1.0.1", + "memory-chunk-store": "^1.3.5", + "mime": "^3.0.0", + "package-json-versionify": "^1.0.4", + "parse-torrent": "^9.1.5", + "pump": "^3.0.0", + "queue-microtask": "^1.2.3", + "random-iterate": "^1.0.1", + "randombytes": "^2.1.0", + "range-parser": "^1.2.1", + "render-media": "^4.1.0", + "run-parallel": "^1.2.0", + "run-parallel-limit": "^1.1.0", + "simple-concat": "^1.0.1", + "simple-get": "^4.0.1", + "simple-peer": "^9.11.1", + "simple-sha1": "^3.1.0", + "speed-limiter": "^1.0.2", + "stream-with-known-length-to-buffer": "^1.0.4", + "streamx": "^2.12.5", + "throughput": "^1.0.1", + "torrent-discovery": "^9.4.15", + "torrent-piece": "^2.0.1", + "unordered-array-remove": "^1.0.2", + "ut_metadata": "^3.5.2", + "ut_pex": "^3.0.2" + }, + "engines": { + "node": ">=14" + }, + "optionalDependencies": { + "utp-native": "^2.5.3" + } + }, + "node_modules/webtorrent/node_modules/mime": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", + "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/whatwg-url": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", @@ -9659,6 +11568,26 @@ "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, + "node_modules/ws": { + "version": "7.5.9", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", + "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/xml2js": { "version": "0.4.23", "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz", @@ -11256,6 +13185,15 @@ "@babel/types": "^7.3.0" } }, + "@types/bittorrent-protocol": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@types/bittorrent-protocol/-/bittorrent-protocol-3.1.2.tgz", + "integrity": "sha512-7k9nivNeG7Sc8wVuBs+XjBp2u7pH8tqW3BB93/SAg3xht/cZEK+Rqkj79xSyJqyj86eA0F6n85EKkkyGki8afg==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/body-parser": { "version": "1.19.2", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz", @@ -11266,6 +13204,15 @@ "@types/node": "*" } }, + "@types/cheerio": { + "version": "0.22.31", + "resolved": "https://registry.npmjs.org/@types/cheerio/-/cheerio-0.22.31.tgz", + "integrity": "sha512-Kt7Cdjjdi2XWSfrZ53v4Of0wG3ZcmaegFXjMmz9tfNrZSkzzo36G0AL1YqSdcIA78Etjt6E609pt5h1xnQkPUw==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/connect": { "version": "3.4.35", "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", @@ -11334,6 +13281,7 @@ "version": "2.1.20", "resolved": "https://registry.npmjs.org/@types/fluent-ffmpeg/-/fluent-ffmpeg-2.1.20.tgz", "integrity": "sha512-B+OvhCdJ3LgEq2PhvWNOiB/EfwnXLElfMCgc4Z1K5zXgSfo9I6uGKwR/lqmNPFQuebNnes7re3gqkV77SyypLg==", + "dev": true, "requires": { "@types/node": "*" } @@ -11387,6 +13335,15 @@ "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", "dev": true }, + "@types/magnet-uri": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/@types/magnet-uri/-/magnet-uri-5.1.3.tgz", + "integrity": "sha512-FvJN1yYdLhvU6zWJ2YnWQ2GnpFLsA8bt+85WY0tLh6ehzGNrvBorjlcc53/zY43r/IKn+ctFs1nt7andwGnQCQ==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/mime": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-3.0.1.tgz", @@ -11396,7 +13353,8 @@ "@types/node": { "version": "16.18.4", "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.4.tgz", - "integrity": "sha512-9qGjJ5GyShZjUfx2ArBIGM+xExdfLvvaCyQR0t6yRXKPcWCVYF/WemtX/uIU3r7FYECXRXkIiw2Vnhn6y8d+pw==" + "integrity": "sha512-9qGjJ5GyShZjUfx2ArBIGM+xExdfLvvaCyQR0t6yRXKPcWCVYF/WemtX/uIU3r7FYECXRXkIiw2Vnhn6y8d+pw==", + "devOptional": true }, "@types/parse-json": { "version": "4.0.0", @@ -11404,6 +13362,26 @@ "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", "dev": true }, + "@types/parse-torrent": { + "version": "5.8.4", + "resolved": "https://registry.npmjs.org/@types/parse-torrent/-/parse-torrent-5.8.4.tgz", + "integrity": "sha512-FdKs5yN5iYO5Cu9gVz1Zl30CbZe6HTsqloWmCf+LfbImgSzlsUkov2+npQWCQSQ3zi/a2G5C824K0UpZ2sRufA==", + "dev": true, + "requires": { + "@types/magnet-uri": "*", + "@types/node": "*", + "@types/parse-torrent-file": "*" + } + }, + "@types/parse-torrent-file": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@types/parse-torrent-file/-/parse-torrent-file-4.0.3.tgz", + "integrity": "sha512-dFkPnJPKiFWiGX+HXmyTVt2js3k0d9dThmUxX8nfGC22hbyZ5BTmetsEl45sQhHLcFo43njVrIKMXM3F1ahXRw==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/prettier": { "version": "2.7.1", "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.1.tgz", @@ -11438,6 +13416,15 @@ "@types/node": "*" } }, + "@types/simple-peer": { + "version": "9.11.5", + "resolved": "https://registry.npmjs.org/@types/simple-peer/-/simple-peer-9.11.5.tgz", + "integrity": "sha512-haXgWcAa3Y3Sn+T8lzkE4ErQUpYzhW6Cz2lh00RhQTyWt+xZ3s87wJPztUxlqSdFRqGhe2MQIBd0XsyHP3No4w==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/stack-utils": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", @@ -11463,6 +13450,18 @@ "@types/superagent": "*" } }, + "@types/webtorrent": { + "version": "0.109.3", + "resolved": "https://registry.npmjs.org/@types/webtorrent/-/webtorrent-0.109.3.tgz", + "integrity": "sha512-EJLsxMEcEjPXHcBqL6TRAbUwIpxAul5ULrXHJ0zwig7Oe70FS6dAzCWLq4MBafX3QrQG1DzGAS0fS8iJEOjD0g==", + "dev": true, + "requires": { + "@types/bittorrent-protocol": "*", + "@types/node": "*", + "@types/parse-torrent": "*", + "@types/simple-peer": "*" + } + }, "@types/yargs": { "version": "17.0.15", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.15.tgz", @@ -11722,6 +13721,15 @@ "@xtuc/long": "4.2.2" } }, + "@webtorrent/http-node": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@webtorrent/http-node/-/http-node-1.3.0.tgz", + "integrity": "sha512-GWZQKroPES4z91Ijx6zsOsb7+USOxjy66s8AoTWg0HiBBdfnbtf9aeh3Uav0MgYn4BL8Q7tVSUpd0gGpngKGEQ==", + "requires": { + "freelist": "^1.0.3", + "http-parser-js": "^0.4.3" + } + }, "@xtuc/ieee754": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", @@ -11774,6 +13782,11 @@ "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", "devOptional": true }, + "addr-to-ip-port": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/addr-to-ip-port/-/addr-to-ip-port-1.5.4.tgz", + "integrity": "sha512-ByxmJgv8vjmDcl3IDToxL2yrWFrRtFpZAToY0f46XFXl8zS081t7El5MXIodwm7RC6DhHBRoOSMLFSPKCtHukg==" + }, "agent-base": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", @@ -11967,6 +13980,11 @@ "proxy-from-env": "^1.1.0" } }, + "b4a": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.1.tgz", + "integrity": "sha512-AsKjNhz72yxteo/0EtQEiwkMUgk/tGmycXlbG4g3Ard2/ULtNLUykGOkeK0egmN27h0xMAhb76jYccW+XTBExA==" + }, "babel-jest": { "version": "28.1.3", "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-28.1.3.tgz", @@ -12059,12 +14077,134 @@ "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" }, + "bencode": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/bencode/-/bencode-2.0.3.tgz", + "integrity": "sha512-D/vrAD4dLVX23NalHwb8dSvsUsxeRPO8Y7ToKA015JQYq69MLDOMkC0uGZYA/MPpltLO8rt8eqFC2j8DxjTZ/w==" + }, + "bep53-range": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/bep53-range/-/bep53-range-1.1.1.tgz", + "integrity": "sha512-ct6s33iiwRCUPp9KXnJ4QMWDgHIgaw36caK/5XEQ9L8dCzSQlJt1Vk6VmHh1VD4AlGCAI4C2zmtfItifBBPrhQ==" + }, "binary-extensions": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", "dev": true }, + "binary-search": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/binary-search/-/binary-search-1.3.6.tgz", + "integrity": "sha512-nbE1WxOTTrUWIfsfZ4aHGYu5DOuNkbxGokjV6Z2kxfJK3uaAb8zNK1muzOeipoLHZjInT4Br88BHpzevc681xA==" + }, + "bitfield": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bitfield/-/bitfield-4.1.0.tgz", + "integrity": "sha512-6cEDG3K+PK9f+B7WyhWYjp09bqSa+uaAaecVA7Y5giFixyVe1s6HKGnvOqYNR4Mi4fBMjfDPLBpHkKvzzgP7kg==" + }, + "bittorrent-dht": { + "version": "10.0.7", + "resolved": "https://registry.npmjs.org/bittorrent-dht/-/bittorrent-dht-10.0.7.tgz", + "integrity": "sha512-o6elCANGteECXz82LFqG1Ov2fG4uNzfUU7pBMx9ixxKUh99ZXNrhbiNLRNN2F2vBnqKSN7SHlUW4LJ5Z2u1eKw==", + "requires": { + "bencode": "^2.0.3", + "debug": "^4.3.4", + "k-bucket": "^5.1.0", + "k-rpc": "^5.1.0", + "last-one-wins": "^1.0.4", + "lru": "^3.1.0", + "randombytes": "^2.1.0", + "record-cache": "^1.2.0", + "simple-sha1": "^3.1.0" + } + }, + "bittorrent-lsd": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/bittorrent-lsd/-/bittorrent-lsd-1.1.1.tgz", + "integrity": "sha512-dWxU2Mr2lU6jzIKgZrTsXgeXDCIcYpR1b6f2n89fn7juwPAYbNU04OgWjcQPLiNliY0filsX5CQAWntVErpk+Q==", + "requires": { + "chrome-dgram": "^3.0.6", + "debug": "^4.2.0" + } + }, + "bittorrent-peerid": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/bittorrent-peerid/-/bittorrent-peerid-1.3.6.tgz", + "integrity": "sha512-VyLcUjVMEOdSpHaCG/7odvCdLbAB1y3l9A2V6WIje24uV7FkJPrQrH/RrlFmKxP89pFVDEnE+YlHaFujlFIZsg==" + }, + "bittorrent-protocol": { + "version": "3.5.5", + "resolved": "https://registry.npmjs.org/bittorrent-protocol/-/bittorrent-protocol-3.5.5.tgz", + "integrity": "sha512-cfzO//WtJGNLHXS58a4exJCSq1U0dkP2DZCQxgADInYFPdOfV1EmtpEN9toLOluVCXJRYAdwW5H6Li/hrn697A==", + "requires": { + "bencode": "^2.0.2", + "bitfield": "^4.0.0", + "debug": "^4.3.4", + "randombytes": "^2.1.0", + "rc4": "^0.1.5", + "readable-stream": "^3.6.0", + "simple-sha1": "^3.1.0", + "speedometer": "^1.1.0", + "unordered-array-remove": "^1.0.2" + }, + "dependencies": { + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } + } + }, + "bittorrent-tracker": { + "version": "9.19.0", + "resolved": "https://registry.npmjs.org/bittorrent-tracker/-/bittorrent-tracker-9.19.0.tgz", + "integrity": "sha512-09d0aD2b+MC+zWvWajkUAKkYMynYW4tMbTKiRSthKtJZbafzEoNQSUHyND24SoCe3ZOb2fKfa6fu2INAESL9wA==", + "requires": { + "bencode": "^2.0.1", + "bittorrent-peerid": "^1.3.3", + "bn.js": "^5.2.0", + "bufferutil": "^4.0.3", + "chrome-dgram": "^3.0.6", + "clone": "^2.0.0", + "compact2string": "^1.4.1", + "debug": "^4.1.1", + "ip": "^1.1.5", + "lru": "^3.1.0", + "minimist": "^1.2.5", + "once": "^1.4.0", + "queue-microtask": "^1.2.3", + "random-iterate": "^1.0.1", + "randombytes": "^2.1.0", + "run-parallel": "^1.2.0", + "run-series": "^1.1.9", + "simple-get": "^4.0.0", + "simple-peer": "^9.11.0", + "simple-websocket": "^9.1.0", + "socks": "^2.0.0", + "string2compact": "^1.3.0", + "unordered-array-remove": "^1.0.2", + "utf-8-validate": "^5.0.5", + "ws": "^7.4.5" + }, + "dependencies": { + "clone": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==" + }, + "ip": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.8.tgz", + "integrity": "sha512-PuExPYUiu6qMBQb4l06ecm6T6ujzhmh+MeJcW9wa89PoAz5pvd4zPgN5WJV104mb6S2T1AwNIAaB70JNrLQWhg==" + } + } + }, "bl": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", @@ -12089,6 +14229,41 @@ } } }, + "blob-to-buffer": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/blob-to-buffer/-/blob-to-buffer-1.2.9.tgz", + "integrity": "sha512-BF033y5fN6OCofD3vgHmNtwZWRcq9NLyyxyILx9hfMy1sXYy4ojFl765hJ2lP0YaN2fuxPaLO2Vzzoxy0FLFFA==" + }, + "block-iterator": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/block-iterator/-/block-iterator-1.1.1.tgz", + "integrity": "sha512-DrjdVWZemVO4iBf4tiOXjUrY5cNesjzy0t7sIiu2rdl8cOCHRxAgKjSJFc3vBZYYMMmshUAxajl8QQh/uxXTKQ==" + }, + "block-stream2": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/block-stream2/-/block-stream2-2.1.0.tgz", + "integrity": "sha512-suhjmLI57Ewpmq00qaygS8UgEq2ly2PCItenIyhMqVjo4t4pGzqMvfgJuX8iWTeSDdfSSqS6j38fL4ToNL7Pfg==", + "requires": { + "readable-stream": "^3.4.0" + }, + "dependencies": { + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } + } + }, + "bn.js": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", + "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==" + }, "body-parser": { "version": "1.20.1", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", @@ -12123,6 +14298,11 @@ } } }, + "boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==" + }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -12141,6 +14321,11 @@ "fill-range": "^7.0.1" } }, + "browserify-package-json": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/browserify-package-json/-/browserify-package-json-1.0.1.tgz", + "integrity": "sha512-CikZxJGNyNOBERbeALo0NUUeJgHs5NyEvuYChX/PcsBV91TAvEq4hYDaWSenSieT8XwAutNnS3FGvyzIMOughQ==" + }, "browserslist": { "version": "4.21.4", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.4.tgz", @@ -12181,11 +14366,39 @@ "ieee754": "^1.1.13" } }, + "buffer-alloc": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz", + "integrity": "sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==", + "requires": { + "buffer-alloc-unsafe": "^1.1.0", + "buffer-fill": "^1.0.0" + } + }, + "buffer-alloc-unsafe": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz", + "integrity": "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==" + }, + "buffer-fill": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz", + "integrity": "sha512-T7zexNBwiiaCOGDg9xNX9PBmjrubblRkENuptryuI64URkXDFum9il/JGL8Lm8wYfAXpredVXXZz7eMHilimiQ==" + }, "buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" }, + "bufferutil": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.7.tgz", + "integrity": "sha512-kukuqc39WOHtdxtw4UScxF/WVnMFVSQVKhtx3AjZJzhd0RGZZldcrfSEbVsWWe6KNH253574cq5F+wpv0G9pJw==", + "optional": true, + "requires": { + "node-gyp-build": "^4.3.0" + } + }, "busboy": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", @@ -12233,6 +14446,15 @@ } } }, + "cache-chunk-store": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/cache-chunk-store/-/cache-chunk-store-3.2.2.tgz", + "integrity": "sha512-2lJdWbgHFFxcSth9s2wpId3CR3v1YC63KjP4T9WhpW7LWlY7Hiiei3QwwqzkWqlJTfR8lSy9F5kRQECeyj+yQA==", + "requires": { + "lru": "^3.1.0", + "queue-microtask": "^1.2.3" + } + }, "call-bind": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", @@ -12282,6 +14504,52 @@ "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", "dev": true }, + "cheerio": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.12.tgz", + "integrity": "sha512-VqR8m68vM46BNnuZ5NtnGBKIE/DfN0cRIzg9n40EIq9NOv90ayxLBXA8fXC5gquFRGJSTRqBq25Jt2ECLR431Q==", + "requires": { + "cheerio-select": "^2.1.0", + "dom-serializer": "^2.0.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1", + "htmlparser2": "^8.0.1", + "parse5": "^7.0.0", + "parse5-htmlparser2-tree-adapter": "^7.0.0" + }, + "dependencies": { + "parse5": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", + "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", + "requires": { + "entities": "^4.4.0" + } + }, + "parse5-htmlparser2-tree-adapter": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.0.0.tgz", + "integrity": "sha512-B77tOZrqqfUfnVcOrUvfdLbz4pu4RopLD/4vmu3HUPswwTA8OH0EMW9BlWR2B0RCoiZRAHEUu7IxeP1Pd1UU+g==", + "requires": { + "domhandler": "^5.0.2", + "parse5": "^7.0.0" + } + } + } + }, + "cheerio-select": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz", + "integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==", + "requires": { + "boolbase": "^1.0.0", + "css-select": "^5.1.0", + "css-what": "^6.1.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1" + } + }, "chokidar": { "version": "3.5.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", @@ -12303,12 +14571,58 @@ "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==" }, + "chrome-dgram": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/chrome-dgram/-/chrome-dgram-3.0.6.tgz", + "integrity": "sha512-bqBsUuaOiXiqxXt/zA/jukNJJ4oaOtc7ciwqJpZVEaaXwwxqgI2/ZdG02vXYWUhHGziDlvGMQWk0qObgJwVYKA==", + "requires": { + "inherits": "^2.0.4", + "run-series": "^1.1.9" + } + }, + "chrome-dns": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/chrome-dns/-/chrome-dns-1.0.1.tgz", + "integrity": "sha512-HqsYJgIc8ljJJOqOzLphjAs79EUuWSX3nzZi2LNkzlw3GIzAeZbaSektC8iT/tKvLqZq8yl1GJu5o6doA4TRbg==", + "requires": { + "chrome-net": "^3.3.2" + } + }, + "chrome-net": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/chrome-net/-/chrome-net-3.3.4.tgz", + "integrity": "sha512-Jzy2EnzmE+ligqIZUsmWnck9RBXLuUy6CaKyuNMtowFG3ZvLt8d+WBJCTPEludV0DHpIKjAOlwjFmTaEdfdWCw==", + "requires": { + "inherits": "^2.0.1" + } + }, "chrome-trace-event": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", "dev": true }, + "chunk-store-stream": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/chunk-store-stream/-/chunk-store-stream-4.3.0.tgz", + "integrity": "sha512-qby+/RXoiMoTVtPiylWZt7KFF1jy6M829TzMi2hxZtBIH9ptV19wxcft6zGiXLokJgCbuZPGNGab6DWHqiSEKw==", + "requires": { + "block-stream2": "^2.0.0", + "readable-stream": "^3.6.0" + }, + "dependencies": { + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } + } + }, "ci-info": { "version": "3.7.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.7.0.tgz", @@ -12471,6 +14785,14 @@ "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", "dev": true }, + "compact2string": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/compact2string/-/compact2string-1.4.1.tgz", + "integrity": "sha512-3D+EY5nsRhqnOwDxveBv5T8wGo4DEvYxjDtPGmdOX+gfr5gE92c2RC0w2wa+xEefm07QuVqqcF3nZJUZ92l/og==", + "requires": { + "ipaddr.js": ">= 0.1.5" + } + }, "component-emitter": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", @@ -12620,12 +14942,35 @@ "yaml": "^1.10.0" } }, + "cpus": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/cpus/-/cpus-1.0.3.tgz", + "integrity": "sha512-PXHBvGLuL69u55IkLa5e5838fLhIMHxmkV4ge42a8alGyn7BtawYgI0hQ849EedvtHIOLNNH3i6eQU1BiE9SUA==" + }, "create-require": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", "devOptional": true }, + "create-torrent": { + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/create-torrent/-/create-torrent-5.0.9.tgz", + "integrity": "sha512-WQ/bMe+aCBSa5EonIkgw7CTM/1JnJDQuLJhA78omSWvuEbXDwaUy0rG3a+IYt+EiO+rdTLxdsBwrsn/wfWOMQA==", + "requires": { + "bencode": "^2.0.3", + "block-iterator": "^1.0.1", + "fast-readable-async-iterator": "^1.1.1", + "is-file": "^1.0.0", + "join-async-iterator": "^1.1.1", + "junk": "^3.1.0", + "minimist": "^1.2.7", + "piece-length": "^2.0.1", + "queue-microtask": "^1.2.3", + "run-parallel": "^1.2.0", + "simple-sha1": "^3.1.0" + } + }, "cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -12637,6 +14982,23 @@ "which": "^2.0.1" } }, + "css-select": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", + "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", + "requires": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + } + }, + "css-what": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", + "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==" + }, "date-fns": { "version": "2.29.3", "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.29.3.tgz", @@ -12650,6 +15012,14 @@ "ms": "2.1.2" } }, + "decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "requires": { + "mimic-response": "^3.1.0" + } + }, "dedent": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", @@ -12748,6 +15118,39 @@ "esutils": "^2.0.2" } }, + "dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "requires": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + } + }, + "domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==" + }, + "domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "requires": { + "domelementtype": "^2.3.0" + } + }, + "domutils": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.0.1.tgz", + "integrity": "sha512-z08c1l761iKhDFtfXO04C7kTdPBLi41zwOZl00WS8b5eiaebNpY00HKbztwBq+e3vyqWNwWF3mP9YLUeqIrF+Q==", + "requires": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.1" + } + }, "dotenv": { "version": "16.0.3", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.3.tgz", @@ -12804,7 +15207,6 @@ "version": "1.4.4", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "dev": true, "requires": { "once": "^1.4.0" } @@ -12819,6 +15221,11 @@ "tapable": "^2.2.0" } }, + "entities": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.4.0.tgz", + "integrity": "sha512-oYp7156SP8LkeGD0GF85ad1X9Ai79WtRsZ2gxJqtBuzH+98YUV6jkHEKlZkMbcrjJjIVJNIDP/3WL9wQkoPbWA==" + }, "env-paths": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", @@ -13084,8 +15491,7 @@ "events": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", - "dev": true + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==" }, "execa": { "version": "5.1.1", @@ -13192,6 +15598,15 @@ "tmp": "^0.0.33" } }, + "fast-blob-stream": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/fast-blob-stream/-/fast-blob-stream-1.1.1.tgz", + "integrity": "sha512-wdRazMMeM2pl8hq1lFG8fzix8p1VLAJunTTE2RADiFBwbUfZwybUm6IwPrmMS7qTthiayr166NoXeqWe3hfR5w==", + "requires": { + "fast-readable-async-iterator": "^1.1.1", + "streamx": "^2.12.4" + } + }, "fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -13204,6 +15619,11 @@ "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==", "dev": true }, + "fast-fifo": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.1.0.tgz", + "integrity": "sha512-Kl29QoNbNvn4nhDsLYjyIAaIqaJB6rBx5p3sL9VjaefJ+eMFBWVZiaoguaoZfzEKr5RhAti0UgM8703akGPJ6g==" + }, "fast-glob": { "version": "3.2.12", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", @@ -13229,6 +15649,11 @@ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "dev": true }, + "fast-readable-async-iterator": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/fast-readable-async-iterator/-/fast-readable-async-iterator-1.1.1.tgz", + "integrity": "sha512-xEHkLUEmStETI+15zhglJLO9TjXxNkkp2ldEfYVZdcqxFhM172EfGl1irI6mVlTxXspYKH1/kjevnt/XSsPeFA==" + }, "fast-safe-stringify": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", @@ -13425,11 +15850,28 @@ "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==" }, + "freelist": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/freelist/-/freelist-1.0.3.tgz", + "integrity": "sha512-Ji7fEnMdZDGbS5oXElpRJsn9jPvBR8h/037D3bzreNmS8809cISq/2D9//JbA/TaZmkkN8cmecXwmQHmM+NHhg==" + }, "fresh": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==" }, + "fs-chunk-store": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/fs-chunk-store/-/fs-chunk-store-3.0.1.tgz", + "integrity": "sha512-YrOFuXtUJQBkOZ2QBXBoIrjLJ/TNTpEaGnxV+TmL1qaW5J4ah6lxMh/X9pb3To+hbaoT/pRuBXLkkqoavQoQFw==", + "requires": { + "queue-microtask": "^1.2.2", + "random-access-file": "^2.0.1", + "randombytes": "^2.0.3", + "run-parallel": "^1.1.2", + "thunky": "^1.0.1" + } + }, "fs-extra": { "version": "10.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", @@ -13494,6 +15936,11 @@ "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", "dev": true }, + "get-browser-rtc": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/get-browser-rtc/-/get-browser-rtc-1.1.0.tgz", + "integrity": "sha512-MghbMJ61EJrRsDe7w1Bvqt3ZsBuqhce5nrn/XAwgwOXhcsz53/ltdxOse1h/8eKXj5slzxdsz56g5rzOFSGwfQ==" + }, "get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", @@ -13515,6 +15962,11 @@ "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", "dev": true }, + "get-stdin": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-8.0.0.tgz", + "integrity": "sha512-sY22aA6xchAzprjyqmSEQv4UbAAzRN0L2dQB0NlN5acTTK9Don6nhoc3eAbUnpZiCANAMfd/+40kVdKfFygohg==" + }, "get-stream": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", @@ -13624,6 +16076,17 @@ "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", "dev": true }, + "htmlparser2": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.1.tgz", + "integrity": "sha512-4lVbmc1diZC7GUJQtRQ5yBAeUCL1exyMwmForWkRLnwyzWBFxN633SALPMGYaWZvKe9j1pRZJpauvmxENSp/EA==", + "requires": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "entities": "^4.3.0" + } + }, "http-cache-semantics": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", @@ -13642,6 +16105,11 @@ "toidentifier": "1.0.1" } }, + "http-parser-js": { + "version": "0.4.13", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.4.13.tgz", + "integrity": "sha512-u8u5ZaG0Tr/VvHlucK2ufMuOp4/5bvwgneXle+y228K5rMbJOlVjThONcaAw3ikAy8b2OO9RfEucdMHFz3UWMA==" + }, "http-proxy-agent": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", @@ -13696,6 +16164,14 @@ "integrity": "sha512-d2qQLzTJ9WxQftPAuEQpSPmKqzxePjzVbpAVv62AQ64NTL+wR4JkrVqR/LqFsFEUsHDAiId52mJteHDFuDkElA==", "dev": true }, + "immediate-chunk-store": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/immediate-chunk-store/-/immediate-chunk-store-2.2.0.tgz", + "integrity": "sha512-1bHBna0hCa6arRXicu91IiL9RvvkbNYLVq+mzWdaLGZC3hXvX4doh8e1dLhMKez5siu63CYgO5NrGJbRX5lbPA==", + "requires": { + "queue-microtask": "^1.2.3" + } + }, "import-fresh": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", @@ -13805,8 +16281,22 @@ "ip": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz", - "integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==", - "optional": true + "integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==" + }, + "ip-set": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ip-set/-/ip-set-2.1.0.tgz", + "integrity": "sha512-JdHz4tSMx1IeFj8yEcQU0i58qiSkOlmZXkZ8+HJ0ROV5KcgLRDO9F703oJ1GeZCvqggrcCbmagD/V7hghY62wA==", + "requires": { + "ip": "^1.1.5" + }, + "dependencies": { + "ip": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.8.tgz", + "integrity": "sha512-PuExPYUiu6qMBQb4l06ecm6T6ujzhmh+MeJcW9wa89PoAz5pvd4zPgN5WJV104mb6S2T1AwNIAaB70JNrLQWhg==" + } + } }, "ipaddr.js": { "version": "1.9.1", @@ -13819,6 +16309,11 @@ "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", "dev": true }, + "is-ascii": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-ascii/-/is-ascii-1.0.0.tgz", + "integrity": "sha512-CXMaB/+EWCSGlLPs7ZlXRBpaPRRSRnrOfq0N3+RGeCZfqQaHQtiDLlkPCn63+LCkRUc1iRE0AXiI+sm2/Hi3qQ==" + }, "is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", @@ -13843,6 +16338,11 @@ "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", "dev": true }, + "is-file": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-file/-/is-file-1.0.0.tgz", + "integrity": "sha512-ZGMuc+xA8mRnrXtmtf2l/EkIW2zaD2LSBWlaOVEF6yH4RTndHob65V4SwWWdtGKVthQfXPVKsXqw4TDUjbVxVQ==" + }, "is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", @@ -14589,6 +17089,11 @@ } } }, + "join-async-iterator": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/join-async-iterator/-/join-async-iterator-1.1.1.tgz", + "integrity": "sha512-ATse+nuNeKZ9K1y27LKdvPe/GCe9R/u9dw9vI248e+vILeRK3IcJP4JUPAlSmKRCDK0cKhEwfmiw4Skqx7UnGQ==" + }, "js-sdsl": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.2.0.tgz", @@ -14655,12 +17160,51 @@ "universalify": "^2.0.0" } }, + "junk": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/junk/-/junk-3.1.0.tgz", + "integrity": "sha512-pBxcB3LFc8QVgdggvZWyeys+hnrNWg4OcZIU/1X59k5jQdLBlCsYGRQaz234SqoRLTCgMH00fY0xRJH+F9METQ==" + }, + "k-bucket": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/k-bucket/-/k-bucket-5.1.0.tgz", + "integrity": "sha512-Fac7iINEovXIWU20GPnOMLUbjctiS+cnmyjC4zAUgvs3XPf1vo9akfCHkigftSic/jiKqKl+KA3a/vFcJbHyCg==", + "requires": { + "randombytes": "^2.1.0" + } + }, + "k-rpc": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/k-rpc/-/k-rpc-5.1.0.tgz", + "integrity": "sha512-FGc+n70Hcjoa/X2JTwP+jMIOpBz+pkRffHnSl9yrYiwUxg3FIgD50+u1ePfJUOnRCnx6pbjmVk5aAeB1wIijuQ==", + "requires": { + "k-bucket": "^5.0.0", + "k-rpc-socket": "^1.7.2", + "randombytes": "^2.0.5" + } + }, + "k-rpc-socket": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/k-rpc-socket/-/k-rpc-socket-1.11.1.tgz", + "integrity": "sha512-8xtA8oqbZ6v1Niryp2/g4GxW16EQh5MvrUylQoOG+zcrDff5CKttON2XUXvMwlIHq4/2zfPVFiinAccJ+WhxoA==", + "requires": { + "bencode": "^2.0.0", + "chrome-dgram": "^3.0.2", + "chrome-dns": "^1.0.0", + "chrome-net": "^3.3.2" + } + }, "kleur": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", "dev": true }, + "last-one-wins": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/last-one-wins/-/last-one-wins-1.0.4.tgz", + "integrity": "sha512-t+KLJFkHPQk8lfN6WBOiGkiUXoub+gnb2XTYI2P3aiISL+94xgZ1vgz1SXN/N4hthuOoLXarXfBZPUruyjQtfA==" + }, "leven": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", @@ -14677,12 +17221,29 @@ "type-check": "~0.4.0" } }, + "limiter": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/limiter/-/limiter-1.1.5.tgz", + "integrity": "sha512-FWWMIEOxz3GwUI4Ts/IvgVy6LPvoMPgjMdQ185nN6psJyBJ4yOpzqm695/h5umdLJg2vW3GR5iG11MAkR2AzJA==" + }, "lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", "dev": true }, + "load-ip-set": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/load-ip-set/-/load-ip-set-2.2.1.tgz", + "integrity": "sha512-G3hQXehU2LTOp52e+lPffpK4EvidfjwbvHaGqmFcp4ptiZagR4xFdL+D08kMX906dxeqZyWhfonEjdUxrWcldg==", + "requires": { + "ip-set": "^2.1.0", + "netmask": "^2.0.1", + "once": "^1.4.0", + "simple-get": "^4.0.0", + "split": "^1.0.1" + } + }, "loader-runner": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", @@ -14701,8 +17262,7 @@ "lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, "lodash.memoize": { "version": "4.1.2", @@ -14738,6 +17298,14 @@ } } }, + "lru": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/lru/-/lru-3.1.0.tgz", + "integrity": "sha512-5OUtoiVIGU4VXBOshidmtOsvBIvcQR6FD/RzWSvaeHyxCGB+PCUCu+52lqMfdc0h/2CLvHhZS4TwUmMQrrMbBQ==", + "requires": { + "inherits": "^2.0.1" + } + }, "lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -14746,6 +17314,15 @@ "yallist": "^4.0.0" } }, + "lt_donthave": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lt_donthave/-/lt_donthave-1.0.1.tgz", + "integrity": "sha512-PfOXfDN9GnUjlNHjjxKQuMxPC8s12iSrnmg+Ff1BU1uLn7S1BFAKzpZCu6Gwg3WsCUvTZrZoDSHvy6B/j+N4/Q==", + "requires": { + "debug": "^4.2.0", + "unordered-array-remove": "^1.0.2" + } + }, "macos-release": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/macos-release/-/macos-release-2.5.0.tgz", @@ -14761,6 +17338,15 @@ "sourcemap-codec": "^1.4.8" } }, + "magnet-uri": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/magnet-uri/-/magnet-uri-6.2.0.tgz", + "integrity": "sha512-O9AgdDwT771fnUj0giPYu/rACpz8173y8UXCSOdLITjOVfBenZ9H9q3FqQmveK+ORUMuD+BkKNSZP8C3+IMAKQ==", + "requires": { + "bep53-range": "^1.1.0", + "thirty-two": "^1.0.2" + } + }, "make-dir": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", @@ -14820,6 +17406,28 @@ "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==" }, + "mediasource": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/mediasource/-/mediasource-2.4.0.tgz", + "integrity": "sha512-SKUMrbFMHgiCUZFOWZcL0aiF/KgHx9SPIKzxrl6+7nMUMDK/ZnOmJdY/9wKzYeM0g3mybt3ueg+W+/mrYfmeFQ==", + "requires": { + "inherits": "^2.0.4", + "readable-stream": "^3.6.0", + "to-arraybuffer": "^1.0.1" + }, + "dependencies": { + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } + } + }, "memfs": { "version": "3.4.12", "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.4.12.tgz", @@ -14829,6 +17437,14 @@ "fs-monkey": "^1.0.3" } }, + "memory-chunk-store": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/memory-chunk-store/-/memory-chunk-store-1.3.5.tgz", + "integrity": "sha512-E1Xc1U4ifk/FkC2ZsWhCaW1xg9HbE/OBmQTLe2Tr9c27YPSLbW7kw1cnb3kQWD1rDtErFJHa7mB9EVrs7aTx9g==", + "requires": { + "queue-microtask": "^1.2.3" + } + }, "merge-descriptors": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", @@ -14885,6 +17501,11 @@ "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", "dev": true }, + "mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==" + }, "minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -14971,6 +17592,42 @@ "minimist": "^1.2.6" } }, + "mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==" + }, + "mp4-box-encoding": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/mp4-box-encoding/-/mp4-box-encoding-1.4.1.tgz", + "integrity": "sha512-2/PRtGGiqPc/VEhbm7xAQ+gbb7yzHjjMAv6MpAifr5pCpbh3fQUdj93uNgwPiTppAGu8HFKe3PeU+OdRyAxStA==", + "requires": { + "uint64be": "^2.0.2" + } + }, + "mp4-stream": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/mp4-stream/-/mp4-stream-3.1.3.tgz", + "integrity": "sha512-DUT8f0x2jHbZjNMdqe9h6lZdt6RENWTTdGn8z3TXa4uEsoltuNY9lCCij84mdm0q7xcV0E2W25WRxlKBMo4hSw==", + "requires": { + "mp4-box-encoding": "^1.3.0", + "next-event": "^1.0.0", + "queue-microtask": "^1.2.2", + "readable-stream": "^3.0.6" + }, + "dependencies": { + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } + } + }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -15006,6 +17663,12 @@ "thenify-all": "^1.0.0" } }, + "napi-macros": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/napi-macros/-/napi-macros-2.2.2.tgz", + "integrity": "sha512-hmEVtAGYzVQpCKdbQea4skABsdXW4RUh5t5mJ2zzqowJS2OyXZTU1KhDVFhx+NlWZ4ap9mqR9TcDO3LTTttd+g==", + "optional": true + }, "natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -15029,6 +17692,16 @@ "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", "dev": true }, + "netmask": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.0.2.tgz", + "integrity": "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==" + }, + "next-event": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/next-event/-/next-event-1.0.0.tgz", + "integrity": "sha512-IXGPhl/yAiUU597gz+k5OYxYZkmLSWTcPPcpQjWABud9OK6m/ZNLrVdcEu4e7NgmOObFIhgZVg1jecPYT/6AoA==" + }, "node-abort-controller": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/node-abort-controller/-/node-abort-controller-3.0.1.tgz", @@ -15126,6 +17799,12 @@ } } }, + "node-gyp-build": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.6.0.tgz", + "integrity": "sha512-NTZVKn9IylLwUzaKjkas1e4u2DLNcV4rdYagA4PWdPwW87Bi7z+BznyKSRwS/761tV/lzCGXplWsiaMjLqP2zQ==", + "optional": true + }, "node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", @@ -15208,6 +17887,14 @@ "set-blocking": "^2.0.0" } }, + "nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "requires": { + "boolbase": "^1.0.0" + } + }, "object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -15340,6 +18027,14 @@ "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", "dev": true }, + "package-json-versionify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/package-json-versionify/-/package-json-versionify-1.0.4.tgz", + "integrity": "sha512-mtKKtCeSZMtWcc5hHJS6OlEGP7J9g7WN6vWCCZi2hCXFag/Zmjokh6WFFTQb9TuMnBcZpRjhhMQyOyglPCAahw==", + "requires": { + "browserify-package-json": "^1.0.0" + } + }, "parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -15361,6 +18056,20 @@ "lines-and-columns": "^1.1.6" } }, + "parse-torrent": { + "version": "9.1.5", + "resolved": "https://registry.npmjs.org/parse-torrent/-/parse-torrent-9.1.5.tgz", + "integrity": "sha512-K8FXRwTOaZMI0/xuv0dpng1MVHZRtMJ0jRWBJ3qZWVNTrC1MzWUxm9QwaXDz/2qPhV2XC4UIHI92IGHwseAwaA==", + "requires": { + "bencode": "^2.0.2", + "blob-to-buffer": "^1.2.9", + "get-stdin": "^8.0.0", + "magnet-uri": "^6.2.0", + "queue-microtask": "^1.2.3", + "simple-get": "^4.0.1", + "simple-sha1": "^3.1.0" + } + }, "parse5": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz", @@ -15432,6 +18141,11 @@ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", "dev": true }, + "piece-length": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/piece-length/-/piece-length-2.0.1.tgz", + "integrity": "sha512-dBILiDmm43y0JPISWEmVGKBETQjwJe6mSU9GND+P9KW0SJGUwoU/odyH1nbalOP9i8WSYuqf1lQnaj92Bhw+Ug==" + }, "pirates": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz", @@ -15582,7 +18296,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "dev": true, "requires": { "end-of-stream": "^1.1.0", "once": "^1.3.1" @@ -15605,14 +18318,41 @@ "queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==" + }, + "queue-tick": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/queue-tick/-/queue-tick-1.0.1.tgz", + "integrity": "sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==" + }, + "random-access-file": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/random-access-file/-/random-access-file-2.2.1.tgz", + "integrity": "sha512-RGU0xmDqdOyEiynob1KYSeh8+9c9Td1MJ74GT1viMEYAn8SJ9oBtWCXLsYZukCF46yududHOdM449uRYbzBrZQ==", + "requires": { + "mkdirp-classic": "^0.5.2", + "random-access-storage": "^1.1.1" + } + }, + "random-access-storage": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/random-access-storage/-/random-access-storage-1.4.3.tgz", + "integrity": "sha512-D5e2iIC5dNENWyBxsjhEnNOMCwZZ64TARK6dyMN+3g4OTC4MJxyjh9hKLjTGoNhDOPrgjI+YlFEHFnrp/cSnzQ==", + "requires": { + "events": "^3.3.0", + "inherits": "^2.0.3", + "queue-tick": "^1.0.0" + } + }, + "random-iterate": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/random-iterate/-/random-iterate-1.0.1.tgz", + "integrity": "sha512-Jdsdnezu913Ot8qgKgSgs63XkAjEsnMcS1z+cC6D6TNXsUXsMxy0RpclF2pzGZTEiTXL9BiArdGTEexcv4nqcA==" }, "randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, "requires": { "safe-buffer": "^5.1.0" } @@ -15622,6 +18362,26 @@ "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" }, + "range-slice-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/range-slice-stream/-/range-slice-stream-2.0.0.tgz", + "integrity": "sha512-PPYLwZ63lXi6Tv2EZ8w3M4FzC0rVqvxivaOVS8pXSp5FMIHFnvi4MWHL3UdFLhwSy50aNtJsgjY0mBC6oFL26Q==", + "requires": { + "readable-stream": "^3.0.2" + }, + "dependencies": { + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } + } + }, "raw-body": { "version": "2.5.1", "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", @@ -15633,6 +18393,11 @@ "unpipe": "1.0.0" } }, + "rc4": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/rc4/-/rc4-0.1.5.tgz", + "integrity": "sha512-xdDTNV90z5x5u25Oc871Xnvu7yAr4tV7Eluh0VSvrhUkry39q1k+zkz7xroqHbRq+8PiazySHJPArqifUvz9VA==" + }, "react-is": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", @@ -15678,6 +18443,14 @@ "resolve": "^1.1.6" } }, + "record-cache": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/record-cache/-/record-cache-1.2.0.tgz", + "integrity": "sha512-kyy3HWCez2WrotaL3O4fTn0rsIdfRKOdQQcEJ9KpvmKmbffKVvwsloX063EgRUlpJIXHiDQFhJcTbZequ2uTZw==", + "requires": { + "b4a": "^1.3.1" + } + }, "reflect-metadata": { "version": "0.1.13", "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz", @@ -15689,6 +18462,18 @@ "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", "dev": true }, + "render-media": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/render-media/-/render-media-4.1.0.tgz", + "integrity": "sha512-F5BMWDmgATEoyPCtKjmGNTGN1ghoZlfRQ3MJh8dS/MrvIUIxupiof/Y9uahChipXcqQ57twVbgMmyQmuO1vokw==", + "requires": { + "debug": "^4.2.0", + "is-ascii": "^1.0.0", + "mediasource": "^2.4.0", + "stream-to-blob-url": "^3.0.2", + "videostream": "^3.2.2" + } + }, "require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -15780,11 +18565,28 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, "requires": { "queue-microtask": "^1.2.2" } }, + "run-parallel-limit": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/run-parallel-limit/-/run-parallel-limit-1.1.0.tgz", + "integrity": "sha512-jJA7irRNM91jaKc3Hcl1npHsFLOXOoTkPCUL1JEa1R82O2miplXXRaGdjW/KM/98YQWDhJLiSs793CnXfblJUw==", + "requires": { + "queue-microtask": "^1.2.2" + } + }, + "run-series": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/run-series/-/run-series-1.1.9.tgz", + "integrity": "sha512-Arc4hUN896vjkqCYrUXquBFtRZdv1PfLbTYP71efP6butxyQ0kWpiNJyAgsxscmQg1cqvHY32/UCBzXedTpU2g==" + }, + "rusha": { + "version": "0.8.14", + "resolved": "https://registry.npmjs.org/rusha/-/rusha-0.8.14.tgz", + "integrity": "sha512-cLgakCUf6PedEu15t8kbsjnwIFFR2D4RfL+W3iWFJ4iac7z4B0ZI8fxy4R3J956kAI68HclCFGL8MPoUVC3qVA==" + }, "rxjs": { "version": "7.5.7", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.5.7.tgz", @@ -15976,6 +18778,94 @@ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" }, + "simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==" + }, + "simple-get": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", + "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", + "requires": { + "decompress-response": "^6.0.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, + "simple-peer": { + "version": "9.11.1", + "resolved": "https://registry.npmjs.org/simple-peer/-/simple-peer-9.11.1.tgz", + "integrity": "sha512-D1SaWpOW8afq1CZGWB8xTfrT3FekjQmPValrqncJMX7QFl8YwhrPTZvMCANLtgBwwdS+7zURyqxDDEmY558tTw==", + "requires": { + "buffer": "^6.0.3", + "debug": "^4.3.2", + "err-code": "^3.0.1", + "get-browser-rtc": "^1.1.0", + "queue-microtask": "^1.2.3", + "randombytes": "^2.1.0", + "readable-stream": "^3.6.0" + }, + "dependencies": { + "buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "err-code": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-3.0.1.tgz", + "integrity": "sha512-GiaH0KJUewYok+eeY05IIgjtAe4Yltygk9Wqp1V5yVWLdhf0hYZchRjNIT9bb0mSwRcIusT3cx7PJUf3zEIfUA==" + }, + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } + } + }, + "simple-sha1": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/simple-sha1/-/simple-sha1-3.1.0.tgz", + "integrity": "sha512-ArTptMRC1v08H8ihPD6l0wesKvMfF9e8XL5rIHPanI7kGOsSsbY514MwVu6X1PITHCTB2F08zB7cyEbfc4wQjg==", + "requires": { + "queue-microtask": "^1.2.2", + "rusha": "^0.8.13" + } + }, + "simple-websocket": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/simple-websocket/-/simple-websocket-9.1.0.tgz", + "integrity": "sha512-8MJPnjRN6A8UCp1I+H/dSFyjwJhp6wta4hsVRhjf8w9qBHRzxYt14RaOcjvQnhD1N4yKOddEjflwMnQM4VtXjQ==", + "requires": { + "debug": "^4.3.1", + "queue-microtask": "^1.2.2", + "randombytes": "^2.1.0", + "readable-stream": "^3.6.0", + "ws": "^7.4.2" + }, + "dependencies": { + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } + } + }, "sisteransi": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", @@ -15991,14 +18881,12 @@ "smart-buffer": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", - "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", - "optional": true + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==" }, "socks": { "version": "2.7.1", "resolved": "https://registry.npmjs.org/socks/-/socks-2.7.1.tgz", "integrity": "sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ==", - "optional": true, "requires": { "ip": "^2.0.0", "smart-buffer": "^4.2.0" @@ -16045,6 +18933,28 @@ "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", "dev": true }, + "speed-limiter": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/speed-limiter/-/speed-limiter-1.0.2.tgz", + "integrity": "sha512-Ax+TbUOho84bWUc3AKqWtkIvAIVws7d6QI4oJkgH4yQ5Yil+lR3vjd/7qd51dHKGzS5bFxg0++QwyNRN7s6rZA==", + "requires": { + "limiter": "^1.1.5", + "streamx": "^2.10.3" + } + }, + "speedometer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/speedometer/-/speedometer-1.1.0.tgz", + "integrity": "sha512-z/wAiTESw2XVPssY2XRcme4niTc4S5FkkJ4gknudtVoc33Zil8TdTxHy5torRcgqMqksJV2Yz8HQcvtbsnw0mQ==" + }, + "split": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz", + "integrity": "sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==", + "requires": { + "through": "2" + } + }, "sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", @@ -16098,11 +19008,41 @@ "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==" }, + "stream-to-blob": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/stream-to-blob/-/stream-to-blob-2.0.1.tgz", + "integrity": "sha512-GXlqXt3svqwIVWoICenix5Poxi4KbCF0BdXXUbpU1X4vq1V8wmjiEIU3aFJzCGNFpKxfbnG0uoowS3nKUgSPYg==" + }, + "stream-to-blob-url": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/stream-to-blob-url/-/stream-to-blob-url-3.0.2.tgz", + "integrity": "sha512-PS6wT2ZyyR38Cy+lE6PBEI1ZmO2HdzZoLeDGG0zZbYikCZd0dh8FUoSeFzgWLItpBYw1WJmPVRLpykRV+lAWLQ==", + "requires": { + "stream-to-blob": "^2.0.0" + } + }, + "stream-with-known-length-to-buffer": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/stream-with-known-length-to-buffer/-/stream-with-known-length-to-buffer-1.0.4.tgz", + "integrity": "sha512-ztP79ug6S+I7td0Nd2GBeIKCm+vA54c+e60FY87metz5n/l6ydPELd2lxsljz8OpIhsRM9HkIiAwz85+S5G5/A==", + "requires": { + "once": "^1.4.0" + } + }, "streamsearch": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==" }, + "streamx": { + "version": "2.13.2", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.13.2.tgz", + "integrity": "sha512-+TWqixPhGDXEG9L/XczSbhfkmwAtGs3BJX5QNU6cvno+pOLKeszByWcnaTu6dg8efsTYqR8ZZuXWHhZfgrxMvA==", + "requires": { + "fast-fifo": "^1.1.0", + "queue-tick": "^1.0.1" + } + }, "string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", @@ -16138,6 +19078,22 @@ "strip-ansi": "^6.0.1" } }, + "string2compact": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/string2compact/-/string2compact-1.3.2.tgz", + "integrity": "sha512-3XUxUgwhj7Eqh2djae35QHZZT4mN3fsO7kagZhSGmhhlrQagVvWSFuuFIWnpxFS0CdTB2PlQcaL16RDi14I8uw==", + "requires": { + "addr-to-ip-port": "^1.0.1", + "ipaddr.js": "^2.0.0" + }, + "dependencies": { + "ipaddr.js": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.0.1.tgz", + "integrity": "sha512-1qTgH9NG+IIJ4yfKs2e6Pp1bZg8wbDbKHT21HrLIeYBTRLgMYKnMTPAuI3Lcs61nfx5h1xlXnbJtH1kX5/d/ng==" + } + } + }, "strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -16362,11 +19318,15 @@ "thenify": ">= 3.1.0 < 4" } }, + "thirty-two": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/thirty-two/-/thirty-two-1.0.2.tgz", + "integrity": "sha512-OEI0IWCe+Dw46019YLl6V10Us5bi574EvlJEOcAkB29IzQ/mYD1A6RyNHLjZPiHCmuodxvgF6U+vZO1L15lxVA==" + }, "through": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", - "dev": true + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==" }, "through2": { "version": "2.0.5", @@ -16378,6 +19338,22 @@ "xtend": "~4.0.1" } }, + "throughput": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/throughput/-/throughput-1.0.1.tgz", + "integrity": "sha512-4Mvv5P4xyVz6RM07wS3tGyZ/kPAiKtLeqznq3hK4pxDiTUSyQ5xeFlBiWxflCWexvSnxo2aAfedzKajJqihz4Q==" + }, + "thunky": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", + "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==" + }, + "timeout-refresh": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/timeout-refresh/-/timeout-refresh-1.0.3.tgz", + "integrity": "sha512-Mz0CX4vBGM5lj8ttbIFt7o4ZMxk/9rgudJRh76EvB7xXZMur7T/cjRiH2w4Fmkq0zxf2QpM8IFvOSRn8FEu3gA==", + "optional": true + }, "tmp": { "version": "0.0.33", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", @@ -16393,6 +19369,11 @@ "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", "dev": true }, + "to-arraybuffer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz", + "integrity": "sha512-okFlQcoGTi4LQBG/PgSYblw9VOyptsz2KJZqc6qtgGdes8VktzUQkj4BI2blit072iS8VODNcMA+tvnS9dnuMA==" + }, "to-fast-properties": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", @@ -16413,16 +19394,57 @@ "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==" }, + "torrent-discovery": { + "version": "9.4.15", + "resolved": "https://registry.npmjs.org/torrent-discovery/-/torrent-discovery-9.4.15.tgz", + "integrity": "sha512-71nx+TpLaF27mbsSj/tZTr588Dfk7XVzx+Rf1+nrxfXqe8qn5dIlRhgA+yY4cg8Ib69vWwkKFhAzbRqg8z42aw==", + "requires": { + "bittorrent-dht": "^10.0.7", + "bittorrent-lsd": "^1.1.1", + "bittorrent-tracker": "^9.19.0", + "debug": "^4.3.4", + "run-parallel": "^1.2.0" + } + }, "torrent-name-parser": { "version": "0.6.5", "resolved": "https://registry.npmjs.org/torrent-name-parser/-/torrent-name-parser-0.6.5.tgz", "integrity": "sha512-ao92tzD6DjYKN0dedEZxfylL4ZWYaYFgPktKxkEmlsLrF/ia41Sg+l3rJ2XLJufQUR3uTbd7bYMqxbh/kxb1pQ==" }, + "torrent-piece": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/torrent-piece/-/torrent-piece-2.0.1.tgz", + "integrity": "sha512-JLSOyvQVLI6JTWqioY4vFL0JkEUKQcaHQsU3loxkCvPTSttw8ePs2tFwsP4XIjw99Fz8EdOzt/4faykcbnPbCQ==" + }, "tr46": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" }, + "transmission": { + "version": "0.4.10", + "resolved": "https://registry.npmjs.org/transmission/-/transmission-0.4.10.tgz", + "integrity": "sha512-GXss/sNTuHqgvy7TTE3soF8wuS8BzqZO3HV3mc61tg7vVT5u5Gr5kja82QF23GfGeZjdzFUeJGwJrEHU1DWq8Q==", + "requires": { + "async": "^2.1.4", + "yargs": "^1.3.3" + }, + "dependencies": { + "async": { + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", + "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", + "requires": { + "lodash": "^4.17.14" + } + }, + "yargs": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-1.3.3.tgz", + "integrity": "sha512-7OGt4xXoWJQh5ulgZ78rKaqY7dNWbjfK+UKxGcIlaM2j7C4fqGchyv8CPvEWdRPrHp6Ula/YU8yGRpYGOHrI+g==" + } + } + }, "tree-kill": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", @@ -16649,6 +19671,14 @@ "integrity": "sha512-CIfGzTelbKNEnLpLdGFgdyKhG23CKdKgQPOBc+OUNrkJ2vr+KSzsSV5kq5iWhEQbok+quxgGzrAtGWCyU7tHnA==", "devOptional": true }, + "uint64be": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/uint64be/-/uint64be-2.0.2.tgz", + "integrity": "sha512-9QqdvpGQTXgxthP+lY4e/gIBy+RuqcBaC6JVwT5I3bDLgT/btL6twZMR0pI3/Fgah9G/pdwzIprE5gL6v9UvyQ==", + "requires": { + "buffer-alloc": "^1.1.0" + } + }, "unique-filename": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz", @@ -16673,6 +19703,17 @@ "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", "dev": true }, + "unordered-array-remove": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unordered-array-remove/-/unordered-array-remove-1.0.2.tgz", + "integrity": "sha512-45YsfD6svkgaCBNyvD+dFHm4qFX9g3wRSIVgWVPtm2OCnphvPxzJoe20ATsiNpNJrmzHifnxm+BN5F7gFT/4gw==" + }, + "unordered-set": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/unordered-set/-/unordered-set-2.0.1.tgz", + "integrity": "sha512-eUmNTPzdx+q/WvOHW0bgGYLWvWHNT3PTKEQLg0MAQhc0AHASHVHoP/9YytYd4RBVariqno/mEUhVZN98CmD7bg==", + "optional": true + }, "unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -16703,6 +19744,36 @@ "punycode": "^2.1.0" } }, + "ut_metadata": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ut_metadata/-/ut_metadata-3.5.2.tgz", + "integrity": "sha512-3XZZuJSeoIUyMYSuDbTbVtP4KAVGHPfU8nmHFkr8LJc+THCaUXwnu/2AV+LCSLarET/hL9IlbNfYTGrt6fOVuQ==", + "requires": { + "bencode": "^2.0.1", + "bitfield": "^4.0.0", + "debug": "^4.2.0", + "simple-sha1": "^3.0.1" + } + }, + "ut_pex": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/ut_pex/-/ut_pex-3.0.2.tgz", + "integrity": "sha512-3xM88t+AVU5GR0sIY3tmRMLUS+YKiwStc7U7+ZFQ+UHQpX7BjVJOomhmtm0Bs+8R2n812Dt2ymXm01EqDrOOpQ==", + "requires": { + "bencode": "^2.0.2", + "compact2string": "^1.4.1", + "string2compact": "^1.3.2" + } + }, + "utf-8-validate": { + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.10.tgz", + "integrity": "sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==", + "optional": true, + "requires": { + "node-gyp-build": "^4.3.0" + } + }, "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -16713,6 +19784,32 @@ "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==" }, + "utp-native": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/utp-native/-/utp-native-2.5.3.tgz", + "integrity": "sha512-sWTrWYXPhhWJh+cS2baPzhaZc89zwlWCfwSthUjGhLkZztyPhcQllo+XVVCbNGi7dhyRlxkWxN4NKU6FbA9Y8w==", + "optional": true, + "requires": { + "napi-macros": "^2.0.0", + "node-gyp-build": "^4.2.0", + "readable-stream": "^3.0.2", + "timeout-refresh": "^1.0.0", + "unordered-set": "^2.0.1" + }, + "dependencies": { + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "optional": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } + } + }, "uuid": { "version": "9.0.0", "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", @@ -16740,6 +19837,19 @@ "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==" }, + "videostream": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/videostream/-/videostream-3.2.2.tgz", + "integrity": "sha512-4tz23yGGeATmbzj/ZnUm6wgQ4E1lzmMXu2mUA/c0G6adtWKxm1Di5YejdZdRsK6SdkLjKjhplFFYT7r+UUDKvA==", + "requires": { + "binary-search": "^1.3.4", + "mediasource": "^2.2.2", + "mp4-box-encoding": "^1.3.0", + "mp4-stream": "^3.0.0", + "pump": "^3.0.0", + "range-slice-stream": "^2.0.0" + } + }, "walker": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", @@ -16818,6 +19928,65 @@ "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", "dev": true }, + "webtorrent": { + "version": "1.9.7", + "resolved": "https://registry.npmjs.org/webtorrent/-/webtorrent-1.9.7.tgz", + "integrity": "sha512-N+hRuVctWviTAYem/sI6tuFP2J/Rn3/ETEh++7GnJv6Oro49kDjcPuz1W6s+vfS65xKr3Eh4HMuxf3hH82LGfg==", + "requires": { + "@webtorrent/http-node": "^1.3.0", + "addr-to-ip-port": "^1.5.4", + "bitfield": "^4.1.0", + "bittorrent-dht": "^10.0.7", + "bittorrent-protocol": "^3.5.5", + "cache-chunk-store": "^3.2.2", + "chrome-net": "^3.3.4", + "chunk-store-stream": "^4.3.0", + "cpus": "^1.0.3", + "create-torrent": "^5.0.9", + "debug": "^4.3.4", + "end-of-stream": "^1.4.4", + "escape-html": "^1.0.3", + "fast-blob-stream": "^1.1.1", + "fs-chunk-store": "^3.0.1", + "immediate-chunk-store": "^2.2.0", + "join-async-iterator": "^1.1.1", + "load-ip-set": "^2.2.1", + "lt_donthave": "^1.0.1", + "memory-chunk-store": "^1.3.5", + "mime": "^3.0.0", + "package-json-versionify": "^1.0.4", + "parse-torrent": "^9.1.5", + "pump": "^3.0.0", + "queue-microtask": "^1.2.3", + "random-iterate": "^1.0.1", + "randombytes": "^2.1.0", + "range-parser": "^1.2.1", + "render-media": "^4.1.0", + "run-parallel": "^1.2.0", + "run-parallel-limit": "^1.1.0", + "simple-concat": "^1.0.1", + "simple-get": "^4.0.1", + "simple-peer": "^9.11.1", + "simple-sha1": "^3.1.0", + "speed-limiter": "^1.0.2", + "stream-with-known-length-to-buffer": "^1.0.4", + "streamx": "^2.12.5", + "throughput": "^1.0.1", + "torrent-discovery": "^9.4.15", + "torrent-piece": "^2.0.1", + "unordered-array-remove": "^1.0.2", + "ut_metadata": "^3.5.2", + "ut_pex": "^3.0.2", + "utp-native": "^2.5.3" + }, + "dependencies": { + "mime": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", + "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==" + } + } + }, "whatwg-url": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", @@ -16918,6 +20087,12 @@ "signal-exit": "^3.0.7" } }, + "ws": { + "version": "7.5.9", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", + "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", + "requires": {} + }, "xml2js": { "version": "0.4.23", "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz", diff --git a/package.json b/package.json index c99d528..f1c495c 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "version": "0.0.1", "description": "", "author": "", - "private": true, + "private": false, "license": "UNLICENSED", "scripts": { "prebuild": "rimraf dist", @@ -33,7 +33,7 @@ "@nestjs/platform-express": "^9.0.0", "@nestjs/serve-static": "^3.0.0", "@nestjs/typeorm": "^9.0.1", - "@types/fluent-ffmpeg": "^2.1.20", + "cheerio": "^1.0.0-rc.12", "dotenv": "^16.0.3", "fluent-ffmpeg": "^2.1.2", "reflect-metadata": "^0.1.13", @@ -42,16 +42,21 @@ "sqlite": "^4.1.2", "sqlite3": "^5.1.2", "torrent-name-parser": "^0.6.5", - "typeorm": "^0.3.11" + "transmission": "^0.4.10", + "typeorm": "^0.3.11", + "webtorrent": "^1.9.7" }, "devDependencies": { "@nestjs/cli": "^9.0.0", "@nestjs/schematics": "^9.0.0", "@nestjs/testing": "^9.0.0", + "@types/cheerio": "^0.22.31", "@types/express": "^4.17.13", + "@types/fluent-ffmpeg": "^2.1.20", "@types/jest": "28.1.8", "@types/node": "^16.0.0", "@types/supertest": "^2.0.11", + "@types/webtorrent": "^0.109.3", "@typescript-eslint/eslint-plugin": "^5.0.0", "@typescript-eslint/parser": "^5.0.0", "copyfiles": "^2.4.1", diff --git a/src/KodiApi/AllFiles/all-files.controller.ts b/src/KodiApi/AllFiles/all-files.controller.ts index 5fdf93c..964dacf 100644 --- a/src/KodiApi/AllFiles/all-files.controller.ts +++ b/src/KodiApi/AllFiles/all-files.controller.ts @@ -2,7 +2,9 @@ import { Controller, Get, Head, Param, Req, Res } from '@nestjs/common'; import ApiResponse from '../Dto/api-response.dto'; import { AllFilesService } from './all-files.service'; import { Request, Response } from 'express'; -import { TitleTypeEnum } from '../../VideoFiles/Enum/title-type.enum'; +import { TitleTypeEnum } from '../../Shared/Enum/title-type.enum'; +import { KodiApiResponse } from '../Dto/kodi-api-response.type'; +import NotificationResponse from '../Dto/notification-response.dto'; @Controller('api/all') export class AllFilesController { @@ -23,6 +25,16 @@ export class AllFilesController { return this.allFilesService.getListOfTitles(TitleTypeEnum.movie); } + @Get('scan') + scanNewFiles(): KodiApiResponse { + return this.allFilesService.scanNewFiles(); + } + + @Get('delete/:fileId') + async deleteFile(@Param('fileId') fileId: string): Promise { + return this.allFilesService.deleteFile(fileId); + } + @Get('play/:fileId') @Head('play/:fileId') play( @@ -37,7 +49,7 @@ export class AllFilesController { getTitle( @Param('titleId') titleId: string, //actually a number, how to cast? @Param('seasonId') seasonId: string, //actually a number, how to cast? - ): Promise { + ): Promise { return this.allFilesService.loadTitle( parseInt(titleId), parseInt(seasonId), diff --git a/src/KodiApi/AllFiles/all-files.module.ts b/src/KodiApi/AllFiles/all-files.module.ts index bac7ab2..7ec972c 100644 --- a/src/KodiApi/AllFiles/all-files.module.ts +++ b/src/KodiApi/AllFiles/all-files.module.ts @@ -9,5 +9,6 @@ import { VideoFilesModule } from '../../VideoFiles/video-files.module'; imports: [StreamerModule, VideoFilesModule], controllers: [AllFilesController], providers: [AllFilesService, KodiApiResponseFactory], + exports: [AllFilesService], }) export class AllFilesModule {} diff --git a/src/KodiApi/AllFiles/all-files.service.ts b/src/KodiApi/AllFiles/all-files.service.ts index 5432fc0..59bb187 100644 --- a/src/KodiApi/AllFiles/all-files.service.ts +++ b/src/KodiApi/AllFiles/all-files.service.ts @@ -4,9 +4,11 @@ import { KodiApiResponseFactory } from '../kodi-api-response.factory'; import { StreamerFacade } from '../../Streamer/streamer.facade'; import { Request, Response } from 'express'; import { VideoFilesFacade } from '../../VideoFiles/video-files.facade'; -import { TitleTypeEnum } from '../../VideoFiles/Enum/title-type.enum'; -import { FileEntity } from '../../VideoFiles/Entity/file.entity'; -import { TitleEntity } from '../../VideoFiles/Entity/title.entity'; +import { TitleTypeEnum } from '../../Shared/Enum/title-type.enum'; +import { FileEntity } from '../../Shared/Entity/file.entity'; +import { TitleEntity } from '../../Shared/Entity/title.entity'; +import { KodiApiResponse } from '../Dto/kodi-api-response.type'; +import NotificationResponse from '../Dto/notification-response.dto'; @Injectable() export class AllFilesService { @@ -17,19 +19,17 @@ export class AllFilesService { ) {} getMenu(): ApiResponse { - const apiResponse = this.koiApiResponseFactory.createApiResponse(); - apiResponse - .createItem() - .setLabel('Movies') - .setPath('all/movie') - .setToFolder(); - apiResponse - .createItem() - .setLabel('Shows') - .setPath('all/show') - .setToFolder(); - - return apiResponse; + return this.koiApiResponseFactory + .createApiResponse() + .addNavigationItems( + [ + ['Movies', 'movie'], + ['Shows', 'show'], + ['Scan for new Files', 'scan'], + ], + 'all/', + ) + .setNoSort(); } async getListOfTitles(type: TitleTypeEnum): Promise { @@ -50,16 +50,16 @@ export class AllFilesService { return response; } - async loadTitle(titleId: number, seasonId: number): Promise { + async loadTitle(titleId: number, seasonId: number): Promise { const titleEntity = titleId ? await this.videoFilesFacade.getTitleWithFiles(titleId) : null; if (titleEntity === null) { - //todo - alert instead of full api response - return this.koiApiResponseFactory - .createApiResponse() - .setTitle('Not found' + (titleId ? ' ' + titleId : '')); + return this.koiApiResponseFactory.createNotificationResponse( + 'Not found' + (titleId ? ' ' + titleId : ''), + false, + ); } if (titleEntity.type === TitleTypeEnum.movie) { @@ -78,21 +78,33 @@ export class AllFilesService { return this.buildSeasonsResponse(titleEntity); } - private buildFilesResponse( + buildFilesResponse( title: string, fileEntities: Array, + torrentContext = false, ): ApiResponse { const apiResponse = this.koiApiResponseFactory.createApiResponse(); apiResponse.setTitle(title); for (const fileEntity of fileEntities) { - apiResponse + const item = apiResponse .createItem() .setLabel(fileEntity.fileName) .setPath('/api?path=all/play/' + fileEntity.id) .setToPlayable() - .setPlot('Size ' + fileEntity.size) + .setPlot(this.buildPlot(fileEntity)) .setSize(fileEntity.size) .setDuration(fileEntity.duration); + + if (!torrentContext) { + if (fileEntity.torrentId) { + item.addContextMenu( + 'Show torrent', + 'torr/torrent-context/' + fileEntity.torrentId, + ); + } else { + item.addContextMenu('Delete file', 'all/delete/' + fileEntity.id); + } + } } return apiResponse; @@ -102,7 +114,6 @@ export class AllFilesService { const apiResponse = this.koiApiResponseFactory.createApiResponse(); apiResponse.setTitle(titleEntity.title); - //todo - add num files count const seasons = []; const filesCount = {}; for (const fileEntity of titleEntity.files) { @@ -144,4 +155,60 @@ export class AllFilesService { return this.streamerFacade.streamVideoFile(request, response, fileEntity); } + + getHRFileSize(size: number) { + // convert to human readable format. + const i: number = Math.floor(Math.log(size) / Math.log(1024)); + const iPow: number = Math.pow(1024, i); + const sizePow: number = size / iPow; + + return sizePow.toFixed(2) + ' ' + ['B', 'KB', 'MB', 'GB', 'TB'][i]; + } + + buildPlot(fileEntity: FileEntity): string { + const plot: string[] = []; + if (fileEntity.size) { + plot.push('Size ' + this.getHRFileSize(fileEntity.size)); + } + + const arrTorrProps: string[] = []; + if (fileEntity.linkomanija) { + arrTorrProps.push('LM'); + } + if (fileEntity.transmissionId) { + arrTorrProps.push('transm'); + } + if (fileEntity.torrentId) { + arrTorrProps.push('wt'); + } + const torrProps = arrTorrProps.join(' + '); + if (torrProps) { + plot.push(torrProps); + } + + if (fileEntity.progress) { + const progress = Math.round(fileEntity.progress * 1000) / 10; + const stopped = fileEntity.torrent.stopped ? ' Stopped' : ''; + plot.push(`${progress}%${stopped}`); + } + + return plot.join('\n'); + } + + public scanNewFiles(): NotificationResponse { + this.videoFilesFacade.updateFsVideoFiles(); + return this.koiApiResponseFactory.createNotificationResponse( + 'Scanning for new files and deleting old.', + false, + ); + } + + async deleteFile(fileId: string) { + const id = parseInt(fileId); + const result = await this.videoFilesFacade.deleteFile(id); + return new NotificationResponse( + result ? 'successfully deleted' : 'delete failed', + result, + ); + } } diff --git a/src/KodiApi/Dto/api-response.dto.ts b/src/KodiApi/Dto/api-response.dto.ts index 53ba6f6..712bd48 100644 --- a/src/KodiApi/Dto/api-response.dto.ts +++ b/src/KodiApi/Dto/api-response.dto.ts @@ -1,9 +1,11 @@ import ResponseItem from './response-item.dto'; +import { NavigationItem } from '../../Shared/Dto/navigation-item.dto'; export default class ApiResponse { items: Array = []; content = 'videos'; updateList = true; + selectList: boolean | undefined; category: string | undefined; play: string | undefined; msgBoxOK: string | undefined; @@ -40,4 +42,25 @@ export default class ApiResponse { return this; } + + setShowAsSelectList() { + this.updateList = false; + this.selectList = true; + + return this; + } + + addNavigationItems(navigationItems: NavigationItem[], pathPrefix = '') { + for (const [label, path, searchFor] of navigationItems) { + const item = this.createItem() + .setLabel(label) + .setToFolder() + .setPath(pathPrefix + path ?? ''); + if (searchFor !== undefined) { + item.setActionSearch(searchFor); + } + } + + return this; + } } diff --git a/src/KodiApi/Dto/kodi-api-response.type.ts b/src/KodiApi/Dto/kodi-api-response.type.ts new file mode 100644 index 0000000..df4c1fa --- /dev/null +++ b/src/KodiApi/Dto/kodi-api-response.type.ts @@ -0,0 +1,4 @@ +import ApiResponse from './api-response.dto'; +import NotificationResponse from './notification-response.dto'; + +export type KodiApiResponse = ApiResponse | NotificationResponse; diff --git a/src/KodiApi/Dto/notification-response.dto.ts b/src/KodiApi/Dto/notification-response.dto.ts index 48f0cae..ceb6554 100644 --- a/src/KodiApi/Dto/notification-response.dto.ts +++ b/src/KodiApi/Dto/notification-response.dto.ts @@ -1,6 +1,7 @@ export default class NotificationResponse { - notification: string; - refreshContainer: boolean; + readonly notification: string; + readonly refreshContainer: boolean; + readonly updateList = false; constructor(message: string, refreshContainer = true) { this.notification = message; diff --git a/src/KodiApi/LRT/LrtApiClient/Client/lrt-api-category.client.ts b/src/KodiApi/LRT/LrtApiClient/Client/lrt-api-category.client.ts index 54d87e4..8cb1ad7 100644 --- a/src/KodiApi/LRT/LrtApiClient/Client/lrt-api-category.client.ts +++ b/src/KodiApi/LRT/LrtApiClient/Client/lrt-api-category.client.ts @@ -4,10 +4,17 @@ import { firstValueFrom } from 'rxjs'; import { SearchResponseInterface } from '../Interface/search-response.interface'; import { SearchResponseDto } from '../Dto/search-response.dto'; import { SearchResponseItemDto } from '../Dto/search-response-item.dto'; +import { InjectRepository } from '@nestjs/typeorm'; +import { LrtCategory } from '../Entity/lrt-category.entity'; +import { IsNull, Not, Repository } from 'typeorm'; @Injectable() export class LrtApiCategoryClient { - constructor(private readonly httpService: HttpService) {} + constructor( + private readonly httpService: HttpService, + @InjectRepository(LrtCategory) + private lrtCategoriesRepository: Repository, + ) {} async getCategory(catId: string): Promise { const url = 'https://www.lrt.lt/api/search?type=3&category_id=' + catId; @@ -36,6 +43,23 @@ export class LrtApiCategoryClient { responseDto.addItem(itemDto); } + //update last access + this.lrtCategoriesRepository + .update({ categoryId: catId }, { lastAccess: Date.now() }) + .then((r) => { + if (r.affected === 0) { + //todo - search-update category info + } + }); + return responseDto; } + + getRecentCategories(count: number): Promise { + return this.lrtCategoriesRepository.find({ + order: { lastAccess: 'DESC' }, + take: count, + where: { lastAccess: Not(IsNull()) }, + }); + } } diff --git a/src/KodiApi/LRT/LrtApiClient/Client/lrt-api-search.client.ts b/src/KodiApi/LRT/LrtApiClient/Client/lrt-api-search.client.ts index d91a640..8c382fb 100644 --- a/src/KodiApi/LRT/LrtApiClient/Client/lrt-api-search.client.ts +++ b/src/KodiApi/LRT/LrtApiClient/Client/lrt-api-search.client.ts @@ -18,7 +18,8 @@ export class LrtApiSearchClient { async searchCategories(query: string): Promise { query = query === 'viskas' ? '' : query; - const url = 'https://www.lrt.lt/api/search?type=3&tema=' + query; + //const url = 'https://www.lrt.lt/api/search?type=3&tema=' + query; + const url = 'https://www.lrt.lt/api/search?type=3&order=desc&tema=' + query; //https://www.lrt.lt/api/search?page=1&count=44&order=desc //https://www.lrt.lt/api/search?get_terms=1 const resp = await firstValueFrom(this.httpService.get(url)); @@ -43,6 +44,18 @@ export class LrtApiSearchClient { searchItem.img_path_prefix + '282x158' + searchItem.img_path_postfix; + let categoryId = await this.findCategoryIdInDb(searchItem.category_url); + + if (categoryId === null) { + categoryId = await this.findCategoryIdInLrt(searchItem.category_url); + this.saveCategory( + searchItem.category_url, + categoryId, + searchItem.category_title, + itemDto.thumb, + ); + } + itemDto.categoryId = await this.findCategoryId(searchItem.category_url); loadedCategories.add(searchItem.category_title); @@ -60,10 +73,17 @@ export class LrtApiSearchClient { return this.findCategoryIdInLrt(categoryUrl); } - private saveCategory(categoryUrl: string, catId: string) { + private saveCategory( + categoryUrl: string, + catId: string, + catTitle: string, + thumb: string, + ) { const cat = new LrtCategory(); cat.categoryUrl = categoryUrl; cat.categoryId = catId; + cat.title = catTitle; + cat.thumb = thumb; this.lrtCategoriesRepository.save(cat).then(); } @@ -91,9 +111,7 @@ export class LrtApiSearchClient { const interim = body.indexOf('"', start); const end = body.indexOf('"', interim + 1); if (end > interim) { - const catId = body.substring(interim + 1, end); - this.saveCategory(categoryUrl, catId); - return catId; + return body.substring(interim + 1, end); } else { throw 'unexpected response body 1'; } diff --git a/src/KodiApi/LRT/LrtApiClient/Entity/lrt-category.entity.ts b/src/KodiApi/LRT/LrtApiClient/Entity/lrt-category.entity.ts index 6b80371..b54398b 100644 --- a/src/KodiApi/LRT/LrtApiClient/Entity/lrt-category.entity.ts +++ b/src/KodiApi/LRT/LrtApiClient/Entity/lrt-category.entity.ts @@ -7,4 +7,13 @@ export class LrtCategory { @Column({ name: 'category_id' }) categoryId: string; + + @Column() + title: string; + + @Column({ name: 'last_access' }) + lastAccess: number; + + @Column() + thumb: string; } diff --git a/src/KodiApi/LRT/LrtApiClient/lrt-api.client.ts b/src/KodiApi/LRT/LrtApiClient/lrt-api.client.ts index dd2229f..7b9aa40 100644 --- a/src/KodiApi/LRT/LrtApiClient/lrt-api.client.ts +++ b/src/KodiApi/LRT/LrtApiClient/lrt-api.client.ts @@ -3,6 +3,7 @@ import { LrtApiSearchClient } from './Client/lrt-api-search.client'; import { SearchResponseDto } from './Dto/search-response.dto'; import { LrtApiCategoryClient } from './Client/lrt-api-category.client'; import { LrtApiPlaylistClient } from './Client/lrt-api-playlist.client'; +import { LrtCategory } from './Entity/lrt-category.entity'; @Injectable() export class LrtApiClient { @@ -23,4 +24,8 @@ export class LrtApiClient { getPlaylist(mediaUrl: string): Promise { return this.playlistClient.getPlaylistUrl(mediaUrl); } + + getRecentCategories(count: number): Promise { + return this.categoryClient.getRecentCategories(count); + } } diff --git a/src/KodiApi/LRT/lrt.controller.ts b/src/KodiApi/LRT/lrt.controller.ts index eaa7047..3ab1408 100644 --- a/src/KodiApi/LRT/lrt.controller.ts +++ b/src/KodiApi/LRT/lrt.controller.ts @@ -12,7 +12,7 @@ export class LrtController { ) {} @Get() - getLrtMenu(): ApiResponse { + getLrtMenu(): Promise { return this.lrtService.getMainMenu(); } diff --git a/src/KodiApi/LRT/lrt.module.ts b/src/KodiApi/LRT/lrt.module.ts index 9280313..5a57f6d 100644 --- a/src/KodiApi/LRT/lrt.module.ts +++ b/src/KodiApi/LRT/lrt.module.ts @@ -3,11 +3,11 @@ import { LrtController } from './lrt.controller'; import { LrtService } from './lrt.service'; import { KodiApiResponseFactory } from '../kodi-api-response.factory'; import { LrtApiClientModule } from './LrtApiClient/lrt-api-client.module'; -import { RecentSearchesService } from '../RecentSearches/recent-searches.service'; +import { RecentSearchesModule } from '../RecentSearches/recent-searches.module'; @Module({ - imports: [LrtApiClientModule], + imports: [LrtApiClientModule, RecentSearchesModule], controllers: [LrtController], - providers: [KodiApiResponseFactory, LrtService, RecentSearchesService], + providers: [KodiApiResponseFactory, LrtService], }) export class LrtModule {} diff --git a/src/KodiApi/LRT/lrt.service.ts b/src/KodiApi/LRT/lrt.service.ts index c795e95..bd9026e 100644 --- a/src/KodiApi/LRT/lrt.service.ts +++ b/src/KodiApi/LRT/lrt.service.ts @@ -12,34 +12,34 @@ export class LrtService { private readonly recentSearchesService: RecentSearchesService, ) {} - getMainMenu(): ApiResponse { + getMainMenu(): Promise { const apiResponse = this.kodiApiResponseFactory.createApiResponse(); apiResponse.setNoSort().setTitle('LRT.lt'); - this.createMainMenu(apiResponse); - return apiResponse; + return this.createMainMenu(apiResponse); } - private createMainMenu(apiResponse: ApiResponse): void { - //todo - refactor to 5 most recent searches + viskas? - const items = { - 'lrt/recent': 'neseniai ieskoti', - 'lrt/tema/vaikams': 'vaikams', - 'lrt/tema/sportas': 'sportas', - 'lrt/tema/kultura': 'kultura', - 'lrt/tema/muzika': 'muzika', - 'lrt/tema/viskas': 'viskas', - 'lrt/tema/tv-laidos': 'tv-laidos', - }; - apiResponse - .createItem() - .setPath('lrt/search') - .setActionSearch() - .setLabel('paieška'); + private async createMainMenu(apiResponse: ApiResponse): Promise { + apiResponse.addNavigationItems( + [ + ['paieška', 'search', ''], + ['neseniai ieskoti', 'recent'], + ['viskas', 'tema/viskas'], + ], + 'lrt/', + ); - for (const [path, label] of Object.entries(items)) { - apiResponse.createItem().setLabel(label).setToFolder().setPath(path); + const recentCategories = await this.lrtApiClient.getRecentCategories(7); + for (const category of recentCategories) { + apiResponse + .createItem() + .setPath('/lrt/cat/' + category.categoryId) + .setThumb(category.thumb) + .setLabel(category.title) + .setToFolder(); } + + return apiResponse; } async searchCategories(query: string): Promise { diff --git a/src/KodiApi/RecentSearches/recent-searches.service.ts b/src/KodiApi/RecentSearches/recent-searches.service.ts index 55cc4e4..5c877f7 100644 --- a/src/KodiApi/RecentSearches/recent-searches.service.ts +++ b/src/KodiApi/RecentSearches/recent-searches.service.ts @@ -4,16 +4,21 @@ import { configService, ConfigService } from '../../config/config.service'; import { KodiApiResponseFactory } from '../kodi-api-response.factory'; import { join } from 'path'; import { readFile, writeFile } from 'fs/promises'; +import { RecentSearchesConfig } from '../../config/recent-searches.config'; @Injectable() export class RecentSearchesService { - private readonly configService: ConfigService; + private readonly config: RecentSearchesConfig; + constructor(private readonly kodiApiResponseFactory: KodiApiResponseFactory) { - this.configService = configService; + this.config = configService.getRecentSearchesConfig(); } + async getRecentSearches(module: string, path: string): Promise { const data = await this.getRecentSearchesData(module); - const response = this.kodiApiResponseFactory.createApiResponse(); + const response = this.kodiApiResponseFactory + .createApiResponse() + .setNoSort(); for (const recentSearch of data) { response .createItem() @@ -28,14 +33,19 @@ export class RecentSearchesService { async addRecentSearch(module: string, term: string): Promise { const data = await this.getRecentSearchesData(module); const index = data.indexOf(term); - if (index > -1) { + if (index === 0) { + return Promise.resolve(); + } + if (index > 0) { data.splice(index, 1); } - data.push(term); - const rawData = JSON.stringify(data); - const filePath = this.getFilePath(module); + data.unshift(term); - await writeFile(filePath, rawData); + const rawData = JSON.stringify( + data.slice(0, this.config.getRecentSearchesLimit()), + ); + const filePath = this.getFilePath(module); + writeFile(filePath, rawData).then(); } private async getRecentSearchesData(module: string): Promise> { @@ -46,9 +56,6 @@ export class RecentSearchesService { } private getFilePath(module: string): string { - return join( - this.configService.getPaths().getRecentSearchesFolder(), - module + '.json', - ); + return join(this.config.getRecentSearchesFolder(), module + '.json'); } } diff --git a/src/KodiApi/Torrent/torrent.controller.ts b/src/KodiApi/Torrent/torrent.controller.ts new file mode 100644 index 0000000..c76e60d --- /dev/null +++ b/src/KodiApi/Torrent/torrent.controller.ts @@ -0,0 +1,86 @@ +import { Controller, Get, Param, Query } from '@nestjs/common'; +import ApiResponse from '../Dto/api-response.dto'; +import { TorrentService } from './torrent.service'; + +@Controller('api/torr') +export class TorrentController { + constructor(private readonly torrService: TorrentService) {} + + @Get() + async getMenu(): Promise { + return this.torrService.getMenu(); + } + + @Get('torrs') + async getTorrents(): Promise { + return this.torrService.getTorrents(); + } + + @Get('torr/:id') + async getTorrFiles(@Param('id') id: string): Promise { + return this.torrService.getTorrentFiles(id); + } + + @Get('torrent/:id') + async getOneTorrentAsList(@Param('id') id: string) { + return this.torrService.getOneTorrentAsList(id); + } + + @Get('torrent-context/:id') + async getOneTorrentAsListFromContextMenu(@Param('id') id: string) { + return this.torrService.getOneTorrentAsListFromContextMenu(id); + } + + @Get('search/:provider') + getSearch( + @Query('search') query: string, + @Param('provider') provider: string, + ) { + return this.torrService.search(query, provider); + } + + @Get('recent/:provider') + getRecent(@Param('provider') provider: string) { + return this.torrService.getRecentSearches(provider); + } + + @Get('add/:torrId') + add(@Param('torrId') torrId: string) { + return this.torrService.add(torrId); + } + + @Get('custom-menu/:provider') + async getCustomMenu(@Param() params: string[]) { + return this.torrService.getCustomMenu(params['provider'], ''); + } + + @Get('custom-menu/:provider/*') + async getCustomMenuAction(@Param() params: string[]) { + return this.torrService.getCustomMenu(params['provider'], params[0]); + } + + @Get('delete/:id') + deleteTorrent(@Param('id') id: string) { + return this.torrService.deleteTorrent(id); + } + + @Get('resume/:id') + async resumeTorrent(@Param('id') id: string) { + return this.torrService.resumeTorrent(id); + } + + @Get('stop/:id') + async stopTorrent(@Param('id') id: string) { + return this.torrService.stopTorrent(id); + } + + @Get('stop-transmission/:id') + async stopTransmissionTorrent(@Param('id') id: string) { + return this.torrService.stopTransmissionTorrent(id); + } + + @Get('add-transmission/:id') + async addTransmissionTorrent(@Param('id') id: string) { + return this.torrService.addTransmissionTorrent(id); + } +} diff --git a/src/KodiApi/Torrent/torrent.module.ts b/src/KodiApi/Torrent/torrent.module.ts new file mode 100644 index 0000000..e8cadd8 --- /dev/null +++ b/src/KodiApi/Torrent/torrent.module.ts @@ -0,0 +1,22 @@ +import { Module } from '@nestjs/common'; +import { TorrentService } from './torrent.service'; +import { TorrentController } from './torrent.controller'; +import { KodiApiResponseFactory } from '../kodi-api-response.factory'; +import { SearchClientModule } from '../../Torrent/SearchClient/search-client.module'; +import { RecentSearchesModule } from '../RecentSearches/recent-searches.module'; +import { TorrentDownloadClientModule } from '../../Torrent/DownloadClient/torrent-download-client.module'; +import { AllFilesModule } from '../AllFiles/all-files.module'; +import { TorrentSeedClientModule } from '../../Torrent/SeedClient/torrent-seed-client.module'; + +@Module({ + imports: [ + SearchClientModule, + RecentSearchesModule, + TorrentDownloadClientModule, + AllFilesModule, + TorrentSeedClientModule, + ], + controllers: [TorrentController], + providers: [TorrentService, KodiApiResponseFactory], +}) +export class TorrentModule {} diff --git a/src/KodiApi/Torrent/torrent.service.ts b/src/KodiApi/Torrent/torrent.service.ts new file mode 100644 index 0000000..a27765c --- /dev/null +++ b/src/KodiApi/Torrent/torrent.service.ts @@ -0,0 +1,344 @@ +import { Injectable, NotFoundException } from '@nestjs/common'; +import { KodiApiResponseFactory } from '../kodi-api-response.factory'; +import ApiResponse from '../Dto/api-response.dto'; +import { SearchClientFacade } from '../../Torrent/SearchClient/search-client.facade'; +import { TorrentSearchResult } from '../../Torrent/Dto/torrent-search-result.class'; +import { RecentSearchesService } from '../RecentSearches/recent-searches.service'; +import { TorrentSearchClientResponse } from '../../Torrent/Dto/torrent-search-client-response.dto'; +import { TorrentDownloadClient } from '../../Torrent/DownloadClient/torrent-download.client'; +import { AllFilesService } from '../AllFiles/all-files.service'; +import NotificationResponse from '../Dto/notification-response.dto'; +import { TorrentEntity } from '../../Shared/Entity/torrent.entity'; +import { TorrentSeedClient } from '../../Torrent/SeedClient/torrent-seed.client'; + +@Injectable() +export class TorrentService { + private torrentsCache: Record = {}; //no need for all that info, magnet is enough + + constructor( + private readonly koiApiResponseFactory: KodiApiResponseFactory, + private readonly torrSearchClient: SearchClientFacade, + private readonly recentSearchesService: RecentSearchesService, + private readonly torrDownloadClient: TorrentDownloadClient, + private readonly torrSeedClient: TorrentSeedClient, + private readonly allFilesService: AllFilesService, + ) {} + + async getMenu(): Promise { + const apiResponse = this.koiApiResponseFactory + .createApiResponse() + .setNoSort(); + const searchClientNames = this.torrSearchClient.getSearchClientNames(); + apiResponse.addNavigationItems([['Torrs', 'torrs']], 'torr/'); + apiResponse.setTitle('Torrent service'); + for (const clientName of searchClientNames) { + apiResponse.addNavigationItems( + [ + [clientName + ' Search', 'search/' + clientName, ''], + [clientName + ' Neseniai ieškoti', 'recent/' + clientName], + ], + 'torr/', + ); + const customMenu = await this.torrSearchClient.getCustomMenu(clientName); + apiResponse.addNavigationItems( + customMenu.navigationItems, + 'torr/custom-menu/' + clientName + '/', + ); + this.addTorrentSearchResultsToApiResponse( + apiResponse, + customMenu.searchResults, + ); + } + + return apiResponse; + } + + async getCustomMenu(provider: string, path: string) { + const apiResponse = this.koiApiResponseFactory.createApiResponse(); + apiResponse.setNoSort(); + const customMenu: TorrentSearchClientResponse | false = + await this.torrSearchClient + .getCustomMenu(provider, path) + .catch(() => false); + + if (customMenu === false) { + return this.koiApiResponseFactory.createNotificationResponse( + 'Error while searching', + false, + ); + } + apiResponse.setTitle(customMenu.title); + apiResponse.addNavigationItems( + customMenu.navigationItems, + 'torr/custom-menu/' + provider + '/', + ); + this.addTorrentSearchResultsToApiResponse( + apiResponse, + customMenu.searchResults, + ); + + return apiResponse; + } + + async search( + query: string, + provider: string, + ): Promise { + this.recentSearchesService.addRecentSearch('torr', query).then(/*ignore*/); + const searchResults: TorrentSearchClientResponse | false = + await this.torrSearchClient.search(query, provider).catch(() => false); + + if (searchResults === false) { + return this.koiApiResponseFactory.createNotificationResponse( + 'Error while searching', + false, + ); + } + + const apiResponse = this.koiApiResponseFactory.createApiResponse(); + apiResponse.setNoSort(); + this.addTorrentSearchResultsToApiResponse( + apiResponse, + searchResults.searchResults, + ); + + return apiResponse; + } + + async getRecentSearches(provider: string): Promise { + return this.recentSearchesService.getRecentSearches( + 'torr', + 'torr/search/' + provider, + ); + } + + async add(torrId: string): Promise { + const torrSearchData = this.torrentsCache[torrId]; + if (torrSearchData === undefined) { + throw new NotFoundException('torrent search data not found'); + } + const fileEntities = await this.torrDownloadClient + .addTorrent(torrSearchData.magnet) + .catch(() => null); + + if (fileEntities === null) { + return this.koiApiResponseFactory.createNotificationResponse( + 'error adding torrent', + false, + ); + } + + return this.allFilesService.buildFilesResponse( + torrSearchData.name, + fileEntities, + true, + ); + } + + private addTorrentSearchResultsToApiResponse( + apiResponse: ApiResponse, + searchResults: TorrentSearchResult[], + ): void { + for (const searchResult of searchResults) { + this.torrentsCache[searchResult.id] = searchResult; + apiResponse + .createItem() + .setDate(searchResult.date) + .setLabel(searchResult.name) + .setToFolder() + .setPath('torr/add/' + searchResult.id) + .setPlot( + [ + searchResult.date, + searchResult.size, + 'S' + searchResult.seeders + ' L' + searchResult.leechers, + searchResult.info, + ].join('\n'), + ); + } + } + + async getTorrents(): Promise { + const torrentEntities = await this.torrDownloadClient.getTorrents(); + const apiResponse = this.koiApiResponseFactory.createApiResponse(); + apiResponse.setTitle('Torrents'); + + for (const torrentEntity of torrentEntities) { + this.addTorrentToApiResponse(torrentEntity, apiResponse); + } + + return apiResponse; + } + + private addTorrentToApiResponse( + torrentEntity: TorrentEntity, + apiResponse: ApiResponse, + ): void { + const firstFileEntity = torrentEntity.files[0]; + if (!firstFileEntity) { + return; + } + + firstFileEntity.progress = torrentEntity.progress; + firstFileEntity.size = torrentEntity.size; + const item = apiResponse + .createItem() + .setPlot(this.buildPlot(torrentEntity)) + .setLabel(torrentEntity.name) + .setPath('/torr/torr/' + torrentEntity.id) + .setToFolder(); + + if (torrentEntity.stopped && torrentEntity.progress !== 1) { + item.addContextMenu('resume wt', '/torr/resume/' + torrentEntity.id); + } + + if (!torrentEntity.stopped) { + item.addContextMenu('stop wt', '/torr/stop/' + torrentEntity.id); + } + + if (torrentEntity.transmissionId) { + item.addContextMenu( + 'stop transmission', + '/torr/stop-transmission/' + torrentEntity.id, + ); + } + + if (!torrentEntity.transmissionId && torrentEntity.progress === 1) { + item.addContextMenu( + 'add transmission', + '/torr/add-transmission/' + torrentEntity.id, + ); + } + + item.addContextMenu('delete', '/torr/delete/' + torrentEntity.id); + } + + buildPlot(torrentEntity: TorrentEntity): string { + const plot: string[] = []; + if (torrentEntity.size) { + plot.push( + 'Size ' + this.allFilesService.getHRFileSize(torrentEntity.size), + ); + } + + const arrTorrProps: string[] = []; + if (torrentEntity.linkomanija) { + arrTorrProps.push('LM'); + } + if (torrentEntity.transmissionId) { + arrTorrProps.push('transm'); + } + + arrTorrProps.push('wt'); + + const torrProps = arrTorrProps.join(' + '); + if (torrProps) { + plot.push(torrProps); + } + + if (torrentEntity.progress) { + const progress = Math.round(torrentEntity.progress * 1000) / 10; + const stopped = torrentEntity.stopped ? ' Stopped' : ''; + plot.push(`${progress}%${stopped}`); + } + + return plot.join('\n'); + } + + getOneTorrentAsListFromContextMenu(torrentId: string) { + //by default context menu does not update container (view), we need to force it + return { + forceUpdate: { + urlParams: { + action: 'query', + path: 'torr/torrent/' + torrentId, + }, + }, + }; + } + + async getOneTorrentAsList(torrentId: string): Promise { + const id = parseInt(torrentId); + const torrentEntity = await this.torrDownloadClient.getTorrentById(id); + const apiResponse = this.koiApiResponseFactory.createApiResponse(); + if (torrentEntity) { + this.addTorrentToApiResponse(torrentEntity, apiResponse); + } + + return apiResponse; + } + + async getTorrentFiles(id: string): Promise { + const torrentEntity = await this.torrDownloadClient.getTorrentById( + parseInt(id), + ); + + return this.allFilesService.buildFilesResponse( + torrentEntity.name, + torrentEntity.files, + true, + ); + } + + async stopTorrent(id: string) { + const torrId = parseInt(id); + try { + await this.torrDownloadClient.stopTorrent(torrId); + } catch (e) { + return this.koiApiResponseFactory.createNotificationResponse( + 'error stopping', + ); + } + + return this.koiApiResponseFactory.createNotificationResponse('stopped'); + } + + async resumeTorrent(id: string) { + const torrId = parseInt(id); + try { + await this.torrDownloadClient.resumeTorrent(torrId); + } catch (e) { + return this.koiApiResponseFactory.createNotificationResponse( + 'error resuming', + ); + } + + return this.koiApiResponseFactory.createNotificationResponse('resumed'); + } + + async stopTransmissionTorrent(id: string) { + const torrId = parseInt(id); + const result = await this.torrSeedClient + .removeTorrent(torrId) + .catch(() => false); + + return new NotificationResponse( + result ? 'torrent removed from transmission' : 'removal failed', + true, + ); + } + + async addTransmissionTorrent(id: string) { + const torrId = parseInt(id); + const result = await this.torrSeedClient + .addTorrentById(torrId) + .catch(() => false); + + return new NotificationResponse( + result ? 'torrent added to transmission' : 'adding failed', + true, + ); + } + + async deleteTorrent(id: string) { + const torrId = parseInt(id); + const result = await this.torrSeedClient + .removeTorrent(torrId, true) + .catch(() => false); + + return new NotificationResponse( + result ? 'torrent deleted' : 'deleting from wt not implemented', + true, + ); + } +} diff --git a/src/KodiApi/UrlRewrite/url-rewrite.middleware.ts b/src/KodiApi/UrlRewrite/url-rewrite.middleware.ts index fa21977..ec314a9 100644 --- a/src/KodiApi/UrlRewrite/url-rewrite.middleware.ts +++ b/src/KodiApi/UrlRewrite/url-rewrite.middleware.ts @@ -5,12 +5,12 @@ import { join } from 'path'; export class KodiApiUrlRewriteMiddleware implements NestMiddleware { async use(req: Request, res: Response, next: NextFunction) { - let path = ''; if (req.query && req.query.path) { + let path = ''; path = req.query.path as string; + const query = req.url.split('?').pop(); + req.url = join('/api/', path) + (query ? '?' + query : ''); } - const query = req.url.split('?').pop(); - req.url = join('/api/', path) + (query ? '?' + query : ''); next(); } diff --git a/src/KodiApi/kodi-api.controller.ts b/src/KodiApi/kodi-api.controller.ts index d90b0c0..62f3fe7 100644 --- a/src/KodiApi/kodi-api.controller.ts +++ b/src/KodiApi/kodi-api.controller.ts @@ -8,4 +8,9 @@ export class KodiApiController { getMainMenu() { return this.kodiApiService.getMainMenu(); } + + @Get('test') + getCurrentTest() { + return this.kodiApiService.getCurrentTest(); + } } diff --git a/src/KodiApi/kodi-api.module.ts b/src/KodiApi/kodi-api.module.ts index fa4eaa4..b5b3004 100644 --- a/src/KodiApi/kodi-api.module.ts +++ b/src/KodiApi/kodi-api.module.ts @@ -10,9 +10,10 @@ import { LrtModule } from './LRT/lrt.module'; import { KodiApiService } from './kodi-api.service'; import { KodiApiResponseFactory } from './kodi-api-response.factory'; import { AllFilesModule } from './AllFiles/all-files.module'; +import { TorrentModule } from './Torrent/torrent.module'; @Module({ - imports: [LrtModule, AllFilesModule], + imports: [LrtModule, AllFilesModule, TorrentModule], controllers: [KodiApiController], providers: [KodiApiService, KodiApiResponseFactory], }) diff --git a/src/KodiApi/kodi-api.service.ts b/src/KodiApi/kodi-api.service.ts index 1c150f7..9e8a7ad 100644 --- a/src/KodiApi/kodi-api.service.ts +++ b/src/KodiApi/kodi-api.service.ts @@ -9,23 +9,27 @@ export class KodiApiService { ) {} getMainMenu(): ApiResponse { - const apiResponse = this.kodiApiResponseFactory.createApiResponse(); - - apiResponse.setTitle('Menu'); - this.createMainMenu(apiResponse); - - return apiResponse; + return this.kodiApiResponseFactory + .createApiResponse() + .setTitle('Menu') + .addNavigationItems([ + ['All Files', 'all'], + ['Torr', 'torr'], + ['LRT.lt', 'lrt'], + //['Test', 'test'], + ]); } - private createMainMenu(apiResponse: ApiResponse): void { - const items = { - all: 'All files', - torr: 'Torr', - lrt: 'LRT.lt', - }; - - for (const [path, label] of Object.entries(items)) { - apiResponse.createItem().setLabel(label).setToFolder().setPath(path); - } + getCurrentTest() { + return this.kodiApiResponseFactory + .createApiResponse() + .setTitle('Menu') + .addNavigationItems([ + ['select 1', 'all'], + ['select 2', 'lrt'], + ['select 3', 'torr'], + ['select 4 search', 'test', ''], + ]) + .setShowAsSelectList(); } } diff --git a/src/RequestLog/request-log.middleware.ts b/src/RequestLog/request-log.middleware.ts index 892d788..888b7e0 100644 --- a/src/RequestLog/request-log.middleware.ts +++ b/src/RequestLog/request-log.middleware.ts @@ -8,6 +8,7 @@ export class RequestLogMiddleware implements NestMiddleware { async use(req: Request, res: Response, next: NextFunction) { const strIP = this.extractIp(req); this.log(strIP + ' ' + req.method + ' ' + decodeURI(req.url)); + console.log(decodeURI(req.url)); next(); } diff --git a/src/Shared/Dto/navigation-item.dto.ts b/src/Shared/Dto/navigation-item.dto.ts new file mode 100644 index 0000000..8549a3c --- /dev/null +++ b/src/Shared/Dto/navigation-item.dto.ts @@ -0,0 +1 @@ +export type NavigationItem = [label: string, path?: string, searchFor?: string]; diff --git a/src/VideoFiles/Entity/file.entity.ts b/src/Shared/Entity/file.entity.ts similarity index 66% rename from src/VideoFiles/Entity/file.entity.ts rename to src/Shared/Entity/file.entity.ts index 0315d8b..5f04e3c 100644 --- a/src/VideoFiles/Entity/file.entity.ts +++ b/src/Shared/Entity/file.entity.ts @@ -1,6 +1,7 @@ import { Column, Entity, JoinColumn, ManyToOne, PrimaryColumn } from 'typeorm'; import { TitleEntity } from './title.entity'; -import { StreamProviderEnum } from '../../Streamer/ReadStreamProvider/stream-provider.enum'; +import { StreamProviderEnum } from '../Enum/stream-provider.enum'; +import { TorrentEntity } from './torrent.entity'; @Entity('files') export class FileEntity { @@ -44,13 +45,19 @@ export class FileEntity { @Column({ name: 'relative_path' }) relativePath: string; - //todo - consider if these 3 below should be better organized - @Column() - transmission: boolean; + @Column({ name: 'transmission_id' }) + transmissionId: number; @Column() linkomanija: boolean; - @Column() - webtorrent: boolean; + @Column({ name: 'torrent_id' }) + torrentId: number; + + @ManyToOne(() => TorrentEntity, (torrent) => torrent.files) + @JoinColumn({ name: 'torrent_id' }) + torrent: TorrentEntity; + + @Column({ type: 'float' }) + progress: number; } diff --git a/src/VideoFiles/Entity/title.entity.ts b/src/Shared/Entity/title.entity.ts similarity index 100% rename from src/VideoFiles/Entity/title.entity.ts rename to src/Shared/Entity/title.entity.ts diff --git a/src/Shared/Entity/torrent.entity.ts b/src/Shared/Entity/torrent.entity.ts new file mode 100644 index 0000000..2c90ab7 --- /dev/null +++ b/src/Shared/Entity/torrent.entity.ts @@ -0,0 +1,42 @@ +import { Column, Entity, OneToMany, PrimaryColumn } from 'typeorm'; +import { FileEntity } from './file.entity'; + +@Entity('torrents') +export class TorrentEntity { + @PrimaryColumn() + id: number; + + @Column() + magnet: string; + + @Column() + name: string; + + @Column() + path: string; + + @Column() + stopped: boolean; + + @Column({ name: 'info_hash' }) + infoHash: string; + + @Column({ type: 'float' }) + progress: number; + + @OneToMany(() => FileEntity, (file) => file.torrent) + files: FileEntity[]; + + @Column() + size: number; + + @Column({ name: 'transmission_id' }) + transmissionId: number; + + //todo - consider changing to a must-seed property for other private trackers (trl, etc) + @Column() + linkomanija: boolean; + + @Column() + info: string; +} diff --git a/src/Streamer/ReadStreamProvider/stream-provider.enum.ts b/src/Shared/Enum/stream-provider.enum.ts similarity index 100% rename from src/Streamer/ReadStreamProvider/stream-provider.enum.ts rename to src/Shared/Enum/stream-provider.enum.ts diff --git a/src/VideoFiles/Enum/title-type.enum.ts b/src/Shared/Enum/title-type.enum.ts similarity index 100% rename from src/VideoFiles/Enum/title-type.enum.ts rename to src/Shared/Enum/title-type.enum.ts diff --git a/src/Streamer/ReadStreamProvider/FileSystem/fs-read-stream-creatable.provider.ts b/src/Streamer/ReadStreamProvider/FileSystem/fs-read-stream-creatable.provider.ts index cfc92b7..b30f1c3 100644 --- a/src/Streamer/ReadStreamProvider/FileSystem/fs-read-stream-creatable.provider.ts +++ b/src/Streamer/ReadStreamProvider/FileSystem/fs-read-stream-creatable.provider.ts @@ -1,4 +1,4 @@ -import { FileEntity } from '../../../VideoFiles/Entity/file.entity'; +import { FileEntity } from '../../../Shared/Entity/file.entity'; import { ReadStreamCreatable } from '../../Interface/read-stream-creatable.interface'; import { MimeService } from '../../Mime/mime.service'; import { Stats } from 'fs'; @@ -6,13 +6,17 @@ import { stat } from 'fs/promises'; import { Injectable, NotFoundException } from '@nestjs/common'; import { ReadStreamCreatableFile } from './read-stream-creatable-file.dto'; import { ReadStreamCreatableProviderInterface } from '../read-stream-creatable-provider.interface'; -import { StreamProviderEnum } from '../stream-provider.enum'; +import { StreamProviderEnum } from '../../../Shared/Enum/stream-provider.enum'; +import { VideoFilesFacade } from '../../../VideoFiles/video-files.facade'; @Injectable() export class FsReadStreamCreatableProvider implements ReadStreamCreatableProviderInterface { - constructor(private readonly mimeService: MimeService) {} + constructor( + private readonly mimeService: MimeService, + private readonly videoFilesService: VideoFilesFacade, + ) {} supports(fileEntity: FileEntity): boolean { return fileEntity.streamProvider === StreamProviderEnum.fs; @@ -24,7 +28,7 @@ export class FsReadStreamCreatableProvider const filePath = fileEntity.path; const stats: Stats | false = await stat(filePath).catch(() => false); if (stats === false) { - //todo - set the file to deleted, since no longer available. Events? + this.videoFilesService.updateFsVideoFiles(); throw new NotFoundException('file not found'); } const mimeType = this.mimeService.getMime(filePath); diff --git a/src/Streamer/ReadStreamProvider/Webtorrent/read-stream-creatable-torrent-file.dto.ts b/src/Streamer/ReadStreamProvider/Webtorrent/read-stream-creatable-torrent-file.dto.ts new file mode 100644 index 0000000..68525d7 --- /dev/null +++ b/src/Streamer/ReadStreamProvider/Webtorrent/read-stream-creatable-torrent-file.dto.ts @@ -0,0 +1,24 @@ +import { ReadStream } from 'fs'; +import { ReadStreamOptions } from '../../Interface/read-stream-options.interface'; +import { ReadStreamCreatable } from '../../Interface/read-stream-creatable.interface'; +import { Injectable } from '@nestjs/common'; +import { TorrentFile } from 'webtorrent'; + +@Injectable() +export class ReadStreamCreatableTorrentFile implements ReadStreamCreatable { + constructor( + private readonly torrentFile: TorrentFile, + readonly length: number, + readonly mimeType: string, + ) { + this.torrentFile = torrentFile; + this.length = length; + this.mimeType = mimeType; + } + + createReadStream(options: ReadStreamOptions): ReadStream { + return this.torrentFile.createReadStream( + options as { start: number; end: number }, + ) as ReadStream; + } +} diff --git a/src/Streamer/ReadStreamProvider/Webtorrent/wt-read-stream-creatable.provider.ts b/src/Streamer/ReadStreamProvider/Webtorrent/wt-read-stream-creatable.provider.ts new file mode 100644 index 0000000..f9461b6 --- /dev/null +++ b/src/Streamer/ReadStreamProvider/Webtorrent/wt-read-stream-creatable.provider.ts @@ -0,0 +1,42 @@ +import { FileEntity } from '../../../Shared/Entity/file.entity'; +import { Injectable, NotFoundException } from '@nestjs/common'; +import { MimeService } from '../../Mime/mime.service'; +import { ReadStreamCreatableProviderInterface } from '../read-stream-creatable-provider.interface'; +import { TorrentDownloadClient } from '../../../Torrent/DownloadClient/torrent-download.client'; +import { StreamProviderEnum } from '../../../Shared/Enum/stream-provider.enum'; +import { ReadStreamCreatable } from '../../Interface/read-stream-creatable.interface'; +import { ReadStreamCreatableTorrentFile } from './read-stream-creatable-torrent-file.dto'; + +@Injectable() +export class WtReadStreamCreatableProvider + implements ReadStreamCreatableProviderInterface +{ + constructor( + private readonly mimeService: MimeService, + private readonly torrDlClient: TorrentDownloadClient, + ) {} + + supports(fileEntity: FileEntity): boolean { + return fileEntity.streamProvider === StreamProviderEnum.wt; + } + + async getReadStreamCreatable( + fileEntity: FileEntity, + ): Promise { + if (!fileEntity.torrent) { + throw new NotFoundException('torrent not found'); + } + const torrentFile = await this.torrDlClient.getTorrentFile(fileEntity); + if (torrentFile === null) { + //todo - rescan files, probably moved to fs provider. Events? + throw new NotFoundException('torrent file not found'); + } + const mimeType = this.mimeService.getMime(fileEntity.relativePath); + + return new ReadStreamCreatableTorrentFile( + torrentFile, + torrentFile.length, + mimeType, + ); + } +} diff --git a/src/Streamer/ReadStreamProvider/read-stream-creatable-provider.interface.ts b/src/Streamer/ReadStreamProvider/read-stream-creatable-provider.interface.ts index c1ca70b..bf7e724 100644 --- a/src/Streamer/ReadStreamProvider/read-stream-creatable-provider.interface.ts +++ b/src/Streamer/ReadStreamProvider/read-stream-creatable-provider.interface.ts @@ -1,4 +1,4 @@ -import { FileEntity } from '../../VideoFiles/Entity/file.entity'; +import { FileEntity } from '../../Shared/Entity/file.entity'; import { ReadStreamCreatable } from '../Interface/read-stream-creatable.interface'; export interface ReadStreamCreatableProviderInterface { diff --git a/src/Streamer/ReadStreamProvider/read-stream-creatable.provider.ts b/src/Streamer/ReadStreamProvider/read-stream-creatable.provider.ts index b259c82..876a315 100644 --- a/src/Streamer/ReadStreamProvider/read-stream-creatable.provider.ts +++ b/src/Streamer/ReadStreamProvider/read-stream-creatable.provider.ts @@ -1,5 +1,5 @@ import { ReadStreamCreatable } from '../Interface/read-stream-creatable.interface'; -import { FileEntity } from '../../VideoFiles/Entity/file.entity'; +import { FileEntity } from '../../Shared/Entity/file.entity'; import { Inject, Injectable } from '@nestjs/common'; import { ReadStreamCreatableProviderInterface } from './read-stream-creatable-provider.interface'; @@ -17,5 +17,6 @@ export class ReadStreamCreatableProvider { return provider.getReadStreamCreatable(fileEntity); } } + return Promise.reject('no stream provider'); } } diff --git a/src/Streamer/ResponseSender/response-sender.service.ts b/src/Streamer/ResponseSender/response-sender.service.ts index f3c3ee9..e348a2d 100644 --- a/src/Streamer/ResponseSender/response-sender.service.ts +++ b/src/Streamer/ResponseSender/response-sender.service.ts @@ -17,5 +17,8 @@ export class ResponseSenderService { } readable.pipe(response); + readable.on('error', (e) => { + //todo - figure out the Error: Writable stream closed prematurely + }); } } diff --git a/src/Streamer/streamer.facade.ts b/src/Streamer/streamer.facade.ts index f0fbddc..da7c437 100644 --- a/src/Streamer/streamer.facade.ts +++ b/src/Streamer/streamer.facade.ts @@ -1,6 +1,6 @@ import { Injectable } from '@nestjs/common'; import { Request, Response } from 'express'; -import { FileEntity } from '../VideoFiles/Entity/file.entity'; +import { FileEntity } from '../Shared/Entity/file.entity'; import { ReadStreamCreatableProvider } from './ReadStreamProvider/read-stream-creatable.provider'; import { StreamerService } from './StreamerService/streamer.service'; diff --git a/src/Streamer/streamer.module.ts b/src/Streamer/streamer.module.ts index 7f6ba02..44ae762 100644 --- a/src/Streamer/streamer.module.ts +++ b/src/Streamer/streamer.module.ts @@ -10,9 +10,12 @@ import { ReadStreamCreatableProviders, } from './ReadStreamProvider/read-stream-creatable.provider'; import { FsReadStreamCreatableProvider } from './ReadStreamProvider/FileSystem/fs-read-stream-creatable.provider'; +import { WtReadStreamCreatableProvider } from './ReadStreamProvider/Webtorrent/wt-read-stream-creatable.provider'; +import { TorrentDownloadClientModule } from '../Torrent/DownloadClient/torrent-download-client.module'; +import { VideoFilesModule } from '../VideoFiles/video-files.module'; @Module({ - imports: [], + imports: [TorrentDownloadClientModule, VideoFilesModule], providers: [ MimeService, RequestRangeService, @@ -22,10 +25,11 @@ import { FsReadStreamCreatableProvider } from './ReadStreamProvider/FileSystem/f StreamerFacade, ReadStreamCreatableProvider, FsReadStreamCreatableProvider, + WtReadStreamCreatableProvider, { provide: ReadStreamCreatableProviders, useFactory: (...providers) => providers, - inject: [FsReadStreamCreatableProvider], + inject: [FsReadStreamCreatableProvider, WtReadStreamCreatableProvider], }, ], exports: [StreamerFacade], diff --git a/src/Torrent/DownloadClient/WebtorrentClient/webtorrent.client.ts b/src/Torrent/DownloadClient/WebtorrentClient/webtorrent.client.ts new file mode 100644 index 0000000..53f56d4 --- /dev/null +++ b/src/Torrent/DownloadClient/WebtorrentClient/webtorrent.client.ts @@ -0,0 +1,299 @@ +import { Injectable } from '@nestjs/common'; +import * as WebTorrent from 'webtorrent'; +import { Instance, Torrent, TorrentFile, TorrentOptions } from 'webtorrent'; +import { InjectRepository } from '@nestjs/typeorm'; +import { TorrentEntity } from '../../../Shared/Entity/torrent.entity'; +import { Repository } from 'typeorm'; +import { configService } from '../../../config/config.service'; +import { extname, join } from 'path'; +import { FileEntity } from '../../../Shared/Entity/file.entity'; +import { StreamProviderEnum } from '../../../Shared/Enum/stream-provider.enum'; +import { VideoFilesFacade } from '../../../VideoFiles/video-files.facade'; +import { TorrentSeedClient } from '../../SeedClient/torrent-seed.client'; + +@Injectable() +export class WebtorrentClient { + private client: Instance; + private readonly torrentOptions: TorrentOptions; + private readonly configService = configService.getVideoFilesConfig(); + + constructor( + @InjectRepository(TorrentEntity) + private readonly torrentRepository: Repository, + private readonly videoFilesFacade: VideoFilesFacade, + @InjectRepository(FileEntity) + private readonly filesRepository: Repository, + private readonly seedClient: TorrentSeedClient, + ) { + this.client = new WebTorrent(); + + this.torrentOptions = {}; + this.torrentOptions.path = this.configService.getTorrentDownloadDir(); + this.torrentOptions.getAnnounceOpts = function() { + const that = this as Torrent; + + return { + uploaded: 0, //this.uploaded, + downloaded: 0, //this.downloaded, + left: that.length, //Math.max(this.length - this.downloaded, 0) + }; + }; + + this.resumeNotStopped(); + } + + resumeNotStopped(): void { + this.torrentRepository + .find({ + where: { stopped: false }, + }) + .then((torrentEntities) => { + for (const torrentEntity of torrentEntities) { + this.addTorrent(torrentEntity.magnet).then(); + } + }); + } + + async addTorrent(magnet: string): Promise { + let torrentEntity = await this.torrentRepository + .findOne({ + where: { magnet: magnet }, + relations: { files: { torrent: true } }, + }) + .catch(() => null); + + if (torrentEntity && torrentEntity.infoHash) { + //if stopped and complete - return immediately + if (torrentEntity.stopped && torrentEntity.progress === 1) { + return torrentEntity.files; + } + //if already in wt - return files, if not continue as always + const torrent = this.client.get(torrentEntity.infoHash); + if (torrent) { + return torrentEntity.files; + } + } + + if (torrentEntity === null) { + torrentEntity = new TorrentEntity(); + torrentEntity.magnet = magnet; + + await this.torrentRepository.save(torrentEntity); + torrentEntity = await this.torrentRepository + .findOneByOrFail({ magnet: magnet }) + .catch(() => null); + } + + const torrent = await this.addTorrentReturnOnReady(torrentEntity); + + this.mapTorrentToEntity(torrentEntity, torrent); + + await this.torrentRepository.save(torrentEntity); + + if (torrent.done && torrentEntity.files) { + //in case we added already downloaded one + this.registerDone(torrent); + return torrentEntity.files; + } + + const partialFileEntities = this.buildPartialVideoFileEntities( + torrentEntity, + torrent, + ); + + const fileEntities = await this.videoFilesFacade.addVideoFiles( + partialFileEntities, + ); + + this.registerProgressSave(torrent); + this.registerDone(torrent); + + return fileEntities; + } + + private async addTorrentReturnOnReady( + torrentEntity: TorrentEntity, + ): Promise { + let torrent = null; + if (torrentEntity.infoHash) { + torrent = this.client.get(torrentEntity.infoHash) as Torrent | undefined; + } + + if (!torrent) { + return new Promise((resolve, reject) => { + const torrent = this.client.add( + torrentEntity.magnet, + this.torrentOptions, + ); + + torrent.on('ready', () => { + resolve(torrent); + }); + + torrent.on('error', () => { + reject('torrent error'); + }); + }); + } + + return Promise.resolve(torrent); + } + + private registerProgressSave(torrent: Torrent) { + const notOftenThan = 1000 * 5; + let lastUpdateTime = 0; + + torrent.on('download', async () => { + if (lastUpdateTime < Date.now() - notOftenThan) { + await this.torrentRepository.update( + { infoHash: torrent.infoHash }, + { progress: torrent.progress }, + ); + for (const file of torrent.files) { + await this.filesRepository.update( + { relativePath: file.path }, + { progress: file.progress }, + ); + } + lastUpdateTime = Date.now(); + } + }); + } + + private registerDone(torrent: Torrent) { + if (torrent.done) { + this.actWhenDone(torrent); + return; + } + + torrent.on('done', () => { + this.actWhenDone(torrent); + }); + } + + private actWhenDone(torrent: Torrent) { + this.torrentRepository + .update({ infoHash: torrent.infoHash }, { progress: 1, stopped: true }) + .then(async () => { + const torrentEntity = await this.torrentRepository.findOne({ + where: { infoHash: torrent.infoHash }, + relations: { files: { torrent: true } }, + }); + + //update files to progress 1 + await this.filesRepository.update( + { torrentId: torrentEntity.id }, + { progress: 1 }, + ); + + return torrentEntity; + }) + .then(async (torrentEntity) => { + //move file to seeding + if (torrentEntity.magnet.toLowerCase().includes('linkomanija')) { + const torrentInfo = await this.seedClient + .addTorrent(torrentEntity) + .catch(() => null); + if (torrentInfo) { + torrentEntity.transmissionId = parseInt(torrentInfo.id); + await this.torrentRepository.save(torrentEntity); + } + } + + return torrentEntity; + }) + .then(async (torrentEntity) => { + //update files info with expanders and move to fs provider + const filePaths: string[] = []; + for (const file of torrentEntity.files) { + filePaths.push(file.path); + } + this.videoFilesFacade.updateEntitiesByPath(filePaths).then(); + torrent.destroy(); + + return torrentEntity; + }); + } + + private mapTorrentToEntity(torrentEntity: TorrentEntity, torrent: Torrent) { + torrentEntity.path = torrent.path; + torrentEntity.infoHash = torrent.infoHash; + torrentEntity.name = torrent.name; + torrentEntity.stopped = torrent.progress === 1; + torrentEntity.progress = torrent.progress; + torrentEntity.size = torrent.length; + torrentEntity.linkomanija = torrentEntity.magnet + .toLowerCase() + .includes('linkomanija'); + + //map all files (include not video files, will be needed for deletion) + torrentEntity.info = JSON.stringify({ + files: torrent.files.map((file) => { + return { path: join(torrent.path, file.path) }; + }), + }); + } + + private buildPartialVideoFileEntities( + torrentEntity: TorrentEntity, + torrent: Torrent, + ): Partial[] { + return torrent.files.reduce( + (filtered: Partial[], file: TorrentFile) => { + if ( + this.configService + .getVideoFilesExt() + .indexOf(extname(file.name).toLowerCase()) > -1 + ) { + const partialFileEntity: Partial = { + path: join(torrent.path, file.path), + relativePath: file.path, + torrent: torrentEntity, + size: file.length, + streamProvider: StreamProviderEnum.wt, + fileName: file.name, + linkomanija: torrentEntity.magnet + .toLowerCase() + .includes('linkomanija'), + }; + filtered.push(partialFileEntity); + } + + return filtered; + }, + [], + ); + } + + getTorrentByInfoHash(torrentInfoHash: string): Torrent | void { + return this.client.get(torrentInfoHash); + } + + async stopTorrent(torrentId: number): Promise { + const torrentEntity = await this.torrentRepository.findOne({ + where: { id: torrentId }, + }); + + if (!torrentEntity || !torrentEntity.infoHash) { + return; + } + + const torrent = await this.getTorrentByInfoHash(torrentEntity.infoHash); + + if (!torrent || torrent.done) { + return; + } + + return new Promise((resolve, reject) => { + torrent.destroy({ destroyStore: false }, (e) => { + if (e) { + return reject(e); + } + torrentEntity.stopped = true; + this.torrentRepository.save(torrentEntity); + + return resolve(); + }); + }); + } +} diff --git a/src/Torrent/DownloadClient/torrent-download-client.module.ts b/src/Torrent/DownloadClient/torrent-download-client.module.ts new file mode 100644 index 0000000..efdc570 --- /dev/null +++ b/src/Torrent/DownloadClient/torrent-download-client.module.ts @@ -0,0 +1,19 @@ +import { Module } from '@nestjs/common'; +import { TorrentDownloadClient } from './torrent-download.client'; +import { VideoFilesModule } from '../../VideoFiles/video-files.module'; +import { WebtorrentClient } from './WebtorrentClient/webtorrent.client'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { TorrentEntity } from '../../Shared/Entity/torrent.entity'; +import { FileEntity } from '../../Shared/Entity/file.entity'; +import { TorrentSeedClientModule } from '../SeedClient/torrent-seed-client.module'; + +@Module({ + imports: [ + VideoFilesModule, + TypeOrmModule.forFeature([TorrentEntity, FileEntity]), + TorrentSeedClientModule, + ], + providers: [WebtorrentClient, TorrentDownloadClient], + exports: [TorrentDownloadClient], +}) +export class TorrentDownloadClientModule {} diff --git a/src/Torrent/DownloadClient/torrent-download.client.ts b/src/Torrent/DownloadClient/torrent-download.client.ts new file mode 100644 index 0000000..3fcde3f --- /dev/null +++ b/src/Torrent/DownloadClient/torrent-download.client.ts @@ -0,0 +1,68 @@ +import { Injectable } from '@nestjs/common'; +import { WebtorrentClient } from './WebtorrentClient/webtorrent.client'; +import { FileEntity } from '../../Shared/Entity/file.entity'; +import { TorrentFile } from 'webtorrent'; +import { InjectRepository } from '@nestjs/typeorm'; +import { TorrentEntity } from '../../Shared/Entity/torrent.entity'; +import { Repository } from 'typeorm'; + +@Injectable() +export class TorrentDownloadClient { + constructor( + private readonly webtorrentClient: WebtorrentClient, + @InjectRepository(TorrentEntity) + private readonly torrentRepository: Repository, + ) {} + + addTorrent(magnet: string): Promise { + return this.webtorrentClient.addTorrent(magnet); + } + + async getTorrentFile(fileEntity: FileEntity): Promise { + const torrentInfoHash = fileEntity.torrent.infoHash; + let torrent = this.webtorrentClient.getTorrentByInfoHash(torrentInfoHash); + if (!torrent) { + await this.webtorrentClient.addTorrent(fileEntity.torrent.magnet); + } + + torrent = this.webtorrentClient.getTorrentByInfoHash(torrentInfoHash); + if (!torrent) { + return null; + } + + for (const file of torrent.files) { + if (file.path === fileEntity.relativePath) { + return file; + } + } + } + + getTorrents(): Promise { + return this.torrentRepository.find({ + relations: { files: { torrent: true } }, + }); + } + + getTorrentById(id: number): Promise { + return this.torrentRepository.findOne({ + where: { id: id }, + relations: { files: { torrent: true } }, + }); + } + + stopTorrent(torrentId: number): Promise { + return this.webtorrentClient.stopTorrent(torrentId); + } + + async resumeTorrent(torrentId: number): Promise { + const torrentEntity = await this.torrentRepository.findOne({ + where: { id: torrentId }, + }); + + if (!torrentEntity || !torrentEntity.magnet) { + return Promise.reject('torrent not found'); + } + + return this.addTorrent(torrentEntity.magnet); + } +} diff --git a/src/Torrent/Dto/torrent-search-client-response.dto.ts b/src/Torrent/Dto/torrent-search-client-response.dto.ts new file mode 100644 index 0000000..328f2d0 --- /dev/null +++ b/src/Torrent/Dto/torrent-search-client-response.dto.ts @@ -0,0 +1,9 @@ +import { NavigationItem } from '../../Shared/Dto/navigation-item.dto'; +import { TorrentSearchResult } from './torrent-search-result.class'; + +export class TorrentSearchClientResponse { + title = ''; + navigationItems: NavigationItem[] = []; + searchResults: TorrentSearchResult[] = []; + //todo pagination +} diff --git a/src/Torrent/Dto/torrent-search-result.class.ts b/src/Torrent/Dto/torrent-search-result.class.ts new file mode 100644 index 0000000..4373df9 --- /dev/null +++ b/src/Torrent/Dto/torrent-search-result.class.ts @@ -0,0 +1,10 @@ +export class TorrentSearchResult { + id: string; + magnet: string; + name: string; + size: string; + seeders: string; + leechers: string; + date: string; + info: string; //anything that does not fit to above +} diff --git a/src/Torrent/SearchClient/ClientBridge/linkomanija-client.bridge.ts b/src/Torrent/SearchClient/ClientBridge/linkomanija-client.bridge.ts new file mode 100644 index 0000000..b212413 --- /dev/null +++ b/src/Torrent/SearchClient/ClientBridge/linkomanija-client.bridge.ts @@ -0,0 +1,24 @@ +import { SearchClientInterface } from '../Interface/search-client.interface'; +import { LinkomanijaClient } from '../LinkomanijaClient/linkomanija.client'; +import { Injectable } from '@nestjs/common'; +import { TorrentSearchClientResponse } from '../../Dto/torrent-search-client-response.dto'; + +/** + * Bridge is needed as it seems you cannot inject providers from other modules using factory in module level + */ +@Injectable() +export class LinkomanijaClientBridge implements SearchClientInterface { + clientName: string; + + constructor(private readonly client: LinkomanijaClient) { + this.clientName = client.clientName; + } + + search(query: string) { + return this.client.search(query); + } + + customMenu(path: string): Promise { + return this.client.customMenu(path); + } +} diff --git a/src/Torrent/SearchClient/Interface/search-client.interface.ts b/src/Torrent/SearchClient/Interface/search-client.interface.ts new file mode 100644 index 0000000..d4c98b7 --- /dev/null +++ b/src/Torrent/SearchClient/Interface/search-client.interface.ts @@ -0,0 +1,10 @@ +import { TorrentSearchResult } from '../../Dto/torrent-search-result.class'; +import { TorrentSearchClientResponse } from '../../Dto/torrent-search-client-response.dto'; + +export interface SearchClientInterface { + clientName: string; + + search(query: string): Promise; + + customMenu(path: string): Promise; +} diff --git a/src/Torrent/SearchClient/LinkomanijaClient/HttpClient/linkomanija-http.client.ts b/src/Torrent/SearchClient/LinkomanijaClient/HttpClient/linkomanija-http.client.ts new file mode 100644 index 0000000..26f9e37 --- /dev/null +++ b/src/Torrent/SearchClient/LinkomanijaClient/HttpClient/linkomanija-http.client.ts @@ -0,0 +1,155 @@ +import { Agent, RequestOptions, request, get } from 'https'; +import { + configService, + ConfigService, +} from '../../../../config/config.service'; +import { IncomingMessage } from 'http'; +import { Injectable } from '@nestjs/common'; + +@Injectable() +export class LinkomanijaHttpClient { + private readonly configService: ConfigService; + private readonly requestOptions: RequestOptions; + private consecutiveLoginFails: number; + private lastLoginFailTimeStamp = 0; + + constructor() { + this.requestOptions = { + headers: {}, + agent: new Agent({ keepAlive: true }), + rejectUnauthorized: false, + hostname: 'www.linkomanija.net', + port: 443, + }; + + this.configService = configService; + } + + public async search(query: string): Promise { + const c = ['c29', 'c52', 'c30', 'c60', 'c53', 'c61', 'c28', 'c62']; + const path = `/browse.php?${c.join('=1&')}=1&incldead=0&search=${query}`; + + return this.getPage(path); + } + + public async getTop(category: string, time: string): Promise { + const path = `/browse.php?top=${category}&time=${time}`; + + return this.getPage(path); + } + + private async getPage(path: string): Promise { + const options = { + ...this.requestOptions, + path: encodeURI(path), + method: 'POST', + }; + + const response: IncomingMessage | false = await this.httpGet(options).catch( + () => false, + ); + + if (response === false) { + return Promise.reject(); + } + + this.parseAndSetCookie(response); + + if ( + response.statusCode === 302 && + response.headers.location.includes('login.php') + ) { + //need to log in + const loggedIn = await this.login().catch(() => false); + if (loggedIn || this.checkLoginSafeToAttempt()) { + return this.getPage(path); + } + return Promise.reject(); + } + + return new Promise((resolve) => { + let rawData = ''; + response.on('data', (chunk) => (rawData += chunk)); + response.on('end', () => { + resolve(rawData); + }); + }); + } + + private httpGet(options: RequestOptions): Promise { + return new Promise((resolve, reject) => { + const req = get(options, (res) => { + resolve(res); + }); + req.on('error', (err) => { + reject(err); + }); + }); + } + + private login(): Promise { + if (!this.checkLoginSafeToAttempt()) { + console.log('LM too many login attempts reached'); + return Promise.reject(); + } + + const options = { + ...this.requestOptions, + path: '/takelogin.php', + method: 'POST', + }; + + const userName = process.env['LINKOMANIJA_USERNAME']; + const password = process.env['LINKOMANIJA_PASSWORD']; + const postData = `username=${userName}&password=${password}&commit=Prisijungti`; + + options.headers = JSON.parse(JSON.stringify(options.headers)); + options.headers['Content-Type'] = 'application/x-www-form-urlencoded'; + options.headers['Content-Length'] = Buffer.byteLength(postData); + + return new Promise((resolve, reject) => { + const req = request(options, (res) => { + this.parseAndSetCookie(res); + + if ( + res.statusCode === 302 && + !res.headers.location.includes('login.php') + ) { + this.consecutiveLoginFails = 0; + resolve(true); + return; + } + + this.increaseLoginFailures(); + reject(); + }); + + req.on('error', (e) => { + this.increaseLoginFailures(); + reject(); + }); + + req.write(postData); + req.end(); + }); + } + + private parseAndSetCookie(response: IncomingMessage): void { + const cookieToSet = response.headers['set-cookie']; + if (cookieToSet) { + this.requestOptions.headers['cookie'] = cookieToSet[0].split(';')[0]; + } + } + + private increaseLoginFailures(): void { + this.consecutiveLoginFails++; + this.lastLoginFailTimeStamp = Date.now(); + } + + private checkLoginSafeToAttempt(): boolean { + return ( + this.consecutiveLoginFails <= 5 || + this.lastLoginFailTimeStamp < Date.now() - 1000 * 60 * 5 + ); + } +} diff --git a/src/Torrent/SearchClient/LinkomanijaClient/ResponseParser/linkomanija-response.parser.ts b/src/Torrent/SearchClient/LinkomanijaClient/ResponseParser/linkomanija-response.parser.ts new file mode 100644 index 0000000..48ddd08 --- /dev/null +++ b/src/Torrent/SearchClient/LinkomanijaClient/ResponseParser/linkomanija-response.parser.ts @@ -0,0 +1,72 @@ +import { Injectable } from '@nestjs/common'; +import * as cheerio from 'cheerio'; +import * as url from 'url'; +import { TorrentSearchResult } from '../../../Dto/torrent-search-result.class'; + +@Injectable() +export class LinkomanijaResponseParser { + parseRaw(rawData: string): TorrentSearchResult[] { + const $ = cheerio.load(rawData); + const torrents = []; + + $('body').find('br').replaceWith('\n'); + + $('table').each(function (iTable, elTable) { + $(elTable) + .find('a.index') + .each(function (iA, elA) { + const torrent = new TorrentSearchResult(); + const arrTorDetails = $(elA.parent.parent).find('td'); + + let sNameAndDesc = $(arrTorDetails[1]).text(); + sNameAndDesc = sNameAndDesc.split('\t').join(''); + const arrNameAndDesc = sNameAndDesc + .split('\n') + .filter(function (val) { + return val; + }); + + torrent.name = arrNameAndDesc[0] ? arrNameAndDesc[0].trim() : ''; + torrent.info = arrNameAndDesc[1] ? arrNameAndDesc[1].trim() : ''; + torrent.date = $(arrTorDetails[4]).text().split('\n')[0]; + torrent.size = $(arrTorDetails[5]).text().split('\n').join(' '); + torrent.seeders = $(arrTorDetails[7]).text().split('\n').join(' '); + torrent.leechers = $(arrTorDetails[8]).text().split('\n').join(' '); + + if ('attribs' in elA) { + const { id, name } = url.parse(elA.attribs['href'], true).query; + torrent.id = id as string; + + const relDlUrl = encodeURI( + '/download.php?id=' + + id + + '&passkey=' + + process.env['LINKOMANIJA_PASSKEY'] + + '&name=' + + name, + ); + + torrent.magnet = 'https://www.linkomanija.net' + relDlUrl; + } + + const arrImgs = $(arrTorDetails).find('img'); + + if ( + arrImgs && + arrImgs[0] && + 'attribs' in arrImgs[0] && + arrImgs[0].attribs && + arrImgs[0].attribs.alt + ) { + torrent.info = + arrImgs[0].attribs.alt + + (torrent.info ? ': ' + torrent.info : ''); + } + + torrents.push(torrent); + }); + }); + + return torrents; + } +} diff --git a/src/Torrent/SearchClient/LinkomanijaClient/linkomanija.client.ts b/src/Torrent/SearchClient/LinkomanijaClient/linkomanija.client.ts new file mode 100644 index 0000000..699ed48 --- /dev/null +++ b/src/Torrent/SearchClient/LinkomanijaClient/linkomanija.client.ts @@ -0,0 +1,83 @@ +import { Injectable } from '@nestjs/common'; +import { LinkomanijaHttpClient } from './HttpClient/linkomanija-http.client'; +import { LinkomanijaResponseParser } from './ResponseParser/linkomanija-response.parser'; +import { TorrentSearchResult } from '../../Dto/torrent-search-result.class'; +import { SearchClientInterface } from '../Interface/search-client.interface'; +import { NavigationItem } from '../../../Shared/Dto/navigation-item.dto'; +import { TorrentSearchClientResponse } from '../../Dto/torrent-search-client-response.dto'; + +@Injectable() +export class LinkomanijaClient implements SearchClientInterface { + clientName = 'LM'; + + constructor( + private readonly lmHttpClient: LinkomanijaHttpClient, + private readonly lmResponseParser: LinkomanijaResponseParser, + ) {} + + async search(query: string): Promise { + const rawData = await this.lmHttpClient.search(query).catch((e) => e); + + return this.lmResponseParser.parseRaw(rawData); + } + + async getTop(category: string, time: string): Promise { + const rawData = await this.lmHttpClient + .getTop(category, time) + .catch((e) => e); + + return this.lmResponseParser.parseRaw(rawData); + } + + async customMenu(path: string): Promise { + const [service, category, time] = path.split('/'); + const customMenuResponse = new TorrentSearchClientResponse(); + + if (!service) { + customMenuResponse.navigationItems.push(['LM Top', 'top']); + return Promise.resolve(customMenuResponse); + } + + const categories: NavigationItem[] = [ + ['Movies LT', 'top/53'], + ['Movies LT HD', 'top/61'], + ['Movies', 'top/29'], + ['Movies HD', 'top/52'], + ['TV LT', 'top/28'], + ['TV LT HD', 'top/62'], + ['TV', 'top/30'], + ['TV HD', 'top/60'], + ]; + + if (!category) { + customMenuResponse.navigationItems = categories; + customMenuResponse.title = 'LM Top'; + return Promise.resolve(customMenuResponse); + } + + const times: NavigationItem[] = [ + ['Savaitės', 'top/' + category + '/7'], + ['2 Savaičių', 'top/' + category + '/14'], + ['Mėnesio', 'top/' + category + '/30'], + ['3 Mėnesių', 'top/' + category + '/90'], + ['Metų', 'top/' + category + '/365'], + ['Visų laikų', 'top/' + category + '/0'], + ]; + + if (!time) { + customMenuResponse.navigationItems = times; + customMenuResponse.title = + 'LM Top, ' + categories.find((value) => value[1] == path)[0]; + return Promise.resolve(customMenuResponse); + } + + customMenuResponse.title = + 'LM Top, ' + + categories.find((value) => value[1] == 'top/' + category)[0] + + ', ' + + times.find((value) => value[1] == path)[0]; + customMenuResponse.searchResults = await this.getTop(category, time); + + return customMenuResponse; + } +} diff --git a/src/Torrent/SearchClient/LinkomanijaClient/linkomanija.module.ts b/src/Torrent/SearchClient/LinkomanijaClient/linkomanija.module.ts new file mode 100644 index 0000000..18c7a72 --- /dev/null +++ b/src/Torrent/SearchClient/LinkomanijaClient/linkomanija.module.ts @@ -0,0 +1,15 @@ +import { Module } from '@nestjs/common'; +import { LinkomanijaClient } from './linkomanija.client'; +import { LinkomanijaHttpClient } from './HttpClient/linkomanija-http.client'; +import { LinkomanijaResponseParser } from './ResponseParser/linkomanija-response.parser'; + +@Module({ + imports: [], + providers: [ + LinkomanijaClient, + LinkomanijaHttpClient, + LinkomanijaResponseParser, + ], + exports: [LinkomanijaClient], +}) +export class LinkomanijaModule {} diff --git a/src/Torrent/SearchClient/search-client.facade.ts b/src/Torrent/SearchClient/search-client.facade.ts new file mode 100644 index 0000000..1f93d74 --- /dev/null +++ b/src/Torrent/SearchClient/search-client.facade.ts @@ -0,0 +1,53 @@ +import { Inject, Injectable } from '@nestjs/common'; +import { SearchClientInterface } from './Interface/search-client.interface'; +import { TorrentSearchClientResponse } from '../Dto/torrent-search-client-response.dto'; + +export const SearchClients = 'SearchClients'; + +@Injectable() +export class SearchClientFacade { + constructor( + @Inject(SearchClients) + private readonly searchClients: SearchClientInterface[], + ) {} + + getSearchClientNames(): string[] { + const clients = []; + for (const searchClient of this.searchClients) { + clients.push(searchClient.clientName); + } + + return clients; + } + + //todo - page + async search( + query: string, + clientName: string, + ): Promise { + for (const searchClient of this.searchClients) { + if (searchClient.clientName.toLowerCase() === clientName.toLowerCase()) { + const response = new TorrentSearchClientResponse(); + response.title = searchClient.clientName + ' search for: ' + query; + response.searchResults = await searchClient.search(query); + + return response; + } + } + + return Promise.reject('client not found'); + } + + getCustomMenu( + clientName: string, + path = '', + ): Promise { + for (const searchClient of this.searchClients) { + if (searchClient.clientName.toLowerCase() === clientName.toLowerCase()) { + return searchClient.customMenu(path); + } + } + + return Promise.reject('client not found'); + } +} diff --git a/src/Torrent/SearchClient/search-client.module.ts b/src/Torrent/SearchClient/search-client.module.ts new file mode 100644 index 0000000..bec5f69 --- /dev/null +++ b/src/Torrent/SearchClient/search-client.module.ts @@ -0,0 +1,19 @@ +import { LinkomanijaModule } from './LinkomanijaClient/linkomanija.module'; +import { Module } from '@nestjs/common'; +import { SearchClientFacade, SearchClients } from './search-client.facade'; +import { LinkomanijaClientBridge } from './ClientBridge/linkomanija-client.bridge'; + +@Module({ + imports: [LinkomanijaModule], + providers: [ + SearchClientFacade, + LinkomanijaClientBridge, + { + provide: SearchClients, + useFactory: (...clients) => clients, + inject: [LinkomanijaClientBridge], + }, + ], + exports: [SearchClientFacade], +}) +export class SearchClientModule {} diff --git a/src/Torrent/SeedClient/Dto/torrent-info.dto.ts b/src/Torrent/SeedClient/Dto/torrent-info.dto.ts new file mode 100644 index 0000000..f996b51 --- /dev/null +++ b/src/Torrent/SeedClient/Dto/torrent-info.dto.ts @@ -0,0 +1,4 @@ +export class TorrentInfo { + id: string; + linkomanija: boolean; +} diff --git a/src/Torrent/SeedClient/Transmission/transmission.client.ts b/src/Torrent/SeedClient/Transmission/transmission.client.ts new file mode 100644 index 0000000..949102c --- /dev/null +++ b/src/Torrent/SeedClient/Transmission/transmission.client.ts @@ -0,0 +1,83 @@ +import { Injectable } from '@nestjs/common'; +import * as Transmission from 'transmission'; +import { VideoFilesConfig } from '../../../config/video-files.config'; +import { configService } from '../../../config/config.service'; +import { TorrentEntity } from '../../../Shared/Entity/torrent.entity'; +import { TorrentInfo } from '../Dto/torrent-info.dto'; +import { FileEntity } from '../../../Shared/Entity/file.entity'; + +@Injectable() +export class TransmissionClient { + private transmission: Transmission; + private config: VideoFilesConfig = configService.getVideoFilesConfig(); + + constructor() { + this.transmission = new Transmission(this.config.getTransmissionOptions()); + } + + addTorrent(torrentEntity: TorrentEntity): Promise { + return new Promise((resolve, reject) => { + this.transmission.addUrl( + torrentEntity.magnet, + { 'download-dir': torrentEntity.path }, + (err, arg) => { + if (err) { + return reject(err); + } + + const torrentInfo = new TorrentInfo(); + torrentInfo.id = arg.id; + torrentInfo.linkomanija = torrentEntity.magnet + .toLowerCase() + .includes('linkomanija'); + + return resolve(torrentInfo); + }, + ); + }); + } + + findTorrentInfoByFile(fileEntity: FileEntity): Promise { + return new Promise((resolve, reject) => { + this.transmission.get((err, result) => { + if (err) { + return reject(err); + } + + for (const torr of result.torrents) { + for (const file of torr.files) { + if (fileEntity.path === torr.downloadDir + '/' + file.name) { + const torrentInfo = new TorrentInfo(); + torrentInfo.id = torr.id; + torrentInfo.linkomanija = torr.magnetLink + .toLowerCase() + .includes('linkomanija'); + + return resolve(torrentInfo); + } + } + } + + return reject('file not found'); + }); + }); + } + + removeTorrent(torrentEntity: TorrentEntity, removeData = false) { + if (torrentEntity.transmissionId) { + return new Promise((resolve, reject) => { + this.transmission.remove( + [torrentEntity.transmissionId], + removeData, + (e, r) => { + if (e) { + return reject(e); + } + + return resolve(r); + }, + ); + }); + } + } +} diff --git a/src/Torrent/SeedClient/torrent-seed-client.module.ts b/src/Torrent/SeedClient/torrent-seed-client.module.ts new file mode 100644 index 0000000..91f6259 --- /dev/null +++ b/src/Torrent/SeedClient/torrent-seed-client.module.ts @@ -0,0 +1,13 @@ +import { Module } from '@nestjs/common'; +import { TorrentSeedClient } from './torrent-seed.client'; +import { TransmissionClient } from './Transmission/transmission.client'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { TorrentEntity } from '../../Shared/Entity/torrent.entity'; +import { FileEntity } from '../../Shared/Entity/file.entity'; + +@Module({ + imports: [TypeOrmModule.forFeature([TorrentEntity, FileEntity])], + providers: [TorrentSeedClient, TransmissionClient], + exports: [TorrentSeedClient], +}) +export class TorrentSeedClientModule {} diff --git a/src/Torrent/SeedClient/torrent-seed.client.ts b/src/Torrent/SeedClient/torrent-seed.client.ts new file mode 100644 index 0000000..9af5416 --- /dev/null +++ b/src/Torrent/SeedClient/torrent-seed.client.ts @@ -0,0 +1,88 @@ +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { TorrentEntity } from '../../Shared/Entity/torrent.entity'; +import { Repository } from 'typeorm'; +import { TransmissionClient } from './Transmission/transmission.client'; +import { TorrentInfo } from './Dto/torrent-info.dto'; +import { FileEntity } from '../../Shared/Entity/file.entity'; + +@Injectable() +export class TorrentSeedClient { + constructor( + private readonly transmissionClient: TransmissionClient, + @InjectRepository(TorrentEntity) + private readonly torrentRepository: Repository, + @InjectRepository(FileEntity) + private readonly filesRepository: Repository, + ) {} + + addTorrent(torrentEntity: TorrentEntity): Promise { + return this.transmissionClient.addTorrent(torrentEntity); + } + + async addTorrentById(torrId: number): Promise { + const torrentEntity = await this.torrentRepository.findOne({ + where: { id: torrId }, + }); + + if (!torrentEntity) { + return Promise.reject('torrent not found'); + } + + const torrInfo = await this.addTorrent(torrentEntity).catch(() => null); + + if (torrInfo) { + await this.torrentRepository.update( + { id: torrentEntity.id }, + { transmissionId: torrInfo.id, linkomanija: torrInfo.linkomanija }, + ); + await this.filesRepository.update( + { torrentId: torrentEntity.id }, + { transmissionId: torrInfo.id, linkomanija: torrInfo.linkomanija }, + ); + } + + return torrInfo; + } + + findTorrentInfoByFile(fileEntity: FileEntity): Promise { + return this.transmissionClient.findTorrentInfoByFile(fileEntity); + } + + async removeTorrent(torrId: number, removeData = false) { + const torrentEntity = await this.torrentRepository.findOne({ + where: { id: torrId }, + }); + + if (!torrentEntity) { + return Promise.reject('torrent not found'); + } + + const result = await this.transmissionClient + .removeTorrent(torrentEntity, removeData) + .catch(() => null); + + if (result && !removeData) { + await this.torrentRepository.update( + { id: torrentEntity.id }, + { transmissionId: null }, + ); + await this.filesRepository.update( + { torrentId: torrentEntity.id }, + { transmissionId: null }, + ); + } + + if (result && removeData) { + await this.torrentRepository.delete({ + id: torrentEntity.id, + }); + await this.filesRepository.update( + { torrentId: torrentEntity.id }, + { transmissionId: null, torrentId: null, deleted: true }, + ); + } + + return result; + } +} diff --git a/src/TryOutModule/try.cotroller.ts b/src/TryOutModule/try.cotroller.ts index 26152f8..44613ee 100644 --- a/src/TryOutModule/try.cotroller.ts +++ b/src/TryOutModule/try.cotroller.ts @@ -1,13 +1,9 @@ import { Controller, Get } from '@nestjs/common'; -import { VideoFilesFacade } from '../VideoFiles/video-files.facade'; @Controller('try') export class TryController { - constructor(readonly videoFilesFacade: VideoFilesFacade) {} - @Get('') main() { - this.videoFilesFacade.updateFsVideoFiles(); - return { doing: 'something' }; + return { hello: 'world' }; } } diff --git a/src/TryOutModule/try.module.ts b/src/TryOutModule/try.module.ts index 2d5fee1..af92de8 100644 --- a/src/TryOutModule/try.module.ts +++ b/src/TryOutModule/try.module.ts @@ -1,12 +1,11 @@ import { Module } from '@nestjs/common'; import { TryController } from './try.cotroller'; -import { VideoFilesModule } from '../VideoFiles/video-files.module'; /** * Playground module for playing around */ @Module({ - imports: [VideoFilesModule], + imports: [], controllers: [TryController], providers: [], }) diff --git a/src/VideoFiles/Update/Expander/file-entity-defaults.expander.ts b/src/VideoFiles/Update/Expander/file-entity-defaults.expander.ts index 634c7cf..87c8f5b 100644 --- a/src/VideoFiles/Update/Expander/file-entity-defaults.expander.ts +++ b/src/VideoFiles/Update/Expander/file-entity-defaults.expander.ts @@ -1,5 +1,5 @@ import { FileEntityExpanderInterface } from './file-entity-expander.interface'; -import { FileEntity } from '../../Entity/file.entity'; +import { FileEntity } from '../../../Shared/Entity/file.entity'; import { Injectable } from '@nestjs/common'; @Injectable() diff --git a/src/VideoFiles/Update/Expander/file-entity-duration.expander.ts b/src/VideoFiles/Update/Expander/file-entity-duration.expander.ts index e860c5f..d0a6e20 100644 --- a/src/VideoFiles/Update/Expander/file-entity-duration.expander.ts +++ b/src/VideoFiles/Update/Expander/file-entity-duration.expander.ts @@ -1,12 +1,16 @@ import { FileEntityExpanderInterface } from './file-entity-expander.interface'; -import { FileEntity } from '../../Entity/file.entity'; +import { FileEntity } from '../../../Shared/Entity/file.entity'; import * as ffmpeg from 'fluent-ffmpeg'; import { Injectable } from '@nestjs/common'; +import { StreamProviderEnum } from '../../../Shared/Enum/stream-provider.enum'; @Injectable() export class FileEntityDurationExpander implements FileEntityExpanderInterface { expand(fileEntity: FileEntity): Promise { return new Promise((resolve) => { + if (fileEntity.streamProvider !== StreamProviderEnum.fs) { + return resolve(fileEntity); + } ffmpeg.ffprobe(fileEntity.path, (err, data) => { if (!err) { //no error handling! diff --git a/src/VideoFiles/Update/Expander/file-entity-expander.interface.ts b/src/VideoFiles/Update/Expander/file-entity-expander.interface.ts index bc6ddcb..bb1bf2c 100644 --- a/src/VideoFiles/Update/Expander/file-entity-expander.interface.ts +++ b/src/VideoFiles/Update/Expander/file-entity-expander.interface.ts @@ -1,4 +1,4 @@ -import { FileEntity } from '../../Entity/file.entity'; +import { FileEntity } from '../../../Shared/Entity/file.entity'; export interface FileEntityExpanderInterface { expand(fileEntity: FileEntity): Promise; diff --git a/src/VideoFiles/Update/Expander/file-entity-size.expander.ts b/src/VideoFiles/Update/Expander/file-entity-size.expander.ts index 9003fd8..db3d49f 100644 --- a/src/VideoFiles/Update/Expander/file-entity-size.expander.ts +++ b/src/VideoFiles/Update/Expander/file-entity-size.expander.ts @@ -1,12 +1,16 @@ import { FileEntityExpanderInterface } from './file-entity-expander.interface'; -import { FileEntity } from '../../Entity/file.entity'; +import { FileEntity } from '../../../Shared/Entity/file.entity'; import { Stats } from 'fs'; import { stat } from 'fs/promises'; import { Injectable } from '@nestjs/common'; +import { StreamProviderEnum } from '../../../Shared/Enum/stream-provider.enum'; @Injectable() export class FileEntitySizeExpander implements FileEntityExpanderInterface { async expand(fileEntity: FileEntity): Promise { + if (fileEntity.streamProvider !== StreamProviderEnum.fs) { + return Promise.resolve(fileEntity); + } const stats: Stats | false = await stat(fileEntity.path).catch(() => false); if (stats !== false) { diff --git a/src/VideoFiles/Update/Expander/file-entity-title-info.expander.ts b/src/VideoFiles/Update/Expander/file-entity-title-info.expander.ts index 040dfad..bcced57 100644 --- a/src/VideoFiles/Update/Expander/file-entity-title-info.expander.ts +++ b/src/VideoFiles/Update/Expander/file-entity-title-info.expander.ts @@ -1,9 +1,9 @@ import { Injectable } from '@nestjs/common'; import * as tnp from 'torrent-name-parser'; import { FileEntityExpanderInterface } from './file-entity-expander.interface'; -import { FileEntity } from 'src/VideoFiles/Entity/file.entity'; +import { FileEntity } from 'src/Shared/Entity/file.entity'; import { VideoFilesUpdateRepository } from '../video-files-update.repository'; -import { TitleTypeEnum } from '../../Enum/title-type.enum'; +import { TitleTypeEnum } from '../../../Shared/Enum/title-type.enum'; //todo - refactor this copy-paste, add types, ect. @Injectable() diff --git a/src/VideoFiles/Update/Expander/file-entity-transmission.expander.ts b/src/VideoFiles/Update/Expander/file-entity-transmission.expander.ts new file mode 100644 index 0000000..f350149 --- /dev/null +++ b/src/VideoFiles/Update/Expander/file-entity-transmission.expander.ts @@ -0,0 +1,26 @@ +import { Injectable } from '@nestjs/common'; +import { FileEntityExpanderInterface } from './file-entity-expander.interface'; +import { FileEntity } from '../../../Shared/Entity/file.entity'; +import { TorrentSeedClient } from '../../../Torrent/SeedClient/torrent-seed.client'; + +@Injectable() +export class FileEntityTransmissionExpander + implements FileEntityExpanderInterface +{ + constructor(private readonly torrentSeedClient: TorrentSeedClient) {} + + async expand(fileEntity: FileEntity): Promise { + const torrentInfo = await this.torrentSeedClient + .findTorrentInfoByFile(fileEntity) + .catch(() => null); + + if (torrentInfo === null) { + return Promise.resolve(fileEntity); + } + + fileEntity.transmissionId = torrentInfo.id; + fileEntity.linkomanija = torrentInfo.linkomanija; + + return fileEntity; + } +} diff --git a/src/VideoFiles/Update/Expander/file-entity.expander.ts b/src/VideoFiles/Update/Expander/file-entity.expander.ts index ae38b4d..fd0f1be 100644 --- a/src/VideoFiles/Update/Expander/file-entity.expander.ts +++ b/src/VideoFiles/Update/Expander/file-entity.expander.ts @@ -1,6 +1,6 @@ import { FileEntityExpanderInterface } from './file-entity-expander.interface'; import { Inject, Injectable } from '@nestjs/common'; -import { FileEntity } from '../../Entity/file.entity'; +import { FileEntity } from '../../../Shared/Entity/file.entity'; export const FileEntityExpanders = 'FileEntityExpanders'; @@ -8,7 +8,7 @@ export const FileEntityExpanders = 'FileEntityExpanders'; export class FileEntityExpander implements FileEntityExpanderInterface { constructor( @Inject(FileEntityExpanders) - private expanders: Array, + private expanders: FileEntityExpanderInterface[], ) {} async expand(fileEntity: FileEntity): Promise { @@ -18,11 +18,8 @@ export class FileEntityExpander implements FileEntityExpanderInterface { const expanderPromise = expander.expand(fileEntity); expanderPromises.push(expanderPromise); } + await Promise.all(expanderPromises); - return new Promise((resolve) => { - Promise.all(expanderPromises).then(() => { - resolve(fileEntity); - }); - }); + return fileEntity; } } diff --git a/src/VideoFiles/Update/Scanner/video-files-scanner.service.ts b/src/VideoFiles/Update/Scanner/video-files-scanner.service.ts index a1ff623..69bec4d 100644 --- a/src/VideoFiles/Update/Scanner/video-files-scanner.service.ts +++ b/src/VideoFiles/Update/Scanner/video-files-scanner.service.ts @@ -36,7 +36,7 @@ export class VideoFilesScannerService { } const findOptions = [path, '-name'].concat( - this.config.getVideoFilesExt().join(' -o -name ').split(' '), + ('*' + this.config.getVideoFilesExt().join(' -o -name *')).split(' '), ); const objCommand = spawn('find', findOptions); diff --git a/src/VideoFiles/Update/video-files-update.module.ts b/src/VideoFiles/Update/video-files-update.module.ts index 25b947e..71fe1d7 100644 --- a/src/VideoFiles/Update/video-files-update.module.ts +++ b/src/VideoFiles/Update/video-files-update.module.ts @@ -1,7 +1,7 @@ import { Module, Provider } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; -import { TitleEntity } from '../Entity/title.entity'; -import { FileEntity } from '../Entity/file.entity'; +import { TitleEntity } from '../../Shared/Entity/title.entity'; +import { FileEntity } from '../../Shared/Entity/file.entity'; import { FileEntityExpander, FileEntityExpanders, @@ -13,6 +13,8 @@ import { VideoFilesUpdateRepository } from './video-files-update.repository'; import { FileEntitySizeExpander } from './Expander/file-entity-size.expander'; import { FileEntityDefaultsExpander } from './Expander/file-entity-defaults.expander'; import { FileEntityDurationExpander } from './Expander/file-entity-duration.expander'; +import { FileEntityTransmissionExpander } from './Expander/file-entity-transmission.expander'; +import { TorrentSeedClientModule } from '../../Torrent/SeedClient/torrent-seed-client.module'; const fileEntityExpanders: Provider[] = [ FileEntityExpander, @@ -20,22 +22,26 @@ const fileEntityExpanders: Provider[] = [ FileEntitySizeExpander, FileEntityTitleInfoExpander, FileEntityDurationExpander, + FileEntityTransmissionExpander, { provide: FileEntityExpanders, useFactory: (...expanders) => expanders, inject: [ //expanders run in parallel, make sure they don't rely on each other's result - //todo - linkomanija, webtorrent, transmission expanders FileEntityDefaultsExpander, FileEntitySizeExpander, FileEntityTitleInfoExpander, FileEntityDurationExpander, + FileEntityTransmissionExpander, ], }, ]; @Module({ - imports: [TypeOrmModule.forFeature([TitleEntity, FileEntity])], + imports: [ + TypeOrmModule.forFeature([TitleEntity, FileEntity]), + TorrentSeedClientModule, + ], controllers: [], providers: [ VideoFilesUpdateService, diff --git a/src/VideoFiles/Update/video-files-update.repository.ts b/src/VideoFiles/Update/video-files-update.repository.ts index ae96fc0..7ee0525 100644 --- a/src/VideoFiles/Update/video-files-update.repository.ts +++ b/src/VideoFiles/Update/video-files-update.repository.ts @@ -1,10 +1,10 @@ import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; -import { FileEntity } from '../Entity/file.entity'; +import { FileEntity } from '../../Shared/Entity/file.entity'; import { In, Not, Repository, UpdateResult } from 'typeorm'; -import { StreamProviderEnum } from '../../Streamer/ReadStreamProvider/stream-provider.enum'; -import { TitleEntity } from '../Entity/title.entity'; -import { TitleTypeEnum } from '../Enum/title-type.enum'; +import { StreamProviderEnum } from '../../Shared/Enum/stream-provider.enum'; +import { TitleEntity } from '../../Shared/Entity/title.entity'; +import { TitleTypeEnum } from '../../Shared/Enum/title-type.enum'; @Injectable() export class VideoFilesUpdateRepository { @@ -78,6 +78,7 @@ export class VideoFilesUpdateRepository { ): Promise { let fileEntity = await this.fileRepository.findOne({ where: { path: path }, + relations: { torrent: true }, }); if (fileEntity === null) { @@ -87,6 +88,7 @@ export class VideoFilesUpdateRepository { await this.fileRepository.save(fileEntity); fileEntity = await this.fileRepository.findOne({ where: { path: path }, + relations: { torrent: true }, }); } } @@ -97,4 +99,11 @@ export class VideoFilesUpdateRepository { public saveFiles(fileEntities: FileEntity[]) { return this.fileRepository.save(fileEntities); } + + public findFilesByPaths(paths: string[]): Promise { + return this.fileRepository.find({ + where: { path: In(paths) }, + relations: { torrent: true }, + }); + } } diff --git a/src/VideoFiles/Update/video-files-update.service.ts b/src/VideoFiles/Update/video-files-update.service.ts index 16f0de4..b3261c5 100644 --- a/src/VideoFiles/Update/video-files-update.service.ts +++ b/src/VideoFiles/Update/video-files-update.service.ts @@ -2,11 +2,16 @@ import { Injectable } from '@nestjs/common'; import { VideoFilesScannerService } from './Scanner/video-files-scanner.service'; import { VideoFilesUpdateRepository } from './video-files-update.repository'; import { FileEntityExpander } from './Expander/file-entity.expander'; -import { FileEntity } from '../Entity/file.entity'; -import { StreamProviderEnum } from '../../Streamer/ReadStreamProvider/stream-provider.enum'; +import { FileEntity } from '../../Shared/Entity/file.entity'; +import { StreamProviderEnum } from '../../Shared/Enum/stream-provider.enum'; +import { configService } from '../../config/config.service'; +import { VideoFilesConfig } from '../../config/video-files.config'; @Injectable() export class VideoFilesUpdateService { + private readonly config: VideoFilesConfig = + configService.getVideoFilesConfig(); + constructor( private readonly scanner: VideoFilesScannerService, private readonly repository: VideoFilesUpdateRepository, @@ -20,20 +25,30 @@ export class VideoFilesUpdateService { const filesInDb = await this.repository.getMatchingPaths(filesInFs); const filesToAddToDb = filesInFs.filter( - (filePath) => !filesInDb.includes(filePath), + (filePath) => + !filesInDb.includes(filePath) && !this.isPathToIgnore(filePath), ); const entitiesToSave = []; for (const fileToAddToDb of filesToAddToDb) { - if (this.checkPathToIgnore(fileToAddToDb)) { + const fileEntity = await this.repository.findOrCreateFile( + fileToAddToDb, + false, + ); + + //check if download is complete + if ( + fileEntity.streamProvider === StreamProviderEnum.wt && + fileEntity.torrent && + fileEntity.torrent.stopped === false + ) { continue; } + fileEntity.relativePath = filesToRelativePaths[fileToAddToDb]; + fileEntity.streamProvider = StreamProviderEnum.fs; - const fileEntity = await this.buildFileEntity( - fileToAddToDb, - filesToRelativePaths[fileToAddToDb], - ); + await this.expander.expand(fileEntity); entitiesToSave.push(fileEntity); } @@ -41,19 +56,63 @@ export class VideoFilesUpdateService { await this.repository.saveFiles(entitiesToSave); } - private checkPathToIgnore(filePath: string): boolean { - //todo - move to separate provider, include in settings - return filePath.indexOf('AOE1') > -1; + private isPathToIgnore(filePath: string): boolean { + for (const pathPattern of this.config.getPathPatternsToIgnore()) { + if (filePath.indexOf(pathPattern) > -1) { + return true; + } + } + + return false; } - private async buildFileEntity( - path: string, - relativePath: string, - ): Promise { - const fileEntity = await this.repository.findOrCreateFile(path, false); - fileEntity.relativePath = relativePath; - fileEntity.streamProvider = StreamProviderEnum.fs; + async updateEntitiesByPath(filePaths: string[]) { + const fileEntitiesToUpdate = []; + const fileEntities = await this.repository.findFilesByPaths(filePaths); - return await this.expander.expand(fileEntity); + for (const fileEntity of fileEntities) { + if (!fileEntity.relativePath) { + continue; + } + + if ( + fileEntity.streamProvider === StreamProviderEnum.wt && + fileEntity.torrent && + fileEntity.torrent.stopped === false + ) { + continue; + } + fileEntity.streamProvider = StreamProviderEnum.fs; + + await this.expander.expand(fileEntity); + fileEntitiesToUpdate.push(fileEntity); + } + + await this.repository.saveFiles(fileEntitiesToUpdate); + } + + public async buildAndSaveEntitiesFromPartial( + partialFileEntities: Partial[], + ): Promise { + const entitiesToSave: FileEntity[] = []; + for (const partialFileEntity of partialFileEntities) { + let fileEntity = await this.repository.findOrCreateFile( + partialFileEntity.path, + false, + ); + + fileEntity = { ...fileEntity, ...partialFileEntity }; + + await this.expander.expand(fileEntity); + entitiesToSave.push(fileEntity); + } + + await this.repository.saveFiles(entitiesToSave); + + return this.repository.findFilesByPaths( + entitiesToSave.map((fileEntity) => { + return fileEntity.path; + }), + ); } } diff --git a/src/VideoFiles/video-files.facade.ts b/src/VideoFiles/video-files.facade.ts index 06105d1..11f6efa 100644 --- a/src/VideoFiles/video-files.facade.ts +++ b/src/VideoFiles/video-files.facade.ts @@ -1,12 +1,14 @@ import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; -import { TitleEntity } from './Entity/title.entity'; -import { TitleTypeEnum } from './Enum/title-type.enum'; -import { FileEntity } from './Entity/file.entity'; +import { TitleEntity } from '../Shared/Entity/title.entity'; +import { TitleTypeEnum } from '../Shared/Enum/title-type.enum'; +import { FileEntity } from '../Shared/Entity/file.entity'; import { VideoFilesUpdateService } from './Update/video-files-update.service'; +import NotificationResponse from '../KodiApi/Dto/notification-response.dto'; +import { Stats } from 'fs'; +import { stat, unlink } from 'fs/promises'; -//todo - refactor provider as facade @Injectable() export class VideoFilesFacade { constructor( @@ -29,7 +31,7 @@ export class VideoFilesFacade { async getTitleWithFiles(titleId: number): Promise { return this.titleRepository .findOne({ - relations: { files: true }, + relations: { files: { torrent: true } }, where: { id: titleId, files: { deleted: false } }, }) .catch(() => null); @@ -37,11 +39,57 @@ export class VideoFilesFacade { async getFile(fileId: number): Promise { return this.fileRepository - .findOne({ where: { id: fileId } }) + .findOne({ where: { id: fileId }, relations: { torrent: true } }) .catch(() => null); } + /** + * Scan for new files and remove deleted. + * Once all hooks are working correctly, this one shouldn't be needed anymore + */ updateFsVideoFiles(): void { this.videoFilesUpdateService.updateFsVideoFiles().then(); } + + addVideoFiles( + partialFileEntities: Partial[], + ): Promise { + return this.videoFilesUpdateService.buildAndSaveEntitiesFromPartial( + partialFileEntities, + ); + } + + /** + * if entity with provided path exists and fully downloaded, it will + * - update entity properties, like size, duration, etc. + * - move to fs provider + * @param filePaths + */ + updateEntitiesByPath(filePaths: string[]) { + return this.videoFilesUpdateService.updateEntitiesByPath(filePaths); + } + + async deleteFile(fileId: number): Promise { + const fileEntity = await this.fileRepository.findOne({ + where: { id: fileId }, + }); + + if (!fileEntity) { + return false; + } + + fileEntity.deleted = true; + await this.fileRepository.save(fileEntity); + + const stats: Stats | false = await stat(fileEntity.path).catch(() => false); + + if (stats === false) { + //file already deleted + return true; + } + + return unlink(fileEntity.path) + .then(() => true) + .catch(() => false); + } } diff --git a/src/VideoFiles/video-files.module.ts b/src/VideoFiles/video-files.module.ts index edcda12..c5c6e79 100644 --- a/src/VideoFiles/video-files.module.ts +++ b/src/VideoFiles/video-files.module.ts @@ -1,7 +1,7 @@ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; -import { TitleEntity } from './Entity/title.entity'; -import { FileEntity } from './Entity/file.entity'; +import { TitleEntity } from '../Shared/Entity/title.entity'; +import { FileEntity } from '../Shared/Entity/file.entity'; import { VideoFilesFacade } from './video-files.facade'; import { VideoFilesUpdateModule } from './Update/video-files-update.module'; diff --git a/src/config/config.service.ts b/src/config/config.service.ts index a67ae4e..9348f3b 100644 --- a/src/config/config.service.ts +++ b/src/config/config.service.ts @@ -3,6 +3,7 @@ import { TypeOrmModuleOptions } from '@nestjs/typeorm'; import { TypeOrmConfig } from './type-orm.config'; import { PathsConfig } from './paths.config'; import { VideoFilesConfig } from './video-files.config'; +import { RecentSearchesConfig } from './recent-searches.config'; env.config(); @@ -10,6 +11,7 @@ export class ConfigService { private typeOrmConfig: TypeOrmConfig = new TypeOrmConfig(this); private pathsConfig: PathsConfig = new PathsConfig(this); private videoFilesConfig: VideoFilesConfig = new VideoFilesConfig(this); + private recentSearches: RecentSearchesConfig = new RecentSearchesConfig(this); public getEnv(key: string): any { return process.env[key]; @@ -32,6 +34,10 @@ export class ConfigService { public getVideoFilesConfig(): VideoFilesConfig { return this.videoFilesConfig; } + + public getRecentSearchesConfig(): RecentSearchesConfig { + return this.recentSearches; + } } export const configService = new ConfigService(); diff --git a/src/config/paths.config.ts b/src/config/paths.config.ts index 8d25503..f2875c3 100644 --- a/src/config/paths.config.ts +++ b/src/config/paths.config.ts @@ -14,10 +14,6 @@ export class PathsConfig { return this.getPathByEnvKey('STATIC_SERVE_FOLDER'); } - public getRecentSearchesFolder(): string { - return this.getPathByEnvKey('RECENT_SEARCHES_FOLDER'); - } - public getPathByEnvKey(key: string) { const relPath = this.configService.getEnv(key); diff --git a/src/config/recent-searches.config.ts b/src/config/recent-searches.config.ts new file mode 100644 index 0000000..a7c082a --- /dev/null +++ b/src/config/recent-searches.config.ts @@ -0,0 +1,17 @@ +import { ConfigService } from './config.service'; + +export class RecentSearchesConfig { + constructor(private readonly configService: ConfigService) { + this.configService = configService; + } + + public getRecentSearchesFolder(): string { + return this.configService + .getPaths() + .getPathByEnvKey('RECENT_SEARCHES_FOLDER'); + } + + public getRecentSearchesLimit(): number { + return parseInt(this.configService.getEnv('RECENT_SEARCHES_LIMIT')); + } +} diff --git a/src/config/type-orm.config.ts b/src/config/type-orm.config.ts index c113410..0c3687e 100644 --- a/src/config/type-orm.config.ts +++ b/src/config/type-orm.config.ts @@ -1,14 +1,15 @@ import { LrtCategory } from '../KodiApi/LRT/LrtApiClient/Entity/lrt-category.entity'; import { ConfigService } from './config.service'; import { EntityClassOrSchema } from '@nestjs/typeorm/dist/interfaces/entity-class-or-schema.type'; -import { TitleEntity } from '../VideoFiles/Entity/title.entity'; -import { FileEntity } from '../VideoFiles/Entity/file.entity'; +import { TitleEntity } from '../Shared/Entity/title.entity'; +import { FileEntity } from '../Shared/Entity/file.entity'; +import { TorrentEntity } from '../Shared/Entity/torrent.entity'; export class TypeOrmConfig { constructor(configService: ConfigService) { this.type = 'sqlite'; this.database = configService.getEnv('DB_PATH'); - this.entities = [LrtCategory, TitleEntity, FileEntity]; + this.entities = [LrtCategory, TitleEntity, FileEntity, TorrentEntity]; } type: 'sqlite'; database: string; diff --git a/src/config/video-files.config.ts b/src/config/video-files.config.ts index 0e509a6..fec6103 100644 --- a/src/config/video-files.config.ts +++ b/src/config/video-files.config.ts @@ -1,5 +1,4 @@ import { ConfigService } from './config.service'; -import { join } from 'path'; export class VideoFilesConfig { constructor(private readonly configService: ConfigService) { @@ -13,4 +12,25 @@ export class VideoFilesConfig { public getVideoFilesExt(): string[] { return JSON.parse(this.configService.getEnv('VIDEO_FILES_EXT')); } + + public getPathPatternsToIgnore(): string[] { + return JSON.parse(this.configService.getEnv('PATTERNS_TO_IGNORE')); + } + + public getTorrentDownloadDir(): string { + return this.configService + .getPaths() + .getPathByEnvKey('TORRENT_DOWNLOAD_DIR'); + } + + public getTransmissionOptions() { + return { + host: this.configService.getEnv('TRANSMISSION_CLIENT_HOST'), // # default 'localhost' + //port: 9091, // # default 9091 + //username: "username", // # default blank + //password: "password", // # default blank + //ssl: true, //# default false use https + //url: "/my/other/url" // # default '/transmission/rpc' + }; + } } diff --git a/src/main.ts b/src/main.ts index 79b0789..95c9c45 100644 --- a/src/main.ts +++ b/src/main.ts @@ -6,6 +6,7 @@ import { HttpExceptionFilter } from './Exception/http-exception.filter'; async function bootstrap() { const app = await NestFactory.create(AppModule); app.useGlobalFilters(new HttpExceptionFilter()); + app.getHttpAdapter().getInstance().set('etag', false); await app.listen(configService.getPort()); }