├── .github └── workflows │ └── docs.yml ├── .gitignore ├── .prettierrc ├── .vscode └── settings.json ├── LICENSE ├── README.md ├── benchmarks ├── package-lock.json ├── package.json ├── src │ ├── benchmarker.ts │ ├── index.ts │ ├── mocked.ts │ └── packages │ │ ├── youtube-ext.ts │ │ ├── youtube-sr.ts │ │ ├── ytdl-core.ts │ │ ├── ytpl.ts │ │ └── ytsr.ts └── tsconfig.json ├── examples ├── channelInfo.ts ├── extractStreamInfo.ts ├── playlistInfo.ts ├── search.ts ├── stream.ts └── videoInfo.ts ├── package-lock.json ├── package.json ├── scripts └── docs.ts ├── src ├── channelInfo.ts ├── cookies.ts ├── extractStreamInfo.ts ├── getFormats.ts ├── getReadableStream.ts ├── index.ts ├── playlistInfo.ts ├── search.ts ├── utils │ ├── common.ts │ ├── constants.ts │ ├── index.ts │ ├── undici.ts │ └── youtube.ts └── videoInfo.ts ├── tsconfig.json └── typedoc.json /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | name: Documentation 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | paths: 8 | - src/** 9 | - README.md 10 | jobs: 11 | docs: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v4 15 | 16 | - uses: actions/setup-node@v4 17 | with: 18 | node-version: 20.x 19 | cache: npm 20 | 21 | - name: 🚧 Install dependencies 22 | run: npm install 23 | 24 | - name: 👷 Build site 25 | run: npm run docs 26 | 27 | - name: 🚀 Deploy 28 | uses: zyrouge/gh-push-action@v1 29 | with: 30 | branch: gh-pages 31 | directory: docs-dist 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | docs-dist 4 | *.tgz 5 | *.mp* 6 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 4, 3 | "useTabs": false, 4 | "singleQuote": false 5 | } 6 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.formatOnSave": true, 3 | "editor.defaultFormatter": "esbenp.prettier-vscode" 4 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 ZYROUGE 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # YouTube Extractor 2 | 3 | A simple [YouTube](https://youtube.com) scraper. 4 | 5 | [![Documentation](https://github.com/zyrouge/node-youtube-ext/actions/workflows/docs.yml/badge.svg)](https://github.com/zyrouge/node-youtube-ext/actions/workflows/docs.yml) 6 | 7 | > ⚠️ YouTube stream data is decoded by evaluating arbitrary JavaScript code. By default, youtube-ext uses `eval` or `node:vm`. Please install [isolated-vm](https://www.npmjs.com/package/isolated-vm) or [@ohmyvm/vm](https://www.npmjs.com/package/@ohmyvm/vm) to prevent security issues. 8 | 9 | ## Features 10 | 11 | - Faster and Better! ([comparison](https://runkit.com/zyrouge/606dd634af4a29001a4be694)) 12 | - Supports YouTube stream generation. 13 | - Supports YouTube search. 14 | - Supports YouTube video information. 15 | - Supports YouTube playlist information. 16 | - Supports YouTube channel information. 17 | - No key required! 18 | 19 | ## Installation 20 | 21 | ```bash 22 | npm install youtube-ext 23 | ``` 24 | 25 | ## Usage 26 | 27 | ```ts 28 | const ytext = require("youtube-ext"); 29 | // or 30 | import ytext from "youtube-ext"; 31 | // or 32 | import { ... } from "youtube-ext"; 33 | ``` 34 | 35 | Examples can be found [here](./examples)! 36 | 37 | ## Links 38 | 39 | - [Documentation](https://youtube-ext.js.org) 40 | - [NPM](https://npmjs.com/package/youtube-ext) 41 | - [GitHub](https://github.com/zyrouge/node-youtube-ext) 42 | 43 | ## Similar Packages 44 | 45 | - [youtube-dl](https://www.npmjs.com/package/youtube-dl) (Faster and better search and info scraping) 46 | - [ytdl-core](https://www.npmjs.com/package/ytdl-core) (YouTube Downloader) 47 | - [discord-player](https://www.npmjs.com/package/discord-player) (Discord music framework) 48 | - [discord-ytdl-core](https://www.npmjs.com/package/discord-ytdl-core) (Ytdl-core with ffmpeg args support) 49 | 50 | ## License 51 | 52 | [MIT](./LICENSE) 53 | -------------------------------------------------------------------------------- /benchmarks/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "youtube-ext-benchmarks", 3 | "version": "0.0.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "youtube-ext-benchmarks", 9 | "version": "0.0.0", 10 | "license": "ISC", 11 | "dependencies": { 12 | "youtube-ext": "^1.1.12", 13 | "youtube-sr": "^4.3.4", 14 | "ytdl-core": "^4.11.5", 15 | "ytpl": "^2.3.0", 16 | "ytsr": "^3.8.4" 17 | }, 18 | "devDependencies": { 19 | "tsx": "^3.12.8", 20 | "typescript": "^5.2.2" 21 | } 22 | }, 23 | "node_modules/@esbuild-kit/cjs-loader": { 24 | "version": "2.4.2", 25 | "resolved": "https://registry.npmjs.org/@esbuild-kit/cjs-loader/-/cjs-loader-2.4.2.tgz", 26 | "integrity": "sha512-BDXFbYOJzT/NBEtp71cvsrGPwGAMGRB/349rwKuoxNSiKjPraNNnlK6MIIabViCjqZugu6j+xeMDlEkWdHHJSg==", 27 | "dev": true, 28 | "dependencies": { 29 | "@esbuild-kit/core-utils": "^3.0.0", 30 | "get-tsconfig": "^4.4.0" 31 | } 32 | }, 33 | "node_modules/@esbuild-kit/core-utils": { 34 | "version": "3.2.2", 35 | "resolved": "https://registry.npmjs.org/@esbuild-kit/core-utils/-/core-utils-3.2.2.tgz", 36 | "integrity": "sha512-Ub6LaRaAgF80dTSzUdXpFLM1pVDdmEVB9qb5iAzSpyDlX/mfJTFGOnZ516O05p5uWWteNviMKi4PAyEuRxI5gA==", 37 | "dev": true, 38 | "dependencies": { 39 | "esbuild": "~0.18.20", 40 | "source-map-support": "^0.5.21" 41 | } 42 | }, 43 | "node_modules/@esbuild-kit/esm-loader": { 44 | "version": "2.5.5", 45 | "resolved": "https://registry.npmjs.org/@esbuild-kit/esm-loader/-/esm-loader-2.5.5.tgz", 46 | "integrity": "sha512-Qwfvj/qoPbClxCRNuac1Du01r9gvNOT+pMYtJDapfB1eoGN1YlJ1BixLyL9WVENRx5RXgNLdfYdx/CuswlGhMw==", 47 | "dev": true, 48 | "dependencies": { 49 | "@esbuild-kit/core-utils": "^3.0.0", 50 | "get-tsconfig": "^4.4.0" 51 | } 52 | }, 53 | "node_modules/@esbuild/android-arm": { 54 | "version": "0.18.20", 55 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz", 56 | "integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==", 57 | "cpu": [ 58 | "arm" 59 | ], 60 | "dev": true, 61 | "optional": true, 62 | "os": [ 63 | "android" 64 | ], 65 | "engines": { 66 | "node": ">=12" 67 | } 68 | }, 69 | "node_modules/@esbuild/android-arm64": { 70 | "version": "0.18.20", 71 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz", 72 | "integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==", 73 | "cpu": [ 74 | "arm64" 75 | ], 76 | "dev": true, 77 | "optional": true, 78 | "os": [ 79 | "android" 80 | ], 81 | "engines": { 82 | "node": ">=12" 83 | } 84 | }, 85 | "node_modules/@esbuild/android-x64": { 86 | "version": "0.18.20", 87 | "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.20.tgz", 88 | "integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==", 89 | "cpu": [ 90 | "x64" 91 | ], 92 | "dev": true, 93 | "optional": true, 94 | "os": [ 95 | "android" 96 | ], 97 | "engines": { 98 | "node": ">=12" 99 | } 100 | }, 101 | "node_modules/@esbuild/darwin-arm64": { 102 | "version": "0.18.20", 103 | "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz", 104 | "integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==", 105 | "cpu": [ 106 | "arm64" 107 | ], 108 | "dev": true, 109 | "optional": true, 110 | "os": [ 111 | "darwin" 112 | ], 113 | "engines": { 114 | "node": ">=12" 115 | } 116 | }, 117 | "node_modules/@esbuild/darwin-x64": { 118 | "version": "0.18.20", 119 | "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz", 120 | "integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==", 121 | "cpu": [ 122 | "x64" 123 | ], 124 | "dev": true, 125 | "optional": true, 126 | "os": [ 127 | "darwin" 128 | ], 129 | "engines": { 130 | "node": ">=12" 131 | } 132 | }, 133 | "node_modules/@esbuild/freebsd-arm64": { 134 | "version": "0.18.20", 135 | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz", 136 | "integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==", 137 | "cpu": [ 138 | "arm64" 139 | ], 140 | "dev": true, 141 | "optional": true, 142 | "os": [ 143 | "freebsd" 144 | ], 145 | "engines": { 146 | "node": ">=12" 147 | } 148 | }, 149 | "node_modules/@esbuild/freebsd-x64": { 150 | "version": "0.18.20", 151 | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz", 152 | "integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==", 153 | "cpu": [ 154 | "x64" 155 | ], 156 | "dev": true, 157 | "optional": true, 158 | "os": [ 159 | "freebsd" 160 | ], 161 | "engines": { 162 | "node": ">=12" 163 | } 164 | }, 165 | "node_modules/@esbuild/linux-arm": { 166 | "version": "0.18.20", 167 | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz", 168 | "integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==", 169 | "cpu": [ 170 | "arm" 171 | ], 172 | "dev": true, 173 | "optional": true, 174 | "os": [ 175 | "linux" 176 | ], 177 | "engines": { 178 | "node": ">=12" 179 | } 180 | }, 181 | "node_modules/@esbuild/linux-arm64": { 182 | "version": "0.18.20", 183 | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz", 184 | "integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==", 185 | "cpu": [ 186 | "arm64" 187 | ], 188 | "dev": true, 189 | "optional": true, 190 | "os": [ 191 | "linux" 192 | ], 193 | "engines": { 194 | "node": ">=12" 195 | } 196 | }, 197 | "node_modules/@esbuild/linux-ia32": { 198 | "version": "0.18.20", 199 | "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz", 200 | "integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==", 201 | "cpu": [ 202 | "ia32" 203 | ], 204 | "dev": true, 205 | "optional": true, 206 | "os": [ 207 | "linux" 208 | ], 209 | "engines": { 210 | "node": ">=12" 211 | } 212 | }, 213 | "node_modules/@esbuild/linux-loong64": { 214 | "version": "0.18.20", 215 | "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz", 216 | "integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==", 217 | "cpu": [ 218 | "loong64" 219 | ], 220 | "dev": true, 221 | "optional": true, 222 | "os": [ 223 | "linux" 224 | ], 225 | "engines": { 226 | "node": ">=12" 227 | } 228 | }, 229 | "node_modules/@esbuild/linux-mips64el": { 230 | "version": "0.18.20", 231 | "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz", 232 | "integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==", 233 | "cpu": [ 234 | "mips64el" 235 | ], 236 | "dev": true, 237 | "optional": true, 238 | "os": [ 239 | "linux" 240 | ], 241 | "engines": { 242 | "node": ">=12" 243 | } 244 | }, 245 | "node_modules/@esbuild/linux-ppc64": { 246 | "version": "0.18.20", 247 | "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz", 248 | "integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==", 249 | "cpu": [ 250 | "ppc64" 251 | ], 252 | "dev": true, 253 | "optional": true, 254 | "os": [ 255 | "linux" 256 | ], 257 | "engines": { 258 | "node": ">=12" 259 | } 260 | }, 261 | "node_modules/@esbuild/linux-riscv64": { 262 | "version": "0.18.20", 263 | "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz", 264 | "integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==", 265 | "cpu": [ 266 | "riscv64" 267 | ], 268 | "dev": true, 269 | "optional": true, 270 | "os": [ 271 | "linux" 272 | ], 273 | "engines": { 274 | "node": ">=12" 275 | } 276 | }, 277 | "node_modules/@esbuild/linux-s390x": { 278 | "version": "0.18.20", 279 | "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz", 280 | "integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==", 281 | "cpu": [ 282 | "s390x" 283 | ], 284 | "dev": true, 285 | "optional": true, 286 | "os": [ 287 | "linux" 288 | ], 289 | "engines": { 290 | "node": ">=12" 291 | } 292 | }, 293 | "node_modules/@esbuild/linux-x64": { 294 | "version": "0.18.20", 295 | "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz", 296 | "integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==", 297 | "cpu": [ 298 | "x64" 299 | ], 300 | "dev": true, 301 | "optional": true, 302 | "os": [ 303 | "linux" 304 | ], 305 | "engines": { 306 | "node": ">=12" 307 | } 308 | }, 309 | "node_modules/@esbuild/netbsd-x64": { 310 | "version": "0.18.20", 311 | "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz", 312 | "integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==", 313 | "cpu": [ 314 | "x64" 315 | ], 316 | "dev": true, 317 | "optional": true, 318 | "os": [ 319 | "netbsd" 320 | ], 321 | "engines": { 322 | "node": ">=12" 323 | } 324 | }, 325 | "node_modules/@esbuild/openbsd-x64": { 326 | "version": "0.18.20", 327 | "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz", 328 | "integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==", 329 | "cpu": [ 330 | "x64" 331 | ], 332 | "dev": true, 333 | "optional": true, 334 | "os": [ 335 | "openbsd" 336 | ], 337 | "engines": { 338 | "node": ">=12" 339 | } 340 | }, 341 | "node_modules/@esbuild/sunos-x64": { 342 | "version": "0.18.20", 343 | "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz", 344 | "integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==", 345 | "cpu": [ 346 | "x64" 347 | ], 348 | "dev": true, 349 | "optional": true, 350 | "os": [ 351 | "sunos" 352 | ], 353 | "engines": { 354 | "node": ">=12" 355 | } 356 | }, 357 | "node_modules/@esbuild/win32-arm64": { 358 | "version": "0.18.20", 359 | "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz", 360 | "integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==", 361 | "cpu": [ 362 | "arm64" 363 | ], 364 | "dev": true, 365 | "optional": true, 366 | "os": [ 367 | "win32" 368 | ], 369 | "engines": { 370 | "node": ">=12" 371 | } 372 | }, 373 | "node_modules/@esbuild/win32-ia32": { 374 | "version": "0.18.20", 375 | "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz", 376 | "integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==", 377 | "cpu": [ 378 | "ia32" 379 | ], 380 | "dev": true, 381 | "optional": true, 382 | "os": [ 383 | "win32" 384 | ], 385 | "engines": { 386 | "node": ">=12" 387 | } 388 | }, 389 | "node_modules/@esbuild/win32-x64": { 390 | "version": "0.18.20", 391 | "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz", 392 | "integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==", 393 | "cpu": [ 394 | "x64" 395 | ], 396 | "dev": true, 397 | "optional": true, 398 | "os": [ 399 | "win32" 400 | ], 401 | "engines": { 402 | "node": ">=12" 403 | } 404 | }, 405 | "node_modules/asynckit": { 406 | "version": "0.4.0", 407 | "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", 408 | "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" 409 | }, 410 | "node_modules/axios": { 411 | "version": "1.4.0", 412 | "resolved": "https://registry.npmjs.org/axios/-/axios-1.4.0.tgz", 413 | "integrity": "sha512-S4XCWMEmzvo64T9GfvQDOXgYRDJ/wsSZc7Jvdgx5u1sd0JwsuPLqb3SYmusag+edF6ziyMensPVqLTSc1PiSEA==", 414 | "dependencies": { 415 | "follow-redirects": "^1.15.0", 416 | "form-data": "^4.0.0", 417 | "proxy-from-env": "^1.1.0" 418 | } 419 | }, 420 | "node_modules/buffer-from": { 421 | "version": "1.1.2", 422 | "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", 423 | "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", 424 | "dev": true 425 | }, 426 | "node_modules/combined-stream": { 427 | "version": "1.0.8", 428 | "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", 429 | "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", 430 | "dependencies": { 431 | "delayed-stream": "~1.0.0" 432 | }, 433 | "engines": { 434 | "node": ">= 0.8" 435 | } 436 | }, 437 | "node_modules/delayed-stream": { 438 | "version": "1.0.0", 439 | "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", 440 | "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", 441 | "engines": { 442 | "node": ">=0.4.0" 443 | } 444 | }, 445 | "node_modules/esbuild": { 446 | "version": "0.18.20", 447 | "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz", 448 | "integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==", 449 | "dev": true, 450 | "hasInstallScript": true, 451 | "bin": { 452 | "esbuild": "bin/esbuild" 453 | }, 454 | "engines": { 455 | "node": ">=12" 456 | }, 457 | "optionalDependencies": { 458 | "@esbuild/android-arm": "0.18.20", 459 | "@esbuild/android-arm64": "0.18.20", 460 | "@esbuild/android-x64": "0.18.20", 461 | "@esbuild/darwin-arm64": "0.18.20", 462 | "@esbuild/darwin-x64": "0.18.20", 463 | "@esbuild/freebsd-arm64": "0.18.20", 464 | "@esbuild/freebsd-x64": "0.18.20", 465 | "@esbuild/linux-arm": "0.18.20", 466 | "@esbuild/linux-arm64": "0.18.20", 467 | "@esbuild/linux-ia32": "0.18.20", 468 | "@esbuild/linux-loong64": "0.18.20", 469 | "@esbuild/linux-mips64el": "0.18.20", 470 | "@esbuild/linux-ppc64": "0.18.20", 471 | "@esbuild/linux-riscv64": "0.18.20", 472 | "@esbuild/linux-s390x": "0.18.20", 473 | "@esbuild/linux-x64": "0.18.20", 474 | "@esbuild/netbsd-x64": "0.18.20", 475 | "@esbuild/openbsd-x64": "0.18.20", 476 | "@esbuild/sunos-x64": "0.18.20", 477 | "@esbuild/win32-arm64": "0.18.20", 478 | "@esbuild/win32-ia32": "0.18.20", 479 | "@esbuild/win32-x64": "0.18.20" 480 | } 481 | }, 482 | "node_modules/follow-redirects": { 483 | "version": "1.15.2", 484 | "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", 485 | "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", 486 | "funding": [ 487 | { 488 | "type": "individual", 489 | "url": "https://github.com/sponsors/RubenVerborgh" 490 | } 491 | ], 492 | "engines": { 493 | "node": ">=4.0" 494 | }, 495 | "peerDependenciesMeta": { 496 | "debug": { 497 | "optional": true 498 | } 499 | } 500 | }, 501 | "node_modules/form-data": { 502 | "version": "4.0.0", 503 | "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", 504 | "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", 505 | "dependencies": { 506 | "asynckit": "^0.4.0", 507 | "combined-stream": "^1.0.8", 508 | "mime-types": "^2.1.12" 509 | }, 510 | "engines": { 511 | "node": ">= 6" 512 | } 513 | }, 514 | "node_modules/fsevents": { 515 | "version": "2.3.3", 516 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", 517 | "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", 518 | "dev": true, 519 | "hasInstallScript": true, 520 | "optional": true, 521 | "os": [ 522 | "darwin" 523 | ], 524 | "engines": { 525 | "node": "^8.16.0 || ^10.6.0 || >=11.0.0" 526 | } 527 | }, 528 | "node_modules/get-tsconfig": { 529 | "version": "4.7.0", 530 | "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.7.0.tgz", 531 | "integrity": "sha512-pmjiZ7xtB8URYm74PlGJozDNyhvsVLUcpBa8DZBG3bWHwaHa9bPiRpiSfovw+fjhwONSCWKRyk+JQHEGZmMrzw==", 532 | "dev": true, 533 | "dependencies": { 534 | "resolve-pkg-maps": "^1.0.0" 535 | }, 536 | "funding": { 537 | "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" 538 | } 539 | }, 540 | "node_modules/m3u8stream": { 541 | "version": "0.8.6", 542 | "resolved": "https://registry.npmjs.org/m3u8stream/-/m3u8stream-0.8.6.tgz", 543 | "integrity": "sha512-LZj8kIVf9KCphiHmH7sbFQTVe4tOemb202fWwvJwR9W5ENW/1hxJN6ksAWGhQgSBSa3jyWhnjKU1Fw1GaOdbyA==", 544 | "dependencies": { 545 | "miniget": "^4.2.2", 546 | "sax": "^1.2.4" 547 | }, 548 | "engines": { 549 | "node": ">=12" 550 | } 551 | }, 552 | "node_modules/mime-db": { 553 | "version": "1.52.0", 554 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", 555 | "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", 556 | "engines": { 557 | "node": ">= 0.6" 558 | } 559 | }, 560 | "node_modules/mime-types": { 561 | "version": "2.1.35", 562 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", 563 | "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", 564 | "dependencies": { 565 | "mime-db": "1.52.0" 566 | }, 567 | "engines": { 568 | "node": ">= 0.6" 569 | } 570 | }, 571 | "node_modules/miniget": { 572 | "version": "4.2.3", 573 | "resolved": "https://registry.npmjs.org/miniget/-/miniget-4.2.3.tgz", 574 | "integrity": "sha512-SjbDPDICJ1zT+ZvQwK0hUcRY4wxlhhNpHL9nJOB2MEAXRGagTljsO8MEDzQMTFf0Q8g4QNi8P9lEm/g7e+qgzA==", 575 | "engines": { 576 | "node": ">=12" 577 | } 578 | }, 579 | "node_modules/proxy-from-env": { 580 | "version": "1.1.0", 581 | "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", 582 | "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" 583 | }, 584 | "node_modules/resolve-pkg-maps": { 585 | "version": "1.0.0", 586 | "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", 587 | "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", 588 | "dev": true, 589 | "funding": { 590 | "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" 591 | } 592 | }, 593 | "node_modules/sax": { 594 | "version": "1.2.4", 595 | "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", 596 | "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" 597 | }, 598 | "node_modules/source-map": { 599 | "version": "0.6.1", 600 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", 601 | "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", 602 | "dev": true, 603 | "engines": { 604 | "node": ">=0.10.0" 605 | } 606 | }, 607 | "node_modules/source-map-support": { 608 | "version": "0.5.21", 609 | "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", 610 | "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", 611 | "dev": true, 612 | "dependencies": { 613 | "buffer-from": "^1.0.0", 614 | "source-map": "^0.6.0" 615 | } 616 | }, 617 | "node_modules/tsx": { 618 | "version": "3.12.8", 619 | "resolved": "https://registry.npmjs.org/tsx/-/tsx-3.12.8.tgz", 620 | "integrity": "sha512-Lt9KYaRGF023tlLInPj8rgHwsZU8qWLBj4iRXNWxTfjIkU7canGL806AqKear1j722plHuiYNcL2ZCo6uS9UJA==", 621 | "dev": true, 622 | "dependencies": { 623 | "@esbuild-kit/cjs-loader": "^2.4.2", 624 | "@esbuild-kit/core-utils": "^3.2.2", 625 | "@esbuild-kit/esm-loader": "^2.5.5" 626 | }, 627 | "bin": { 628 | "tsx": "dist/cli.js" 629 | }, 630 | "optionalDependencies": { 631 | "fsevents": "~2.3.2" 632 | } 633 | }, 634 | "node_modules/typescript": { 635 | "version": "5.2.2", 636 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", 637 | "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", 638 | "dev": true, 639 | "bin": { 640 | "tsc": "bin/tsc", 641 | "tsserver": "bin/tsserver" 642 | }, 643 | "engines": { 644 | "node": ">=14.17" 645 | } 646 | }, 647 | "node_modules/youtube-ext": { 648 | "version": "1.1.12", 649 | "resolved": "https://registry.npmjs.org/youtube-ext/-/youtube-ext-1.1.12.tgz", 650 | "integrity": "sha512-spIjdupBuqcZ/ziuAt3XgWh75t7q80BY+gkGVtrCy3dOnrkN4VOvSoLzN/17PbWUcsygVIP99XQ03EAd+Q10QQ==", 651 | "dependencies": { 652 | "axios": "^1.4.0" 653 | } 654 | }, 655 | "node_modules/youtube-sr": { 656 | "version": "4.3.4", 657 | "resolved": "https://registry.npmjs.org/youtube-sr/-/youtube-sr-4.3.4.tgz", 658 | "integrity": "sha512-olSYcR80XigutCrePEXBX3/RJJrWfonJQj7+/ggBiWU0CzTDLE1q8+lpWTWCG0JpzhzILp/IB/Bq/glGqqr1TQ==" 659 | }, 660 | "node_modules/ytdl-core": { 661 | "version": "4.11.5", 662 | "resolved": "https://registry.npmjs.org/ytdl-core/-/ytdl-core-4.11.5.tgz", 663 | "integrity": "sha512-27LwsW4n4nyNviRCO1hmr8Wr5J1wLLMawHCQvH8Fk0hiRqrxuIu028WzbJetiYH28K8XDbeinYW4/wcHQD1EXA==", 664 | "dependencies": { 665 | "m3u8stream": "^0.8.6", 666 | "miniget": "^4.2.2", 667 | "sax": "^1.1.3" 668 | }, 669 | "engines": { 670 | "node": ">=12" 671 | } 672 | }, 673 | "node_modules/ytpl": { 674 | "version": "2.3.0", 675 | "resolved": "https://registry.npmjs.org/ytpl/-/ytpl-2.3.0.tgz", 676 | "integrity": "sha512-Cfw2rxq3PFK6qgWr2Z8gsRefVahEzbn9XEuiJldqdXHE6GhO7kTfEvbZKdfXing1SmgW635uJ/UL2g8r0fvu2Q==", 677 | "dependencies": { 678 | "miniget": "^4.2.2" 679 | }, 680 | "engines": { 681 | "node": ">=8" 682 | } 683 | }, 684 | "node_modules/ytsr": { 685 | "version": "3.8.4", 686 | "resolved": "https://registry.npmjs.org/ytsr/-/ytsr-3.8.4.tgz", 687 | "integrity": "sha512-rrJo59vDDf98mz/Cuw7Y2YiuTwSm3cs4XsXrP6yjYDXYup/aE0lRxY6XMKR3mGOHKwgLouZqFq8QRllVVVN88w==", 688 | "dependencies": { 689 | "miniget": "^4.2.2" 690 | }, 691 | "engines": { 692 | "node": ">=8" 693 | } 694 | } 695 | } 696 | } 697 | -------------------------------------------------------------------------------- /benchmarks/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "youtube-ext-benchmarks", 3 | "private": true, 4 | "version": "0.0.0", 5 | "description": "", 6 | "scripts": { 7 | "start": "tsx ./src/index.ts" 8 | }, 9 | "keywords": [], 10 | "author": "Zyrouge", 11 | "license": "ISC", 12 | "devDependencies": { 13 | "tsx": "^3.12.8", 14 | "typescript": "^5.2.2" 15 | }, 16 | "dependencies": { 17 | "youtube-ext": "^1.1.12", 18 | "youtube-sr": "^4.3.4", 19 | "ytdl-core": "^4.11.5", 20 | "ytpl": "^2.3.0", 21 | "ytsr": "^3.8.4" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /benchmarks/src/benchmarker.ts: -------------------------------------------------------------------------------- 1 | export interface BenchmarkTaskResult { 2 | success: boolean; 3 | time: number; 4 | } 5 | 6 | export const BenchmarkTaskNames = [ 7 | "search", 8 | "video", 9 | "playlist", 10 | "channel", 11 | "download", 12 | ] as const; 13 | 14 | export type BenchmarkTaskNameType = (typeof BenchmarkTaskNames)[number]; 15 | 16 | export type BenchmarkTasks = Partial< 17 | Record Promise> 18 | >; 19 | 20 | export type BenchmarkTasksResult = Partial< 21 | Record 22 | >; 23 | 24 | export interface Benchmarkable { 25 | name: string; 26 | tasks: BenchmarkTasks; 27 | } 28 | 29 | export const benchmark = async ( 30 | name: string, 31 | tasks: BenchmarkTasks 32 | ): Promise => { 33 | const result: BenchmarkTasksResult = {}; 34 | for (const x of BenchmarkTaskNames) { 35 | const fn = tasks[x]; 36 | if (!fn) continue; 37 | let success: boolean; 38 | let time: number; 39 | const started = Date.now(); 40 | let error; 41 | try { 42 | await fn(); 43 | time = Date.now() - started; 44 | success = true; 45 | } catch (err) { 46 | time = Date.now() - started; 47 | success = false; 48 | error = err; 49 | } 50 | console.log(`* ${name}/${x}: ${success ? "Pass" : "Fail"} (${time}ms)`); 51 | if (error) { 52 | console.error(` ${name}/${x}: ${error}`); 53 | } 54 | result[x] = { success, time }; 55 | } 56 | return result; 57 | }; 58 | -------------------------------------------------------------------------------- /benchmarks/src/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | BenchmarkTaskNameType, 3 | BenchmarkTaskResult, 4 | BenchmarkTasks, 5 | benchmark, 6 | } from "./benchmarker"; 7 | import * as ytext from "./packages/youtube-ext"; 8 | import * as youtubeSr from "./packages/youtube-sr"; 9 | import * as ytdl from "./packages/ytdl-core"; 10 | import * as ytsr from "./packages/ytsr"; 11 | import * as ytpl from "./packages/ytpl"; 12 | 13 | export const start = async () => { 14 | const packages: Record = { 15 | [ytext.name]: ytext.tasks, 16 | [youtubeSr.name]: youtubeSr.tasks, 17 | [ytdl.name]: ytdl.tasks, 18 | [ytsr.name]: ytsr.tasks, 19 | [ytpl.name]: ytpl.tasks, 20 | }; 21 | const results: Partial< 22 | Record< 23 | BenchmarkTaskNameType, 24 | { 25 | name: string; 26 | result: BenchmarkTaskResult; 27 | }[] 28 | > 29 | > = {}; 30 | console.log("Testing:"); 31 | for (const [name, tasks] of recordEntries(packages)) { 32 | const result = await benchmark(name, tasks); 33 | for (const [x, y] of partialRecordEntries(result)) { 34 | if (!y.success) continue; 35 | results[x] ??= []; 36 | results[x]!.push({ 37 | name, 38 | result: y, 39 | }); 40 | } 41 | } 42 | console.log(); 43 | console.log("Results:"); 44 | for (const [k, x] of partialRecordEntries(results)) { 45 | const sorted = x.sort((a, b) => a.result.time - b.result.time); 46 | console.log( 47 | ` * ${k}: ${sorted 48 | .map((x) => `${x.name} (${x.result.time}ms)`) 49 | .join(" < ")}` 50 | ); 51 | } 52 | }; 53 | 54 | start(); 55 | 56 | function recordEntries( 57 | record: Record 58 | ) { 59 | return Object.entries(record) as [K, V][]; 60 | } 61 | 62 | function partialRecordEntries( 63 | record: Partial> 64 | ) { 65 | return Object.entries(record) as [K, V][]; 66 | } 67 | -------------------------------------------------------------------------------- /benchmarks/src/mocked.ts: -------------------------------------------------------------------------------- 1 | import { Readable } from "stream"; 2 | 3 | export class MockQueries { 4 | static searchText = "faded"; 5 | 6 | static videoID = "Sn1rJbZ8nI4"; 7 | static videoURL = `https://www.youtube.com/watch?v=${this.videoID}`; 8 | 9 | static playlistID = "PL8F6B0753B2CCA128"; 10 | static playlistURL = `https://www.youtube.com/playlist?list=${this.playlistID}`; 11 | 12 | static channelID = "UCBUK-I-ILqsQoqIe8i6zrVg"; 13 | static channelURL = `https://www.youtube.com/channel/${this.channelID}`; 14 | } 15 | 16 | export const consumeStream = async (stream: Readable) => { 17 | return new Promise((resolve, reject) => { 18 | stream.on("data", () => {}); 19 | stream.on("error", (err) => { 20 | reject(err); 21 | }); 22 | stream.on("end", () => { 23 | resolve(); 24 | }); 25 | }); 26 | }; 27 | -------------------------------------------------------------------------------- /benchmarks/src/packages/youtube-ext.ts: -------------------------------------------------------------------------------- 1 | import * as ytext from "youtube-ext"; 2 | import { BenchmarkTasks } from "../benchmarker"; 3 | import { MockQueries, consumeStream } from "../mocked"; 4 | 5 | export const name = "youtube-ext"; 6 | 7 | export const tasks: BenchmarkTasks = { 8 | search: async () => { 9 | await ytext.search(MockQueries.searchText); 10 | }, 11 | video: async () => { 12 | await ytext.videoInfo(MockQueries.videoURL); 13 | }, 14 | playlist: async () => { 15 | await ytext.playlistInfo(MockQueries.playlistURL); 16 | }, 17 | channel: async () => { 18 | await ytext.channelInfo(MockQueries.channelURL); 19 | }, 20 | download: async () => { 21 | const info = await ytext.extractStreamInfo(MockQueries.videoURL); 22 | const formats = await ytext.getFormats(info); 23 | const format = formats.find((x) => x.itag === 18); 24 | if (!format) { 25 | throw new Error("No suitable video format"); 26 | } 27 | const stream = await ytext.getReadableStream(format, { 28 | requestOptions: {}, 29 | }); 30 | await consumeStream(stream); 31 | }, 32 | }; 33 | -------------------------------------------------------------------------------- /benchmarks/src/packages/youtube-sr.ts: -------------------------------------------------------------------------------- 1 | import youtubeSr from "youtube-sr"; 2 | import { BenchmarkTasks } from "../benchmarker"; 3 | import { MockQueries } from "../mocked"; 4 | 5 | export const name = "youtube-sr"; 6 | 7 | export const tasks: BenchmarkTasks = { 8 | search: async () => { 9 | await youtubeSr.search(MockQueries.searchText); 10 | }, 11 | video: async () => { 12 | await youtubeSr.getVideo(MockQueries.videoURL); 13 | }, 14 | playlist: async () => { 15 | await youtubeSr.getPlaylist(MockQueries.playlistURL, { 16 | fetchAll: true, 17 | }); 18 | }, 19 | }; 20 | -------------------------------------------------------------------------------- /benchmarks/src/packages/ytdl-core.ts: -------------------------------------------------------------------------------- 1 | import ytdl from "ytdl-core"; 2 | import { BenchmarkTasks } from "../benchmarker"; 3 | import { MockQueries, consumeStream } from "../mocked"; 4 | 5 | export const name = "ytdl-core"; 6 | 7 | export const tasks: BenchmarkTasks = { 8 | video: async () => { 9 | await ytdl.getBasicInfo(MockQueries.videoURL); 10 | }, 11 | download: async () => { 12 | const stream = ytdl(MockQueries.videoURL, { 13 | quality: 18, 14 | }); 15 | await consumeStream(stream); 16 | }, 17 | }; 18 | -------------------------------------------------------------------------------- /benchmarks/src/packages/ytpl.ts: -------------------------------------------------------------------------------- 1 | import ytpl from "ytpl"; 2 | import { BenchmarkTasks } from "../benchmarker"; 3 | import { MockQueries } from "../mocked"; 4 | 5 | export const name = "ytpl"; 6 | 7 | export const tasks: BenchmarkTasks = { 8 | playlist: async () => { 9 | await ytpl(MockQueries.playlistURL, { 10 | limit: Infinity, 11 | }); 12 | }, 13 | }; 14 | -------------------------------------------------------------------------------- /benchmarks/src/packages/ytsr.ts: -------------------------------------------------------------------------------- 1 | import ytsr from "ytsr"; 2 | import { BenchmarkTasks } from "../benchmarker"; 3 | import { MockQueries } from "../mocked"; 4 | 5 | export const name = "ytsr"; 6 | 7 | export const tasks: BenchmarkTasks = { 8 | search: async () => { 9 | await ytsr(MockQueries.searchText, { 10 | limit: 25, 11 | }); 12 | }, 13 | }; 14 | -------------------------------------------------------------------------------- /benchmarks/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "module": "commonjs", 5 | "noEmit": true, 6 | "strict": true, 7 | "noImplicitAny": true, 8 | "strictNullChecks": true, 9 | "strictFunctionTypes": true, 10 | "strictBindCallApply": true, 11 | "strictPropertyInitialization": true, 12 | "noImplicitThis": true, 13 | "alwaysStrict": true, 14 | "noUnusedLocals": true, 15 | "noUnusedParameters": true, 16 | "noImplicitReturns": true, 17 | "noFallthroughCasesInSwitch": true, 18 | "noUncheckedIndexedAccess": true, 19 | "noPropertyAccessFromIndexSignature": true, 20 | "esModuleInterop": true, 21 | "skipLibCheck": true, 22 | "forceConsistentCasingInFileNames": true 23 | }, 24 | "include": ["src"] 25 | } 26 | -------------------------------------------------------------------------------- /examples/channelInfo.ts: -------------------------------------------------------------------------------- 1 | import { channelInfo } from "../src"; 2 | 3 | const query = "https://www.youtube.com/channel/UC_aEa8K-EOJ3D6gOs7HcyNg"; 4 | 5 | const start = async () => { 6 | const result = await channelInfo(query); 7 | console.log(JSON.stringify(result, null, 4)); 8 | }; 9 | 10 | start(); 11 | -------------------------------------------------------------------------------- /examples/extractStreamInfo.ts: -------------------------------------------------------------------------------- 1 | import { extractStreamInfo } from "../src"; 2 | 3 | const query = "https://www.youtube.com/watch?v=Sn1rJbZ8nI4"; 4 | 5 | const start = async () => { 6 | const result = await extractStreamInfo(query); 7 | console.log(JSON.stringify(result, null, 4)); 8 | }; 9 | 10 | start(); 11 | -------------------------------------------------------------------------------- /examples/playlistInfo.ts: -------------------------------------------------------------------------------- 1 | import { playlistInfo } from "../src"; 2 | 3 | const query = 4 | "https://www.youtube.com/playlist?list=PLNKs8mJ6MlqAx7nqsUi6tRJFDFBJxuLiV"; 5 | 6 | // has 100 videos 7 | // const query = "https://www.youtube.com/playlist?list=PL8F6B0753B2CCA128"; 8 | 9 | const start = async () => { 10 | const result = await playlistInfo(query); 11 | console.log(JSON.stringify(result, null, 4)); 12 | }; 13 | 14 | start(); 15 | -------------------------------------------------------------------------------- /examples/search.ts: -------------------------------------------------------------------------------- 1 | import { search } from "../src"; 2 | 3 | const query = "ncs"; 4 | 5 | const start = async () => { 6 | const result = await search(query); 7 | console.log(JSON.stringify(result, null, 4)); 8 | }; 9 | 10 | start(); 11 | -------------------------------------------------------------------------------- /examples/stream.ts: -------------------------------------------------------------------------------- 1 | import fs from "fs"; 2 | import path from "path"; 3 | import { videoInfo, getFormats, getReadableStream } from "../src"; 4 | 5 | const query = "https://www.youtube.com/watch?v=jzJE2ZSH6Dk"; 6 | 7 | const start = async () => { 8 | await new Promise(async (resolve, reject) => { 9 | try { 10 | const info = await videoInfo(query); 11 | const formats = await getFormats(info.stream); 12 | // Dont use this condition for livestreams 13 | const format = formats.find((x) => x.fps && x.audioChannels); 14 | const stream = await getReadableStream(format!); 15 | 16 | const filename = `${info.title} - ${info.channel.name}.mp4`.replace( 17 | /[^(\w|\d|-| |\.)+]/g, 18 | "" 19 | ); 20 | const file = fs.createWriteStream( 21 | path.resolve(__dirname, filename) 22 | ); 23 | const started = Date.now(); 24 | let downloaded = 0; 25 | 26 | file.on("error", console.error); 27 | stream.on("error", console.error); 28 | 29 | stream.on("data", (data: any) => { 30 | downloaded += data.length; 31 | console.log(`Downloaded ${downloaded / 1000}kb`); 32 | }); 33 | stream.pipe(file); 34 | stream.on("close", () => { 35 | console.log( 36 | `Downloaded in ${(Date.now() - started) / 1000} seconds!` 37 | ); 38 | return resolve(); 39 | }); 40 | } catch (err) { 41 | reject(err); 42 | } 43 | }); 44 | }; 45 | 46 | start(); 47 | -------------------------------------------------------------------------------- /examples/videoInfo.ts: -------------------------------------------------------------------------------- 1 | import { videoInfo } from "../src"; 2 | 3 | const query = "https://www.youtube.com/watch?v=iUnobJp3eH0"; 4 | 5 | const start = async () => { 6 | const result = await videoInfo(query); 7 | console.log(JSON.stringify(result, null, 4)); 8 | }; 9 | 10 | start(); 11 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "youtube-ext", 3 | "version": "1.1.25", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "youtube-ext", 9 | "version": "1.1.25", 10 | "license": "MIT", 11 | "dependencies": { 12 | "undici": "^6.11.1" 13 | }, 14 | "devDependencies": { 15 | "@ohmyvm/vm": "^0.1.0", 16 | "@types/node": "^20.12.4", 17 | "isolated-vm": "^4.7.2", 18 | "m3u8stream": "^0.8.6", 19 | "rimraf": "^5.0.5", 20 | "tsx": "^4.7.2", 21 | "typedoc": "^0.25.12", 22 | "typescript": "^5.4.3" 23 | } 24 | }, 25 | "node_modules/@esbuild/aix-ppc64": { 26 | "version": "0.19.12", 27 | "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.19.12.tgz", 28 | "integrity": "sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==", 29 | "cpu": [ 30 | "ppc64" 31 | ], 32 | "dev": true, 33 | "optional": true, 34 | "os": [ 35 | "aix" 36 | ], 37 | "engines": { 38 | "node": ">=12" 39 | } 40 | }, 41 | "node_modules/@esbuild/android-arm": { 42 | "version": "0.19.12", 43 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.12.tgz", 44 | "integrity": "sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==", 45 | "cpu": [ 46 | "arm" 47 | ], 48 | "dev": true, 49 | "optional": true, 50 | "os": [ 51 | "android" 52 | ], 53 | "engines": { 54 | "node": ">=12" 55 | } 56 | }, 57 | "node_modules/@esbuild/android-arm64": { 58 | "version": "0.19.12", 59 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.12.tgz", 60 | "integrity": "sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==", 61 | "cpu": [ 62 | "arm64" 63 | ], 64 | "dev": true, 65 | "optional": true, 66 | "os": [ 67 | "android" 68 | ], 69 | "engines": { 70 | "node": ">=12" 71 | } 72 | }, 73 | "node_modules/@esbuild/android-x64": { 74 | "version": "0.19.12", 75 | "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.12.tgz", 76 | "integrity": "sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==", 77 | "cpu": [ 78 | "x64" 79 | ], 80 | "dev": true, 81 | "optional": true, 82 | "os": [ 83 | "android" 84 | ], 85 | "engines": { 86 | "node": ">=12" 87 | } 88 | }, 89 | "node_modules/@esbuild/darwin-arm64": { 90 | "version": "0.19.12", 91 | "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.12.tgz", 92 | "integrity": "sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==", 93 | "cpu": [ 94 | "arm64" 95 | ], 96 | "dev": true, 97 | "optional": true, 98 | "os": [ 99 | "darwin" 100 | ], 101 | "engines": { 102 | "node": ">=12" 103 | } 104 | }, 105 | "node_modules/@esbuild/darwin-x64": { 106 | "version": "0.19.12", 107 | "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.12.tgz", 108 | "integrity": "sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==", 109 | "cpu": [ 110 | "x64" 111 | ], 112 | "dev": true, 113 | "optional": true, 114 | "os": [ 115 | "darwin" 116 | ], 117 | "engines": { 118 | "node": ">=12" 119 | } 120 | }, 121 | "node_modules/@esbuild/freebsd-arm64": { 122 | "version": "0.19.12", 123 | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.12.tgz", 124 | "integrity": "sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==", 125 | "cpu": [ 126 | "arm64" 127 | ], 128 | "dev": true, 129 | "optional": true, 130 | "os": [ 131 | "freebsd" 132 | ], 133 | "engines": { 134 | "node": ">=12" 135 | } 136 | }, 137 | "node_modules/@esbuild/freebsd-x64": { 138 | "version": "0.19.12", 139 | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.12.tgz", 140 | "integrity": "sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==", 141 | "cpu": [ 142 | "x64" 143 | ], 144 | "dev": true, 145 | "optional": true, 146 | "os": [ 147 | "freebsd" 148 | ], 149 | "engines": { 150 | "node": ">=12" 151 | } 152 | }, 153 | "node_modules/@esbuild/linux-arm": { 154 | "version": "0.19.12", 155 | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.12.tgz", 156 | "integrity": "sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==", 157 | "cpu": [ 158 | "arm" 159 | ], 160 | "dev": true, 161 | "optional": true, 162 | "os": [ 163 | "linux" 164 | ], 165 | "engines": { 166 | "node": ">=12" 167 | } 168 | }, 169 | "node_modules/@esbuild/linux-arm64": { 170 | "version": "0.19.12", 171 | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.12.tgz", 172 | "integrity": "sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==", 173 | "cpu": [ 174 | "arm64" 175 | ], 176 | "dev": true, 177 | "optional": true, 178 | "os": [ 179 | "linux" 180 | ], 181 | "engines": { 182 | "node": ">=12" 183 | } 184 | }, 185 | "node_modules/@esbuild/linux-ia32": { 186 | "version": "0.19.12", 187 | "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.12.tgz", 188 | "integrity": "sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==", 189 | "cpu": [ 190 | "ia32" 191 | ], 192 | "dev": true, 193 | "optional": true, 194 | "os": [ 195 | "linux" 196 | ], 197 | "engines": { 198 | "node": ">=12" 199 | } 200 | }, 201 | "node_modules/@esbuild/linux-loong64": { 202 | "version": "0.19.12", 203 | "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.12.tgz", 204 | "integrity": "sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==", 205 | "cpu": [ 206 | "loong64" 207 | ], 208 | "dev": true, 209 | "optional": true, 210 | "os": [ 211 | "linux" 212 | ], 213 | "engines": { 214 | "node": ">=12" 215 | } 216 | }, 217 | "node_modules/@esbuild/linux-mips64el": { 218 | "version": "0.19.12", 219 | "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.12.tgz", 220 | "integrity": "sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==", 221 | "cpu": [ 222 | "mips64el" 223 | ], 224 | "dev": true, 225 | "optional": true, 226 | "os": [ 227 | "linux" 228 | ], 229 | "engines": { 230 | "node": ">=12" 231 | } 232 | }, 233 | "node_modules/@esbuild/linux-ppc64": { 234 | "version": "0.19.12", 235 | "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.12.tgz", 236 | "integrity": "sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==", 237 | "cpu": [ 238 | "ppc64" 239 | ], 240 | "dev": true, 241 | "optional": true, 242 | "os": [ 243 | "linux" 244 | ], 245 | "engines": { 246 | "node": ">=12" 247 | } 248 | }, 249 | "node_modules/@esbuild/linux-riscv64": { 250 | "version": "0.19.12", 251 | "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.12.tgz", 252 | "integrity": "sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==", 253 | "cpu": [ 254 | "riscv64" 255 | ], 256 | "dev": true, 257 | "optional": true, 258 | "os": [ 259 | "linux" 260 | ], 261 | "engines": { 262 | "node": ">=12" 263 | } 264 | }, 265 | "node_modules/@esbuild/linux-s390x": { 266 | "version": "0.19.12", 267 | "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.12.tgz", 268 | "integrity": "sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==", 269 | "cpu": [ 270 | "s390x" 271 | ], 272 | "dev": true, 273 | "optional": true, 274 | "os": [ 275 | "linux" 276 | ], 277 | "engines": { 278 | "node": ">=12" 279 | } 280 | }, 281 | "node_modules/@esbuild/linux-x64": { 282 | "version": "0.19.12", 283 | "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.12.tgz", 284 | "integrity": "sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==", 285 | "cpu": [ 286 | "x64" 287 | ], 288 | "dev": true, 289 | "optional": true, 290 | "os": [ 291 | "linux" 292 | ], 293 | "engines": { 294 | "node": ">=12" 295 | } 296 | }, 297 | "node_modules/@esbuild/netbsd-x64": { 298 | "version": "0.19.12", 299 | "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.12.tgz", 300 | "integrity": "sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==", 301 | "cpu": [ 302 | "x64" 303 | ], 304 | "dev": true, 305 | "optional": true, 306 | "os": [ 307 | "netbsd" 308 | ], 309 | "engines": { 310 | "node": ">=12" 311 | } 312 | }, 313 | "node_modules/@esbuild/openbsd-x64": { 314 | "version": "0.19.12", 315 | "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.12.tgz", 316 | "integrity": "sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==", 317 | "cpu": [ 318 | "x64" 319 | ], 320 | "dev": true, 321 | "optional": true, 322 | "os": [ 323 | "openbsd" 324 | ], 325 | "engines": { 326 | "node": ">=12" 327 | } 328 | }, 329 | "node_modules/@esbuild/sunos-x64": { 330 | "version": "0.19.12", 331 | "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.12.tgz", 332 | "integrity": "sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==", 333 | "cpu": [ 334 | "x64" 335 | ], 336 | "dev": true, 337 | "optional": true, 338 | "os": [ 339 | "sunos" 340 | ], 341 | "engines": { 342 | "node": ">=12" 343 | } 344 | }, 345 | "node_modules/@esbuild/win32-arm64": { 346 | "version": "0.19.12", 347 | "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.12.tgz", 348 | "integrity": "sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==", 349 | "cpu": [ 350 | "arm64" 351 | ], 352 | "dev": true, 353 | "optional": true, 354 | "os": [ 355 | "win32" 356 | ], 357 | "engines": { 358 | "node": ">=12" 359 | } 360 | }, 361 | "node_modules/@esbuild/win32-ia32": { 362 | "version": "0.19.12", 363 | "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.12.tgz", 364 | "integrity": "sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==", 365 | "cpu": [ 366 | "ia32" 367 | ], 368 | "dev": true, 369 | "optional": true, 370 | "os": [ 371 | "win32" 372 | ], 373 | "engines": { 374 | "node": ">=12" 375 | } 376 | }, 377 | "node_modules/@esbuild/win32-x64": { 378 | "version": "0.19.12", 379 | "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.12.tgz", 380 | "integrity": "sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==", 381 | "cpu": [ 382 | "x64" 383 | ], 384 | "dev": true, 385 | "optional": true, 386 | "os": [ 387 | "win32" 388 | ], 389 | "engines": { 390 | "node": ">=12" 391 | } 392 | }, 393 | "node_modules/@isaacs/cliui": { 394 | "version": "8.0.2", 395 | "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", 396 | "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", 397 | "dev": true, 398 | "dependencies": { 399 | "string-width": "^5.1.2", 400 | "string-width-cjs": "npm:string-width@^4.2.0", 401 | "strip-ansi": "^7.0.1", 402 | "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", 403 | "wrap-ansi": "^8.1.0", 404 | "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" 405 | }, 406 | "engines": { 407 | "node": ">=12" 408 | } 409 | }, 410 | "node_modules/@ohmyvm/vm": { 411 | "version": "0.1.0", 412 | "resolved": "https://registry.npmjs.org/@ohmyvm/vm/-/vm-0.1.0.tgz", 413 | "integrity": "sha512-g+X4K8X3JKupirynh0+UfBFtvgTwgihMADS2ZRZCHs8QGok57wT5sE18prEsSL+WwSViwxH8Zbis4V4OJM3rHg==", 414 | "dev": true, 415 | "engines": { 416 | "node": ">= 10" 417 | }, 418 | "optionalDependencies": { 419 | "@ohmyvm/vm-android-arm-eabi": "0.1.0", 420 | "@ohmyvm/vm-android-arm64": "0.1.0", 421 | "@ohmyvm/vm-darwin-arm64": "0.1.0", 422 | "@ohmyvm/vm-darwin-universal": "0.1.0", 423 | "@ohmyvm/vm-darwin-x64": "0.1.0", 424 | "@ohmyvm/vm-freebsd-x64": "0.1.0", 425 | "@ohmyvm/vm-linux-arm-gnueabihf": "0.1.0", 426 | "@ohmyvm/vm-linux-arm-musleabihf": "0.1.0", 427 | "@ohmyvm/vm-linux-arm64-gnu": "0.1.0", 428 | "@ohmyvm/vm-linux-arm64-musl": "0.1.0", 429 | "@ohmyvm/vm-linux-riscv64-gnu": "0.1.0", 430 | "@ohmyvm/vm-linux-x64-gnu": "0.1.0", 431 | "@ohmyvm/vm-linux-x64-musl": "0.1.0", 432 | "@ohmyvm/vm-win32-arm64-msvc": "0.1.0", 433 | "@ohmyvm/vm-win32-ia32-msvc": "0.1.0", 434 | "@ohmyvm/vm-win32-x64-msvc": "0.1.0" 435 | } 436 | }, 437 | "node_modules/@ohmyvm/vm-android-arm-eabi": { 438 | "version": "0.1.0", 439 | "resolved": "https://registry.npmjs.org/@ohmyvm/vm-android-arm-eabi/-/vm-android-arm-eabi-0.1.0.tgz", 440 | "integrity": "sha512-fKQQ0TgODTc/04onwPkN/jdlSn33TibA9YjsU9Ermu4kdPDVUDwbMzYtnIuN4zEwPCS9MJYw5JOFqr76ufafhA==", 441 | "cpu": [ 442 | "arm" 443 | ], 444 | "dev": true, 445 | "optional": true, 446 | "os": [ 447 | "android" 448 | ], 449 | "engines": { 450 | "node": ">= 10" 451 | } 452 | }, 453 | "node_modules/@ohmyvm/vm-android-arm64": { 454 | "version": "0.1.0", 455 | "resolved": "https://registry.npmjs.org/@ohmyvm/vm-android-arm64/-/vm-android-arm64-0.1.0.tgz", 456 | "integrity": "sha512-ExmoiSxET0ydRETSXIX3uSW/1cMRwxFBPmtDahwCM6/H4n+Cptck6hffNl+08DS5TG8nijGRiYRzLNiW4JjiEg==", 457 | "cpu": [ 458 | "arm64" 459 | ], 460 | "dev": true, 461 | "optional": true, 462 | "os": [ 463 | "android" 464 | ], 465 | "engines": { 466 | "node": ">= 10" 467 | } 468 | }, 469 | "node_modules/@ohmyvm/vm-darwin-arm64": { 470 | "version": "0.1.0", 471 | "resolved": "https://registry.npmjs.org/@ohmyvm/vm-darwin-arm64/-/vm-darwin-arm64-0.1.0.tgz", 472 | "integrity": "sha512-8PwQ29IHf+w0CNE4ULbTh4cMrED4QNWFdp55+NcUABY1Hp1TKrOElXra7FzPpN66bOZYml11vsfvJkrjxfvgdA==", 473 | "cpu": [ 474 | "arm64" 475 | ], 476 | "dev": true, 477 | "optional": true, 478 | "os": [ 479 | "darwin" 480 | ], 481 | "engines": { 482 | "node": ">= 10" 483 | } 484 | }, 485 | "node_modules/@ohmyvm/vm-darwin-universal": { 486 | "version": "0.1.0", 487 | "resolved": "https://registry.npmjs.org/@ohmyvm/vm-darwin-universal/-/vm-darwin-universal-0.1.0.tgz", 488 | "integrity": "sha512-dP3f+aPT5INcZQZwSIeW9mFbv88/MMWo47Dr8/R1G1kZymhAdOBrdwIHdHN2gK8oLvywqVHYGYtL1/GTlnhsLg==", 489 | "dev": true, 490 | "optional": true, 491 | "os": [ 492 | "darwin" 493 | ], 494 | "engines": { 495 | "node": ">= 10" 496 | } 497 | }, 498 | "node_modules/@ohmyvm/vm-darwin-x64": { 499 | "version": "0.1.0", 500 | "resolved": "https://registry.npmjs.org/@ohmyvm/vm-darwin-x64/-/vm-darwin-x64-0.1.0.tgz", 501 | "integrity": "sha512-gK9/Ak1qbtFrXEGrWm35pKv6Helw/ElOKkwhr9IROjr1Zbntp0BQaM6TKqgzwNcWZ+qsUFXLIcc+cvHLyQuX0w==", 502 | "cpu": [ 503 | "x64" 504 | ], 505 | "dev": true, 506 | "optional": true, 507 | "os": [ 508 | "darwin" 509 | ], 510 | "engines": { 511 | "node": ">= 10" 512 | } 513 | }, 514 | "node_modules/@ohmyvm/vm-freebsd-x64": { 515 | "version": "0.1.0", 516 | "resolved": "https://registry.npmjs.org/@ohmyvm/vm-freebsd-x64/-/vm-freebsd-x64-0.1.0.tgz", 517 | "integrity": "sha512-R8emWK/3FyttixOMZs9gpq6cSs5tu7SfIofAEze5DGLaJz3FFZusJ4wHEK82RLZPauFLfmLTJ60MwAHoWl1WfA==", 518 | "cpu": [ 519 | "x64" 520 | ], 521 | "dev": true, 522 | "optional": true, 523 | "os": [ 524 | "freebsd" 525 | ], 526 | "engines": { 527 | "node": ">= 10" 528 | } 529 | }, 530 | "node_modules/@ohmyvm/vm-linux-arm-gnueabihf": { 531 | "version": "0.1.0", 532 | "resolved": "https://registry.npmjs.org/@ohmyvm/vm-linux-arm-gnueabihf/-/vm-linux-arm-gnueabihf-0.1.0.tgz", 533 | "integrity": "sha512-uSG9VcGvhflhvEe4lOxwyat5Z1Ax2Sutq+FVDTTOXPvy+7aEiDhsgBP6yCbME8N41l0WRUj4U9AYF4TbcN3Tkw==", 534 | "cpu": [ 535 | "arm" 536 | ], 537 | "dev": true, 538 | "optional": true, 539 | "os": [ 540 | "linux" 541 | ], 542 | "engines": { 543 | "node": ">= 10" 544 | } 545 | }, 546 | "node_modules/@ohmyvm/vm-linux-arm-musleabihf": { 547 | "version": "0.1.0", 548 | "resolved": "https://registry.npmjs.org/@ohmyvm/vm-linux-arm-musleabihf/-/vm-linux-arm-musleabihf-0.1.0.tgz", 549 | "integrity": "sha512-P4jPrS10mI8X6yvsHgktAl74twCa38/FvoizwpP3DBNzgSoQ30usuoN3pQDjAN1JYzgE+R0SyGgJac5DjFIixg==", 550 | "cpu": [ 551 | "arm" 552 | ], 553 | "dev": true, 554 | "optional": true, 555 | "os": [ 556 | "linux" 557 | ], 558 | "engines": { 559 | "node": ">= 10" 560 | } 561 | }, 562 | "node_modules/@ohmyvm/vm-linux-arm64-gnu": { 563 | "version": "0.1.0", 564 | "resolved": "https://registry.npmjs.org/@ohmyvm/vm-linux-arm64-gnu/-/vm-linux-arm64-gnu-0.1.0.tgz", 565 | "integrity": "sha512-rv2d6zFKHfiycOImxnmoEhY069jj8GwfOiT4R9BxnVMJeqCg5/ABdOpvVpWwptASvcZsYeALrTHQnZbcD9MVhQ==", 566 | "cpu": [ 567 | "arm64" 568 | ], 569 | "dev": true, 570 | "optional": true, 571 | "os": [ 572 | "linux" 573 | ], 574 | "engines": { 575 | "node": ">= 10" 576 | } 577 | }, 578 | "node_modules/@ohmyvm/vm-linux-arm64-musl": { 579 | "version": "0.1.0", 580 | "resolved": "https://registry.npmjs.org/@ohmyvm/vm-linux-arm64-musl/-/vm-linux-arm64-musl-0.1.0.tgz", 581 | "integrity": "sha512-DklLgDsOJTESWuy73W2I56u57j83YWE+J4VCFKSHubXGEJgobCnYfia6zQFCGreEnFLchwANn+jtKsZlWubXsw==", 582 | "cpu": [ 583 | "arm64" 584 | ], 585 | "dev": true, 586 | "optional": true, 587 | "os": [ 588 | "linux" 589 | ], 590 | "engines": { 591 | "node": ">= 10" 592 | } 593 | }, 594 | "node_modules/@ohmyvm/vm-linux-riscv64-gnu": { 595 | "version": "0.1.0", 596 | "resolved": "https://registry.npmjs.org/@ohmyvm/vm-linux-riscv64-gnu/-/vm-linux-riscv64-gnu-0.1.0.tgz", 597 | "integrity": "sha512-3RkKvKzfzJXbl5Vca30Hl9lpqTZ/eI4ZY51aFBogt1bqLDAUq0snYit/1/wQvJh8kY7cewSrsyLCccHRC8Bu3w==", 598 | "cpu": [ 599 | "riscv64" 600 | ], 601 | "dev": true, 602 | "optional": true, 603 | "os": [ 604 | "linux" 605 | ], 606 | "engines": { 607 | "node": ">= 10" 608 | } 609 | }, 610 | "node_modules/@ohmyvm/vm-linux-x64-gnu": { 611 | "version": "0.1.0", 612 | "resolved": "https://registry.npmjs.org/@ohmyvm/vm-linux-x64-gnu/-/vm-linux-x64-gnu-0.1.0.tgz", 613 | "integrity": "sha512-hbVewKPtaO0oaBV6OxCuztUOXmQkLQ1TTR5HqzndKrpfsfQeNPZJgjNLOo5Rd/PtdSK5Ujkhy19oiGDmLwjwNQ==", 614 | "cpu": [ 615 | "x64" 616 | ], 617 | "dev": true, 618 | "optional": true, 619 | "os": [ 620 | "linux" 621 | ], 622 | "engines": { 623 | "node": ">= 10" 624 | } 625 | }, 626 | "node_modules/@ohmyvm/vm-linux-x64-musl": { 627 | "version": "0.1.0", 628 | "resolved": "https://registry.npmjs.org/@ohmyvm/vm-linux-x64-musl/-/vm-linux-x64-musl-0.1.0.tgz", 629 | "integrity": "sha512-8JWoTw/bxBowDC28dqJqQcjKEYj2Zp+NW2kCzMXcd5zvRE+2/45w185vXt4VJs6LJ2TfoouzNSKIMfksbE06Sw==", 630 | "cpu": [ 631 | "x64" 632 | ], 633 | "dev": true, 634 | "optional": true, 635 | "os": [ 636 | "linux" 637 | ], 638 | "engines": { 639 | "node": ">= 10" 640 | } 641 | }, 642 | "node_modules/@ohmyvm/vm-win32-arm64-msvc": { 643 | "version": "0.1.0", 644 | "resolved": "https://registry.npmjs.org/@ohmyvm/vm-win32-arm64-msvc/-/vm-win32-arm64-msvc-0.1.0.tgz", 645 | "integrity": "sha512-v8154KEqtI0LG7m0Z7A1wJ74XdHJz5aPiZWMAMjDYQiDBhRvq0bQRTj8t3vWkC4nzRhUg2kuwTGrNk5IENX/CQ==", 646 | "cpu": [ 647 | "arm64" 648 | ], 649 | "dev": true, 650 | "optional": true, 651 | "os": [ 652 | "win32" 653 | ], 654 | "engines": { 655 | "node": ">= 10" 656 | } 657 | }, 658 | "node_modules/@ohmyvm/vm-win32-ia32-msvc": { 659 | "version": "0.1.0", 660 | "resolved": "https://registry.npmjs.org/@ohmyvm/vm-win32-ia32-msvc/-/vm-win32-ia32-msvc-0.1.0.tgz", 661 | "integrity": "sha512-isjVAHfSGhpqKIwGJ/ZWng79Cg5y0bPyKcHpYAN1DF2eBME7mHDNaA+Iw2zlE5uHrXpoyL1GS54sDWLF+MgZZw==", 662 | "cpu": [ 663 | "ia32" 664 | ], 665 | "dev": true, 666 | "optional": true, 667 | "os": [ 668 | "win32" 669 | ], 670 | "engines": { 671 | "node": ">= 10" 672 | } 673 | }, 674 | "node_modules/@ohmyvm/vm-win32-x64-msvc": { 675 | "version": "0.1.0", 676 | "resolved": "https://registry.npmjs.org/@ohmyvm/vm-win32-x64-msvc/-/vm-win32-x64-msvc-0.1.0.tgz", 677 | "integrity": "sha512-7YQIQSWfJrO46c6xLKk4Xw8k8ecAufckT5No+C92ktSKHn+ehJqO1/ne6gY55cUz3/tLFNeQtmkcWWJkmvKtqQ==", 678 | "cpu": [ 679 | "x64" 680 | ], 681 | "dev": true, 682 | "optional": true, 683 | "os": [ 684 | "win32" 685 | ], 686 | "engines": { 687 | "node": ">= 10" 688 | } 689 | }, 690 | "node_modules/@pkgjs/parseargs": { 691 | "version": "0.11.0", 692 | "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", 693 | "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", 694 | "dev": true, 695 | "optional": true, 696 | "engines": { 697 | "node": ">=14" 698 | } 699 | }, 700 | "node_modules/@types/node": { 701 | "version": "20.12.4", 702 | "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.4.tgz", 703 | "integrity": "sha512-E+Fa9z3wSQpzgYQdYmme5X3OTuejnnTx88A6p6vkkJosR3KBz+HpE3kqNm98VE6cfLFcISx7zW7MsJkH6KwbTw==", 704 | "dev": true, 705 | "dependencies": { 706 | "undici-types": "~5.26.4" 707 | } 708 | }, 709 | "node_modules/ansi-regex": { 710 | "version": "6.0.1", 711 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", 712 | "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", 713 | "dev": true, 714 | "engines": { 715 | "node": ">=12" 716 | }, 717 | "funding": { 718 | "url": "https://github.com/chalk/ansi-regex?sponsor=1" 719 | } 720 | }, 721 | "node_modules/ansi-sequence-parser": { 722 | "version": "1.1.1", 723 | "resolved": "https://registry.npmjs.org/ansi-sequence-parser/-/ansi-sequence-parser-1.1.1.tgz", 724 | "integrity": "sha512-vJXt3yiaUL4UU546s3rPXlsry/RnM730G1+HkpKE012AN0sx1eOrxSu95oKDIonskeLTijMgqWZ3uDEe3NFvyg==", 725 | "dev": true 726 | }, 727 | "node_modules/ansi-styles": { 728 | "version": "6.2.1", 729 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", 730 | "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", 731 | "dev": true, 732 | "engines": { 733 | "node": ">=12" 734 | }, 735 | "funding": { 736 | "url": "https://github.com/chalk/ansi-styles?sponsor=1" 737 | } 738 | }, 739 | "node_modules/balanced-match": { 740 | "version": "1.0.2", 741 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", 742 | "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", 743 | "dev": true 744 | }, 745 | "node_modules/base64-js": { 746 | "version": "1.5.1", 747 | "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", 748 | "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", 749 | "dev": true, 750 | "funding": [ 751 | { 752 | "type": "github", 753 | "url": "https://github.com/sponsors/feross" 754 | }, 755 | { 756 | "type": "patreon", 757 | "url": "https://www.patreon.com/feross" 758 | }, 759 | { 760 | "type": "consulting", 761 | "url": "https://feross.org/support" 762 | } 763 | ] 764 | }, 765 | "node_modules/bl": { 766 | "version": "4.1.0", 767 | "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", 768 | "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", 769 | "dev": true, 770 | "dependencies": { 771 | "buffer": "^5.5.0", 772 | "inherits": "^2.0.4", 773 | "readable-stream": "^3.4.0" 774 | } 775 | }, 776 | "node_modules/brace-expansion": { 777 | "version": "2.0.1", 778 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", 779 | "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", 780 | "dev": true, 781 | "dependencies": { 782 | "balanced-match": "^1.0.0" 783 | } 784 | }, 785 | "node_modules/buffer": { 786 | "version": "5.7.1", 787 | "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", 788 | "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", 789 | "dev": true, 790 | "funding": [ 791 | { 792 | "type": "github", 793 | "url": "https://github.com/sponsors/feross" 794 | }, 795 | { 796 | "type": "patreon", 797 | "url": "https://www.patreon.com/feross" 798 | }, 799 | { 800 | "type": "consulting", 801 | "url": "https://feross.org/support" 802 | } 803 | ], 804 | "dependencies": { 805 | "base64-js": "^1.3.1", 806 | "ieee754": "^1.1.13" 807 | } 808 | }, 809 | "node_modules/chownr": { 810 | "version": "1.1.4", 811 | "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", 812 | "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", 813 | "dev": true 814 | }, 815 | "node_modules/color-convert": { 816 | "version": "2.0.1", 817 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", 818 | "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", 819 | "dev": true, 820 | "dependencies": { 821 | "color-name": "~1.1.4" 822 | }, 823 | "engines": { 824 | "node": ">=7.0.0" 825 | } 826 | }, 827 | "node_modules/color-name": { 828 | "version": "1.1.4", 829 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", 830 | "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", 831 | "dev": true 832 | }, 833 | "node_modules/cross-spawn": { 834 | "version": "7.0.3", 835 | "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", 836 | "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", 837 | "dev": true, 838 | "dependencies": { 839 | "path-key": "^3.1.0", 840 | "shebang-command": "^2.0.0", 841 | "which": "^2.0.1" 842 | }, 843 | "engines": { 844 | "node": ">= 8" 845 | } 846 | }, 847 | "node_modules/decompress-response": { 848 | "version": "6.0.0", 849 | "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", 850 | "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", 851 | "dev": true, 852 | "dependencies": { 853 | "mimic-response": "^3.1.0" 854 | }, 855 | "engines": { 856 | "node": ">=10" 857 | }, 858 | "funding": { 859 | "url": "https://github.com/sponsors/sindresorhus" 860 | } 861 | }, 862 | "node_modules/deep-extend": { 863 | "version": "0.6.0", 864 | "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", 865 | "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", 866 | "dev": true, 867 | "engines": { 868 | "node": ">=4.0.0" 869 | } 870 | }, 871 | "node_modules/detect-libc": { 872 | "version": "2.0.2", 873 | "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.2.tgz", 874 | "integrity": "sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==", 875 | "dev": true, 876 | "engines": { 877 | "node": ">=8" 878 | } 879 | }, 880 | "node_modules/eastasianwidth": { 881 | "version": "0.2.0", 882 | "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", 883 | "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", 884 | "dev": true 885 | }, 886 | "node_modules/emoji-regex": { 887 | "version": "9.2.2", 888 | "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", 889 | "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", 890 | "dev": true 891 | }, 892 | "node_modules/end-of-stream": { 893 | "version": "1.4.4", 894 | "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", 895 | "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", 896 | "dev": true, 897 | "dependencies": { 898 | "once": "^1.4.0" 899 | } 900 | }, 901 | "node_modules/esbuild": { 902 | "version": "0.19.12", 903 | "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.12.tgz", 904 | "integrity": "sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==", 905 | "dev": true, 906 | "hasInstallScript": true, 907 | "bin": { 908 | "esbuild": "bin/esbuild" 909 | }, 910 | "engines": { 911 | "node": ">=12" 912 | }, 913 | "optionalDependencies": { 914 | "@esbuild/aix-ppc64": "0.19.12", 915 | "@esbuild/android-arm": "0.19.12", 916 | "@esbuild/android-arm64": "0.19.12", 917 | "@esbuild/android-x64": "0.19.12", 918 | "@esbuild/darwin-arm64": "0.19.12", 919 | "@esbuild/darwin-x64": "0.19.12", 920 | "@esbuild/freebsd-arm64": "0.19.12", 921 | "@esbuild/freebsd-x64": "0.19.12", 922 | "@esbuild/linux-arm": "0.19.12", 923 | "@esbuild/linux-arm64": "0.19.12", 924 | "@esbuild/linux-ia32": "0.19.12", 925 | "@esbuild/linux-loong64": "0.19.12", 926 | "@esbuild/linux-mips64el": "0.19.12", 927 | "@esbuild/linux-ppc64": "0.19.12", 928 | "@esbuild/linux-riscv64": "0.19.12", 929 | "@esbuild/linux-s390x": "0.19.12", 930 | "@esbuild/linux-x64": "0.19.12", 931 | "@esbuild/netbsd-x64": "0.19.12", 932 | "@esbuild/openbsd-x64": "0.19.12", 933 | "@esbuild/sunos-x64": "0.19.12", 934 | "@esbuild/win32-arm64": "0.19.12", 935 | "@esbuild/win32-ia32": "0.19.12", 936 | "@esbuild/win32-x64": "0.19.12" 937 | } 938 | }, 939 | "node_modules/expand-template": { 940 | "version": "2.0.3", 941 | "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", 942 | "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", 943 | "dev": true, 944 | "engines": { 945 | "node": ">=6" 946 | } 947 | }, 948 | "node_modules/foreground-child": { 949 | "version": "3.1.1", 950 | "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", 951 | "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==", 952 | "dev": true, 953 | "dependencies": { 954 | "cross-spawn": "^7.0.0", 955 | "signal-exit": "^4.0.1" 956 | }, 957 | "engines": { 958 | "node": ">=14" 959 | }, 960 | "funding": { 961 | "url": "https://github.com/sponsors/isaacs" 962 | } 963 | }, 964 | "node_modules/fs-constants": { 965 | "version": "1.0.0", 966 | "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", 967 | "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", 968 | "dev": true 969 | }, 970 | "node_modules/fsevents": { 971 | "version": "2.3.3", 972 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", 973 | "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", 974 | "dev": true, 975 | "hasInstallScript": true, 976 | "optional": true, 977 | "os": [ 978 | "darwin" 979 | ], 980 | "engines": { 981 | "node": "^8.16.0 || ^10.6.0 || >=11.0.0" 982 | } 983 | }, 984 | "node_modules/get-tsconfig": { 985 | "version": "4.7.3", 986 | "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.7.3.tgz", 987 | "integrity": "sha512-ZvkrzoUA0PQZM6fy6+/Hce561s+faD1rsNwhnO5FelNjyy7EMGJ3Rz1AQ8GYDWjhRs/7dBLOEJvhK8MiEJOAFg==", 988 | "dev": true, 989 | "dependencies": { 990 | "resolve-pkg-maps": "^1.0.0" 991 | }, 992 | "funding": { 993 | "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" 994 | } 995 | }, 996 | "node_modules/github-from-package": { 997 | "version": "0.0.0", 998 | "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", 999 | "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", 1000 | "dev": true 1001 | }, 1002 | "node_modules/glob": { 1003 | "version": "10.3.12", 1004 | "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.12.tgz", 1005 | "integrity": "sha512-TCNv8vJ+xz4QiqTpfOJA7HvYv+tNIRHKfUWw/q+v2jdgN4ebz+KY9tGx5J4rHP0o84mNP+ApH66HRX8us3Khqg==", 1006 | "dev": true, 1007 | "dependencies": { 1008 | "foreground-child": "^3.1.0", 1009 | "jackspeak": "^2.3.6", 1010 | "minimatch": "^9.0.1", 1011 | "minipass": "^7.0.4", 1012 | "path-scurry": "^1.10.2" 1013 | }, 1014 | "bin": { 1015 | "glob": "dist/esm/bin.mjs" 1016 | }, 1017 | "engines": { 1018 | "node": ">=16 || 14 >=14.17" 1019 | }, 1020 | "funding": { 1021 | "url": "https://github.com/sponsors/isaacs" 1022 | } 1023 | }, 1024 | "node_modules/ieee754": { 1025 | "version": "1.2.1", 1026 | "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", 1027 | "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", 1028 | "dev": true, 1029 | "funding": [ 1030 | { 1031 | "type": "github", 1032 | "url": "https://github.com/sponsors/feross" 1033 | }, 1034 | { 1035 | "type": "patreon", 1036 | "url": "https://www.patreon.com/feross" 1037 | }, 1038 | { 1039 | "type": "consulting", 1040 | "url": "https://feross.org/support" 1041 | } 1042 | ] 1043 | }, 1044 | "node_modules/inherits": { 1045 | "version": "2.0.4", 1046 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 1047 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", 1048 | "dev": true 1049 | }, 1050 | "node_modules/ini": { 1051 | "version": "1.3.8", 1052 | "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", 1053 | "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", 1054 | "dev": true 1055 | }, 1056 | "node_modules/is-fullwidth-code-point": { 1057 | "version": "3.0.0", 1058 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", 1059 | "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", 1060 | "dev": true, 1061 | "engines": { 1062 | "node": ">=8" 1063 | } 1064 | }, 1065 | "node_modules/isexe": { 1066 | "version": "2.0.0", 1067 | "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", 1068 | "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", 1069 | "dev": true 1070 | }, 1071 | "node_modules/isolated-vm": { 1072 | "version": "4.7.2", 1073 | "resolved": "https://registry.npmjs.org/isolated-vm/-/isolated-vm-4.7.2.tgz", 1074 | "integrity": "sha512-JVEs5gzWObzZK5+OlBplCdYSpokMcdhLSs/xWYYxmYWVfOOFF4oZJsYh7E/FmfX8e7gMioXMpMMeEyX1afuKrg==", 1075 | "dev": true, 1076 | "hasInstallScript": true, 1077 | "dependencies": { 1078 | "prebuild-install": "^7.1.1" 1079 | }, 1080 | "engines": { 1081 | "node": ">=16.0.0" 1082 | } 1083 | }, 1084 | "node_modules/jackspeak": { 1085 | "version": "2.3.6", 1086 | "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz", 1087 | "integrity": "sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==", 1088 | "dev": true, 1089 | "dependencies": { 1090 | "@isaacs/cliui": "^8.0.2" 1091 | }, 1092 | "engines": { 1093 | "node": ">=14" 1094 | }, 1095 | "funding": { 1096 | "url": "https://github.com/sponsors/isaacs" 1097 | }, 1098 | "optionalDependencies": { 1099 | "@pkgjs/parseargs": "^0.11.0" 1100 | } 1101 | }, 1102 | "node_modules/jsonc-parser": { 1103 | "version": "3.2.1", 1104 | "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.1.tgz", 1105 | "integrity": "sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA==", 1106 | "dev": true 1107 | }, 1108 | "node_modules/lru-cache": { 1109 | "version": "10.2.0", 1110 | "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.0.tgz", 1111 | "integrity": "sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==", 1112 | "dev": true, 1113 | "engines": { 1114 | "node": "14 || >=16.14" 1115 | } 1116 | }, 1117 | "node_modules/lunr": { 1118 | "version": "2.3.9", 1119 | "resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz", 1120 | "integrity": "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==", 1121 | "dev": true 1122 | }, 1123 | "node_modules/m3u8stream": { 1124 | "version": "0.8.6", 1125 | "resolved": "https://registry.npmjs.org/m3u8stream/-/m3u8stream-0.8.6.tgz", 1126 | "integrity": "sha512-LZj8kIVf9KCphiHmH7sbFQTVe4tOemb202fWwvJwR9W5ENW/1hxJN6ksAWGhQgSBSa3jyWhnjKU1Fw1GaOdbyA==", 1127 | "dev": true, 1128 | "dependencies": { 1129 | "miniget": "^4.2.2", 1130 | "sax": "^1.2.4" 1131 | }, 1132 | "engines": { 1133 | "node": ">=12" 1134 | } 1135 | }, 1136 | "node_modules/marked": { 1137 | "version": "4.3.0", 1138 | "resolved": "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz", 1139 | "integrity": "sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==", 1140 | "dev": true, 1141 | "bin": { 1142 | "marked": "bin/marked.js" 1143 | }, 1144 | "engines": { 1145 | "node": ">= 12" 1146 | } 1147 | }, 1148 | "node_modules/mimic-response": { 1149 | "version": "3.1.0", 1150 | "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", 1151 | "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", 1152 | "dev": true, 1153 | "engines": { 1154 | "node": ">=10" 1155 | }, 1156 | "funding": { 1157 | "url": "https://github.com/sponsors/sindresorhus" 1158 | } 1159 | }, 1160 | "node_modules/miniget": { 1161 | "version": "4.2.3", 1162 | "resolved": "https://registry.npmjs.org/miniget/-/miniget-4.2.3.tgz", 1163 | "integrity": "sha512-SjbDPDICJ1zT+ZvQwK0hUcRY4wxlhhNpHL9nJOB2MEAXRGagTljsO8MEDzQMTFf0Q8g4QNi8P9lEm/g7e+qgzA==", 1164 | "dev": true, 1165 | "engines": { 1166 | "node": ">=12" 1167 | } 1168 | }, 1169 | "node_modules/minimatch": { 1170 | "version": "9.0.3", 1171 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", 1172 | "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", 1173 | "dev": true, 1174 | "dependencies": { 1175 | "brace-expansion": "^2.0.1" 1176 | }, 1177 | "engines": { 1178 | "node": ">=16 || 14 >=14.17" 1179 | }, 1180 | "funding": { 1181 | "url": "https://github.com/sponsors/isaacs" 1182 | } 1183 | }, 1184 | "node_modules/minimist": { 1185 | "version": "1.2.8", 1186 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", 1187 | "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", 1188 | "dev": true, 1189 | "funding": { 1190 | "url": "https://github.com/sponsors/ljharb" 1191 | } 1192 | }, 1193 | "node_modules/minipass": { 1194 | "version": "7.0.4", 1195 | "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz", 1196 | "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==", 1197 | "dev": true, 1198 | "engines": { 1199 | "node": ">=16 || 14 >=14.17" 1200 | } 1201 | }, 1202 | "node_modules/mkdirp-classic": { 1203 | "version": "0.5.3", 1204 | "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", 1205 | "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", 1206 | "dev": true 1207 | }, 1208 | "node_modules/napi-build-utils": { 1209 | "version": "1.0.2", 1210 | "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz", 1211 | "integrity": "sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==", 1212 | "dev": true 1213 | }, 1214 | "node_modules/node-abi": { 1215 | "version": "3.45.0", 1216 | "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.45.0.tgz", 1217 | "integrity": "sha512-iwXuFrMAcFVi/ZoZiqq8BzAdsLw9kxDfTC0HMyjXfSL/6CSDAGD5UmR7azrAgWV1zKYq7dUUMj4owusBWKLsiQ==", 1218 | "dev": true, 1219 | "dependencies": { 1220 | "semver": "^7.3.5" 1221 | }, 1222 | "engines": { 1223 | "node": ">=10" 1224 | } 1225 | }, 1226 | "node_modules/once": { 1227 | "version": "1.4.0", 1228 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 1229 | "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", 1230 | "dev": true, 1231 | "dependencies": { 1232 | "wrappy": "1" 1233 | } 1234 | }, 1235 | "node_modules/path-key": { 1236 | "version": "3.1.1", 1237 | "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", 1238 | "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", 1239 | "dev": true, 1240 | "engines": { 1241 | "node": ">=8" 1242 | } 1243 | }, 1244 | "node_modules/path-scurry": { 1245 | "version": "1.10.2", 1246 | "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.2.tgz", 1247 | "integrity": "sha512-7xTavNy5RQXnsjANvVvMkEjvloOinkAjv/Z6Ildz9v2RinZ4SBKTWFOVRbaF8p0vpHnyjV/UwNDdKuUv6M5qcA==", 1248 | "dev": true, 1249 | "dependencies": { 1250 | "lru-cache": "^10.2.0", 1251 | "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" 1252 | }, 1253 | "engines": { 1254 | "node": ">=16 || 14 >=14.17" 1255 | }, 1256 | "funding": { 1257 | "url": "https://github.com/sponsors/isaacs" 1258 | } 1259 | }, 1260 | "node_modules/prebuild-install": { 1261 | "version": "7.1.1", 1262 | "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.1.tgz", 1263 | "integrity": "sha512-jAXscXWMcCK8GgCoHOfIr0ODh5ai8mj63L2nWrjuAgXE6tDyYGnx4/8o/rCgU+B4JSyZBKbeZqzhtwtC3ovxjw==", 1264 | "dev": true, 1265 | "dependencies": { 1266 | "detect-libc": "^2.0.0", 1267 | "expand-template": "^2.0.3", 1268 | "github-from-package": "0.0.0", 1269 | "minimist": "^1.2.3", 1270 | "mkdirp-classic": "^0.5.3", 1271 | "napi-build-utils": "^1.0.1", 1272 | "node-abi": "^3.3.0", 1273 | "pump": "^3.0.0", 1274 | "rc": "^1.2.7", 1275 | "simple-get": "^4.0.0", 1276 | "tar-fs": "^2.0.0", 1277 | "tunnel-agent": "^0.6.0" 1278 | }, 1279 | "bin": { 1280 | "prebuild-install": "bin.js" 1281 | }, 1282 | "engines": { 1283 | "node": ">=10" 1284 | } 1285 | }, 1286 | "node_modules/pump": { 1287 | "version": "3.0.0", 1288 | "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", 1289 | "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", 1290 | "dev": true, 1291 | "dependencies": { 1292 | "end-of-stream": "^1.1.0", 1293 | "once": "^1.3.1" 1294 | } 1295 | }, 1296 | "node_modules/rc": { 1297 | "version": "1.2.8", 1298 | "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", 1299 | "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", 1300 | "dev": true, 1301 | "dependencies": { 1302 | "deep-extend": "^0.6.0", 1303 | "ini": "~1.3.0", 1304 | "minimist": "^1.2.0", 1305 | "strip-json-comments": "~2.0.1" 1306 | }, 1307 | "bin": { 1308 | "rc": "cli.js" 1309 | } 1310 | }, 1311 | "node_modules/readable-stream": { 1312 | "version": "3.6.2", 1313 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", 1314 | "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", 1315 | "dev": true, 1316 | "dependencies": { 1317 | "inherits": "^2.0.3", 1318 | "string_decoder": "^1.1.1", 1319 | "util-deprecate": "^1.0.1" 1320 | }, 1321 | "engines": { 1322 | "node": ">= 6" 1323 | } 1324 | }, 1325 | "node_modules/resolve-pkg-maps": { 1326 | "version": "1.0.0", 1327 | "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", 1328 | "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", 1329 | "dev": true, 1330 | "funding": { 1331 | "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" 1332 | } 1333 | }, 1334 | "node_modules/rimraf": { 1335 | "version": "5.0.5", 1336 | "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.5.tgz", 1337 | "integrity": "sha512-CqDakW+hMe/Bz202FPEymy68P+G50RfMQK+Qo5YUqc9SPipvbGjCGKd0RSKEelbsfQuw3g5NZDSrlZZAJurH1A==", 1338 | "dev": true, 1339 | "dependencies": { 1340 | "glob": "^10.3.7" 1341 | }, 1342 | "bin": { 1343 | "rimraf": "dist/esm/bin.mjs" 1344 | }, 1345 | "engines": { 1346 | "node": ">=14" 1347 | }, 1348 | "funding": { 1349 | "url": "https://github.com/sponsors/isaacs" 1350 | } 1351 | }, 1352 | "node_modules/safe-buffer": { 1353 | "version": "5.2.1", 1354 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", 1355 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", 1356 | "dev": true, 1357 | "funding": [ 1358 | { 1359 | "type": "github", 1360 | "url": "https://github.com/sponsors/feross" 1361 | }, 1362 | { 1363 | "type": "patreon", 1364 | "url": "https://www.patreon.com/feross" 1365 | }, 1366 | { 1367 | "type": "consulting", 1368 | "url": "https://feross.org/support" 1369 | } 1370 | ] 1371 | }, 1372 | "node_modules/sax": { 1373 | "version": "1.2.4", 1374 | "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", 1375 | "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", 1376 | "dev": true 1377 | }, 1378 | "node_modules/semver": { 1379 | "version": "7.5.4", 1380 | "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", 1381 | "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", 1382 | "dev": true, 1383 | "dependencies": { 1384 | "lru-cache": "^6.0.0" 1385 | }, 1386 | "bin": { 1387 | "semver": "bin/semver.js" 1388 | }, 1389 | "engines": { 1390 | "node": ">=10" 1391 | } 1392 | }, 1393 | "node_modules/semver/node_modules/lru-cache": { 1394 | "version": "6.0.0", 1395 | "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", 1396 | "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", 1397 | "dev": true, 1398 | "dependencies": { 1399 | "yallist": "^4.0.0" 1400 | }, 1401 | "engines": { 1402 | "node": ">=10" 1403 | } 1404 | }, 1405 | "node_modules/shebang-command": { 1406 | "version": "2.0.0", 1407 | "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", 1408 | "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", 1409 | "dev": true, 1410 | "dependencies": { 1411 | "shebang-regex": "^3.0.0" 1412 | }, 1413 | "engines": { 1414 | "node": ">=8" 1415 | } 1416 | }, 1417 | "node_modules/shebang-regex": { 1418 | "version": "3.0.0", 1419 | "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", 1420 | "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", 1421 | "dev": true, 1422 | "engines": { 1423 | "node": ">=8" 1424 | } 1425 | }, 1426 | "node_modules/shiki": { 1427 | "version": "0.14.7", 1428 | "resolved": "https://registry.npmjs.org/shiki/-/shiki-0.14.7.tgz", 1429 | "integrity": "sha512-dNPAPrxSc87ua2sKJ3H5dQ/6ZaY8RNnaAqK+t0eG7p0Soi2ydiqbGOTaZCqaYvA/uZYfS1LJnemt3Q+mSfcPCg==", 1430 | "dev": true, 1431 | "dependencies": { 1432 | "ansi-sequence-parser": "^1.1.0", 1433 | "jsonc-parser": "^3.2.0", 1434 | "vscode-oniguruma": "^1.7.0", 1435 | "vscode-textmate": "^8.0.0" 1436 | } 1437 | }, 1438 | "node_modules/signal-exit": { 1439 | "version": "4.1.0", 1440 | "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", 1441 | "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", 1442 | "dev": true, 1443 | "engines": { 1444 | "node": ">=14" 1445 | }, 1446 | "funding": { 1447 | "url": "https://github.com/sponsors/isaacs" 1448 | } 1449 | }, 1450 | "node_modules/simple-concat": { 1451 | "version": "1.0.1", 1452 | "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", 1453 | "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", 1454 | "dev": true, 1455 | "funding": [ 1456 | { 1457 | "type": "github", 1458 | "url": "https://github.com/sponsors/feross" 1459 | }, 1460 | { 1461 | "type": "patreon", 1462 | "url": "https://www.patreon.com/feross" 1463 | }, 1464 | { 1465 | "type": "consulting", 1466 | "url": "https://feross.org/support" 1467 | } 1468 | ] 1469 | }, 1470 | "node_modules/simple-get": { 1471 | "version": "4.0.1", 1472 | "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", 1473 | "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", 1474 | "dev": true, 1475 | "funding": [ 1476 | { 1477 | "type": "github", 1478 | "url": "https://github.com/sponsors/feross" 1479 | }, 1480 | { 1481 | "type": "patreon", 1482 | "url": "https://www.patreon.com/feross" 1483 | }, 1484 | { 1485 | "type": "consulting", 1486 | "url": "https://feross.org/support" 1487 | } 1488 | ], 1489 | "dependencies": { 1490 | "decompress-response": "^6.0.0", 1491 | "once": "^1.3.1", 1492 | "simple-concat": "^1.0.0" 1493 | } 1494 | }, 1495 | "node_modules/string_decoder": { 1496 | "version": "1.3.0", 1497 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", 1498 | "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", 1499 | "dev": true, 1500 | "dependencies": { 1501 | "safe-buffer": "~5.2.0" 1502 | } 1503 | }, 1504 | "node_modules/string-width": { 1505 | "version": "5.1.2", 1506 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", 1507 | "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", 1508 | "dev": true, 1509 | "dependencies": { 1510 | "eastasianwidth": "^0.2.0", 1511 | "emoji-regex": "^9.2.2", 1512 | "strip-ansi": "^7.0.1" 1513 | }, 1514 | "engines": { 1515 | "node": ">=12" 1516 | }, 1517 | "funding": { 1518 | "url": "https://github.com/sponsors/sindresorhus" 1519 | } 1520 | }, 1521 | "node_modules/string-width-cjs": { 1522 | "name": "string-width", 1523 | "version": "4.2.3", 1524 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", 1525 | "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", 1526 | "dev": true, 1527 | "dependencies": { 1528 | "emoji-regex": "^8.0.0", 1529 | "is-fullwidth-code-point": "^3.0.0", 1530 | "strip-ansi": "^6.0.1" 1531 | }, 1532 | "engines": { 1533 | "node": ">=8" 1534 | } 1535 | }, 1536 | "node_modules/string-width-cjs/node_modules/ansi-regex": { 1537 | "version": "5.0.1", 1538 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", 1539 | "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", 1540 | "dev": true, 1541 | "engines": { 1542 | "node": ">=8" 1543 | } 1544 | }, 1545 | "node_modules/string-width-cjs/node_modules/emoji-regex": { 1546 | "version": "8.0.0", 1547 | "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", 1548 | "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", 1549 | "dev": true 1550 | }, 1551 | "node_modules/string-width-cjs/node_modules/strip-ansi": { 1552 | "version": "6.0.1", 1553 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", 1554 | "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", 1555 | "dev": true, 1556 | "dependencies": { 1557 | "ansi-regex": "^5.0.1" 1558 | }, 1559 | "engines": { 1560 | "node": ">=8" 1561 | } 1562 | }, 1563 | "node_modules/strip-ansi": { 1564 | "version": "7.1.0", 1565 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", 1566 | "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", 1567 | "dev": true, 1568 | "dependencies": { 1569 | "ansi-regex": "^6.0.1" 1570 | }, 1571 | "engines": { 1572 | "node": ">=12" 1573 | }, 1574 | "funding": { 1575 | "url": "https://github.com/chalk/strip-ansi?sponsor=1" 1576 | } 1577 | }, 1578 | "node_modules/strip-ansi-cjs": { 1579 | "name": "strip-ansi", 1580 | "version": "6.0.1", 1581 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", 1582 | "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", 1583 | "dev": true, 1584 | "dependencies": { 1585 | "ansi-regex": "^5.0.1" 1586 | }, 1587 | "engines": { 1588 | "node": ">=8" 1589 | } 1590 | }, 1591 | "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { 1592 | "version": "5.0.1", 1593 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", 1594 | "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", 1595 | "dev": true, 1596 | "engines": { 1597 | "node": ">=8" 1598 | } 1599 | }, 1600 | "node_modules/strip-json-comments": { 1601 | "version": "2.0.1", 1602 | "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", 1603 | "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", 1604 | "dev": true, 1605 | "engines": { 1606 | "node": ">=0.10.0" 1607 | } 1608 | }, 1609 | "node_modules/tar-fs": { 1610 | "version": "2.1.1", 1611 | "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", 1612 | "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", 1613 | "dev": true, 1614 | "dependencies": { 1615 | "chownr": "^1.1.1", 1616 | "mkdirp-classic": "^0.5.2", 1617 | "pump": "^3.0.0", 1618 | "tar-stream": "^2.1.4" 1619 | } 1620 | }, 1621 | "node_modules/tar-stream": { 1622 | "version": "2.2.0", 1623 | "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", 1624 | "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", 1625 | "dev": true, 1626 | "dependencies": { 1627 | "bl": "^4.0.3", 1628 | "end-of-stream": "^1.4.1", 1629 | "fs-constants": "^1.0.0", 1630 | "inherits": "^2.0.3", 1631 | "readable-stream": "^3.1.1" 1632 | }, 1633 | "engines": { 1634 | "node": ">=6" 1635 | } 1636 | }, 1637 | "node_modules/tsx": { 1638 | "version": "4.7.2", 1639 | "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.7.2.tgz", 1640 | "integrity": "sha512-BCNd4kz6fz12fyrgCTEdZHGJ9fWTGeUzXmQysh0RVocDY3h4frk05ZNCXSy4kIenF7y/QnrdiVpTsyNRn6vlAw==", 1641 | "dev": true, 1642 | "dependencies": { 1643 | "esbuild": "~0.19.10", 1644 | "get-tsconfig": "^4.7.2" 1645 | }, 1646 | "bin": { 1647 | "tsx": "dist/cli.mjs" 1648 | }, 1649 | "engines": { 1650 | "node": ">=18.0.0" 1651 | }, 1652 | "optionalDependencies": { 1653 | "fsevents": "~2.3.3" 1654 | } 1655 | }, 1656 | "node_modules/tunnel-agent": { 1657 | "version": "0.6.0", 1658 | "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", 1659 | "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", 1660 | "dev": true, 1661 | "dependencies": { 1662 | "safe-buffer": "^5.0.1" 1663 | }, 1664 | "engines": { 1665 | "node": "*" 1666 | } 1667 | }, 1668 | "node_modules/typedoc": { 1669 | "version": "0.25.12", 1670 | "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.25.12.tgz", 1671 | "integrity": "sha512-F+qhkK2VoTweDXd1c42GS/By2DvI2uDF4/EpG424dTexSHdtCH52C6IcAvMA6jR3DzAWZjHpUOW+E02kyPNUNw==", 1672 | "dev": true, 1673 | "dependencies": { 1674 | "lunr": "^2.3.9", 1675 | "marked": "^4.3.0", 1676 | "minimatch": "^9.0.3", 1677 | "shiki": "^0.14.7" 1678 | }, 1679 | "bin": { 1680 | "typedoc": "bin/typedoc" 1681 | }, 1682 | "engines": { 1683 | "node": ">= 16" 1684 | }, 1685 | "peerDependencies": { 1686 | "typescript": "4.6.x || 4.7.x || 4.8.x || 4.9.x || 5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x" 1687 | } 1688 | }, 1689 | "node_modules/typescript": { 1690 | "version": "5.4.3", 1691 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.3.tgz", 1692 | "integrity": "sha512-KrPd3PKaCLr78MalgiwJnA25Nm8HAmdwN3mYUYZgG/wizIo9EainNVQI9/yDavtVFRN2h3k8uf3GLHuhDMgEHg==", 1693 | "dev": true, 1694 | "bin": { 1695 | "tsc": "bin/tsc", 1696 | "tsserver": "bin/tsserver" 1697 | }, 1698 | "engines": { 1699 | "node": ">=14.17" 1700 | } 1701 | }, 1702 | "node_modules/undici": { 1703 | "version": "6.11.1", 1704 | "resolved": "https://registry.npmjs.org/undici/-/undici-6.11.1.tgz", 1705 | "integrity": "sha512-KyhzaLJnV1qa3BSHdj4AZ2ndqI0QWPxYzaIOio0WzcEJB9gvuysprJSLtpvc2D9mhR9jPDUk7xlJlZbH2KR5iw==", 1706 | "engines": { 1707 | "node": ">=18.0" 1708 | } 1709 | }, 1710 | "node_modules/undici-types": { 1711 | "version": "5.26.5", 1712 | "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", 1713 | "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", 1714 | "dev": true 1715 | }, 1716 | "node_modules/util-deprecate": { 1717 | "version": "1.0.2", 1718 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", 1719 | "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", 1720 | "dev": true 1721 | }, 1722 | "node_modules/vscode-oniguruma": { 1723 | "version": "1.7.0", 1724 | "resolved": "https://registry.npmjs.org/vscode-oniguruma/-/vscode-oniguruma-1.7.0.tgz", 1725 | "integrity": "sha512-L9WMGRfrjOhgHSdOYgCt/yRMsXzLDJSL7BPrOZt73gU0iWO4mpqzqQzOz5srxqTvMBaR0XZTSrVWo4j55Rc6cA==", 1726 | "dev": true 1727 | }, 1728 | "node_modules/vscode-textmate": { 1729 | "version": "8.0.0", 1730 | "resolved": "https://registry.npmjs.org/vscode-textmate/-/vscode-textmate-8.0.0.tgz", 1731 | "integrity": "sha512-AFbieoL7a5LMqcnOF04ji+rpXadgOXnZsxQr//r83kLPr7biP7am3g9zbaZIaBGwBRWeSvoMD4mgPdX3e4NWBg==", 1732 | "dev": true 1733 | }, 1734 | "node_modules/which": { 1735 | "version": "2.0.2", 1736 | "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", 1737 | "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", 1738 | "dev": true, 1739 | "dependencies": { 1740 | "isexe": "^2.0.0" 1741 | }, 1742 | "bin": { 1743 | "node-which": "bin/node-which" 1744 | }, 1745 | "engines": { 1746 | "node": ">= 8" 1747 | } 1748 | }, 1749 | "node_modules/wrap-ansi": { 1750 | "version": "8.1.0", 1751 | "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", 1752 | "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", 1753 | "dev": true, 1754 | "dependencies": { 1755 | "ansi-styles": "^6.1.0", 1756 | "string-width": "^5.0.1", 1757 | "strip-ansi": "^7.0.1" 1758 | }, 1759 | "engines": { 1760 | "node": ">=12" 1761 | }, 1762 | "funding": { 1763 | "url": "https://github.com/chalk/wrap-ansi?sponsor=1" 1764 | } 1765 | }, 1766 | "node_modules/wrap-ansi-cjs": { 1767 | "name": "wrap-ansi", 1768 | "version": "7.0.0", 1769 | "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", 1770 | "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", 1771 | "dev": true, 1772 | "dependencies": { 1773 | "ansi-styles": "^4.0.0", 1774 | "string-width": "^4.1.0", 1775 | "strip-ansi": "^6.0.0" 1776 | }, 1777 | "engines": { 1778 | "node": ">=10" 1779 | }, 1780 | "funding": { 1781 | "url": "https://github.com/chalk/wrap-ansi?sponsor=1" 1782 | } 1783 | }, 1784 | "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { 1785 | "version": "5.0.1", 1786 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", 1787 | "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", 1788 | "dev": true, 1789 | "engines": { 1790 | "node": ">=8" 1791 | } 1792 | }, 1793 | "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { 1794 | "version": "4.3.0", 1795 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", 1796 | "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", 1797 | "dev": true, 1798 | "dependencies": { 1799 | "color-convert": "^2.0.1" 1800 | }, 1801 | "engines": { 1802 | "node": ">=8" 1803 | }, 1804 | "funding": { 1805 | "url": "https://github.com/chalk/ansi-styles?sponsor=1" 1806 | } 1807 | }, 1808 | "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { 1809 | "version": "8.0.0", 1810 | "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", 1811 | "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", 1812 | "dev": true 1813 | }, 1814 | "node_modules/wrap-ansi-cjs/node_modules/string-width": { 1815 | "version": "4.2.3", 1816 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", 1817 | "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", 1818 | "dev": true, 1819 | "dependencies": { 1820 | "emoji-regex": "^8.0.0", 1821 | "is-fullwidth-code-point": "^3.0.0", 1822 | "strip-ansi": "^6.0.1" 1823 | }, 1824 | "engines": { 1825 | "node": ">=8" 1826 | } 1827 | }, 1828 | "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { 1829 | "version": "6.0.1", 1830 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", 1831 | "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", 1832 | "dev": true, 1833 | "dependencies": { 1834 | "ansi-regex": "^5.0.1" 1835 | }, 1836 | "engines": { 1837 | "node": ">=8" 1838 | } 1839 | }, 1840 | "node_modules/wrappy": { 1841 | "version": "1.0.2", 1842 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 1843 | "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", 1844 | "dev": true 1845 | }, 1846 | "node_modules/yallist": { 1847 | "version": "4.0.0", 1848 | "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", 1849 | "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", 1850 | "dev": true 1851 | } 1852 | } 1853 | } 1854 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "youtube-ext", 3 | "version": "1.1.25", 4 | "description": "A simple YouTube scraper and downloader.", 5 | "main": "dist/index.js", 6 | "files": [ 7 | "dist" 8 | ], 9 | "scripts": { 10 | "build": "rimraf dist && tsc", 11 | "docs": "typedoc && tsx ./scripts/docs" 12 | }, 13 | "keywords": [ 14 | "youtube", 15 | "scrape", 16 | "ytsr", 17 | "ytdl", 18 | "api", 19 | "search", 20 | "video", 21 | "playlist", 22 | "channel", 23 | "promise" 24 | ], 25 | "repository": { 26 | "type": "git", 27 | "url": "git+https://github.com/zyrouge/node-youtube-ext.git" 28 | }, 29 | "author": "ZYROUGE", 30 | "license": "MIT", 31 | "bugs": { 32 | "url": "https://github.com/zyrouge/node-youtube-ext/issues" 33 | }, 34 | "homepage": "youtube-ext.js.org", 35 | "devDependencies": { 36 | "@ohmyvm/vm": "^0.1.0", 37 | "@types/node": "^20.12.8", 38 | "isolated-vm": "^4.7.2", 39 | "m3u8stream": "^0.8.6", 40 | "rimraf": "^5.0.5", 41 | "tsx": "^4.8.2", 42 | "typedoc": "^0.25.13", 43 | "typescript": "^5.4.5" 44 | }, 45 | "dependencies": { 46 | "undici": "^6.15.0" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /scripts/docs.ts: -------------------------------------------------------------------------------- 1 | import fs from "fs/promises"; 2 | import path from "path"; 3 | 4 | const { homepage } = require("../package.json"); 5 | 6 | const start = async () => { 7 | const file = path.resolve(__dirname, "../docs-dist/CNAME"); 8 | await fs.writeFile(file, homepage); 9 | console.log(`Wrote CNAME file to "${file}" pointing to "${homepage}"`); 10 | }; 11 | 12 | start(); 13 | -------------------------------------------------------------------------------- /src/channelInfo.ts: -------------------------------------------------------------------------------- 1 | import { request } from "undici"; 2 | import { cookieJar } from "./cookies"; 3 | import { 4 | UndiciRequestOptions, 5 | assertUndiciOkResponse, 6 | constants, 7 | contentBetween, 8 | mergeObj, 9 | parseYoutubeKeywords, 10 | } from "./utils"; 11 | 12 | export interface ChannelInfoOptions { 13 | requestOptions?: UndiciRequestOptions; 14 | includeVideos?: boolean; 15 | } 16 | 17 | export interface ChannelVideo { 18 | title: string; 19 | id: string; 20 | url: string; 21 | channel: { 22 | name: string; 23 | id: string; 24 | url: string; 25 | }; 26 | thumbnails: { 27 | url: string; 28 | width: number; 29 | height: number; 30 | }[]; 31 | duration: { 32 | pretty: string; 33 | text: string; 34 | }; 35 | views: { 36 | text: string; 37 | pretty: string; 38 | simpleText: string; 39 | }; 40 | published: { 41 | text: string; 42 | }; 43 | } 44 | 45 | export interface ChannelInfo { 46 | name: string; 47 | id: string; 48 | url: string; 49 | rssUrl: string; 50 | vanityUrl: string; 51 | description: string; 52 | subscribers: { 53 | pretty: string; 54 | text: string; 55 | }; 56 | thumbnails: { 57 | url: string; 58 | width: number; 59 | height: number; 60 | }[]; 61 | banner: { 62 | url: string; 63 | width: number; 64 | height: number; 65 | }[]; 66 | tvBanner: { 67 | url: string; 68 | width: number; 69 | height: number; 70 | }[]; 71 | mobileBanner: { 72 | url: string; 73 | width: number; 74 | height: number; 75 | }[]; 76 | badges: string[]; 77 | tags: string[]; 78 | videos: ChannelVideo[]; 79 | unlisted: boolean; 80 | familySafe: boolean; 81 | } 82 | 83 | /** 84 | * Get full information about a YouTube channel. 85 | */ 86 | export const channelInfo = async ( 87 | url: string, 88 | options: ChannelInfoOptions = {} 89 | ) => { 90 | if (typeof url !== "string") { 91 | throw new Error(constants.errors.type("url", "string", typeof url)); 92 | } 93 | if (typeof options !== "object") { 94 | throw new Error( 95 | constants.errors.type("options", "object", typeof options) 96 | ); 97 | } 98 | 99 | options = mergeObj( 100 | { 101 | requestOptions: { 102 | headers: { 103 | "User-Agent": constants.requestOptions.userAgent, 104 | Cookie: cookieJar.cookieHeaderValue(), 105 | }, 106 | maxRedirections: constants.requestOptions.maxRedirections, 107 | }, 108 | includeVideos: false, 109 | }, 110 | options 111 | ); 112 | if (!url.startsWith("http")) { 113 | url = constants.urls.channel.base(url); 114 | } 115 | 116 | let data: string; 117 | try { 118 | const resp = await request(url, options.requestOptions); 119 | assertUndiciOkResponse(resp); 120 | data = await resp.body.text(); 121 | cookieJar.utilizeResponseHeaders(resp.headers); 122 | } catch (err) { 123 | throw new Error(`Failed to fetch url "${url}". (${err})`); 124 | } 125 | 126 | let initialData: any; 127 | try { 128 | const raw = contentBetween(data, "var ytInitialData = ", ";"); 129 | initialData = JSON.parse(raw); 130 | } catch (err) { 131 | throw new Error(`Failed to parse data from webpage. (${err})`); 132 | } 133 | 134 | const channel: ChannelInfo = { 135 | name: initialData?.metadata?.channelMetadataRenderer?.title, 136 | id: initialData?.metadata?.channelMetadataRenderer?.externalId, 137 | url: initialData?.metadata?.channelMetadataRenderer?.channelUrl, 138 | rssUrl: initialData?.metadata?.channelMetadataRenderer?.rssUrl, 139 | vanityUrl: 140 | initialData?.microformat?.microformatDataRenderer?.vanityChannelUrl, 141 | description: 142 | initialData?.metadata?.channelMetadataRenderer?.description, 143 | subscribers: { 144 | pretty: initialData?.header?.c4TabbedHeaderRenderer 145 | ?.subscriberCountText?.simpleText, 146 | text: initialData?.header?.c4TabbedHeaderRenderer 147 | ?.subscriberCountText?.accessibility?.accessibilityData?.label, 148 | }, 149 | banner: initialData?.header?.c4TabbedHeaderRenderer?.banner?.thumbnails, 150 | tvBanner: 151 | initialData?.header?.c4TabbedHeaderRenderer?.tvBanner?.thumbnails, 152 | mobileBanner: 153 | initialData?.header?.c4TabbedHeaderRenderer?.mobileBanner 154 | ?.thumbnails, 155 | badges: initialData?.header?.c4TabbedHeaderRenderer?.badges 156 | ?.map((x: any) => x?.metadataBadgeRenderer?.tooltip) 157 | ?.filter((x: string) => x), 158 | thumbnails: 159 | initialData?.metadata?.channelMetadataRenderer?.avatar?.thumbnails, 160 | tags: parseYoutubeKeywords( 161 | initialData?.metadata?.channelMetadataRenderer?.keywords ?? "" 162 | ), 163 | videos: [], 164 | unlisted: initialData?.microformat?.microformatDataRenderer?.unlisted, 165 | familySafe: 166 | initialData?.metadata?.channelMetadataRenderer?.isFamilySafe, 167 | }; 168 | 169 | if (options.includeVideos) { 170 | initialData?.contents?.twoColumnBrowseResultsRenderer?.tabs 171 | ?.find((x: any) => x?.tabRenderer?.title === "Home") 172 | ?.tabRenderer?.content?.sectionListRenderer?.contents?.find( 173 | (x: any) => 174 | x?.itemSectionRenderer?.contents[0]?.shelfRenderer?.content 175 | ?.horizontalListRenderer?.items 176 | ) 177 | ?.itemSectionRenderer?.contents[0]?.shelfRenderer?.content?.horizontalListRenderer?.items?.forEach( 178 | ({ gridVideoRenderer: x }: any) => { 179 | const video: ChannelVideo = { 180 | title: x?.title?.simpleText, 181 | id: x?.videoId, 182 | url: 183 | constants.urls.base + 184 | x?.navigationEndpoint?.commandMetadata 185 | ?.webCommandMetadata?.url, 186 | channel: { 187 | name: channel?.name, 188 | id: channel?.id, 189 | url: channel?.url, 190 | }, 191 | thumbnails: x?.thumbnail?.thumbnails, 192 | duration: { 193 | pretty: x?.thumbnailOverlays?.find( 194 | (x: any) => 195 | x?.thumbnailOverlayTimeStatusRenderer 196 | )?.thumbnailOverlayTimeStatusRenderer?.text 197 | ?.simpleText, 198 | text: x?.thumbnailOverlays?.find( 199 | (x: any) => 200 | x?.thumbnailOverlayTimeStatusRenderer 201 | )?.thumbnailOverlayTimeStatusRenderer?.text 202 | ?.accessibility?.accessibilityData?.label, 203 | }, 204 | views: { 205 | pretty: x?.shortViewCountText?.simpleText, 206 | text: x?.shortViewCountText?.accessibility 207 | ?.accessibilityData?.label, 208 | simpleText: x?.viewCountText?.simpleText, 209 | }, 210 | published: { 211 | text: x?.publishedTimeText?.simpleText, 212 | }, 213 | }; 214 | channel.videos.push(video); 215 | } 216 | ); 217 | } 218 | 219 | return channel; 220 | }; 221 | 222 | export default channelInfo; 223 | -------------------------------------------------------------------------------- /src/cookies.ts: -------------------------------------------------------------------------------- 1 | export class CookieJar { 2 | cookieMap: Record = {}; 3 | disabled = false; 4 | 5 | cookieHeaderValue() { 6 | if (this.disabled) return; 7 | return CookieJar.stringifyCookieMap(this.cookieMap); 8 | } 9 | 10 | utilizeResponseHeaders( 11 | headers: Record 12 | ) { 13 | const setCookie = headers["set-cookie"]; 14 | if (!setCookie) return; 15 | try { 16 | CookieJar.parseSetCookie(setCookie, this.cookieMap); 17 | } catch (_) {} 18 | } 19 | 20 | static stringifyCookieMap(cookies: Record) { 21 | return Object.entries(cookies) 22 | .map(([k, v]) => `${k}=${encodeURIComponent(v)}`) 23 | .join("; "); 24 | } 25 | 26 | static parseCookieString( 27 | cookie: string, 28 | cookieMap: Record = {} 29 | ) { 30 | return cookie.split(";").reduce((pv, cv) => { 31 | const [k, v] = cv.trim().split("="); 32 | if (!k || !v || CookieJar.shouldIgnoreCookie(k, v)) { 33 | return pv; 34 | } 35 | pv[k] = decodeURIComponent(v); 36 | return pv; 37 | }, cookieMap); 38 | } 39 | 40 | static parseSetCookie( 41 | cookies: string | string[], 42 | cookieMap: Record = {} 43 | ) { 44 | if (Array.isArray(cookies)) { 45 | for (const x of cookies) { 46 | CookieJar.parseCookieString(x, cookieMap); 47 | } 48 | return cookieMap; 49 | } 50 | return CookieJar.parseCookieString(cookies, cookieMap); 51 | } 52 | 53 | static ignoredCookieKeys = [ 54 | "expires", 55 | "max-age", 56 | "secure", 57 | "httponly", 58 | "samesite", 59 | "path", 60 | "domain", 61 | "gps", 62 | "priority", 63 | "login_info", 64 | ]; 65 | 66 | static shouldIgnoreCookie(key: string, value: string) { 67 | return ( 68 | value === "EXPIRED" || 69 | CookieJar.ignoredCookieKeys.includes(key.toLowerCase()) 70 | ); 71 | } 72 | } 73 | 74 | export const cookieJar = new CookieJar(); 75 | -------------------------------------------------------------------------------- /src/extractStreamInfo.ts: -------------------------------------------------------------------------------- 1 | import { request } from "undici"; 2 | import { VideoStream } from "./videoInfo"; 3 | import { cookieJar } from "./cookies"; 4 | import { 5 | constants, 6 | UndiciRequestOptions, 7 | contentBetween, 8 | contentBetweenEnds, 9 | mergeObj, 10 | assertUndiciOkResponse, 11 | } from "./utils"; 12 | 13 | export interface ExtractStreamInfoOptions { 14 | requestOptions?: UndiciRequestOptions; 15 | } 16 | 17 | /** 18 | * Get only stream information about a YouTube video. 19 | * 20 | * Note: This might break often. 21 | */ 22 | export const extractStreamInfo = async ( 23 | url: string, 24 | options: ExtractStreamInfoOptions = {} 25 | ) => { 26 | if (typeof url !== "string") { 27 | throw new Error(constants.errors.type("url", "string", typeof url)); 28 | } 29 | if (typeof options !== "object") { 30 | throw new Error( 31 | constants.errors.type("options", "object", typeof options) 32 | ); 33 | } 34 | 35 | options = mergeObj( 36 | { 37 | requestOptions: { 38 | headers: { 39 | "User-Agent": constants.requestOptions.userAgent, 40 | Cookie: cookieJar.cookieHeaderValue(), 41 | }, 42 | maxRedirections: constants.requestOptions.maxRedirections, 43 | }, 44 | }, 45 | options 46 | ); 47 | 48 | if (!url.startsWith("http")) { 49 | url = constants.urls.video.base(url); 50 | } 51 | 52 | let data: string; 53 | try { 54 | const resp = await request(url, options.requestOptions); 55 | assertUndiciOkResponse(resp); 56 | data = await resp.body.text(); 57 | cookieJar.utilizeResponseHeaders(resp.headers); 58 | } catch (err) { 59 | throw new Error(`Failed to fetch url "${url}". (${err})`); 60 | } 61 | 62 | let streamingData: any; 63 | try { 64 | const streamingDataRaw = contentBetweenEnds(data, '"streamingData":', [ 65 | ['},"playbackTracking":{', "}"], 66 | ['}]},"', "}]}"], 67 | ]); 68 | streamingData = JSON.parse(streamingDataRaw); 69 | } catch (err) { 70 | throw new Error(`Failed to parse data from webpage. (${err})`); 71 | } 72 | 73 | const stream: VideoStream = streamingData; 74 | prepareStreamInfo(data, stream); 75 | 76 | return stream; 77 | }; 78 | 79 | export const prepareStreamInfo = (data: string, stream: VideoStream) => { 80 | try { 81 | const playerJsURL = contentBetween(data, '"PLAYER_JS_URL":"', '"'); 82 | stream.player = { 83 | url: constants.urls.base + playerJsURL, 84 | }; 85 | } catch (err) {} 86 | }; 87 | 88 | export default extractStreamInfo; 89 | -------------------------------------------------------------------------------- /src/getFormats.ts: -------------------------------------------------------------------------------- 1 | import type NodeVM from "vm"; 2 | import type OhMyVm from "@ohmyvm/vm"; 3 | import type IsolatedVM from "isolated-vm"; 4 | import { request } from "undici"; 5 | import { VideoStream, VideoFormat } from "./videoInfo"; 6 | import { cookieJar } from "./cookies"; 7 | import { 8 | UndiciRequestOptions, 9 | assertUndiciOkResponse, 10 | constants, 11 | contentBetween, 12 | isModuleInstalled, 13 | mergeObj, 14 | parseNumberOr, 15 | parseQueryString, 16 | requireOrThrow, 17 | } from "./utils"; 18 | 19 | export type GetFormatsEvaluator = 20 | | "auto" 21 | | "eval" 22 | | "vm" 23 | | "isolated-vm" 24 | | "ohmyvm" 25 | | GetFormatsCustomEvaluator; 26 | 27 | export type GetFormatsCustomEvaluator = ( 28 | code: string 29 | ) => Promise; 30 | 31 | interface GetFormatsEvaluatorResult { 32 | decoder: (a: string) => string; 33 | isDisposed: () => boolean; 34 | dispose: () => void; 35 | } 36 | 37 | export interface GetFormatsOptions { 38 | requestOptions?: UndiciRequestOptions; 39 | filterBy?: (value: VideoFormat) => boolean; 40 | evaluator?: GetFormatsEvaluator; 41 | } 42 | 43 | /** 44 | * Generates Stream URL(s). 45 | * 46 | * Always use this to get streams before getting readable streams! 47 | */ 48 | export const getFormats = async ( 49 | stream: VideoStream, 50 | options: GetFormatsOptions = {} 51 | ) => { 52 | if (typeof stream !== "object") { 53 | throw new Error( 54 | constants.errors.type("formats", "object", typeof stream) 55 | ); 56 | } 57 | if (typeof options !== "object") { 58 | throw new Error( 59 | constants.errors.type("options", "object", typeof options) 60 | ); 61 | } 62 | 63 | options = mergeObj( 64 | { 65 | requestOptions: { 66 | headers: { 67 | "User-Agent": constants.requestOptions.userAgent, 68 | Cookie: cookieJar.cookieHeaderValue(), 69 | }, 70 | maxRedirections: constants.requestOptions.maxRedirections, 71 | }, 72 | }, 73 | options 74 | ); 75 | 76 | const resolved: VideoFormat[] = []; 77 | 78 | let directFormats = [ 79 | ...(stream.formats || []), 80 | ...(stream.adaptiveFormats || []), 81 | ].sort( 82 | (a, b) => 83 | (a.bitrate ? +a.bitrate : 0) - 84 | (b.bitrate ? +b.bitrate : 0) + 85 | (a.audioSampleRate ? parseInt(a.audioSampleRate) : 0) - 86 | (b.audioSampleRate ? parseInt(b.audioSampleRate) : 0) 87 | ); 88 | if (typeof options.filterBy === "function") { 89 | directFormats = directFormats.filter(options.filterBy); 90 | } 91 | 92 | let decipher: GetFormatsEvaluatorResult | undefined; 93 | try { 94 | for (const x of directFormats) { 95 | if (!(options.filterBy?.(x) ?? true)) { 96 | continue; 97 | } 98 | if (stream.player?.url && x.signatureCipher) { 99 | decipher ??= await getCipherFunction(stream.player.url, { 100 | requestOptions: options.requestOptions, 101 | }); 102 | const cipherData = parseQueryString(x.signatureCipher) as { 103 | url: string; 104 | sp: string; 105 | s: string; 106 | }; 107 | x.url = `${cipherData.url}&${cipherData.sp}=${decipher.decoder( 108 | cipherData.s 109 | )}`; 110 | x.__decoded = true; 111 | } 112 | // not really sure about this. 113 | if (x.url?.startsWith("https://")) { 114 | x.__decoded = true; 115 | } 116 | resolved.push(x); 117 | } 118 | decipher?.dispose(); 119 | } catch (err) { 120 | if (decipher && !decipher.isDisposed()) { 121 | decipher.dispose(); 122 | } 123 | throw err; 124 | } 125 | 126 | if (stream.hlsManifestUrl) { 127 | const hlsResp = await request( 128 | stream.hlsManifestUrl, 129 | options.requestOptions 130 | ); 131 | assertUndiciOkResponse(hlsResp); 132 | const hlsData = await hlsResp.body.text(); 133 | cookieJar.utilizeResponseHeaders(hlsResp.headers); 134 | 135 | const hlsStreams = hlsData.matchAll( 136 | /#EXT-X-STREAM-INF:([^\n]*)\n([^\n]+)/g 137 | ); 138 | for (const x of hlsStreams) { 139 | const [, tagsRaw, url] = x; 140 | if (!url) continue; 141 | 142 | const tags: Record = {}; 143 | if (tagsRaw) { 144 | for (const x of tagsRaw.matchAll(/(\w+)=([^,\n]+)/g)) { 145 | const [, k, v] = x; 146 | if (k && v) { 147 | tags[k] = v; 148 | } 149 | } 150 | } 151 | 152 | const codecs = tags["CODECS"]; 153 | const resolution = tags["RESOLUTION"]?.split("x") ?? []; 154 | 155 | resolved.push({ 156 | itag: parseNumberOr(url.match(/itag\/(\d+)\//)?.[1], 0), 157 | url, 158 | mimeType: codecs ? `codes=${codecs[1]}` : "", 159 | contentLength: tags["BANDWIDTH"] ?? "0", 160 | fps: parseNumberOr(tags["RATE"], 0), 161 | height: parseNumberOr(resolution[1], 0), 162 | width: parseNumberOr(resolution[0], 0), 163 | __decoded: true, 164 | }); 165 | } 166 | } 167 | 168 | return resolved; 169 | }; 170 | 171 | const getCipherFunction = async ( 172 | url: string, 173 | options: { 174 | requestOptions?: UndiciRequestOptions; 175 | evaluator?: GetFormatsEvaluator; 176 | } = {} 177 | ): Promise => { 178 | const resp = await request(url, options.requestOptions); 179 | assertUndiciOkResponse(resp); 180 | const data = await resp.body.text(); 181 | 182 | const aFuncStart = 'a=a.split("")'; 183 | const aFuncEnd = "};"; 184 | const aFuncBody = contentBetween(data, aFuncStart, aFuncEnd); 185 | const aFunc = "(a) => {" + aFuncStart + aFuncBody + aFuncEnd; 186 | 187 | const bVar = contentBetween(aFuncBody, ";", "."); 188 | const bVarStart = `var ${bVar}=`; 189 | const bVarEnd = "}};"; 190 | const bFuncBody = contentBetween(data, bVarStart, bVarEnd); 191 | const bFunc = bVarStart + bFuncBody + bVarEnd; 192 | 193 | const decoderCode = aFunc + "\n" + bFunc; 194 | 195 | let evaluator: GetFormatsCustomEvaluator; 196 | if (typeof options.evaluator === "function") { 197 | evaluator = options.evaluator; 198 | } else if ( 199 | typeof options.evaluator === "string" && 200 | options.evaluator !== "auto" 201 | ) { 202 | switch (options.evaluator) { 203 | case "ohmyvm": 204 | evaluator = evalInOhMyVM; 205 | break; 206 | 207 | case "isolated-vm": 208 | evaluator = evalInIsolatedVM; 209 | break; 210 | 211 | case "vm": 212 | evaluator = evalInNodeVM; 213 | break; 214 | 215 | case "eval": 216 | evaluator = evalInEval; 217 | break; 218 | } 219 | } else { 220 | if (isModuleInstalled("@ohmyvm/vm")) { 221 | evaluator = evalInOhMyVM; 222 | } else if (isModuleInstalled("isolated-vm")) { 223 | evaluator = evalInIsolatedVM; 224 | } else if (isModuleInstalled("vm")) { 225 | evaluator = evalInNodeVM; 226 | } else { 227 | evaluator = evalInEval; 228 | } 229 | } 230 | 231 | const result = await evaluator(decoderCode); 232 | return result; 233 | }; 234 | 235 | const evalInEval: GetFormatsCustomEvaluator = async (code: string) => { 236 | return { 237 | decoder: eval(code), 238 | isDisposed: () => true, 239 | dispose: () => {}, 240 | }; 241 | }; 242 | const evalInNodeVM: GetFormatsCustomEvaluator = async (code: string) => { 243 | const vm: typeof NodeVM = requireOrThrow("vm"); 244 | return { 245 | decoder: vm.runInNewContext(code), 246 | isDisposed: () => true, 247 | dispose: () => {}, 248 | }; 249 | }; 250 | 251 | const evalInOhMyVM: GetFormatsCustomEvaluator = async (code: string) => { 252 | const vm: typeof OhMyVm = requireOrThrow("@ohmyvm/vm"); 253 | const context = new vm.OhMyVm(); 254 | 255 | return { 256 | decoder: (str: string): string => { 257 | const src = `var __cafeBabe__ = ${code}${ 258 | code.endsWith(";") ? "" : ";" 259 | }__cafeBabe__("${str}");`; 260 | 261 | const output = context.eval(Buffer.from(src)); 262 | 263 | return output.replace(/"/g, ""); 264 | }, 265 | isDisposed: () => true, 266 | dispose: () => {}, 267 | }; 268 | }; 269 | 270 | const evalInIsolatedVM: GetFormatsCustomEvaluator = async ( 271 | code: string, 272 | options: { 273 | memoryLimit?: number; 274 | } = {} 275 | ) => { 276 | const ivm: typeof IsolatedVM = requireOrThrow("isolated-vm"); 277 | const isolate = new ivm.Isolate({ memoryLimit: options?.memoryLimit ?? 8 }); 278 | const context = isolate.createContextSync(); 279 | return { 280 | decoder: await context.eval(code), 281 | isDisposed: () => isolate.isDisposed, 282 | dispose: () => { 283 | if (isolate.isDisposed) return; 284 | isolate.dispose(); 285 | }, 286 | }; 287 | }; 288 | -------------------------------------------------------------------------------- /src/getReadableStream.ts: -------------------------------------------------------------------------------- 1 | import { PassThrough, type Readable } from "stream"; 2 | import type M3U8Stream from "m3u8stream"; 3 | import { request } from "undici"; 4 | import { 5 | UndiciRequestOptions, 6 | constants, 7 | mergeObj, 8 | requireOrThrow, 9 | isDashContentURL, 10 | isHlsContentURL, 11 | isLiveContentURL, 12 | } from "./utils"; 13 | 14 | export interface GetReadableStreamOptions { 15 | begin?: number; 16 | end?: number; 17 | requestOptions?: UndiciRequestOptions; 18 | m3u8streamRequestOptions?: M3U8Stream.Options["requestOptions"]; 19 | } 20 | 21 | /** 22 | * Returns a YouTube stream. 23 | * 24 | * - Install "m3u8stream" using `npm install m3u8stream` for livestream support. 25 | */ 26 | export const getReadableStream = async ( 27 | stream: { url: string; contentLength?: number | string }, 28 | options: GetReadableStreamOptions = {} 29 | ): Promise => { 30 | if (typeof stream !== "object") { 31 | throw new Error( 32 | constants.errors.type("streams", "object", typeof stream) 33 | ); 34 | } 35 | if (typeof options !== "object") { 36 | throw new Error( 37 | constants.errors.type("options", "object", typeof options) 38 | ); 39 | } 40 | 41 | const commonRequestOptions = { 42 | headers: { 43 | "User-Agent": constants.requestOptions.userAgent, 44 | }, 45 | maxRedirections: constants.requestOptions.maxRedirections, 46 | }; 47 | options = mergeObj( 48 | { 49 | requestOptions: commonRequestOptions, 50 | m3u8streamRequestOptions: commonRequestOptions, 51 | }, 52 | options 53 | ); 54 | 55 | if (isDashContentURL(stream.url) || isHlsContentURL(stream.url)) { 56 | const m3u8stream: typeof M3U8Stream = requireOrThrow("m3u8stream"); 57 | let begin = options.begin; 58 | if (typeof begin === "undefined" && isLiveContentURL(stream.url)) { 59 | begin = Date.now(); 60 | } 61 | return m3u8stream(stream.url, { 62 | begin, 63 | requestOptions: options.m3u8streamRequestOptions, 64 | }); 65 | } 66 | 67 | let contentLength = 68 | typeof stream.contentLength === "string" 69 | ? parseInt(stream.contentLength) 70 | : stream.contentLength; 71 | let streamURL = stream.url; 72 | if (typeof options.begin === "number") { 73 | streamURL += `&begin=${options.begin}`; 74 | } 75 | const output = new PassThrough(); 76 | let received = 0; 77 | const requestData = async (): Promise => { 78 | mergeObj(options.requestOptions, { 79 | headers: { 80 | range: `bytes=${received}-${contentLength ?? ""}`, 81 | }, 82 | }); 83 | const resp = await request(streamURL, options.requestOptions); 84 | if (typeof resp.headers["content-length"] === "string") { 85 | contentLength = parseInt(resp.headers["content-length"]!); 86 | } 87 | resp.body.pause(); 88 | resp.body.on("data", (data) => { 89 | received += data.length; 90 | }); 91 | resp.body.pipe(output, { end: false }); 92 | resp.body.once("end", (): void => { 93 | if (output.destroyed) return; 94 | if (received < (contentLength ?? -1)) { 95 | requestData(); 96 | return; 97 | } 98 | output.push(null); 99 | }); 100 | }; 101 | requestData(); 102 | return output; 103 | }; 104 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./search"; 2 | export * from "./videoInfo"; 3 | export * from "./playlistInfo"; 4 | export * from "./channelInfo"; 5 | export * from "./getReadableStream"; 6 | export * from "./extractStreamInfo"; 7 | export * from "./getFormats"; 8 | export * from "./cookies"; 9 | export * as utils from "./utils"; 10 | 11 | /** 12 | * Package version. 13 | */ 14 | export const version: string = require("../package.json").version; 15 | -------------------------------------------------------------------------------- /src/playlistInfo.ts: -------------------------------------------------------------------------------- 1 | import { request } from "undici"; 2 | import { cookieJar } from "./cookies"; 3 | import { 4 | UndiciRequestOptions, 5 | assertUndiciOkResponse, 6 | constants, 7 | contentBetween, 8 | mergeObj, 9 | } from "./utils"; 10 | 11 | export interface PlaylistInfoOptions { 12 | requestOptions?: UndiciRequestOptions; 13 | } 14 | 15 | export interface PlaylistVideo { 16 | title: string; 17 | id: string; 18 | url: string; 19 | channel: { 20 | name: string; 21 | id: string; 22 | url: string; 23 | }; 24 | thumbnails: { 25 | url: string; 26 | width: number; 27 | height: number; 28 | }[]; 29 | duration: { 30 | pretty: string; 31 | text: string; 32 | lengthSec: string; 33 | }; 34 | } 35 | 36 | export interface PlaylistInfo { 37 | title: string; 38 | id: string; 39 | url: string; 40 | description: string; 41 | videos: PlaylistVideo[]; 42 | thumbnails: { 43 | url: string; 44 | width: number; 45 | height: number; 46 | }[]; 47 | } 48 | 49 | /** 50 | * Get full information about a YouTube playlist. 51 | */ 52 | export const playlistInfo = async ( 53 | url: string, 54 | options: PlaylistInfoOptions = {} 55 | ) => { 56 | if (typeof url !== "string") { 57 | throw new Error(constants.errors.type("url", "string", typeof url)); 58 | } 59 | if (typeof options !== "object") { 60 | throw new Error( 61 | constants.errors.type("options", "object", typeof options) 62 | ); 63 | } 64 | 65 | options = mergeObj( 66 | { 67 | requestOptions: { 68 | headers: { 69 | "User-Agent": constants.requestOptions.userAgent, 70 | Cookie: cookieJar.cookieHeaderValue(), 71 | }, 72 | maxRedirections: constants.requestOptions.maxRedirections, 73 | }, 74 | }, 75 | options 76 | ); 77 | 78 | const id = url.match(constants.urls.playlist.getIdRegex)?.[2] ?? url; 79 | if (!url.startsWith("http")) { 80 | url = constants.urls.playlist.base(id); 81 | } 82 | 83 | let data: string; 84 | try { 85 | const resp = await request(url, options.requestOptions); 86 | assertUndiciOkResponse(resp); 87 | data = await resp.body.text(); 88 | cookieJar.utilizeResponseHeaders(resp.headers); 89 | } catch (err) { 90 | throw new Error(`Failed to fetch url "${url}". (${err})`); 91 | } 92 | 93 | let initialDataRaw: string; 94 | try { 95 | initialDataRaw = contentBetween( 96 | data, 97 | "var ytInitialData = ", 98 | ";" 99 | ); 100 | } catch (err) { 101 | throw new Error(`Failed to parse data from webpage. (${err})`); 102 | } 103 | 104 | let contents: any; 105 | try { 106 | const raw = initialDataRaw.substring( 107 | initialDataRaw.lastIndexOf( 108 | '"playlistVideoListRenderer":{"contents":' 109 | ) + 40, 110 | initialDataRaw.lastIndexOf('],"playlistId"') + 1 111 | ); 112 | contents = JSON.parse(raw); 113 | } catch (err) { 114 | throw new Error(`Failed to parse contents from data. (${err})`); 115 | } 116 | 117 | let microformat: any; 118 | try { 119 | const raw = initialDataRaw.substring( 120 | initialDataRaw.lastIndexOf('"microformat":') + 14, 121 | initialDataRaw.lastIndexOf(',"sidebar"') 122 | ); 123 | microformat = JSON.parse(raw); 124 | } catch (err) { 125 | throw new Error(`Failed to parse micro-formats from data. (${err})`); 126 | } 127 | 128 | const playlist: PlaylistInfo = { 129 | title: microformat?.microformatDataRenderer?.title, 130 | id, 131 | url: microformat?.microformatDataRenderer?.urlCanonical, 132 | description: microformat?.microformatDataRenderer?.description, 133 | videos: [], 134 | thumbnails: microformat?.microformatDataRenderer?.thumbnail?.thumbnails, 135 | }; 136 | 137 | for (const { playlistVideoRenderer } of contents) { 138 | if (playlistVideoRenderer) { 139 | const video = parsePlaylistVideo(playlistVideoRenderer); 140 | playlist.videos.push(video); 141 | } 142 | } 143 | 144 | try { 145 | const initialContinuationToken = contentBetween( 146 | data, 147 | '"continuationCommand":{"token":"', 148 | '","' 149 | ); 150 | const innerTubeRaw = contentBetween( 151 | data, 152 | '"INNERTUBE_API_KEY":', 153 | ',"INNERTUBE_CONTEXT":' 154 | ); 155 | const { INNERTUBE_API_KEY, INNERTUBE_CLIENT_VERSION } = JSON.parse( 156 | '{"INNERTUBE_API_KEY":' + innerTubeRaw + "}" 157 | ); 158 | let continuationToken: string | undefined = initialContinuationToken; 159 | while (continuationToken) { 160 | const resp = await request( 161 | constants.urls.playlist.continuation(INNERTUBE_API_KEY), 162 | { 163 | ...options.requestOptions, 164 | method: "POST", 165 | body: JSON.stringify({ 166 | continuation: continuationToken, 167 | context: { 168 | client: { 169 | utcOffsetMinutes: 0, 170 | gl: "US", 171 | hl: "en", 172 | clientName: "WEB", 173 | clientVersion: INNERTUBE_CLIENT_VERSION, 174 | }, 175 | user: {}, 176 | request: {}, 177 | }, 178 | }), 179 | } 180 | ); 181 | assertUndiciOkResponse(resp); 182 | const data = (await resp.body.json()) as any; 183 | continuationToken = undefined; 184 | for (const x of data?.onResponseReceivedActions ?? []) { 185 | for (const { 186 | playlistVideoRenderer, 187 | continuationItemRenderer, 188 | } of x?.appendContinuationItemsAction?.continuationItems) { 189 | if (playlistVideoRenderer) { 190 | const video = parsePlaylistVideo(playlistVideoRenderer); 191 | playlist.videos.push(video); 192 | } 193 | if (continuationItemRenderer) { 194 | const nextContinuationToken: string | undefined = 195 | continuationItemRenderer?.continuationEndpoint 196 | ?.continuationCommand?.token as 197 | | string 198 | | undefined; 199 | continuationToken = nextContinuationToken; 200 | } 201 | } 202 | } 203 | } 204 | } catch (err) {} 205 | 206 | return playlist; 207 | }; 208 | 209 | export default playlistInfo; 210 | 211 | const parsePlaylistVideo = (x: any) => { 212 | const video: PlaylistVideo = { 213 | title: x?.title?.runs[0]?.text, 214 | id: x?.videoId, 215 | url: 216 | constants.urls.base + 217 | x?.navigationEndpoint?.commandMetadata?.webCommandMetadata?.url, 218 | channel: { 219 | name: x?.shortBylineText?.runs[0]?.text, 220 | id: x?.shortBylineText?.runs[0]?.navigationEndpoint?.commandMetadata 221 | ?.webCommandMetadata?.url, 222 | url: 223 | constants.urls.base + 224 | x?.shortBylineText?.runs[0]?.navigationEndpoint?.browseEndpoint 225 | ?.browseId, 226 | }, 227 | thumbnails: x?.thumbnail?.thumbnails, 228 | duration: { 229 | pretty: x?.lengthText?.simpleText, 230 | text: x?.lengthText?.accessibility?.accessibilityData?.label, 231 | lengthSec: x?.lengthSeconds, 232 | }, 233 | }; 234 | return video; 235 | }; 236 | -------------------------------------------------------------------------------- /src/search.ts: -------------------------------------------------------------------------------- 1 | import { request } from "undici"; 2 | import { cookieJar } from "./cookies"; 3 | import { 4 | UndiciRequestOptions, 5 | assertUndiciOkResponse, 6 | constants, 7 | mergeObj, 8 | } from "./utils"; 9 | 10 | export interface SearchOptions { 11 | requestOptions?: UndiciRequestOptions; 12 | filterType?: keyof typeof constants.urls.search.filters; 13 | } 14 | 15 | export interface SearchVideo { 16 | title: string; 17 | id: string; 18 | url: string; 19 | channel: { 20 | name: string; 21 | id: string; 22 | url: string; 23 | }; 24 | duration: { 25 | text: string; 26 | pretty: string; 27 | }; 28 | published: { 29 | pretty: string; 30 | }; 31 | views: { 32 | text: string; 33 | pretty: string; 34 | prettyLong: string; 35 | }; 36 | thumbnails: { 37 | url: string; 38 | width: number; 39 | height: number; 40 | }[]; 41 | } 42 | 43 | export interface SearchChannel { 44 | name: string; 45 | id: string; 46 | url: string; 47 | subscribers: { 48 | text: string; 49 | pretty: string; 50 | }; 51 | icons: { 52 | url: string; 53 | width: number; 54 | height: number; 55 | }[]; 56 | badges: string[]; 57 | } 58 | 59 | export interface SearchPlaylist { 60 | name: string; 61 | id: string; 62 | url: string; 63 | thumbnails: { 64 | url: string; 65 | width: number; 66 | height: number; 67 | }[]; 68 | videoCount: string; 69 | published: { 70 | pretty?: string; 71 | }; 72 | } 73 | 74 | /** 75 | * Search for videos, channels, playlists, etc... 76 | */ 77 | export const search = async (terms: string, options: SearchOptions = {}) => { 78 | if (typeof terms !== "string") { 79 | throw new Error(constants.errors.type("terms", "string", typeof terms)); 80 | } 81 | if (typeof options !== "object") { 82 | throw new Error( 83 | constants.errors.type("options", "object", typeof options) 84 | ); 85 | } 86 | 87 | options = mergeObj( 88 | { 89 | requestOptions: { 90 | headers: { 91 | "User-Agent": constants.requestOptions.userAgent, 92 | Cookie: cookieJar.cookieHeaderValue(), 93 | }, 94 | maxRedirections: constants.requestOptions.maxRedirections, 95 | }, 96 | }, 97 | options 98 | ); 99 | 100 | let url = constants.urls.search.base(terms); 101 | if ( 102 | options.filterType && 103 | constants.urls.search.filters[options.filterType] 104 | ) { 105 | url += constants.urls.search.filters[options.filterType]; 106 | } 107 | 108 | let data: string; 109 | try { 110 | const resp = await request(url, options.requestOptions); 111 | assertUndiciOkResponse(resp); 112 | data = await resp.body.text(); 113 | cookieJar.utilizeResponseHeaders(resp.headers); 114 | } catch (err) { 115 | throw new Error(`Failed to fetch url "${url}". (${err})`); 116 | } 117 | 118 | let contents: any; 119 | try { 120 | const raw = data.substring( 121 | data.lastIndexOf( 122 | '"sectionListRenderer":{"contents":[{"itemSectionRenderer":' 123 | ) + 58, 124 | data.lastIndexOf('},{"continuationItemRenderer"') 125 | ); 126 | contents = JSON.parse(raw)?.contents; 127 | } catch (err) { 128 | throw new Error(`Failed to parse contents from data. (${err})`); 129 | } 130 | 131 | const result: { 132 | videos: SearchVideo[]; 133 | channels: SearchChannel[]; 134 | playlists: SearchPlaylist[]; 135 | } = { 136 | videos: [], 137 | channels: [], 138 | playlists: [], 139 | }; 140 | 141 | for (const { 142 | videoRenderer, 143 | channelRenderer, 144 | playlistRenderer, 145 | } of contents) { 146 | if (videoRenderer) { 147 | const x = videoRenderer; 148 | const video: SearchVideo = { 149 | title: x?.title?.runs[0]?.text, 150 | id: x?.videoId, 151 | url: 152 | constants.urls.base + 153 | x?.navigationEndpoint?.commandMetadata?.webCommandMetadata 154 | ?.url, 155 | channel: { 156 | name: x?.ownerText?.runs[0]?.text, 157 | id: x?.ownerText?.runs[0]?.navigationEndpoint 158 | ?.browseEndpoint?.browseId, 159 | url: 160 | constants.urls.base + 161 | x?.ownerText?.runs[0]?.navigationEndpoint 162 | ?.commandMetadata?.webCommandMetadata?.url, 163 | }, 164 | duration: { 165 | text: x?.lengthText?.simpleText, 166 | pretty: x?.lengthText?.accessibility?.accessibilityData 167 | ?.label, 168 | }, 169 | published: { 170 | pretty: x?.publishedTimeText?.simpleText, 171 | }, 172 | views: { 173 | text: x?.viewCountText?.simpleText, 174 | pretty: x?.shortViewCountText?.simpleText, 175 | prettyLong: 176 | x?.shortViewCountText?.accessibility?.accessibilityData 177 | ?.label, 178 | }, 179 | thumbnails: x?.thumbnail?.thumbnails, 180 | }; 181 | result.videos.push(video); 182 | } 183 | 184 | if (channelRenderer) { 185 | const x = channelRenderer; 186 | const channel: SearchChannel = { 187 | name: x?.title?.simpleText, 188 | id: x?.channelId, 189 | url: 190 | constants.urls.base + 191 | x?.navigationEndpoint?.browseEndpoint?.canonicalBaseUrl, 192 | // TODO: ensure if its `videoCountText` or `subscriberCountText` 193 | subscribers: { 194 | text: x?.videoCountText?.accessibility?.accessibilityData 195 | ?.label, 196 | pretty: x?.videoCountText?.simpleText, 197 | }, 198 | icons: x?.thumbnail?.thumbnails, 199 | badges: ((x?.ownerBadges ?? []) as any[])?.reduce((pv, cv) => { 200 | const name = cv?.metadataBadgeRenderer?.tooltip; 201 | if (name) pv.push(name); 202 | return pv; 203 | }, [] as string[]), 204 | }; 205 | result.channels.push(channel); 206 | } 207 | 208 | if (playlistRenderer) { 209 | const x = playlistRenderer; 210 | const playlist: SearchPlaylist = { 211 | name: x?.title?.simpleText, 212 | id: x?.playlistId, 213 | url: 214 | constants.urls.base + 215 | x?.navigationEndpoint?.commandMetadata?.webCommandMetadata 216 | ?.url, 217 | thumbnails: 218 | x?.thumbnailRenderer?.playlistVideoThumbnailRenderer 219 | ?.thumbnail?.thumbnails, 220 | videoCount: x?.videoCount, 221 | published: { 222 | pretty: x?.publishedTimeText?.simpleText, 223 | }, 224 | }; 225 | result.playlists.push(playlist); 226 | } 227 | } 228 | 229 | return result; 230 | }; 231 | 232 | export default search; 233 | -------------------------------------------------------------------------------- /src/utils/common.ts: -------------------------------------------------------------------------------- 1 | export const mergeObj = (one: T, two: T) => { 2 | for (const key in two) { 3 | if (Object.prototype.hasOwnProperty.call(two, key)) { 4 | const ele = two[key]; 5 | if (typeof ele === "object") one[key] = mergeObj(one[key], ele); 6 | else one[key] = ele; 7 | } 8 | } 9 | return one; 10 | }; 11 | 12 | export const contentBetween = (data: string, start: string, end: string) => { 13 | const first = data.split(start, 2)[1]; 14 | if (typeof first !== "string") { 15 | throw new Error(`Unable to match prefix (${start})`); 16 | } 17 | const second = first.split(end, 1)[0]; 18 | if (typeof second !== "string") { 19 | throw new Error(`Unable to match suffix (${end})`); 20 | } 21 | return second; 22 | }; 23 | 24 | export const contentBetweenEnds = ( 25 | data: string, 26 | start: string, 27 | ends: [string, string][] 28 | ) => { 29 | const first = data.split(start, 2)[1]!; 30 | for (const [x, y] of ends) { 31 | const second = first.split(x, 1)[0]!; 32 | if (second.length !== first.length) { 33 | return second + y; 34 | } 35 | } 36 | throw new Error( 37 | `Unable to match any of the suffixes (${JSON.stringify(ends)})` 38 | ); 39 | }; 40 | 41 | export const parseQueryString = (data: string) => { 42 | const params: Record = {}; 43 | data.split("&").forEach((x) => { 44 | const [k, v] = x.split("=") as [string, string]; 45 | params[k] = decodeURIComponent(v); 46 | }); 47 | return params; 48 | }; 49 | 50 | export const parseNumberOr = (data: string | undefined | null, def: number) => { 51 | if (typeof data === "string") { 52 | return parseInt(data); 53 | } 54 | return def; 55 | }; 56 | 57 | export const requireOrThrow = (moduleName: string): T => { 58 | try { 59 | const module: T = require(moduleName); 60 | return module; 61 | } catch (_) { 62 | throw new Error(`Couldn't access "${moduleName}". Did you install it?`); 63 | } 64 | }; 65 | 66 | export const isModuleInstalled = (moduleName: string) => { 67 | try { 68 | require(moduleName); 69 | return true; 70 | } catch (_) { 71 | return false; 72 | } 73 | }; 74 | -------------------------------------------------------------------------------- /src/utils/constants.ts: -------------------------------------------------------------------------------- 1 | export const constants = { 2 | urls: { 3 | base: "https://www.youtube.com", 4 | search: { 5 | base: (terms: string) => 6 | `${ 7 | constants.urls.base 8 | }/results?search_query=${encodeURIComponent(terms)}`, 9 | filters: { 10 | video: "&sp=EgIQAQ%253D%253D", 11 | channel: "&sp=EgIQAg%253D%253D", 12 | playlist: "&sp=EgIQAw%253D%253D", 13 | film: "&sp=EgIQBA%253D%253D", 14 | programme: "&sp=EgIQBQ%253D%253D", 15 | }, 16 | }, 17 | video: { 18 | base: (id: string) => 19 | `${constants.urls.base}/watch?v=${encodeURIComponent(id)}`, 20 | }, 21 | playlist: { 22 | base: (id: string) => 23 | `${constants.urls.base}/playlist?list=${encodeURIComponent( 24 | id 25 | )}`, 26 | baseUrlRegex: /^(http|https:\/\/).*\/playlist?.*list=\w+/, 27 | getIdRegex: /^(http|https:\/\/).*list=(\w+)/, 28 | continuation: (key: string) => 29 | `${constants.urls.base}/youtubei/v1/browse?key=${key}`, 30 | }, 31 | channel: { 32 | base: (id: string) => `${constants.urls.base}/channel/${id}`, 33 | }, 34 | }, 35 | requestOptions: { 36 | userAgent: 37 | "Mozilla/5.0 (X11; Linux x86_64; rv:125.0) Gecko/20100101 Firefox/125.0", 38 | maxRedirections: 5, 39 | }, 40 | errors: { 41 | type: (key: string, expected: string, received: string) => 42 | `Expected "${key}" to be "${expected}" but received "${received}".`, 43 | }, 44 | }; 45 | -------------------------------------------------------------------------------- /src/utils/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./common"; 2 | export * from "./constants"; 3 | export * from "./undici"; 4 | export * from "./youtube"; 5 | -------------------------------------------------------------------------------- /src/utils/undici.ts: -------------------------------------------------------------------------------- 1 | import { Dispatcher, request } from "undici"; 2 | 3 | export type UndiciRequestOptions = NonNullable[1]>; 4 | 5 | export const assertUndiciOkResponse = (response: Dispatcher.ResponseData) => { 6 | if (response.statusCode !== 200) { 7 | throw new Error(`Unexpected status code ${response.statusCode}`); 8 | } 9 | }; 10 | -------------------------------------------------------------------------------- /src/utils/youtube.ts: -------------------------------------------------------------------------------- 1 | export const isLiveContentURL = (url?: string) => 2 | url?.includes("/yt_live_broadcast/") ?? false; 3 | 4 | export const isDashContentURL = (url?: string) => 5 | url?.includes("/dash/") ?? false; 6 | 7 | export const isHlsContentURL = (url?: string) => 8 | url?.includes("/hls_playlist/") ?? false; 9 | 10 | export const isAudioCodec = (codec?: string) => 11 | codec?.startsWith("audio/") ?? false; 12 | 13 | export const isVideoCodec = (codec?: string) => 14 | codec?.startsWith("video/") ?? false; 15 | 16 | export const youtubeURLRegex = 17 | /https?:\/\/(?:youtu\.be|(?:(?:www|m|music|gaming)\.)?youtube\.com)/; 18 | 19 | export const isYoutubeURL = (url?: string) => hasMatch(youtubeURLRegex, url); 20 | 21 | export const youtubeWatchURLRegex = /\/watch\?v=([a-zA-Z0-9-_]{11})/; 22 | 23 | export const getYoutubeVideoId = (url?: string) => 24 | url?.match(youtubeWatchURLRegex)?.[1]; 25 | 26 | export const isYoutubeWatchURL = (url?: string) => 27 | !!url?.match(youtubeWatchURLRegex); 28 | 29 | export const youtubePlaylistURLRegex = /\/playlist\?list=([A-Za-z0-9_]+)/; 30 | 31 | export const getYoutubePlaylistId = (url?: string) => 32 | url?.match(youtubePlaylistURLRegex)?.[1]; 33 | 34 | export const isYoutubePlaylistURL = (url?: string) => 35 | !!url?.match(youtubePlaylistURLRegex); 36 | 37 | const hasMatch = (regex: RegExp, value: string | undefined) => 38 | !!value?.match(regex); 39 | 40 | const keywordsRegex = /("[^"]+"|[^\s]+)/g; 41 | 42 | export const parseYoutubeKeywords = (value: string) => { 43 | const keywords: string[] = []; 44 | for (const x of value.matchAll(keywordsRegex)) { 45 | let v = x[1]!; 46 | if (v[0] === '"') { 47 | v = v.substring(1, v.length - 1); 48 | } 49 | keywords.push(v); 50 | } 51 | return keywords; 52 | }; 53 | -------------------------------------------------------------------------------- /src/videoInfo.ts: -------------------------------------------------------------------------------- 1 | import { request } from "undici"; 2 | import { 3 | UndiciRequestOptions, 4 | assertUndiciOkResponse, 5 | constants, 6 | contentBetween, 7 | mergeObj, 8 | } from "./utils"; 9 | import { cookieJar } from "./cookies"; 10 | import { prepareStreamInfo } from "./extractStreamInfo"; 11 | 12 | export interface VideoInfoOptions { 13 | requestOptions?: UndiciRequestOptions; 14 | } 15 | 16 | export interface VideoFormat { 17 | /** 18 | * Used to check if stream was passed through `getFormats()`. 19 | */ 20 | __decoded?: boolean; 21 | 22 | itag?: number; 23 | /** 24 | * This will be `undefined`, if `getFormats()` is not called upon this. 25 | */ 26 | url: string; 27 | mimeType?: string; 28 | bitrate?: number; 29 | width?: number; 30 | height?: number; 31 | initRange?: { 32 | start: string; 33 | end: string; 34 | }; 35 | indexRange?: { 36 | start: string; 37 | end: string; 38 | }; 39 | lastModified?: string; 40 | contentLength?: string; 41 | quality?: string; 42 | fps?: number; 43 | qualityLabel?: string; 44 | projectionType?: string; 45 | averageBitrate?: number; 46 | approxDurationMs?: string; 47 | colorInfo?: { 48 | primaries: string; 49 | transferCharacteristics: string; 50 | matrixCoefficients: string; 51 | }; 52 | highReplication?: boolean; 53 | audioQuality?: string; 54 | audioSampleRate?: string; 55 | audioChannels?: number; 56 | loudnessDb?: number; 57 | targetDurationSec?: number; 58 | maxDvrDurationSec?: number; 59 | signatureCipher?: string; 60 | } 61 | 62 | export interface VideoStream { 63 | expiresInSeconds: string; 64 | formats: VideoFormat[]; 65 | adaptiveFormats: VideoFormat[]; 66 | dashManifestUrl?: string; 67 | hlsManifestUrl?: string; 68 | player?: { 69 | url: string; 70 | }; 71 | } 72 | 73 | export interface VideoInfo { 74 | title: string; 75 | id: string; 76 | url: string; 77 | shortDescription: string; 78 | description: string; 79 | channel: { 80 | name: string; 81 | id: string; 82 | url: string; 83 | subscribers: { 84 | pretty: string; 85 | }; 86 | icons: { 87 | url: string; 88 | width: number; 89 | height: number; 90 | }[]; 91 | }; 92 | duration: { 93 | lengthSec: string; 94 | }; 95 | thumbnails: { 96 | url: string; 97 | width: number; 98 | height: number; 99 | }[]; 100 | ratings: { 101 | likes: { 102 | text: string; 103 | pretty: string; 104 | }; 105 | dislikes: { 106 | text: string; 107 | pretty: string; 108 | }; 109 | }; 110 | views: { 111 | text: string; 112 | pretty: string; 113 | }; 114 | published: { 115 | text: string; 116 | pretty: string; 117 | }; 118 | uploaded: { 119 | text: string; 120 | }; 121 | keywords: string[]; 122 | isLive: boolean; 123 | isUnlisted: boolean; 124 | isFamilySafe: boolean; 125 | category: string; 126 | embed: { 127 | iframeUrl: string; 128 | flashUrl: string; 129 | height: number; 130 | width: number; 131 | flashSecureUrl: string; 132 | }; 133 | stream: VideoStream; 134 | } 135 | 136 | /** 137 | * Get full information about a YouTube video. 138 | */ 139 | export const videoInfo = async ( 140 | url: string, 141 | options: VideoInfoOptions = {} 142 | ) => { 143 | if (typeof url !== "string") { 144 | throw new Error(constants.errors.type("url", "string", typeof url)); 145 | } 146 | if (typeof options !== "object") { 147 | throw new Error( 148 | constants.errors.type("options", "object", typeof options) 149 | ); 150 | } 151 | 152 | options = mergeObj( 153 | { 154 | requestOptions: { 155 | headers: { 156 | "User-Agent": constants.requestOptions.userAgent, 157 | Cookie: cookieJar.cookieHeaderValue(), 158 | }, 159 | maxRedirections: constants.requestOptions.maxRedirections, 160 | }, 161 | }, 162 | options 163 | ); 164 | 165 | if (!url.startsWith("http")) { 166 | url = constants.urls.video.base(url); 167 | } 168 | 169 | let data: string; 170 | try { 171 | const resp = await request(url, options.requestOptions); 172 | assertUndiciOkResponse(resp); 173 | data = await resp.body.text(); 174 | cookieJar.utilizeResponseHeaders(resp.headers); 175 | } catch (err) { 176 | throw new Error(`Failed to fetch url "${url}". (${err})`); 177 | } 178 | 179 | let initialData: any; 180 | try { 181 | const initialDataRaw = contentBetween( 182 | data, 183 | "var ytInitialData = ", 184 | ";" 185 | ); 186 | initialData = JSON.parse(initialDataRaw); 187 | } catch (err) { 188 | throw new Error(`Failed to parse data from webpage. (${err})`); 189 | } 190 | 191 | let initialPlayer: any; 192 | try { 193 | const initialPlayerRaw = contentBetween( 194 | data, 195 | "var ytInitialPlayerResponse = ", 196 | ";var meta = " 197 | ); 198 | initialPlayer = JSON.parse(initialPlayerRaw); 199 | } catch (err) { 200 | throw new Error(`Failed to parse player data from webpage. (${err})`); 201 | } 202 | 203 | let contents: any[]; 204 | try { 205 | contents = 206 | initialData?.contents?.twoColumnWatchNextResults?.results?.results 207 | ?.contents; 208 | } catch (err) { 209 | throw new Error(`Failed to parse contents from webpage. (${err})`); 210 | } 211 | 212 | let primary: any; 213 | try { 214 | primary = contents?.find( 215 | (x: any) => x?.videoPrimaryInfoRenderer 216 | )?.videoPrimaryInfoRenderer; 217 | } catch (err) {} 218 | 219 | let secondary: any; 220 | try { 221 | secondary = contents?.find( 222 | (x: any) => x?.videoSecondaryInfoRenderer 223 | )?.videoSecondaryInfoRenderer; 224 | } catch (err) {} 225 | 226 | const info: VideoInfo = { 227 | title: primary?.title?.runs[0]?.text, 228 | id: initialData?.currentVideoEndpoint?.watchEndpoint?.videoId, 229 | url: 230 | constants.urls.base + 231 | initialData?.currentVideoEndpoint?.commandMetadata 232 | ?.webCommandMetadata?.url, 233 | shortDescription: initialPlayer?.videoDetails?.shortDescription, 234 | description: secondary?.description?.runs 235 | ?.map((x: any) => x?.text) 236 | ?.join(""), 237 | channel: { 238 | name: secondary?.owner?.videoOwnerRenderer?.title?.runs[0]?.text, 239 | id: secondary?.owner?.videoOwnerRenderer?.title?.runs[0] 240 | ?.navigationEndpoint?.browseEndpoint?.browseId, 241 | url: 242 | constants.urls.base + 243 | secondary?.owner?.videoOwnerRenderer?.title?.runs[0] 244 | ?.navigationEndpoint?.browseEndpoint?.canonicalBaseUrl, 245 | subscribers: { 246 | pretty: secondary?.owner?.videoOwnerRenderer 247 | ?.subscriberCountText?.simpleText, 248 | }, 249 | icons: secondary?.owner?.videoOwnerRenderer?.thumbnail?.thumbnails, 250 | }, 251 | duration: { 252 | lengthSec: initialPlayer?.videoDetails?.lengthSeconds, 253 | }, 254 | thumbnails: initialPlayer?.videoDetails?.thumbnail?.thumbnails, 255 | ratings: { 256 | likes: { 257 | text: primary?.videoActions?.menuRenderer?.topLevelButtons?.find( 258 | (x: any) => 259 | x?.toggleButtonRenderer?.defaultIcon?.iconType === 260 | "LIKE" 261 | )?.toggleButtonRenderer?.defaultText?.accessibility 262 | ?.accessibilityData?.label, 263 | pretty: primary?.videoActions?.menuRenderer?.topLevelButtons?.find( 264 | (x: any) => 265 | x?.toggleButtonRenderer?.defaultIcon?.iconType === 266 | "LIKE" 267 | )?.toggleButtonRenderer?.defaultText?.simpleText, 268 | }, 269 | dislikes: { 270 | text: primary?.videoActions?.menuRenderer?.topLevelButtons?.find( 271 | (x: any) => 272 | x?.toggleButtonRenderer?.defaultIcon?.iconType === 273 | "DISLIKE" 274 | )?.toggleButtonRenderer?.defaultText?.accessibility 275 | ?.accessibilityData?.label, 276 | pretty: primary?.videoActions?.menuRenderer?.topLevelButtons?.find( 277 | (x: any) => 278 | x?.toggleButtonRenderer?.defaultIcon?.iconType === 279 | "DISLIKE" 280 | )?.toggleButtonRenderer?.defaultText?.simpleText, 281 | }, 282 | }, 283 | views: { 284 | text: primary?.viewCount?.videoViewCountRenderer?.viewCount 285 | ?.simpleText, 286 | pretty: primary?.viewCount?.videoViewCountRenderer?.shortViewCount 287 | ?.simpleText, 288 | }, 289 | published: { 290 | pretty: primary?.dateText?.simpleText, 291 | text: initialPlayer?.microformat?.playerMicroformatRenderer 292 | ?.publishDate, 293 | }, 294 | uploaded: { 295 | text: initialPlayer?.microformat?.playerMicroformatRenderer 296 | ?.uploadDate, 297 | }, 298 | keywords: initialPlayer?.videoDetails?.keywords, 299 | isLive: initialPlayer?.videoDetails?.isLiveContent, 300 | isUnlisted: 301 | initialPlayer?.microformat?.playerMicroformatRenderer?.isUnlisted, 302 | isFamilySafe: 303 | initialPlayer?.microformat?.playerMicroformatRenderer?.isFamilySafe, 304 | category: 305 | initialPlayer?.microformat?.playerMicroformatRenderer?.category, 306 | embed: initialPlayer?.microformat?.playerMicroformatRenderer?.embed, 307 | stream: initialPlayer?.streamingData, 308 | }; 309 | prepareStreamInfo(data, info.stream); 310 | 311 | return info; 312 | }; 313 | 314 | export default videoInfo; 315 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "module": "commonjs", 5 | "declaration": true, 6 | "outDir": "./dist", 7 | "rootDir": "./src", 8 | "strict": true, 9 | "noImplicitAny": true, 10 | "strictNullChecks": true, 11 | "strictFunctionTypes": true, 12 | "strictBindCallApply": true, 13 | "strictPropertyInitialization": true, 14 | "noImplicitThis": true, 15 | "alwaysStrict": true, 16 | "noUnusedLocals": true, 17 | "noUnusedParameters": true, 18 | "noImplicitReturns": true, 19 | "noFallthroughCasesInSwitch": true, 20 | "noUncheckedIndexedAccess": true, 21 | "noPropertyAccessFromIndexSignature": true, 22 | "esModuleInterop": true, 23 | "skipLibCheck": true, 24 | "forceConsistentCasingInFileNames": true 25 | }, 26 | "include": ["src"] 27 | } 28 | -------------------------------------------------------------------------------- /typedoc.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "YouTube Extractor", 3 | "entryPoints": ["src/index.ts"], 4 | "out": "./docs-dist", 5 | "json": "./docs-dist/docs.json" 6 | } 7 | --------------------------------------------------------------------------------