├── .gitignore ├── .gitmodules ├── README.md ├── config.js ├── eslint.config.js ├── flock.bash ├── getInfoHash.js ├── package-lock.json ├── package.json ├── qbittorrent-postdl.sh ├── red-api.js ├── rtorrent-postdl.sh └── trul.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "flac2mp3"] 2 | path = flac2mp3 3 | url = https://github.com/lfence/flac2mp3 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # red-trul: RED TRanscode & UpLoad 2 | 3 | `red-trul` is a lightweight utility designed to non-interactively conditionally transcode FLAC releases, generating and uploading torrents. 4 | 5 | - Detects the edition and media (_edition group_) of a release. 6 | - Transcodes `Lossless` and `24-bit Lossless` to `Lossless`, `MP3 (320)`, and `MP3 (V0)` when the edition group lacks the specific transcode. 7 | - Copies image files from the original, excluding everything else. 8 | - Maintains the original folder structure. 9 | - Rejects releases with bad tagging or incorrect bit-rate (for 24-bit FLAC). 10 | 11 | ## API access 12 | Requires an API key with _Torrents_ capability, created from the settings page 13 | at RED. No further authorization is needed. 14 | 15 | ## Install 16 | 17 | You need: 18 | - `nodejs` version 16 or later. 19 | - `flac` 20 | - `lame` 21 | - `sox` 22 | - `ffmpeg` 23 | - `perl` 24 | 25 | ```bash 26 | npm install -g red-trul 27 | ``` 28 | 29 | ## Use 30 | 31 | ``` 32 | Usage: red-trul [OPTIONS] flac-dir 33 | 34 | Options: 35 | --version Show version number [boolean] 36 | -i, --info-hash Torrent hash. Required unless an origin.yaml exists in 37 | flac-dir. 38 | --torrent-id Use the given torrent id. Alternative to --info-hash. 39 | --api-key 'Torrents'-capable API token. env-definable as RED_API 40 | _KEY 41 | -o, --torrent-dir Where to output torrent files [default: "."] 42 | -t, --transcode-dir Output directory of transcodes 43 | --no-flac Don't transcode into FLAC [boolean] 44 | --no-v0 Don't transcode into V0 [boolean] 45 | --no-320 Don't transcode into 320 [boolean] 46 | --no-upload Don't upload anything [boolean] 47 | --always-transcode Always transcode (if tagged correctly) [boolean] 48 | -h, --help Show help [boolean] 49 | ``` 50 | 51 | ### Example 52 | 53 | ```bash 54 | # RED_API_KEY is set in env. 55 | # --announce=https://.../announce is optional is the 'User' capability is given. 56 | 57 | red-trul --info-hash=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX \ 58 | --torrent-dir=/home/lfen/rtorrent/watch \ 59 | '/home/lfen/Music/TNO Project - There Is No Obsession EP (Flac24)' 60 | # [-] using announce: https://flacsfor.me//announce 61 | # [-] fetch torrent info... 62 | # [-] analyze filelist... 63 | # [-] ffprobe 6 files... 64 | # [+] Required tags are present, would transcode this 65 | # [-] permalink: https://redacted.ch/torrents.php?torrentid=4521975 66 | # [-] grouplink: https://redacted.ch/torrents.php?id=2123754 67 | # [-] fetch torrentgroup... 68 | # [-] Transcoding /home/lfen/Music/TNO Project - There's No Obsession (2019) - WEB FLAC 69 | # [-] Transcoding 01. TNO Project - Eradicating Deviants.flac... 70 | # [-] Transcoding 02. TNO Project - Oneirology (Dance 4 Me).flac... 71 | # [-] Transcoding 03. TNO Project - Exitus Letalis.flac... 72 | # [-] Transcoding 04. TNO Project - Mendacium & Spem.flac... 73 | # [-] Transcoding 05. TNO Project - Neural Interrogation.flac... 74 | # [-] Transcoding 06. TNO Project - N05A (Who Are U).flac... 75 | # [-] Transcoding /home/lfen/Music/TNO Project - There's No Obsession (2019) - WEB V0 76 | # >> [3140914] Using 4 transcoding processes. 77 | # 78 | # [-] Transcoding /home/lfen/Music/TNO Project - There's No Obsession (2019) - WEB 320 79 | # >> [3140942] Using 4 transcoding processes. 80 | # 81 | # [-] Uploading... 82 | # [-] Write torrents to /home/lfen/rtorrent/watch/... 83 | # [*] Done! 84 | ``` 85 | 86 | ## Advanced: post-download hook and watch directory. 87 | 88 | This script runs well non-interactively. Post-download example scripts are 89 | available for [qbittorrent](./qbittorrent-postdl.sh) and 90 | [rtorrent](./rtorrent-postdl.sh). Beside configuring a post-download script for 91 | the torrent client, also configure it to use the torrent-dir of trul as watch 92 | dir for new torrents, and download those to the transcode dir to start seeding. 93 | 94 | On Linux, use `red-trul-flock` to avoid running multiple instances of red-trul. 95 | This waits until the current invocation finishes before starting, like a queue. 96 | TODO for macos 97 | -------------------------------------------------------------------------------- /config.js: -------------------------------------------------------------------------------- 1 | import yargs from "yargs" 2 | import { hideBin } from "yargs/helpers" 3 | import YAML from "yaml" 4 | import path from "path" 5 | import debug from "debug" 6 | const verboseLog = debug("trul:cli") 7 | 8 | import { readFileSync, existsSync, realpathSync } from "fs" 9 | const __dirname = path.dirname(realpathSync(process.argv[1])) 10 | const pkg = JSON.parse(readFileSync(`${__dirname}/package.json`)) 11 | 12 | const _initConfig = (argv) => { 13 | const FLAC_DIR = argv._[0]?.replace(/\/$/, "") 14 | return { 15 | ALWAYS_TRANSCODE: argv["always-transcode"], 16 | API_KEY: argv["api-key"] || getEnv("RED_API_KEY"), 17 | // input dir 18 | FLAC_DIR, 19 | // transcode output 20 | TRANSCODE_DIR: argv["transcode-dir"] || path.dirname(FLAC_DIR ?? ""), 21 | // torrent output 22 | TORRENT_DIR: argv["torrent-dir"], 23 | SOX: getEnv("SOX_PATH") || "sox", 24 | SOX_ARGS: "-G -b16 rate -v -L dither", 25 | // flac2mp3 for idv3 and to copy cover art over. The rest is LAME. 26 | FLAC2MP3: getEnv("FLAC2MP3_PATH") || `${__dirname}/flac2mp3/flac2mp3.pl`, 27 | FLAC2MP3_ARGS: "--lameargs= --processes=", 28 | NO_UPLOAD: argv["upload"] === false, 29 | NO_V0: argv["v0"] === false, 30 | NO_320: argv["320"] === false, 31 | NO_FLAC: argv["flac"] === false, 32 | SCRIPT_NAME: `${pkg.name}@${pkg.version}`, 33 | TORRENT_QUERY: getTorrentQuery(FLAC_DIR, argv), 34 | } 35 | } 36 | 37 | export const initConfig = () => { 38 | let { argv } = yargs(hideBin(process.argv)) 39 | .usage("Usage: $0 [OPTIONS] flac-dir") 40 | .option("info-hash", { 41 | alias: "i", 42 | describe: 43 | "Torrent hash. Required unless an origin.yaml exists in flac-dir.", 44 | }) 45 | .option("torrent-id", { 46 | describe: "Use the given torrent id. Alternative to --info-hash.", 47 | }) 48 | .option("api-key", { 49 | describe: "'Torrents'-capable API token. env-definable as RED_API_KEY", 50 | }) 51 | .option("torrent-dir", { 52 | alias: "o", 53 | describe: "Where to output torrent files", 54 | default: ".", 55 | }) 56 | .option("transcode-dir", { 57 | alias: "t", 58 | describe: "Output directory of transcodes", 59 | }) 60 | .option("no-flac", { 61 | describe: "Don't transcode into FLAC", 62 | boolean: true, 63 | }) 64 | .option("no-v0", { 65 | describe: "Don't transcode into V0", 66 | boolean: true, 67 | }) 68 | .option("no-320", { 69 | describe: "Don't transcode into 320", 70 | boolean: true, 71 | }) 72 | .option("no-upload", { 73 | describe: "Don't upload anything", 74 | boolean: true, 75 | }) 76 | .option("always-transcode", { 77 | boolean: true, 78 | describe: "Always transcode (if tagged correctly)", 79 | default: false, 80 | }) 81 | .help("h") 82 | .alias("h", "help") 83 | 84 | 85 | if (!argv._[0]) { 86 | console.error(`No input, nothing to do. Try '--help'`) 87 | process.exit(0); 88 | } 89 | 90 | const config = _initConfig(argv) 91 | 92 | verboseLog('Config: ') 93 | verboseLog(config) 94 | return config; 95 | } 96 | 97 | export function getEnv(e) { 98 | return process.env[e] 99 | } 100 | 101 | function getTorrentQuery(FLAC_DIR, argv) { 102 | if (argv["info-hash"]) { 103 | return { hash: argv["info-hash"] } 104 | } 105 | 106 | if (argv["torrent-id"]) { 107 | return { id: argv["torrent-id"] } 108 | } 109 | 110 | // if the folder has an origin.yaml file by gazelle-origin, we 111 | // can use that instead. 112 | if (existsSync(`${FLAC_DIR}/origin.yaml`)) { 113 | const originYaml = readFileSync(`${FLAC_DIR}/origin.yaml`) 114 | if (originYaml) { 115 | const parsed = YAML.parse(originYaml.toString("utf-8")) 116 | if (parsed["Format"] !== "FLAC") { 117 | throw new Error("[!] Not a FLAC, not interested.") 118 | } 119 | return { hash: parsed["Info hash"] } 120 | } 121 | } 122 | 123 | throw new Error( 124 | "[!] Unable to find an info hash or id. Try --help\n" + 125 | "Did you forget to pass --info-hash or --id?", 126 | ) 127 | } 128 | -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | import globals from "globals"; 2 | import pluginJs from "@eslint/js"; 3 | 4 | 5 | /** @type {import('eslint').Linter.Config[]} */ 6 | export default [ 7 | {languageOptions: { globals: globals.node }}, 8 | pluginJs.configs.recommended, 9 | ]; 10 | -------------------------------------------------------------------------------- /flock.bash: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # This is a wrapper script that will queue up pending runs so that ongoing ones 3 | # must finish before continuing. 4 | set -e 5 | 6 | LOCKFILE="/tmp/`basename $0`.lock" 7 | TIMEOUT=3600 8 | touch $LOCKFILE 9 | 10 | # Create a file descriptor over the given lockfile. 11 | exec {FD}<>$LOCKFILE 12 | echo [.] flock: wait for our turn 13 | if ! flock -x -w $TIMEOUT $FD; then 14 | echo "Failed to obtain a lock within $TIMEOUT seconds" 15 | echo "Another instance of `basename $0` is probably running." 16 | exit 1 17 | else 18 | echo [+] flock: our turn to go! 19 | `dirname $(realpath "$0")`/trul.js "$@" 20 | fi 21 | -------------------------------------------------------------------------------- /getInfoHash.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import fs from "node:fs" 3 | import { createHash } from "node:crypto" 4 | import { extname } from "node:path" 5 | import bencode from "bencode" 6 | const torrPath = process.argv[2] 7 | if (!torrPath || extname(torrPath) != ".torrent" || !fs.existsSync(torrPath)) { 8 | console.log(`Usage: ${process.argv[1]} `) 9 | process.exit(1) 10 | } 11 | const f = fs.readFileSync(process.argv[2]) 12 | console.log( 13 | createHash("sha1") 14 | .update(bencode.encode(bencode.decode(f).info)) 15 | .digest() 16 | .reduce((str, byte) => str + byte.toString(16), "") 17 | .toUpperCase(), 18 | ) 19 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "red-trul", 3 | "version": "2.3.14", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "red-trul", 9 | "version": "2.3.14", 10 | "license": "ISC", 11 | "dependencies": { 12 | "axios": "^1.6.5", 13 | "bencode": "^4.0.0", 14 | "create-torrent": "^6.0.18", 15 | "debug": "^4.3.7", 16 | "form-data": "^4.0.2", 17 | "he": "^1.2.0", 18 | "yaml": "^1.10.0", 19 | "yargs": "^17.2.1" 20 | }, 21 | "bin": { 22 | "red-trul": "trul.js", 23 | "red-trul-flock": "flock.bash" 24 | }, 25 | "devDependencies": { 26 | "@eslint/js": "^9.22.0", 27 | "eslint": "^9.22.0", 28 | "globals": "^16.0.0" 29 | }, 30 | "engines": { 31 | "node": ">=16.0.0" 32 | } 33 | }, 34 | "node_modules/@eslint-community/eslint-utils": { 35 | "version": "4.5.1", 36 | "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.5.1.tgz", 37 | "integrity": "sha512-soEIOALTfTK6EjmKMMoLugwaP0rzkad90iIWd1hMO9ARkSAyjfMfkRRhLvD5qH7vvM0Cg72pieUfR6yh6XxC4w==", 38 | "dev": true, 39 | "license": "MIT", 40 | "dependencies": { 41 | "eslint-visitor-keys": "^3.4.3" 42 | }, 43 | "engines": { 44 | "node": "^12.22.0 || ^14.17.0 || >=16.0.0" 45 | }, 46 | "funding": { 47 | "url": "https://opencollective.com/eslint" 48 | }, 49 | "peerDependencies": { 50 | "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" 51 | } 52 | }, 53 | "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { 54 | "version": "3.4.3", 55 | "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", 56 | "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", 57 | "dev": true, 58 | "license": "Apache-2.0", 59 | "engines": { 60 | "node": "^12.22.0 || ^14.17.0 || >=16.0.0" 61 | }, 62 | "funding": { 63 | "url": "https://opencollective.com/eslint" 64 | } 65 | }, 66 | "node_modules/@eslint-community/regexpp": { 67 | "version": "4.12.1", 68 | "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", 69 | "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", 70 | "dev": true, 71 | "license": "MIT", 72 | "engines": { 73 | "node": "^12.0.0 || ^14.0.0 || >=16.0.0" 74 | } 75 | }, 76 | "node_modules/@eslint/config-array": { 77 | "version": "0.20.0", 78 | "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.20.0.tgz", 79 | "integrity": "sha512-fxlS1kkIjx8+vy2SjuCB94q3htSNrufYTXubwiBFeaQHbH6Ipi43gFJq2zCMt6PHhImH3Xmr0NksKDvchWlpQQ==", 80 | "dev": true, 81 | "license": "Apache-2.0", 82 | "dependencies": { 83 | "@eslint/object-schema": "^2.1.6", 84 | "debug": "^4.3.1", 85 | "minimatch": "^3.1.2" 86 | }, 87 | "engines": { 88 | "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 89 | } 90 | }, 91 | "node_modules/@eslint/config-helpers": { 92 | "version": "0.2.1", 93 | "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.2.1.tgz", 94 | "integrity": "sha512-RI17tsD2frtDu/3dmI7QRrD4bedNKPM08ziRYaC5AhkGrzIAJelm9kJU1TznK+apx6V+cqRz8tfpEeG3oIyjxw==", 95 | "dev": true, 96 | "license": "Apache-2.0", 97 | "engines": { 98 | "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 99 | } 100 | }, 101 | "node_modules/@eslint/core": { 102 | "version": "0.12.0", 103 | "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.12.0.tgz", 104 | "integrity": "sha512-cmrR6pytBuSMTaBweKoGMwu3EiHiEC+DoyupPmlZ0HxBJBtIxwe+j/E4XPIKNx+Q74c8lXKPwYawBf5glsTkHg==", 105 | "dev": true, 106 | "license": "Apache-2.0", 107 | "dependencies": { 108 | "@types/json-schema": "^7.0.15" 109 | }, 110 | "engines": { 111 | "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 112 | } 113 | }, 114 | "node_modules/@eslint/eslintrc": { 115 | "version": "3.3.1", 116 | "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", 117 | "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", 118 | "dev": true, 119 | "license": "MIT", 120 | "dependencies": { 121 | "ajv": "^6.12.4", 122 | "debug": "^4.3.2", 123 | "espree": "^10.0.1", 124 | "globals": "^14.0.0", 125 | "ignore": "^5.2.0", 126 | "import-fresh": "^3.2.1", 127 | "js-yaml": "^4.1.0", 128 | "minimatch": "^3.1.2", 129 | "strip-json-comments": "^3.1.1" 130 | }, 131 | "engines": { 132 | "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 133 | }, 134 | "funding": { 135 | "url": "https://opencollective.com/eslint" 136 | } 137 | }, 138 | "node_modules/@eslint/eslintrc/node_modules/globals": { 139 | "version": "14.0.0", 140 | "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", 141 | "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", 142 | "dev": true, 143 | "license": "MIT", 144 | "engines": { 145 | "node": ">=18" 146 | }, 147 | "funding": { 148 | "url": "https://github.com/sponsors/sindresorhus" 149 | } 150 | }, 151 | "node_modules/@eslint/js": { 152 | "version": "9.24.0", 153 | "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.24.0.tgz", 154 | "integrity": "sha512-uIY/y3z0uvOGX8cp1C2fiC4+ZmBhp6yZWkojtHL1YEMnRt1Y63HB9TM17proGEmeG7HeUY+UP36F0aknKYTpYA==", 155 | "dev": true, 156 | "license": "MIT", 157 | "engines": { 158 | "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 159 | } 160 | }, 161 | "node_modules/@eslint/object-schema": { 162 | "version": "2.1.6", 163 | "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz", 164 | "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==", 165 | "dev": true, 166 | "license": "Apache-2.0", 167 | "engines": { 168 | "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 169 | } 170 | }, 171 | "node_modules/@eslint/plugin-kit": { 172 | "version": "0.2.8", 173 | "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.8.tgz", 174 | "integrity": "sha512-ZAoA40rNMPwSm+AeHpCq8STiNAwzWLJuP8Xv4CHIc9wv/PSuExjMrmjfYNj682vW0OOiZ1HKxzvjQr9XZIisQA==", 175 | "dev": true, 176 | "license": "Apache-2.0", 177 | "dependencies": { 178 | "@eslint/core": "^0.13.0", 179 | "levn": "^0.4.1" 180 | }, 181 | "engines": { 182 | "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 183 | } 184 | }, 185 | "node_modules/@eslint/plugin-kit/node_modules/@eslint/core": { 186 | "version": "0.13.0", 187 | "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.13.0.tgz", 188 | "integrity": "sha512-yfkgDw1KR66rkT5A8ci4irzDysN7FRpq3ttJolR88OqQikAWqwA8j5VZyas+vjyBNFIJ7MfybJ9plMILI2UrCw==", 189 | "dev": true, 190 | "license": "Apache-2.0", 191 | "dependencies": { 192 | "@types/json-schema": "^7.0.15" 193 | }, 194 | "engines": { 195 | "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 196 | } 197 | }, 198 | "node_modules/@humanfs/core": { 199 | "version": "0.19.1", 200 | "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", 201 | "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", 202 | "dev": true, 203 | "license": "Apache-2.0", 204 | "engines": { 205 | "node": ">=18.18.0" 206 | } 207 | }, 208 | "node_modules/@humanfs/node": { 209 | "version": "0.16.6", 210 | "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz", 211 | "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", 212 | "dev": true, 213 | "license": "Apache-2.0", 214 | "dependencies": { 215 | "@humanfs/core": "^0.19.1", 216 | "@humanwhocodes/retry": "^0.3.0" 217 | }, 218 | "engines": { 219 | "node": ">=18.18.0" 220 | } 221 | }, 222 | "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": { 223 | "version": "0.3.1", 224 | "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", 225 | "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", 226 | "dev": true, 227 | "license": "Apache-2.0", 228 | "engines": { 229 | "node": ">=18.18" 230 | }, 231 | "funding": { 232 | "type": "github", 233 | "url": "https://github.com/sponsors/nzakas" 234 | } 235 | }, 236 | "node_modules/@humanwhocodes/module-importer": { 237 | "version": "1.0.1", 238 | "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", 239 | "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", 240 | "dev": true, 241 | "license": "Apache-2.0", 242 | "engines": { 243 | "node": ">=12.22" 244 | }, 245 | "funding": { 246 | "type": "github", 247 | "url": "https://github.com/sponsors/nzakas" 248 | } 249 | }, 250 | "node_modules/@humanwhocodes/retry": { 251 | "version": "0.4.2", 252 | "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.2.tgz", 253 | "integrity": "sha512-xeO57FpIu4p1Ri3Jq/EXq4ClRm86dVF2z/+kvFnyqVYRavTZmaFaUBbWCOuuTh0o/g7DSsk6kc2vrS4Vl5oPOQ==", 254 | "dev": true, 255 | "license": "Apache-2.0", 256 | "engines": { 257 | "node": ">=18.18" 258 | }, 259 | "funding": { 260 | "type": "github", 261 | "url": "https://github.com/sponsors/nzakas" 262 | } 263 | }, 264 | "node_modules/@types/estree": { 265 | "version": "1.0.7", 266 | "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", 267 | "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==", 268 | "dev": true, 269 | "license": "MIT" 270 | }, 271 | "node_modules/@types/json-schema": { 272 | "version": "7.0.15", 273 | "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", 274 | "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", 275 | "dev": true, 276 | "license": "MIT" 277 | }, 278 | "node_modules/acorn": { 279 | "version": "8.14.1", 280 | "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", 281 | "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", 282 | "dev": true, 283 | "license": "MIT", 284 | "bin": { 285 | "acorn": "bin/acorn" 286 | }, 287 | "engines": { 288 | "node": ">=0.4.0" 289 | } 290 | }, 291 | "node_modules/acorn-jsx": { 292 | "version": "5.3.2", 293 | "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", 294 | "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", 295 | "dev": true, 296 | "license": "MIT", 297 | "peerDependencies": { 298 | "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" 299 | } 300 | }, 301 | "node_modules/ajv": { 302 | "version": "6.12.6", 303 | "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", 304 | "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", 305 | "dev": true, 306 | "license": "MIT", 307 | "dependencies": { 308 | "fast-deep-equal": "^3.1.1", 309 | "fast-json-stable-stringify": "^2.0.0", 310 | "json-schema-traverse": "^0.4.1", 311 | "uri-js": "^4.2.2" 312 | }, 313 | "funding": { 314 | "type": "github", 315 | "url": "https://github.com/sponsors/epoberezkin" 316 | } 317 | }, 318 | "node_modules/ansi-regex": { 319 | "version": "5.0.1", 320 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", 321 | "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", 322 | "license": "MIT", 323 | "engines": { 324 | "node": ">=8" 325 | } 326 | }, 327 | "node_modules/ansi-styles": { 328 | "version": "4.3.0", 329 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", 330 | "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", 331 | "license": "MIT", 332 | "dependencies": { 333 | "color-convert": "^2.0.1" 334 | }, 335 | "engines": { 336 | "node": ">=8" 337 | }, 338 | "funding": { 339 | "url": "https://github.com/chalk/ansi-styles?sponsor=1" 340 | } 341 | }, 342 | "node_modules/argparse": { 343 | "version": "2.0.1", 344 | "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", 345 | "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", 346 | "dev": true, 347 | "license": "Python-2.0" 348 | }, 349 | "node_modules/asynckit": { 350 | "version": "0.4.0", 351 | "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", 352 | "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", 353 | "license": "MIT" 354 | }, 355 | "node_modules/axios": { 356 | "version": "1.8.4", 357 | "resolved": "https://registry.npmjs.org/axios/-/axios-1.8.4.tgz", 358 | "integrity": "sha512-eBSYY4Y68NNlHbHBMdeDmKNtDgXWhQsJcGqzO3iLUM0GraQFSS9cVgPX5I9b3lbdFKyYoAEGAZF1DwhTaljNAw==", 359 | "license": "MIT", 360 | "dependencies": { 361 | "follow-redirects": "^1.15.6", 362 | "form-data": "^4.0.0", 363 | "proxy-from-env": "^1.1.0" 364 | } 365 | }, 366 | "node_modules/balanced-match": { 367 | "version": "1.0.2", 368 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", 369 | "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", 370 | "dev": true, 371 | "license": "MIT" 372 | }, 373 | "node_modules/base64-arraybuffer": { 374 | "version": "1.0.2", 375 | "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz", 376 | "integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==", 377 | "license": "MIT", 378 | "engines": { 379 | "node": ">= 0.6.0" 380 | } 381 | }, 382 | "node_modules/bencode": { 383 | "version": "4.0.0", 384 | "resolved": "https://registry.npmjs.org/bencode/-/bencode-4.0.0.tgz", 385 | "integrity": "sha512-AERXw18df0pF3ziGOCyUjqKZBVNH8HV3lBxnx5w0qtgMIk4a1wb9BkcCQbkp9Zstfrn/dzRwl7MmUHHocX3sRQ==", 386 | "license": "MIT", 387 | "dependencies": { 388 | "uint8-util": "^2.2.2" 389 | }, 390 | "engines": { 391 | "node": ">=12.20.0" 392 | } 393 | }, 394 | "node_modules/block-iterator": { 395 | "version": "1.1.1", 396 | "resolved": "https://registry.npmjs.org/block-iterator/-/block-iterator-1.1.1.tgz", 397 | "integrity": "sha512-DrjdVWZemVO4iBf4tiOXjUrY5cNesjzy0t7sIiu2rdl8cOCHRxAgKjSJFc3vBZYYMMmshUAxajl8QQh/uxXTKQ==", 398 | "license": "MIT" 399 | }, 400 | "node_modules/brace-expansion": { 401 | "version": "1.1.11", 402 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 403 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 404 | "dev": true, 405 | "license": "MIT", 406 | "dependencies": { 407 | "balanced-match": "^1.0.0", 408 | "concat-map": "0.0.1" 409 | } 410 | }, 411 | "node_modules/call-bind-apply-helpers": { 412 | "version": "1.0.2", 413 | "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", 414 | "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", 415 | "license": "MIT", 416 | "dependencies": { 417 | "es-errors": "^1.3.0", 418 | "function-bind": "^1.1.2" 419 | }, 420 | "engines": { 421 | "node": ">= 0.4" 422 | } 423 | }, 424 | "node_modules/callsites": { 425 | "version": "3.1.0", 426 | "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", 427 | "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", 428 | "dev": true, 429 | "license": "MIT", 430 | "engines": { 431 | "node": ">=6" 432 | } 433 | }, 434 | "node_modules/chalk": { 435 | "version": "4.1.2", 436 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", 437 | "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", 438 | "dev": true, 439 | "license": "MIT", 440 | "dependencies": { 441 | "ansi-styles": "^4.1.0", 442 | "supports-color": "^7.1.0" 443 | }, 444 | "engines": { 445 | "node": ">=10" 446 | }, 447 | "funding": { 448 | "url": "https://github.com/chalk/chalk?sponsor=1" 449 | } 450 | }, 451 | "node_modules/cliui": { 452 | "version": "8.0.1", 453 | "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", 454 | "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", 455 | "license": "ISC", 456 | "dependencies": { 457 | "string-width": "^4.2.0", 458 | "strip-ansi": "^6.0.1", 459 | "wrap-ansi": "^7.0.0" 460 | }, 461 | "engines": { 462 | "node": ">=12" 463 | } 464 | }, 465 | "node_modules/color-convert": { 466 | "version": "2.0.1", 467 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", 468 | "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", 469 | "license": "MIT", 470 | "dependencies": { 471 | "color-name": "~1.1.4" 472 | }, 473 | "engines": { 474 | "node": ">=7.0.0" 475 | } 476 | }, 477 | "node_modules/color-name": { 478 | "version": "1.1.4", 479 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", 480 | "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", 481 | "license": "MIT" 482 | }, 483 | "node_modules/combined-stream": { 484 | "version": "1.0.8", 485 | "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", 486 | "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", 487 | "license": "MIT", 488 | "dependencies": { 489 | "delayed-stream": "~1.0.0" 490 | }, 491 | "engines": { 492 | "node": ">= 0.8" 493 | } 494 | }, 495 | "node_modules/concat-map": { 496 | "version": "0.0.1", 497 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 498 | "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", 499 | "dev": true, 500 | "license": "MIT" 501 | }, 502 | "node_modules/create-torrent": { 503 | "version": "6.1.0", 504 | "resolved": "https://registry.npmjs.org/create-torrent/-/create-torrent-6.1.0.tgz", 505 | "integrity": "sha512-War593HCsg4TotHgMGWTJqnDHN0pmEU2RM13xUzzSZ78TpRNOC2bbcsC5yMO3pqIkedHEWFzYNqH1yhwuuBYTg==", 506 | "funding": [ 507 | { 508 | "type": "github", 509 | "url": "https://github.com/sponsors/feross" 510 | }, 511 | { 512 | "type": "patreon", 513 | "url": "https://www.patreon.com/feross" 514 | }, 515 | { 516 | "type": "consulting", 517 | "url": "https://feross.org/support" 518 | } 519 | ], 520 | "license": "MIT", 521 | "dependencies": { 522 | "bencode": "^4.0.0", 523 | "block-iterator": "^1.1.1", 524 | "fast-readable-async-iterator": "^2.0.0", 525 | "is-file": "^1.0.0", 526 | "join-async-iterator": "^1.1.1", 527 | "junk": "^4.0.1", 528 | "minimist": "^1.2.8", 529 | "once": "^1.4.0", 530 | "piece-length": "^2.0.1", 531 | "queue-microtask": "^1.2.3", 532 | "run-parallel": "^1.2.0", 533 | "uint8-util": "^2.2.5" 534 | }, 535 | "bin": { 536 | "create-torrent": "bin/cmd.js" 537 | }, 538 | "engines": { 539 | "node": ">=12" 540 | } 541 | }, 542 | "node_modules/cross-spawn": { 543 | "version": "7.0.6", 544 | "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", 545 | "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", 546 | "dev": true, 547 | "license": "MIT", 548 | "dependencies": { 549 | "path-key": "^3.1.0", 550 | "shebang-command": "^2.0.0", 551 | "which": "^2.0.1" 552 | }, 553 | "engines": { 554 | "node": ">= 8" 555 | } 556 | }, 557 | "node_modules/debug": { 558 | "version": "4.4.0", 559 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", 560 | "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", 561 | "license": "MIT", 562 | "dependencies": { 563 | "ms": "^2.1.3" 564 | }, 565 | "engines": { 566 | "node": ">=6.0" 567 | }, 568 | "peerDependenciesMeta": { 569 | "supports-color": { 570 | "optional": true 571 | } 572 | } 573 | }, 574 | "node_modules/deep-is": { 575 | "version": "0.1.4", 576 | "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", 577 | "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", 578 | "dev": true, 579 | "license": "MIT" 580 | }, 581 | "node_modules/delayed-stream": { 582 | "version": "1.0.0", 583 | "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", 584 | "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", 585 | "license": "MIT", 586 | "engines": { 587 | "node": ">=0.4.0" 588 | } 589 | }, 590 | "node_modules/dunder-proto": { 591 | "version": "1.0.1", 592 | "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", 593 | "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", 594 | "license": "MIT", 595 | "dependencies": { 596 | "call-bind-apply-helpers": "^1.0.1", 597 | "es-errors": "^1.3.0", 598 | "gopd": "^1.2.0" 599 | }, 600 | "engines": { 601 | "node": ">= 0.4" 602 | } 603 | }, 604 | "node_modules/emoji-regex": { 605 | "version": "8.0.0", 606 | "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", 607 | "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", 608 | "license": "MIT" 609 | }, 610 | "node_modules/es-define-property": { 611 | "version": "1.0.1", 612 | "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", 613 | "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", 614 | "license": "MIT", 615 | "engines": { 616 | "node": ">= 0.4" 617 | } 618 | }, 619 | "node_modules/es-errors": { 620 | "version": "1.3.0", 621 | "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", 622 | "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", 623 | "license": "MIT", 624 | "engines": { 625 | "node": ">= 0.4" 626 | } 627 | }, 628 | "node_modules/es-object-atoms": { 629 | "version": "1.1.1", 630 | "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", 631 | "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", 632 | "license": "MIT", 633 | "dependencies": { 634 | "es-errors": "^1.3.0" 635 | }, 636 | "engines": { 637 | "node": ">= 0.4" 638 | } 639 | }, 640 | "node_modules/es-set-tostringtag": { 641 | "version": "2.1.0", 642 | "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", 643 | "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", 644 | "license": "MIT", 645 | "dependencies": { 646 | "es-errors": "^1.3.0", 647 | "get-intrinsic": "^1.2.6", 648 | "has-tostringtag": "^1.0.2", 649 | "hasown": "^2.0.2" 650 | }, 651 | "engines": { 652 | "node": ">= 0.4" 653 | } 654 | }, 655 | "node_modules/escalade": { 656 | "version": "3.2.0", 657 | "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", 658 | "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", 659 | "license": "MIT", 660 | "engines": { 661 | "node": ">=6" 662 | } 663 | }, 664 | "node_modules/escape-string-regexp": { 665 | "version": "4.0.0", 666 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", 667 | "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", 668 | "dev": true, 669 | "license": "MIT", 670 | "engines": { 671 | "node": ">=10" 672 | }, 673 | "funding": { 674 | "url": "https://github.com/sponsors/sindresorhus" 675 | } 676 | }, 677 | "node_modules/eslint": { 678 | "version": "9.24.0", 679 | "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.24.0.tgz", 680 | "integrity": "sha512-eh/jxIEJyZrvbWRe4XuVclLPDYSYYYgLy5zXGGxD6j8zjSAxFEzI2fL/8xNq6O2yKqVt+eF2YhV+hxjV6UKXwQ==", 681 | "dev": true, 682 | "license": "MIT", 683 | "dependencies": { 684 | "@eslint-community/eslint-utils": "^4.2.0", 685 | "@eslint-community/regexpp": "^4.12.1", 686 | "@eslint/config-array": "^0.20.0", 687 | "@eslint/config-helpers": "^0.2.0", 688 | "@eslint/core": "^0.12.0", 689 | "@eslint/eslintrc": "^3.3.1", 690 | "@eslint/js": "9.24.0", 691 | "@eslint/plugin-kit": "^0.2.7", 692 | "@humanfs/node": "^0.16.6", 693 | "@humanwhocodes/module-importer": "^1.0.1", 694 | "@humanwhocodes/retry": "^0.4.2", 695 | "@types/estree": "^1.0.6", 696 | "@types/json-schema": "^7.0.15", 697 | "ajv": "^6.12.4", 698 | "chalk": "^4.0.0", 699 | "cross-spawn": "^7.0.6", 700 | "debug": "^4.3.2", 701 | "escape-string-regexp": "^4.0.0", 702 | "eslint-scope": "^8.3.0", 703 | "eslint-visitor-keys": "^4.2.0", 704 | "espree": "^10.3.0", 705 | "esquery": "^1.5.0", 706 | "esutils": "^2.0.2", 707 | "fast-deep-equal": "^3.1.3", 708 | "file-entry-cache": "^8.0.0", 709 | "find-up": "^5.0.0", 710 | "glob-parent": "^6.0.2", 711 | "ignore": "^5.2.0", 712 | "imurmurhash": "^0.1.4", 713 | "is-glob": "^4.0.0", 714 | "json-stable-stringify-without-jsonify": "^1.0.1", 715 | "lodash.merge": "^4.6.2", 716 | "minimatch": "^3.1.2", 717 | "natural-compare": "^1.4.0", 718 | "optionator": "^0.9.3" 719 | }, 720 | "bin": { 721 | "eslint": "bin/eslint.js" 722 | }, 723 | "engines": { 724 | "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 725 | }, 726 | "funding": { 727 | "url": "https://eslint.org/donate" 728 | }, 729 | "peerDependencies": { 730 | "jiti": "*" 731 | }, 732 | "peerDependenciesMeta": { 733 | "jiti": { 734 | "optional": true 735 | } 736 | } 737 | }, 738 | "node_modules/eslint-scope": { 739 | "version": "8.3.0", 740 | "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.3.0.tgz", 741 | "integrity": "sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==", 742 | "dev": true, 743 | "license": "BSD-2-Clause", 744 | "dependencies": { 745 | "esrecurse": "^4.3.0", 746 | "estraverse": "^5.2.0" 747 | }, 748 | "engines": { 749 | "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 750 | }, 751 | "funding": { 752 | "url": "https://opencollective.com/eslint" 753 | } 754 | }, 755 | "node_modules/eslint-visitor-keys": { 756 | "version": "4.2.0", 757 | "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", 758 | "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", 759 | "dev": true, 760 | "license": "Apache-2.0", 761 | "engines": { 762 | "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 763 | }, 764 | "funding": { 765 | "url": "https://opencollective.com/eslint" 766 | } 767 | }, 768 | "node_modules/espree": { 769 | "version": "10.3.0", 770 | "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", 771 | "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", 772 | "dev": true, 773 | "license": "BSD-2-Clause", 774 | "dependencies": { 775 | "acorn": "^8.14.0", 776 | "acorn-jsx": "^5.3.2", 777 | "eslint-visitor-keys": "^4.2.0" 778 | }, 779 | "engines": { 780 | "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 781 | }, 782 | "funding": { 783 | "url": "https://opencollective.com/eslint" 784 | } 785 | }, 786 | "node_modules/esquery": { 787 | "version": "1.6.0", 788 | "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", 789 | "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", 790 | "dev": true, 791 | "license": "BSD-3-Clause", 792 | "dependencies": { 793 | "estraverse": "^5.1.0" 794 | }, 795 | "engines": { 796 | "node": ">=0.10" 797 | } 798 | }, 799 | "node_modules/esrecurse": { 800 | "version": "4.3.0", 801 | "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", 802 | "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", 803 | "dev": true, 804 | "license": "BSD-2-Clause", 805 | "dependencies": { 806 | "estraverse": "^5.2.0" 807 | }, 808 | "engines": { 809 | "node": ">=4.0" 810 | } 811 | }, 812 | "node_modules/estraverse": { 813 | "version": "5.3.0", 814 | "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", 815 | "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", 816 | "dev": true, 817 | "license": "BSD-2-Clause", 818 | "engines": { 819 | "node": ">=4.0" 820 | } 821 | }, 822 | "node_modules/esutils": { 823 | "version": "2.0.3", 824 | "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", 825 | "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", 826 | "dev": true, 827 | "license": "BSD-2-Clause", 828 | "engines": { 829 | "node": ">=0.10.0" 830 | } 831 | }, 832 | "node_modules/fast-deep-equal": { 833 | "version": "3.1.3", 834 | "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", 835 | "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", 836 | "dev": true, 837 | "license": "MIT" 838 | }, 839 | "node_modules/fast-json-stable-stringify": { 840 | "version": "2.1.0", 841 | "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", 842 | "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", 843 | "dev": true, 844 | "license": "MIT" 845 | }, 846 | "node_modules/fast-levenshtein": { 847 | "version": "2.0.6", 848 | "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", 849 | "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", 850 | "dev": true, 851 | "license": "MIT" 852 | }, 853 | "node_modules/fast-readable-async-iterator": { 854 | "version": "2.0.0", 855 | "resolved": "https://registry.npmjs.org/fast-readable-async-iterator/-/fast-readable-async-iterator-2.0.0.tgz", 856 | "integrity": "sha512-8Sld+DuyWRIftl86ZguJxR2oXCBccOiJxrY/Rj9/7ZBynW8pYMWzIcqxFL1da+25jaWJZVa+HHX/8SsA21JdTA==", 857 | "license": "MIT" 858 | }, 859 | "node_modules/file-entry-cache": { 860 | "version": "8.0.0", 861 | "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", 862 | "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", 863 | "dev": true, 864 | "license": "MIT", 865 | "dependencies": { 866 | "flat-cache": "^4.0.0" 867 | }, 868 | "engines": { 869 | "node": ">=16.0.0" 870 | } 871 | }, 872 | "node_modules/find-up": { 873 | "version": "5.0.0", 874 | "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", 875 | "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", 876 | "dev": true, 877 | "license": "MIT", 878 | "dependencies": { 879 | "locate-path": "^6.0.0", 880 | "path-exists": "^4.0.0" 881 | }, 882 | "engines": { 883 | "node": ">=10" 884 | }, 885 | "funding": { 886 | "url": "https://github.com/sponsors/sindresorhus" 887 | } 888 | }, 889 | "node_modules/flat-cache": { 890 | "version": "4.0.1", 891 | "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", 892 | "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", 893 | "dev": true, 894 | "license": "MIT", 895 | "dependencies": { 896 | "flatted": "^3.2.9", 897 | "keyv": "^4.5.4" 898 | }, 899 | "engines": { 900 | "node": ">=16" 901 | } 902 | }, 903 | "node_modules/flatted": { 904 | "version": "3.3.3", 905 | "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", 906 | "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", 907 | "dev": true, 908 | "license": "ISC" 909 | }, 910 | "node_modules/follow-redirects": { 911 | "version": "1.15.9", 912 | "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", 913 | "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", 914 | "funding": [ 915 | { 916 | "type": "individual", 917 | "url": "https://github.com/sponsors/RubenVerborgh" 918 | } 919 | ], 920 | "license": "MIT", 921 | "engines": { 922 | "node": ">=4.0" 923 | }, 924 | "peerDependenciesMeta": { 925 | "debug": { 926 | "optional": true 927 | } 928 | } 929 | }, 930 | "node_modules/form-data": { 931 | "version": "4.0.2", 932 | "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", 933 | "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", 934 | "license": "MIT", 935 | "dependencies": { 936 | "asynckit": "^0.4.0", 937 | "combined-stream": "^1.0.8", 938 | "es-set-tostringtag": "^2.1.0", 939 | "mime-types": "^2.1.12" 940 | }, 941 | "engines": { 942 | "node": ">= 6" 943 | } 944 | }, 945 | "node_modules/function-bind": { 946 | "version": "1.1.2", 947 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", 948 | "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", 949 | "license": "MIT", 950 | "funding": { 951 | "url": "https://github.com/sponsors/ljharb" 952 | } 953 | }, 954 | "node_modules/get-caller-file": { 955 | "version": "2.0.5", 956 | "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", 957 | "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", 958 | "license": "ISC", 959 | "engines": { 960 | "node": "6.* || 8.* || >= 10.*" 961 | } 962 | }, 963 | "node_modules/get-intrinsic": { 964 | "version": "1.3.0", 965 | "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", 966 | "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", 967 | "license": "MIT", 968 | "dependencies": { 969 | "call-bind-apply-helpers": "^1.0.2", 970 | "es-define-property": "^1.0.1", 971 | "es-errors": "^1.3.0", 972 | "es-object-atoms": "^1.1.1", 973 | "function-bind": "^1.1.2", 974 | "get-proto": "^1.0.1", 975 | "gopd": "^1.2.0", 976 | "has-symbols": "^1.1.0", 977 | "hasown": "^2.0.2", 978 | "math-intrinsics": "^1.1.0" 979 | }, 980 | "engines": { 981 | "node": ">= 0.4" 982 | }, 983 | "funding": { 984 | "url": "https://github.com/sponsors/ljharb" 985 | } 986 | }, 987 | "node_modules/get-proto": { 988 | "version": "1.0.1", 989 | "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", 990 | "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", 991 | "license": "MIT", 992 | "dependencies": { 993 | "dunder-proto": "^1.0.1", 994 | "es-object-atoms": "^1.0.0" 995 | }, 996 | "engines": { 997 | "node": ">= 0.4" 998 | } 999 | }, 1000 | "node_modules/glob-parent": { 1001 | "version": "6.0.2", 1002 | "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", 1003 | "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", 1004 | "dev": true, 1005 | "license": "ISC", 1006 | "dependencies": { 1007 | "is-glob": "^4.0.3" 1008 | }, 1009 | "engines": { 1010 | "node": ">=10.13.0" 1011 | } 1012 | }, 1013 | "node_modules/globals": { 1014 | "version": "16.0.0", 1015 | "resolved": "https://registry.npmjs.org/globals/-/globals-16.0.0.tgz", 1016 | "integrity": "sha512-iInW14XItCXET01CQFqudPOWP2jYMl7T+QRQT+UNcR/iQncN/F0UNpgd76iFkBPgNQb4+X3LV9tLJYzwh+Gl3A==", 1017 | "dev": true, 1018 | "license": "MIT", 1019 | "engines": { 1020 | "node": ">=18" 1021 | }, 1022 | "funding": { 1023 | "url": "https://github.com/sponsors/sindresorhus" 1024 | } 1025 | }, 1026 | "node_modules/gopd": { 1027 | "version": "1.2.0", 1028 | "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", 1029 | "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", 1030 | "license": "MIT", 1031 | "engines": { 1032 | "node": ">= 0.4" 1033 | }, 1034 | "funding": { 1035 | "url": "https://github.com/sponsors/ljharb" 1036 | } 1037 | }, 1038 | "node_modules/has-flag": { 1039 | "version": "4.0.0", 1040 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", 1041 | "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", 1042 | "dev": true, 1043 | "license": "MIT", 1044 | "engines": { 1045 | "node": ">=8" 1046 | } 1047 | }, 1048 | "node_modules/has-symbols": { 1049 | "version": "1.1.0", 1050 | "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", 1051 | "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", 1052 | "license": "MIT", 1053 | "engines": { 1054 | "node": ">= 0.4" 1055 | }, 1056 | "funding": { 1057 | "url": "https://github.com/sponsors/ljharb" 1058 | } 1059 | }, 1060 | "node_modules/has-tostringtag": { 1061 | "version": "1.0.2", 1062 | "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", 1063 | "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", 1064 | "license": "MIT", 1065 | "dependencies": { 1066 | "has-symbols": "^1.0.3" 1067 | }, 1068 | "engines": { 1069 | "node": ">= 0.4" 1070 | }, 1071 | "funding": { 1072 | "url": "https://github.com/sponsors/ljharb" 1073 | } 1074 | }, 1075 | "node_modules/hasown": { 1076 | "version": "2.0.2", 1077 | "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", 1078 | "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", 1079 | "license": "MIT", 1080 | "dependencies": { 1081 | "function-bind": "^1.1.2" 1082 | }, 1083 | "engines": { 1084 | "node": ">= 0.4" 1085 | } 1086 | }, 1087 | "node_modules/he": { 1088 | "version": "1.2.0", 1089 | "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", 1090 | "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", 1091 | "license": "MIT", 1092 | "bin": { 1093 | "he": "bin/he" 1094 | } 1095 | }, 1096 | "node_modules/ignore": { 1097 | "version": "5.3.2", 1098 | "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", 1099 | "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", 1100 | "dev": true, 1101 | "license": "MIT", 1102 | "engines": { 1103 | "node": ">= 4" 1104 | } 1105 | }, 1106 | "node_modules/import-fresh": { 1107 | "version": "3.3.1", 1108 | "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", 1109 | "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", 1110 | "dev": true, 1111 | "license": "MIT", 1112 | "dependencies": { 1113 | "parent-module": "^1.0.0", 1114 | "resolve-from": "^4.0.0" 1115 | }, 1116 | "engines": { 1117 | "node": ">=6" 1118 | }, 1119 | "funding": { 1120 | "url": "https://github.com/sponsors/sindresorhus" 1121 | } 1122 | }, 1123 | "node_modules/imurmurhash": { 1124 | "version": "0.1.4", 1125 | "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", 1126 | "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", 1127 | "dev": true, 1128 | "license": "MIT", 1129 | "engines": { 1130 | "node": ">=0.8.19" 1131 | } 1132 | }, 1133 | "node_modules/is-extglob": { 1134 | "version": "2.1.1", 1135 | "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", 1136 | "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", 1137 | "dev": true, 1138 | "license": "MIT", 1139 | "engines": { 1140 | "node": ">=0.10.0" 1141 | } 1142 | }, 1143 | "node_modules/is-file": { 1144 | "version": "1.0.0", 1145 | "resolved": "https://registry.npmjs.org/is-file/-/is-file-1.0.0.tgz", 1146 | "integrity": "sha512-ZGMuc+xA8mRnrXtmtf2l/EkIW2zaD2LSBWlaOVEF6yH4RTndHob65V4SwWWdtGKVthQfXPVKsXqw4TDUjbVxVQ==", 1147 | "license": "MIT" 1148 | }, 1149 | "node_modules/is-fullwidth-code-point": { 1150 | "version": "3.0.0", 1151 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", 1152 | "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", 1153 | "license": "MIT", 1154 | "engines": { 1155 | "node": ">=8" 1156 | } 1157 | }, 1158 | "node_modules/is-glob": { 1159 | "version": "4.0.3", 1160 | "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", 1161 | "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", 1162 | "dev": true, 1163 | "license": "MIT", 1164 | "dependencies": { 1165 | "is-extglob": "^2.1.1" 1166 | }, 1167 | "engines": { 1168 | "node": ">=0.10.0" 1169 | } 1170 | }, 1171 | "node_modules/isexe": { 1172 | "version": "2.0.0", 1173 | "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", 1174 | "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", 1175 | "dev": true, 1176 | "license": "ISC" 1177 | }, 1178 | "node_modules/join-async-iterator": { 1179 | "version": "1.1.1", 1180 | "resolved": "https://registry.npmjs.org/join-async-iterator/-/join-async-iterator-1.1.1.tgz", 1181 | "integrity": "sha512-ATse+nuNeKZ9K1y27LKdvPe/GCe9R/u9dw9vI248e+vILeRK3IcJP4JUPAlSmKRCDK0cKhEwfmiw4Skqx7UnGQ==", 1182 | "license": "MIT" 1183 | }, 1184 | "node_modules/js-yaml": { 1185 | "version": "4.1.0", 1186 | "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", 1187 | "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", 1188 | "dev": true, 1189 | "license": "MIT", 1190 | "dependencies": { 1191 | "argparse": "^2.0.1" 1192 | }, 1193 | "bin": { 1194 | "js-yaml": "bin/js-yaml.js" 1195 | } 1196 | }, 1197 | "node_modules/json-buffer": { 1198 | "version": "3.0.1", 1199 | "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", 1200 | "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", 1201 | "dev": true, 1202 | "license": "MIT" 1203 | }, 1204 | "node_modules/json-schema-traverse": { 1205 | "version": "0.4.1", 1206 | "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", 1207 | "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", 1208 | "dev": true, 1209 | "license": "MIT" 1210 | }, 1211 | "node_modules/json-stable-stringify-without-jsonify": { 1212 | "version": "1.0.1", 1213 | "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", 1214 | "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", 1215 | "dev": true, 1216 | "license": "MIT" 1217 | }, 1218 | "node_modules/junk": { 1219 | "version": "4.0.1", 1220 | "resolved": "https://registry.npmjs.org/junk/-/junk-4.0.1.tgz", 1221 | "integrity": "sha512-Qush0uP+G8ZScpGMZvHUiRfI0YBWuB3gVBYlI0v0vvOJt5FLicco+IkP0a50LqTTQhmts/m6tP5SWE+USyIvcQ==", 1222 | "license": "MIT", 1223 | "engines": { 1224 | "node": ">=12.20" 1225 | }, 1226 | "funding": { 1227 | "url": "https://github.com/sponsors/sindresorhus" 1228 | } 1229 | }, 1230 | "node_modules/keyv": { 1231 | "version": "4.5.4", 1232 | "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", 1233 | "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", 1234 | "dev": true, 1235 | "license": "MIT", 1236 | "dependencies": { 1237 | "json-buffer": "3.0.1" 1238 | } 1239 | }, 1240 | "node_modules/levn": { 1241 | "version": "0.4.1", 1242 | "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", 1243 | "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", 1244 | "dev": true, 1245 | "license": "MIT", 1246 | "dependencies": { 1247 | "prelude-ls": "^1.2.1", 1248 | "type-check": "~0.4.0" 1249 | }, 1250 | "engines": { 1251 | "node": ">= 0.8.0" 1252 | } 1253 | }, 1254 | "node_modules/locate-path": { 1255 | "version": "6.0.0", 1256 | "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", 1257 | "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", 1258 | "dev": true, 1259 | "license": "MIT", 1260 | "dependencies": { 1261 | "p-locate": "^5.0.0" 1262 | }, 1263 | "engines": { 1264 | "node": ">=10" 1265 | }, 1266 | "funding": { 1267 | "url": "https://github.com/sponsors/sindresorhus" 1268 | } 1269 | }, 1270 | "node_modules/lodash.merge": { 1271 | "version": "4.6.2", 1272 | "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", 1273 | "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", 1274 | "dev": true, 1275 | "license": "MIT" 1276 | }, 1277 | "node_modules/math-intrinsics": { 1278 | "version": "1.1.0", 1279 | "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", 1280 | "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", 1281 | "license": "MIT", 1282 | "engines": { 1283 | "node": ">= 0.4" 1284 | } 1285 | }, 1286 | "node_modules/mime-db": { 1287 | "version": "1.52.0", 1288 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", 1289 | "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", 1290 | "license": "MIT", 1291 | "engines": { 1292 | "node": ">= 0.6" 1293 | } 1294 | }, 1295 | "node_modules/mime-types": { 1296 | "version": "2.1.35", 1297 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", 1298 | "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", 1299 | "license": "MIT", 1300 | "dependencies": { 1301 | "mime-db": "1.52.0" 1302 | }, 1303 | "engines": { 1304 | "node": ">= 0.6" 1305 | } 1306 | }, 1307 | "node_modules/minimatch": { 1308 | "version": "3.1.2", 1309 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", 1310 | "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", 1311 | "dev": true, 1312 | "license": "ISC", 1313 | "dependencies": { 1314 | "brace-expansion": "^1.1.7" 1315 | }, 1316 | "engines": { 1317 | "node": "*" 1318 | } 1319 | }, 1320 | "node_modules/minimist": { 1321 | "version": "1.2.8", 1322 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", 1323 | "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", 1324 | "license": "MIT", 1325 | "funding": { 1326 | "url": "https://github.com/sponsors/ljharb" 1327 | } 1328 | }, 1329 | "node_modules/ms": { 1330 | "version": "2.1.3", 1331 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 1332 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", 1333 | "license": "MIT" 1334 | }, 1335 | "node_modules/natural-compare": { 1336 | "version": "1.4.0", 1337 | "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", 1338 | "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", 1339 | "dev": true, 1340 | "license": "MIT" 1341 | }, 1342 | "node_modules/once": { 1343 | "version": "1.4.0", 1344 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 1345 | "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", 1346 | "license": "ISC", 1347 | "dependencies": { 1348 | "wrappy": "1" 1349 | } 1350 | }, 1351 | "node_modules/optionator": { 1352 | "version": "0.9.4", 1353 | "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", 1354 | "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", 1355 | "dev": true, 1356 | "license": "MIT", 1357 | "dependencies": { 1358 | "deep-is": "^0.1.3", 1359 | "fast-levenshtein": "^2.0.6", 1360 | "levn": "^0.4.1", 1361 | "prelude-ls": "^1.2.1", 1362 | "type-check": "^0.4.0", 1363 | "word-wrap": "^1.2.5" 1364 | }, 1365 | "engines": { 1366 | "node": ">= 0.8.0" 1367 | } 1368 | }, 1369 | "node_modules/p-limit": { 1370 | "version": "3.1.0", 1371 | "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", 1372 | "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", 1373 | "dev": true, 1374 | "license": "MIT", 1375 | "dependencies": { 1376 | "yocto-queue": "^0.1.0" 1377 | }, 1378 | "engines": { 1379 | "node": ">=10" 1380 | }, 1381 | "funding": { 1382 | "url": "https://github.com/sponsors/sindresorhus" 1383 | } 1384 | }, 1385 | "node_modules/p-locate": { 1386 | "version": "5.0.0", 1387 | "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", 1388 | "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", 1389 | "dev": true, 1390 | "license": "MIT", 1391 | "dependencies": { 1392 | "p-limit": "^3.0.2" 1393 | }, 1394 | "engines": { 1395 | "node": ">=10" 1396 | }, 1397 | "funding": { 1398 | "url": "https://github.com/sponsors/sindresorhus" 1399 | } 1400 | }, 1401 | "node_modules/parent-module": { 1402 | "version": "1.0.1", 1403 | "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", 1404 | "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", 1405 | "dev": true, 1406 | "license": "MIT", 1407 | "dependencies": { 1408 | "callsites": "^3.0.0" 1409 | }, 1410 | "engines": { 1411 | "node": ">=6" 1412 | } 1413 | }, 1414 | "node_modules/path-exists": { 1415 | "version": "4.0.0", 1416 | "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", 1417 | "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", 1418 | "dev": true, 1419 | "license": "MIT", 1420 | "engines": { 1421 | "node": ">=8" 1422 | } 1423 | }, 1424 | "node_modules/path-key": { 1425 | "version": "3.1.1", 1426 | "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", 1427 | "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", 1428 | "dev": true, 1429 | "license": "MIT", 1430 | "engines": { 1431 | "node": ">=8" 1432 | } 1433 | }, 1434 | "node_modules/piece-length": { 1435 | "version": "2.0.1", 1436 | "resolved": "https://registry.npmjs.org/piece-length/-/piece-length-2.0.1.tgz", 1437 | "integrity": "sha512-dBILiDmm43y0JPISWEmVGKBETQjwJe6mSU9GND+P9KW0SJGUwoU/odyH1nbalOP9i8WSYuqf1lQnaj92Bhw+Ug==", 1438 | "license": "MIT" 1439 | }, 1440 | "node_modules/prelude-ls": { 1441 | "version": "1.2.1", 1442 | "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", 1443 | "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", 1444 | "dev": true, 1445 | "license": "MIT", 1446 | "engines": { 1447 | "node": ">= 0.8.0" 1448 | } 1449 | }, 1450 | "node_modules/proxy-from-env": { 1451 | "version": "1.1.0", 1452 | "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", 1453 | "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", 1454 | "license": "MIT" 1455 | }, 1456 | "node_modules/punycode": { 1457 | "version": "2.3.1", 1458 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", 1459 | "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", 1460 | "dev": true, 1461 | "license": "MIT", 1462 | "engines": { 1463 | "node": ">=6" 1464 | } 1465 | }, 1466 | "node_modules/queue-microtask": { 1467 | "version": "1.2.3", 1468 | "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", 1469 | "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", 1470 | "funding": [ 1471 | { 1472 | "type": "github", 1473 | "url": "https://github.com/sponsors/feross" 1474 | }, 1475 | { 1476 | "type": "patreon", 1477 | "url": "https://www.patreon.com/feross" 1478 | }, 1479 | { 1480 | "type": "consulting", 1481 | "url": "https://feross.org/support" 1482 | } 1483 | ], 1484 | "license": "MIT" 1485 | }, 1486 | "node_modules/require-directory": { 1487 | "version": "2.1.1", 1488 | "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", 1489 | "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", 1490 | "license": "MIT", 1491 | "engines": { 1492 | "node": ">=0.10.0" 1493 | } 1494 | }, 1495 | "node_modules/resolve-from": { 1496 | "version": "4.0.0", 1497 | "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", 1498 | "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", 1499 | "dev": true, 1500 | "license": "MIT", 1501 | "engines": { 1502 | "node": ">=4" 1503 | } 1504 | }, 1505 | "node_modules/run-parallel": { 1506 | "version": "1.2.0", 1507 | "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", 1508 | "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", 1509 | "funding": [ 1510 | { 1511 | "type": "github", 1512 | "url": "https://github.com/sponsors/feross" 1513 | }, 1514 | { 1515 | "type": "patreon", 1516 | "url": "https://www.patreon.com/feross" 1517 | }, 1518 | { 1519 | "type": "consulting", 1520 | "url": "https://feross.org/support" 1521 | } 1522 | ], 1523 | "license": "MIT", 1524 | "dependencies": { 1525 | "queue-microtask": "^1.2.2" 1526 | } 1527 | }, 1528 | "node_modules/shebang-command": { 1529 | "version": "2.0.0", 1530 | "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", 1531 | "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", 1532 | "dev": true, 1533 | "license": "MIT", 1534 | "dependencies": { 1535 | "shebang-regex": "^3.0.0" 1536 | }, 1537 | "engines": { 1538 | "node": ">=8" 1539 | } 1540 | }, 1541 | "node_modules/shebang-regex": { 1542 | "version": "3.0.0", 1543 | "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", 1544 | "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", 1545 | "dev": true, 1546 | "license": "MIT", 1547 | "engines": { 1548 | "node": ">=8" 1549 | } 1550 | }, 1551 | "node_modules/string-width": { 1552 | "version": "4.2.3", 1553 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", 1554 | "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", 1555 | "license": "MIT", 1556 | "dependencies": { 1557 | "emoji-regex": "^8.0.0", 1558 | "is-fullwidth-code-point": "^3.0.0", 1559 | "strip-ansi": "^6.0.1" 1560 | }, 1561 | "engines": { 1562 | "node": ">=8" 1563 | } 1564 | }, 1565 | "node_modules/strip-ansi": { 1566 | "version": "6.0.1", 1567 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", 1568 | "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", 1569 | "license": "MIT", 1570 | "dependencies": { 1571 | "ansi-regex": "^5.0.1" 1572 | }, 1573 | "engines": { 1574 | "node": ">=8" 1575 | } 1576 | }, 1577 | "node_modules/strip-json-comments": { 1578 | "version": "3.1.1", 1579 | "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", 1580 | "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", 1581 | "dev": true, 1582 | "license": "MIT", 1583 | "engines": { 1584 | "node": ">=8" 1585 | }, 1586 | "funding": { 1587 | "url": "https://github.com/sponsors/sindresorhus" 1588 | } 1589 | }, 1590 | "node_modules/supports-color": { 1591 | "version": "7.2.0", 1592 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", 1593 | "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", 1594 | "dev": true, 1595 | "license": "MIT", 1596 | "dependencies": { 1597 | "has-flag": "^4.0.0" 1598 | }, 1599 | "engines": { 1600 | "node": ">=8" 1601 | } 1602 | }, 1603 | "node_modules/type-check": { 1604 | "version": "0.4.0", 1605 | "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", 1606 | "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", 1607 | "dev": true, 1608 | "license": "MIT", 1609 | "dependencies": { 1610 | "prelude-ls": "^1.2.1" 1611 | }, 1612 | "engines": { 1613 | "node": ">= 0.8.0" 1614 | } 1615 | }, 1616 | "node_modules/uint8-util": { 1617 | "version": "2.2.5", 1618 | "resolved": "https://registry.npmjs.org/uint8-util/-/uint8-util-2.2.5.tgz", 1619 | "integrity": "sha512-/QxVQD7CttWpVUKVPz9znO+3Dd4BdTSnFQ7pv/4drVhC9m4BaL2LFHTkJn6EsYoxT79VDq/2Gg8L0H22PrzyMw==", 1620 | "license": "MIT", 1621 | "dependencies": { 1622 | "base64-arraybuffer": "^1.0.2" 1623 | } 1624 | }, 1625 | "node_modules/uri-js": { 1626 | "version": "4.4.1", 1627 | "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", 1628 | "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", 1629 | "dev": true, 1630 | "license": "BSD-2-Clause", 1631 | "dependencies": { 1632 | "punycode": "^2.1.0" 1633 | } 1634 | }, 1635 | "node_modules/which": { 1636 | "version": "2.0.2", 1637 | "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", 1638 | "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", 1639 | "dev": true, 1640 | "license": "ISC", 1641 | "dependencies": { 1642 | "isexe": "^2.0.0" 1643 | }, 1644 | "bin": { 1645 | "node-which": "bin/node-which" 1646 | }, 1647 | "engines": { 1648 | "node": ">= 8" 1649 | } 1650 | }, 1651 | "node_modules/word-wrap": { 1652 | "version": "1.2.5", 1653 | "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", 1654 | "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", 1655 | "dev": true, 1656 | "license": "MIT", 1657 | "engines": { 1658 | "node": ">=0.10.0" 1659 | } 1660 | }, 1661 | "node_modules/wrap-ansi": { 1662 | "version": "7.0.0", 1663 | "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", 1664 | "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", 1665 | "license": "MIT", 1666 | "dependencies": { 1667 | "ansi-styles": "^4.0.0", 1668 | "string-width": "^4.1.0", 1669 | "strip-ansi": "^6.0.0" 1670 | }, 1671 | "engines": { 1672 | "node": ">=10" 1673 | }, 1674 | "funding": { 1675 | "url": "https://github.com/chalk/wrap-ansi?sponsor=1" 1676 | } 1677 | }, 1678 | "node_modules/wrappy": { 1679 | "version": "1.0.2", 1680 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 1681 | "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", 1682 | "license": "ISC" 1683 | }, 1684 | "node_modules/y18n": { 1685 | "version": "5.0.8", 1686 | "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", 1687 | "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", 1688 | "license": "ISC", 1689 | "engines": { 1690 | "node": ">=10" 1691 | } 1692 | }, 1693 | "node_modules/yaml": { 1694 | "version": "1.10.2", 1695 | "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", 1696 | "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", 1697 | "license": "ISC", 1698 | "engines": { 1699 | "node": ">= 6" 1700 | } 1701 | }, 1702 | "node_modules/yargs": { 1703 | "version": "17.7.2", 1704 | "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", 1705 | "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", 1706 | "license": "MIT", 1707 | "dependencies": { 1708 | "cliui": "^8.0.1", 1709 | "escalade": "^3.1.1", 1710 | "get-caller-file": "^2.0.5", 1711 | "require-directory": "^2.1.1", 1712 | "string-width": "^4.2.3", 1713 | "y18n": "^5.0.5", 1714 | "yargs-parser": "^21.1.1" 1715 | }, 1716 | "engines": { 1717 | "node": ">=12" 1718 | } 1719 | }, 1720 | "node_modules/yargs-parser": { 1721 | "version": "21.1.1", 1722 | "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", 1723 | "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", 1724 | "license": "ISC", 1725 | "engines": { 1726 | "node": ">=12" 1727 | } 1728 | }, 1729 | "node_modules/yocto-queue": { 1730 | "version": "0.1.0", 1731 | "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", 1732 | "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", 1733 | "dev": true, 1734 | "license": "MIT", 1735 | "engines": { 1736 | "node": ">=10" 1737 | }, 1738 | "funding": { 1739 | "url": "https://github.com/sponsors/sindresorhus" 1740 | } 1741 | } 1742 | } 1743 | } 1744 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "bin": { 3 | "red-trul": "trul.js", 4 | "red-trul-flock": "flock.bash" 5 | }, 6 | "engines": { 7 | "node": ">=16.0.0" 8 | }, 9 | "name": "red-trul", 10 | "version": "2.3.14", 11 | "description": "", 12 | "main": "trul.js", 13 | "author": "", 14 | "type": "module", 15 | "license": "ISC", 16 | "dependencies": { 17 | "axios": "^1.6.5", 18 | "bencode": "^4.0.0", 19 | "create-torrent": "^6.0.18", 20 | "debug": "^4.3.7", 21 | "form-data": "^4.0.2", 22 | "he": "^1.2.0", 23 | "yaml": "^1.10.0", 24 | "yargs": "^17.2.1" 25 | }, 26 | "prettier": { 27 | "semi": false 28 | }, 29 | "devDependencies": { 30 | "@eslint/js": "^9.22.0", 31 | "eslint": "^9.22.0", 32 | "globals": "^16.0.0" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /qbittorrent-postdl.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | export RED_API_KEY=... 3 | 4 | 5 | # 0) To run this script, in qbittorrent enable "Run external program on torrent completion" 6 | # Set it to run "/home/USER/qbittorrent-postdl.sh" %I "%F" "%T" "%D" 7 | # 1) Add a transcode directory (e.g., "$HOME/transcodes") 8 | # 2) Add a torrent watch directory (e.g., "$HOME/watch_trul") 9 | # 4) In qbittorrent settings find "Watched Folder", add the directory from 2) and have it download to the directory from 1). 10 | # 11 | # Note by killasnake0: 12 | # > For anyone that wants to get the post-download hook working on Windows in WSL, 13 | # > there are a few minor changes to make: 14 | # > 15 | # > The hook in qB should be 16 | # > `wsl -e /path/to/qbittorrent-postdl.sh %I "%F" %T` 17 | # > 18 | # > Within `qbittorrent-postdl.sh` change line 18 to 19 | # > `INPUT=$(wslpath "$2")` 20 | # > and remove the & at the end of line 32 21 | # > `--transcode-dir="$TRANSCODE_DIR" "$INPUT"` 22 | # 23 | cd "$(dirname "$0")" # change to same dir as this script. 24 | 25 | # redirect stdout and stderr to logfile (the script prints nothing) 26 | # use tail -f $LOG_FILE to watch what it says 27 | LOG_FILE="trul.log" 28 | exec >> $LOG_FILE 29 | exec 2>> $LOG_FILE 30 | 31 | INFO_HASH=$1 32 | INPUT=$2 33 | TRACKER=$3 34 | TRANSCODE_DIR="$HOME/transcodes" # matches your qbit config 35 | TORRENT_DIR="$HOME/watch_trul" # matches your qbit config 36 | 37 | TRUL="flock.bash" 38 | 39 | if ! grep -q flacsfor.me <<< $TRACKER; then 40 | echo not from RED. bye 41 | exit 0 42 | fi 43 | 44 | ./$TRUL --info-hash=$INFO_HASH --torrent-dir="$TORRENT_DIR" \ 45 | --transcode-dir="$TRANSCODE_DIR" "$INPUT" & 46 | -------------------------------------------------------------------------------- /red-api.js: -------------------------------------------------------------------------------- 1 | import axios from "axios" 2 | import q from "querystring" 3 | import FormData from "form-data" 4 | import he from "he" 5 | import path from "path" 6 | import { readFileSync, realpathSync } from "fs" 7 | const pkg = JSON.parse( 8 | readFileSync( 9 | path.join(path.dirname(realpathSync(process.argv[1])), "package.json"), 10 | ), 11 | ) 12 | 13 | /* Recurses over an entire (acyclic) object. Mutates object entries in-place. 14 | * Decodes html-entities, e.g., "Lømsk" to "Lømsk" */ 15 | function decodeEntities(obj) { 16 | if (obj === null) { 17 | return 18 | } 19 | for (const [key, value] of Object.entries(obj)) { 20 | if (typeof value === "string") { 21 | // on rare occasions we get double-encoded filenames like 22 | // "1 - ✺&#120372;ပ&#120520;✺.flac" 23 | obj[key] = he.decode(he.decode(value)) 24 | } else if (Array.isArray(value)) { 25 | // If the property is an array, decode each string element 26 | obj[key] = value.map((item) => { 27 | if (typeof item === "string") { 28 | return he.decode(he.decode(item)) 29 | } else if (typeof item === "object") { 30 | // If the element is an object, recursively decode its strings 31 | decodeEntities(item) 32 | } 33 | return item 34 | }) 35 | } else if (typeof value === "object") { 36 | // If the property is an object, recursively decode its strings 37 | decodeEntities(value) 38 | } 39 | } 40 | } 41 | 42 | export default class REDAPIClient { 43 | constructor(API_KEY, _options = {}) { 44 | const options = { 45 | decodeEntities: true, 46 | ..._options, 47 | } 48 | 49 | this.apiClient = axios.create({ 50 | baseURL: process.env.RED_API || "https://redacted.sh", 51 | headers: { 52 | Authorization: API_KEY, 53 | "user-agent": `${pkg.name}@${pkg.version}`, 54 | }, 55 | validateStatus: (status) => status < 500, 56 | }) 57 | 58 | this.apiClient.interceptors.response.use(function (response) { 59 | if (response.data?.status !== "success") { 60 | // mind that the `response` is `AxiosResponse`. 61 | const { method, url } = response.config 62 | throw new Error(`${method} ${url}: ${JSON.stringify(response.data)}`) 63 | } 64 | if (options.decodeEntities) { 65 | decodeEntities(response.data) 66 | } 67 | return response 68 | }) 69 | } 70 | 71 | async index() { 72 | const resp = await this.apiClient.get(`/ajax.php?action=index`) 73 | 74 | return resp.data.response 75 | } 76 | 77 | async torrent({ id, hash }) { 78 | const query = { 79 | action: "torrent", 80 | } 81 | if (id) { 82 | query.id = id 83 | } else if (hash) { 84 | query.hash = hash 85 | } else { 86 | throw new Error("args") 87 | } 88 | const resp = await this.apiClient.get(`/ajax.php?${q.encode(query)}`) 89 | 90 | return resp.data.response 91 | } 92 | 93 | async torrentgroup({ id, hash }) { 94 | const query = { 95 | action: "torrentgroup", 96 | } 97 | if (id) { 98 | query.id = id 99 | } else if (hash) { 100 | query.hash = hash 101 | } else { 102 | throw new Error("args") 103 | } 104 | const resp = await this.apiClient.get(`/ajax.php?${q.encode(query)}`) 105 | 106 | return resp.data.response 107 | } 108 | 109 | async upload(opts) { 110 | const form = new FormData() 111 | 112 | for (const [k, v] of Object.entries(opts).filter(([, v]) => v)) { 113 | if (v == null) continue 114 | if (Array.isArray(v)) { 115 | for (const el of v) { 116 | form.append(`${k}[]`, el) 117 | } 118 | } else if (["file_input", "extra_file_1", "extra_file_2"].includes(k)) { 119 | form.append(k, v, `${k}.torrent`) 120 | } else { 121 | form.append(k, v) 122 | } 123 | } 124 | 125 | const resp = await this.apiClient.post(`/ajax.php?action=upload`, form, { 126 | headers: { 127 | ...form.getHeaders(), 128 | }, 129 | }) 130 | 131 | return resp.data.response 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /rtorrent-postdl.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | export RED_API_KEY=... 3 | 4 | # 5 | # To run this script, add to .rtorrent.rc. Modify paths to match with your configuration. 6 | # 7 | # schedule2 = watch_directory_red, 10, 10, ((load.start_verbose, (cat,"/home/USER/rtorrent/watch_trul/", "*.torrent"), "d.directory.set=/home/USER/transcodes", "d.delete_tied=")) 8 | # method.set_key = event.download.finished,postrun,"execute2={~/red-trul/rtorrent-postdl.sh,$d.base_path=,$d.hash=,$session.path=}" 9 | cd "$(dirname "$0")" # change to same dir as this script. 10 | 11 | # redirect stdout and stderr to logfile (the script prints nothing) 12 | # use tail -f $LOG_FILE to watch what it says 13 | LOG_FILE="trul.log" 14 | exec >> $LOG_FILE 15 | exec 2>> $LOG_FILE 16 | 17 | # d.base_path 18 | BASE_PATH=$1 19 | # d.hash 20 | INFO_HASH=$2 21 | # session.path 22 | SESSION_PATH=$3 23 | 24 | TRUL="flock.bash" 25 | TRANSCODE_DIR="$HOME/transcodes" # matches .rtorrent.rc 26 | TORRENT_DIR="$HOME/rtorrent/watch_trul" # matches .rtorrent.rc 27 | 28 | if ! grep flacsfor.me "$SESSION_PATH/$INFO_HASH.torrent"; then 29 | # Not a RED torrent. 30 | exit 0 31 | fi 32 | 33 | # show the following command to manually run it on error 34 | set -x 35 | 36 | # Run in background to unblock rtorrent. Otherwise, rtorrent hook is blocked 37 | # until this script finishes. 38 | ./$TRUL --info-hash=$INFO_HASH --torrent-dir="$TORRENT_DIR" --transcode-dir="$TRANSCODE_DIR" "$BASE_PATH" & 39 | -------------------------------------------------------------------------------- /trul.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import REDAPIClient from "./red-api.js" 3 | import { initConfig, getEnv } from "./config.js" 4 | import _createTorrent from "create-torrent" 5 | import os from "os" 6 | import path from "path" 7 | import { execFile as _execFile } from "child_process" 8 | import { promises as fs } from "fs" 9 | import { promisify } from "util" 10 | import debug from "debug" 11 | const verboseLog = debug("trul:cli") 12 | 13 | async function execFile(file, args, ops = {}) { 14 | verboseLog(`execFile: ${file} ${args.join(" ")}...`) 15 | let subprocess = null 16 | try { 17 | subprocess = _execFile(file, args, ops) 18 | } catch (e) { 19 | verboseLog(e) 20 | throw new Error(`[!] execFile failed: ${e.code}`) 21 | } 22 | const out = { stderr: "", stdout: "", code: NaN } 23 | 24 | subprocess.stderr.on("data", (d) => { 25 | verboseLog(`${path.basename(file)} [${subprocess.pid}]: ${d}`) 26 | out.stderr += d 27 | }) 28 | subprocess.stdout.on("data", (d) => { 29 | verboseLog(`${path.basename(file)} [${subprocess.pid}]: ${d}`) 30 | out.stdout += d 31 | }) 32 | await new Promise((res) => { 33 | subprocess.on("close", (code) => { 34 | out.code = code 35 | if (code !== 0) { 36 | verboseLog(out) 37 | throw new Error(`[!] execFile failed: ${code}`) 38 | } 39 | res() 40 | }) 41 | }) 42 | return out 43 | } 44 | 45 | const createTorrent = promisify(_createTorrent) 46 | 47 | 48 | const { 49 | ALWAYS_TRANSCODE, 50 | FLAC_DIR, 51 | API_KEY, 52 | TRANSCODE_DIR, 53 | TORRENT_DIR, 54 | SOX, 55 | SOX_ARGS, 56 | FLAC2MP3, 57 | FLAC2MP3_ARGS, 58 | NO_UPLOAD, 59 | NO_FLAC, 60 | NO_V0, 61 | NO_320, 62 | SCRIPT_NAME, 63 | TORRENT_QUERY, 64 | } = initConfig 65 | 66 | // API_KEY requires 'Torrents' permission. 67 | if (!API_KEY) { 68 | console.error("Missing required argument '--api-key'. Try '--help' for help") 69 | process.exit(1) 70 | } 71 | 72 | const RED_ENC_FLAC24 = "24bit Lossless" 73 | const RED_ENC_FLAC16 = "Lossless" 74 | 75 | const RED_ENC_CBR320 = "320" 76 | const RED_ENC_VBRV0 = "V0 (VBR)" 77 | 78 | const LAME_ARGS = { 79 | [RED_ENC_VBRV0]: "-V0 -h -S", 80 | [RED_ENC_CBR320]: "-b 320 -h -S", 81 | } 82 | 83 | const DIRNAME_FORMAT = { 84 | [RED_ENC_FLAC16]: "FLAC16", 85 | [RED_ENC_VBRV0]: "V0", 86 | [RED_ENC_CBR320]: "320", 87 | } 88 | const encExists = (enc, editionGroup) => 89 | editionGroup.some((torrent) => torrent.encoding === enc) 90 | 91 | const formatPermalink = (torrent) => 92 | `https://redacted.sh/torrents.php?torrentid=${torrent.id}` 93 | 94 | const formatMessage = (torrent, command) => 95 | `[b][code]source:[/code][/b] [url=${formatPermalink(torrent)}][code]${torrent.format} / ${torrent.encoding}[/code][/url] 96 | [b][code]command:[/code][/b] [code]${command}[/code] 97 | [b][code]toolchain:[/code][/b] [url=https://github.com/lfence/red-trul][code]${SCRIPT_NAME}[/code][/url]` 98 | 99 | async function ensureDir(dir) { 100 | try { 101 | await fs.access(dir) 102 | } catch { 103 | throw new Error(`[!] Path "${dir}" does not exist!`) 104 | } 105 | const stats = await fs.stat(dir) 106 | if (!stats.isDirectory()) { 107 | throw new Error(`[!] "${dir}" is not a directory.`) 108 | } 109 | } 110 | 111 | const mkdirpMaybe = (() => { 112 | const exists = {} // remember just-created ones. 113 | return async function mkdirpMaybe(dir) { 114 | if (!exists[dir]) { 115 | await fs.mkdir(dir, { recursive: true }) 116 | exists[dir] = true 117 | } 118 | } 119 | })() 120 | 121 | // gives an iterator<{file, dir}>. Returned filenames exclude baseDir. 122 | async function* traverseFiles(baseDir) { 123 | for (let dirent of await fs.readdir(baseDir, { withFileTypes: true })) { 124 | const fpath = path.join(baseDir, dirent.name) 125 | if (dirent.isDirectory()) { 126 | for await (const f of traverseFiles(fpath)) { 127 | yield { file: f.file, dir: path.join(dirent.name, f.dir) } 128 | } 129 | } 130 | yield { file: dirent.name, dir: "." } 131 | } 132 | } 133 | 134 | // Copy additional (non-music) files too. Only moving png and jpegs right now, 135 | // anything else missing? 136 | async function copyOtherFiles(outDir, inDir, media) { 137 | const tasks = [] 138 | const incFileExts = [".png", ".jpg", ".jpeg", ".pdf"] 139 | if (!["CD", "WEB"].includes(media)) { 140 | incFileExts.push(".txt") // include lineage.txt 141 | } 142 | for await (let { file, dir } of traverseFiles(inDir)) { 143 | if (!incFileExts.includes(path.extname(file))) { 144 | continue 145 | } 146 | const src = path.join(inDir, dir, file) 147 | const dst = path.join(outDir, dir, file) 148 | await mkdirpMaybe(path.join(outDir, dir)) 149 | tasks.push(fs.copyFile(src, dst)) 150 | } 151 | return Promise.all(tasks) 152 | } 153 | 154 | async function makeFlacTranscode(outDir, inDir, files) { 155 | await mkdirpMaybe(outDir) 156 | for (const file of files) { 157 | await mkdirpMaybe(path.join(outDir, path.dirname(file.path))) 158 | const dst = path.join(outDir, file.path) 159 | console.log(`[-] Transcoding ${dst}...`) 160 | await execFile(SOX, [ 161 | "--multi-threaded", 162 | "--buffer=131072", 163 | ...SOX_ARGS.split(" ").map((arg) => 164 | arg 165 | .replace("", path.join(inDir, file.path)) 166 | .replace("", dst) 167 | .replace("", file.sampleRate % 48000 === 0 ? 48000 : 44100), 168 | ), 169 | ]) 170 | } 171 | } 172 | 173 | async function probeMediaFile(filename) { 174 | const { stdout } = await execFile("ffprobe", [ 175 | "-show_streams", 176 | "-show_format", 177 | "-print_format", 178 | "json", 179 | filename, 180 | ]) 181 | return JSON.parse(stdout) 182 | } 183 | 184 | function filterSameEditionGroupAs({ 185 | media, 186 | remasterTitle, 187 | remasterCatalogueNumber, 188 | remasterYear, 189 | remasterRecordLabel, 190 | }) { 191 | return (t) => { 192 | if (t.media !== media) return false 193 | if (t.remasterTitle !== remasterTitle) return false 194 | if (t.remasterCatalogueNumber !== remasterCatalogueNumber) return false 195 | if (t.remasterRecordLabel !== remasterRecordLabel) return false 196 | if (t.remasterYear !== remasterYear) return false 197 | return true 198 | } 199 | } 200 | 201 | function formatArtist(group) { 202 | let artists = group.musicInfo.artists 203 | if (artists.length == 1) return artists[0].name 204 | else if (artists.length == 2) return `${artists[0].name} & ${artists[1].name}` 205 | else return "Various Artists" 206 | } 207 | 208 | function formatDirname(group, torrent, bitrate) { 209 | const format = DIRNAME_FORMAT[bitrate] 210 | // will make dirs for FLAC, V0 and 320 transcodes using this as base 211 | let dirname = `${formatArtist(group)} - ${group.name}` 212 | if (torrent.remasterTitle) { 213 | // e.g., "Special Edition" 214 | dirname += ` (${torrent.remasterTitle})` 215 | } 216 | 217 | const year = torrent.remasterYear || group.year 218 | if (year) { 219 | dirname += ` (${year})` 220 | } 221 | 222 | return `${dirname} [${torrent.media} ${format}]` 223 | .replace(/\//g, "∕") // Note that is not a normal / but a utf-8 one 224 | .replace(/^~/, "") 225 | .replace(/\.$/g, "_") 226 | .replace(/[\x01-\x1f]/g, "_") 227 | .replace(/[<>:"?*|]/g, "_") 228 | .trim() 229 | } 230 | 231 | function findMissingTags(tags) { 232 | const keys = Object.keys(tags || {}).map((key) => key.toUpperCase()) 233 | return ["TITLE", "ARTIST", "ALBUM", "TRACK"].filter( 234 | (tag) => !keys.includes(tag), 235 | ) 236 | } 237 | 238 | async function analyzeFileList(inDir, fileList) { 239 | const flacs = fileList 240 | .split("|||") 241 | .map((e) => { 242 | const m = /(^.*){{{([0-9]*)}}}$/.exec(e) 243 | return [m[1], m[2]] 244 | }) 245 | .map(([filename]) => filename) 246 | .filter((name) => /\.flac$/.test(name)) 247 | 248 | console.log(`[-] Run ffprobe (${flacs.length} flacs)...`) 249 | const results = [] 250 | for (const flacpath of flacs) { 251 | const absPath = path.join(inDir, flacpath) 252 | 253 | const info = await probeMediaFile(absPath) 254 | const missingTags = findMissingTags(info.format.tags) 255 | if (missingTags.length > 0) { 256 | throw new Error( 257 | `[!] ${absPath}: missing required tags: ${missingTags.join()}.`, 258 | ) 259 | } 260 | 261 | const flacStream = info.streams.find( 262 | ({ codec_name }) => codec_name === "flac", 263 | ) 264 | 265 | results.push({ 266 | path: flacpath, // e.g., CD1/01......flac 267 | tags: info.format.tags, 268 | bitRate: Number.parseInt(flacStream.bits_per_raw_sample, 10), 269 | sampleRate: Number.parseInt(flacStream.sample_rate, 10), 270 | channels: flacStream.channels, 271 | }) 272 | } 273 | 274 | return results 275 | } 276 | 277 | function shouldMakeFLAC(torrent, editionGroup, analyzedFiles) { 278 | if (NO_FLAC) { 279 | return false 280 | } 281 | if (torrent.encoding !== RED_ENC_FLAC24) { 282 | // we only make flac16 out of flac24 283 | return false 284 | } 285 | 286 | const non24Bit = analyzedFiles.filter(({ bitRate }) => bitRate !== 24) 287 | if (non24Bit.length !== 0) { 288 | console.log("[-] These are not 24bit FLACs (maybe report)") 289 | non24Bit.forEach((b) => console.log(` ${b.path}: ${b.bitRate}`)) 290 | return false 291 | } 292 | 293 | if (ALWAYS_TRANSCODE) { 294 | return true 295 | } 296 | 297 | // a flac16 already exists. 298 | return !encExists(RED_ENC_FLAC16, editionGroup) 299 | } 300 | 301 | async function main() { 302 | await fs.access(FLAC2MP3) 303 | await ensureDir(TRANSCODE_DIR) 304 | await ensureDir(TORRENT_DIR) 305 | await ensureDir(FLAC_DIR) 306 | 307 | const redAPI = new REDAPIClient(API_KEY) 308 | 309 | const { passkey } = await redAPI.index() 310 | const announce = `https://flacsfor.me/${passkey}/announce` 311 | 312 | console.log(`[-] Fetch torrent...`) 313 | // get the current torrent 314 | const { group, torrent } = await redAPI.torrent(TORRENT_QUERY) 315 | console.log(`[-] Permalink: ${formatPermalink(torrent)}`) 316 | if (torrent.format !== "FLAC") { 317 | throw new Error("[!] Not a FLAC, not interested.") 318 | } 319 | 320 | console.log(`[-] Analyze torrent.fileList...`) 321 | const analyzedFiles = await analyzeFileList(FLAC_DIR, torrent.fileList) 322 | 323 | // mp3 must not be used for anything except mono and stereo, consider AAC... 324 | const mp3incompatible = analyzedFiles.some((flac) => flac.channels > 2) 325 | 326 | console.log(`[-] Fetch torrentgroup...`) 327 | const { torrents } = await redAPI.torrentgroup({ id: group.id }) 328 | 329 | // torrents that belong to this edition (how they are groups by the website) 330 | const editionGroup = torrents.filter(filterSameEditionGroupAs(torrent)) 331 | 332 | if (editionGroup.length === 0) { 333 | throw new Error( 334 | "[!] Edition group should at least contain the current release", 335 | ) 336 | } 337 | 338 | // torrents will be kept in memory before uploading, after uploading they will 339 | // written 340 | const transcodeTasks = [] 341 | 342 | if (shouldMakeFLAC(torrent, editionGroup, analyzedFiles)) { 343 | const outDir = path.join( 344 | TRANSCODE_DIR, 345 | formatDirname(group, torrent, RED_ENC_FLAC16), 346 | ) 347 | transcodeTasks.push({ 348 | skipUpload: NO_UPLOAD || encExists(RED_ENC_FLAC16, editionGroup), 349 | outDir, 350 | doTranscode: () => { 351 | console.log(`[-] Resample as ${RED_ENC_FLAC16}...`) 352 | return makeFlacTranscode(outDir, FLAC_DIR, analyzedFiles) 353 | 354 | }, 355 | message: formatMessage(torrent, `sox ${SOX_ARGS}`), 356 | format: "FLAC", 357 | bitrate: RED_ENC_FLAC16, 358 | }) 359 | } 360 | for (const bitrate of [RED_ENC_VBRV0, RED_ENC_CBR320]) { 361 | const skip = 362 | (bitrate == RED_ENC_CBR320 && NO_320) || 363 | (bitrate == RED_ENC_VBRV0 && NO_V0) || 364 | mp3incompatible 365 | const dirname = formatDirname(group, torrent, bitrate) 366 | const exists = encExists(bitrate, editionGroup) 367 | if (skip) { 368 | verboseLog(`Won't create ${dirname}`) 369 | continue 370 | } 371 | if (!ALWAYS_TRANSCODE && exists) { 372 | verboseLog(`${bitrate} already exists. Skip`) 373 | // this encoding already available. no need to transcode 374 | continue 375 | } 376 | const outDir = path.join(TRANSCODE_DIR, dirname) 377 | const args = FLAC2MP3_ARGS.split(" ").map((arg) => 378 | arg 379 | .replace("", `'${LAME_ARGS[bitrate]}'`) 380 | .replace("", os.cpus().length), 381 | ) 382 | transcodeTasks.push({ 383 | outDir, 384 | skipUpload: NO_UPLOAD || exists, 385 | doTranscode: () => { 386 | console.log(`[-] Transcode mp3 ${bitrate}...`) 387 | return execFile(FLAC2MP3, [...args, FLAC_DIR, outDir]) 388 | }, 389 | message: formatMessage(torrent, `flac2mp3 ${args.join(" ")}`), 390 | format: "MP3", 391 | bitrate, 392 | }) 393 | } 394 | 395 | const files = [] 396 | for (const t of transcodeTasks) { 397 | const { outDir, doTranscode, message, format, bitrate, skipUpload } = t 398 | console.log(`[-] output dir: ${outDir}`) 399 | try { 400 | await mkdirpMaybe(outDir) 401 | } catch (e) { 402 | if (e.code !== 'EEXIST') { 403 | console.log(`[!] mkdirp(${outDir}): ${e.code}`) 404 | throw e; 405 | } 406 | console.log(`[!] ${outDir} already exists! Delete or rename and re-run!`) 407 | continue 408 | } 409 | 410 | await doTranscode() 411 | await copyOtherFiles(outDir, FLAC_DIR, torrent.media) 412 | const torrentBuffer = Buffer.from( 413 | await createTorrent(outDir, { 414 | private: true, 415 | createdBy: SCRIPT_NAME, 416 | announce, 417 | info: { source: "RED" }, 418 | }), 419 | ) 420 | if (skipUpload) continue 421 | 422 | files.push({ 423 | fileName: `${path.basename(outDir)}.torrent`, 424 | postData: { 425 | format, 426 | bitrate, 427 | release_desc: message, 428 | file_input: torrentBuffer, 429 | }, 430 | }) 431 | } 432 | 433 | if (files.length === 0) { 434 | console.log("[-] No torrents made, nothing to upload") 435 | return 436 | } 437 | 438 | const uploadOpts = { 439 | unknown: false, // can this be true? 440 | scene: false, 441 | groupid: group.id, 442 | remaster_year: torrent.remasterYear, 443 | remaster_title: torrent.remasterTitle, 444 | remaster_record_label: torrent.remasterRecordLabel, 445 | remaster_catalogue_number: torrent.remasterCatalogueNumber, 446 | media: torrent.media, 447 | ...files[0].postData, 448 | } 449 | 450 | if (files[1]) { 451 | const { file_input, bitrate, format, release_desc } = files[1].postData 452 | uploadOpts.extra_file_1 = file_input 453 | uploadOpts.extra_format = [format] 454 | uploadOpts.extra_bitrate = [bitrate] 455 | uploadOpts.extra_release_desc = [release_desc] 456 | } 457 | 458 | if (files[2]) { 459 | const { file_input, bitrate, format, release_desc } = files[2].postData 460 | uploadOpts.extra_file_2 = file_input 461 | uploadOpts.extra_format.push(format) 462 | uploadOpts.extra_bitrate.push(bitrate) 463 | uploadOpts.extra_release_desc.push(release_desc) 464 | } 465 | 466 | // yargs does magic and inverts --no-upload.. 467 | console.log("[-] Uploading...") 468 | await redAPI.upload(uploadOpts) 469 | 470 | files.forEach(({ fileName }) => { 471 | console.log(`[-] Write torrents to ${TORRENT_DIR}/${fileName}`) 472 | }) 473 | await Promise.all( 474 | files.map(({ fileName, postData }) => 475 | fs.writeFile(path.join(TORRENT_DIR, fileName), postData.file_input), 476 | ), 477 | ) 478 | console.log("[*] Done!") 479 | } 480 | 481 | ;(async () => { 482 | await main().catch((err) => { 483 | console.error(`${SCRIPT_NAME} failed`) 484 | console.error(err.message) 485 | verboseLog(err) 486 | getEnv("DEBUG") || console.error("Use 'DEBUG=trul:cli' for verbose logs") 487 | }) 488 | })() 489 | --------------------------------------------------------------------------------