Compare commits

...

13 Commits

Author SHA1 Message Date
Luan
4791deed28 chore: fix link to contributing guidelines in readme (round 2)
That didn't work as well as I expected.
2026-05-12 22:42:11 -03:00
Luan
b8545f538d chore: fix link to contributing guidelines in README 2026-05-12 22:40:20 -03:00
Luan
bd888f12aa chore(readme): Remove more redundant text
Why did I even write this garbage? Guess I'll never know.
2026-05-12 22:37:42 -03:00
Luan
4af77d4f5d chore: fix punctuation and add GitHub Sponsors badge
Corrected punctuation and added a badge for Absidue.
2026-05-12 22:10:32 -03:00
Luan
f66d782276 chore: simplify CONTRIBUTING.md
Old one was way too verbose, and contained weird, unnecessary wording.
2026-05-12 22:02:28 -03:00
Luan
717c67db67 chore: refine lang in the readme a bit 2026-05-12 21:48:29 -03:00
dependabot[bot]
1f47665e70 chore(deps): bump the npm_and_yarn group across 1 directory with 5 updates (#1170)
Bumps the npm_and_yarn group with 3 updates in the /examples/browser/web directory: [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite), [picomatch](https://github.com/micromatch/picomatch) and [undici](https://github.com/nodejs/undici).


Updates `vite` from 5.4.20 to 6.4.2
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v6.4.2/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v6.4.2/packages/vite)

Updates `picomatch` from 2.3.1 to 2.3.2
- [Release notes](https://github.com/micromatch/picomatch/releases)
- [Changelog](https://github.com/micromatch/picomatch/blob/master/CHANGELOG.md)
- [Commits](https://github.com/micromatch/picomatch/compare/2.3.1...2.3.2)

Updates `postcss` from 8.5.1 to 8.5.14
- [Release notes](https://github.com/postcss/postcss/releases)
- [Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/postcss/postcss/compare/8.5.1...8.5.14)

Updates `rollup` from 4.30.1 to 4.60.3
- [Release notes](https://github.com/rollup/rollup/releases)
- [Changelog](https://github.com/rollup/rollup/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rollup/rollup/compare/v4.30.1...v4.60.3)

Removes `undici`

---
updated-dependencies:
- dependency-name: vite
  dependency-version: 6.4.2
  dependency-type: direct:development
  dependency-group: npm_and_yarn
- dependency-name: picomatch
  dependency-version: 2.3.2
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: postcss
  dependency-version: 8.5.14
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: rollup
  dependency-version: 4.60.3
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: undici
  dependency-version: 
  dependency-type: indirect
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-12 21:33:02 -03:00
dependabot[bot]
12d07c6b16 chore(deps-dev): bump ts-proto from 2.11.5 to 2.11.8 (#1137)
Bumps [ts-proto](https://github.com/stephenh/ts-proto) from 2.11.5 to 2.11.8.
- [Release notes](https://github.com/stephenh/ts-proto/releases)
- [Changelog](https://github.com/stephenh/ts-proto/blob/main/CHANGELOG.md)
- [Commits](https://github.com/stephenh/ts-proto/compare/v2.11.5...v2.11.8)

---
updated-dependencies:
- dependency-name: ts-proto
  dependency-version: 2.11.2
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-12 19:36:13 -03:00
dependabot[bot]
847863c4eb chore(deps): bump @bufbuild/protobuf from 2.11.0 to 2.12.0 (#1134)
Bumps [@bufbuild/protobuf](https://github.com/bufbuild/protobuf-es/tree/HEAD/packages/protobuf) from 2.11.0 to 2.12.0.
- [Release notes](https://github.com/bufbuild/protobuf-es/releases)
- [Commits](https://github.com/bufbuild/protobuf-es/commits/v2.12.0/packages/protobuf)

---
updated-dependencies:
- dependency-name: "@bufbuild/protobuf"
  dependency-version: 2.11.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-12 19:26:38 -03:00
LuanRT
853a36307b feat(Parser): Add HypeFanCreditsSectionView 2026-05-12 19:11:35 -03:00
dependabot[bot]
358f4258bc chore(deps): bump the npm_and_yarn group across 1 directory with 4 updates (#1169)
Bumps the npm_and_yarn group with 4 updates in the / directory: [flatted](https://github.com/WebReflection/flatted), [picomatch](https://github.com/micromatch/picomatch), [postcss](https://github.com/postcss/postcss) and [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite).


Updates `flatted` from 3.4.1 to 3.4.2
- [Commits](https://github.com/WebReflection/flatted/compare/v3.4.1...v3.4.2)

Updates `picomatch` from 4.0.3 to 4.0.4
- [Release notes](https://github.com/micromatch/picomatch/releases)
- [Changelog](https://github.com/micromatch/picomatch/blob/master/CHANGELOG.md)
- [Commits](https://github.com/micromatch/picomatch/compare/4.0.3...4.0.4)

Updates `picomatch` from 2.3.1 to 2.3.2
- [Release notes](https://github.com/micromatch/picomatch/releases)
- [Changelog](https://github.com/micromatch/picomatch/blob/master/CHANGELOG.md)
- [Commits](https://github.com/micromatch/picomatch/compare/4.0.3...4.0.4)

Updates `postcss` from 8.5.8 to 8.5.14
- [Release notes](https://github.com/postcss/postcss/releases)
- [Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/postcss/postcss/compare/8.5.8...8.5.14)

Updates `vite` from 7.3.1 to 7.3.3
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v7.3.3/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v7.3.3/packages/vite)

---
updated-dependencies:
- dependency-name: flatted
  dependency-version: 3.4.2
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: picomatch
  dependency-version: 4.0.4
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: picomatch
  dependency-version: 2.3.2
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: postcss
  dependency-version: 8.5.14
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: vite
  dependency-version: 7.3.3
  dependency-type: indirect
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-12 17:29:55 -03:00
Luan
f748b8b362 feat(Search): Add support for refinement chips (#1167)
Also, this removes the old code used for refinement cards. Doesn't look like YouTube uses them anymore.
2026-05-12 17:27:55 -03:00
Luan
430fc70888 refactor(cache): Get rid of custom LZW compression (#1168)
It's just buggy and not very efficient, and I can't realistically improve it. Compression algorithms are complicated and would require quite a bit of effort to get right...

fflate is tiny, works anywhere, and is more efficient.

Also, here is an interesting article about compression from fflate's author :  https://gist.github.com/101arrowz/253f31eb5abc3d9275ab943003ffecad
2026-05-12 17:25:59 -03:00
16 changed files with 673 additions and 455 deletions

View File

@@ -8,7 +8,7 @@ This page lists the collaborators who have contributed to the development and su
Owner and maintainer.
## [Wykerd](https://github.com/wykerd/)
Initial parser implementation, several bug fixes, major refactorings and general maintenance.
Initial parser implementation, several bug fixes, major refactorings, and general maintenance.
## [MasterOfBob777](https://github.com/MasterOfBob777)
Bug fixes and TypeScript support.
@@ -17,4 +17,6 @@ Bug fixes and TypeScript support.
Major refactorings, improved YouTube Music support, and bug fixes.
## [Absidue](https://github.com/absidue)
Several bug fixes, new features & improved MPD support.
[![Github Sponsors](https://img.shields.io/badge/donate-30363D?style=flat-square&logo=GitHub-Sponsors&logoColor=#white)](https://github.com/sponsors/absidue)
Several bug fixes, new features & improved MPD support.

View File

@@ -1,24 +1,22 @@
Welcome to YouTube.js! We're thrilled to have you interested in contributing to our project. As a community-driven project, we believe in the power of collaboration and look forward to working with you. To get started, please follow our guidelines:
## Issues
### Creating a new issue
Before creating a new issue, we recommend searching for similar or related issues to avoid duplication efforts. However, if you can't find one, you're more than welcome to create a new issue using a relevant issue form. Please make sure to describe the issue as clearly and concisely as possible.
Before creating a new issue, search for similar or related issues to avoid duplication efforts. If you can't find one, you're more than welcome to create a new issue using a relevant form, and please make sure to describe the issue as clearly as possible.
### Solving an issue
If you want to lend a hand by solving an issue, it's always good to browse existing issues to find one that grabs your attention. You can narrow down the search using tags as filters. If you find an issue you'd like to help with, please feel free to open a Pull Request with a fix. We appreciate documentation updates and grammar fixes too!
If you want to help solve an issue, it's always good to browse existing issues to find one that grabs your attention, you can narrow down the search using tags as filters. Simple documentation updates and grammar fixes are welcome too.
## Making Changes
1. Fork the repository on GitHub.
2. Ensure that you have the latest Node.js v20 version installed.
3. Create a working branch and start making your changes and improvements!
2. Ensure that you have the latest Node.js version installed.
3. Create a working branch and start making your changes!
### Committing updates
When you're done with the changes, make sure to commit them. Don't forget to write a clear, descriptive commit message. We recommend following the [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) specification.
### Committing Your Changes
When you're done with the changes, make sure to commit them. Don't forget to write a clear, descriptive commit message. We follow the [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) specification.
### Creating a Pull Request
Once you're happy with your updates, create a pull request on GitHub. This is the most efficient way to get your contribution reviewed and eventually merged into our codebase.
Once you're happy with your changes, create a pull request on GitHub.
- Use the pull request template to fill in the necessary details.
- If you're solving an issue, link the pull request to that issue.
@@ -35,7 +33,7 @@ npm run test
Linting:
```sh
npm run lint
npm run lint:fix
```
Building:
@@ -55,12 +53,6 @@ npm run build:deno
# ES Module
npm run build:esm
# Node
npm run bundle:node
# Browser
npm run bundle:browser
npm run bundle:browser:prod
```
We appreciate your efforts and contributions to YouTube.js! Together, we can make this project even better.

View File

@@ -12,7 +12,7 @@
<img src="https://luanrt.github.io/assets/img/ytjs.svg" alt="YouTube.js Logo" width="200" />
</a>
</p>
<p>A JavaScript client for YouTube's private API</p>
<p>A JavaScript client for YouTube's internal API.<br/>Runs on Node.js, Deno, modern browsers, and more.</p>
[![Discord](https://img.shields.io/badge/discord-online-brightgreen.svg)][discord]
[![CI](https://github.com/LuanRT/YouTube.js/actions/workflows/test.yml/badge.svg)][actions]
@@ -22,8 +22,6 @@
</div>
YouTube.js is a JavaScript client for YouTube's private API, known as "InnerTube". It allows you to interact with YouTube programmatically, providing access to videos, comments, live chats, streaming data and more. It works seamlessly across Node.js, Deno, and modern browsers.
## Installation
Before installing, make sure your environment meets the [prerequisites](https://ytjs.dev/guide/getting-started.html#prerequisites).
@@ -54,10 +52,10 @@ import { Innertube } from 'youtubei.js';
const innertube = await Innertube.create(/* options */);
```
For detailed usage, check out the [YouTube.js Guide and API Documentation](https://ytjs.dev).
For detailed usage, read the [YouTube.js Guide and API Documentation](https://ytjs.dev).
## Contributing
We welcome all contributions, issues and feature requests, whether small or large. If you want to contribute, feel free to check out our [issues page](https://github.com/LuanRT/YouTube.js/issues) and our [guidelines](https://github.com/LuanRT/YouTube.js/blob/main/CONTRIBUTING.md).
All contributions are welcome, big or small. If you want to contribute, take a look at the [issues page](https://github.com/LuanRT/YouTube.js/issues) and our [guidelines](https://github.com/LuanRT/YouTube.js/blob/main/CONTRIBUTING.md).
## Contributors
<a href="https://github.com/LuanRT/YouTube.js/graphs/contributors">

File diff suppressed because it is too large Load Diff

View File

@@ -12,12 +12,12 @@
"devDependencies": {
"patch-package": "^8.0.1",
"typescript": "^4.6.4",
"vite": "^5.4.20"
"vite": "^6.4.2"
},
"dependencies": {
"bgutils-js": "^3.1.2",
"googlevideo": "^2.0.0",
"shaka-player": "4.11.7",
"youtubei.js": "^12.2.0"
"youtubei.js": "^17.0.1"
}
}

68
package-lock.json generated
View File

@@ -13,6 +13,7 @@
"license": "MIT",
"dependencies": {
"@bufbuild/protobuf": "^2.0.0",
"fflate": "^0.8.2",
"meriyah": "^6.1.4"
},
"devDependencies": {
@@ -37,9 +38,9 @@
}
},
"node_modules/@bufbuild/protobuf": {
"version": "2.11.0",
"resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-2.11.0.tgz",
"integrity": "sha512-sBXGT13cpmPR5BMgHE6UEEfEaShh5Ror6rfN3yEK5si7QVrtZg8LEPQb0VVhiLRUslD2yLnXtnRzG035J/mZXQ==",
"version": "2.12.0",
"resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-2.12.0.tgz",
"integrity": "sha512-B/XlCaFIP8LOwzo+bz5uFzATYokcwCKQcghqnlfwSmM5eX/qTkvDBnDPs+gXtX/RyjxJ4DRikECcPJbyALA8FA==",
"license": "(Apache-2.0 AND BSD-3-Clause)"
},
"node_modules/@esbuild/aix-ppc64": {
@@ -1271,7 +1272,6 @@
"integrity": "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"undici-types": "~7.18.0"
}
@@ -1318,7 +1318,6 @@
"integrity": "sha512-XZzOmihLIr8AD1b9hL9ccNMzEMWt/dE2u7NyTY9jJG6YNiNthaD5XtUHVF2uCXZ15ng+z2hT3MVuxnUYhq6k1g==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@typescript-eslint/scope-manager": "8.57.0",
"@typescript-eslint/types": "8.57.0",
@@ -1638,7 +1637,6 @@
"integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==",
"dev": true,
"license": "MIT",
"peer": true,
"bin": {
"acorn": "bin/acorn"
},
@@ -2107,7 +2105,6 @@
"integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.8.0",
"@eslint-community/regexpp": "^4.12.1",
@@ -2404,6 +2401,12 @@
"reusify": "^1.0.4"
}
},
"node_modules/fflate": {
"version": "0.8.2",
"resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz",
"integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==",
"license": "MIT"
},
"node_modules/file-entry-cache": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
@@ -2462,9 +2465,9 @@
}
},
"node_modules/flatted": {
"version": "3.4.1",
"resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.1.tgz",
"integrity": "sha512-IxfVbRFVlV8V/yRaGzk0UVIcsKKHMSfYw66T/u4nTwlWteQePsxe//LjudR1AMX4tZW3WFCh3Zqa/sjlqpbURQ==",
"version": "3.4.2",
"resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz",
"integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==",
"dev": true,
"license": "ISC"
},
@@ -3267,9 +3270,9 @@
"license": "ISC"
},
"node_modules/picomatch": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz",
"integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==",
"dev": true,
"license": "MIT",
"engines": {
@@ -3280,9 +3283,9 @@
}
},
"node_modules/postcss": {
"version": "8.5.8",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz",
"integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==",
"version": "8.5.14",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.14.tgz",
"integrity": "sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==",
"dev": true,
"funding": [
{
@@ -3852,12 +3855,11 @@
}
},
"node_modules/tinyglobby/node_modules/picomatch": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
"dev": true,
"license": "MIT",
"peer": true,
"engines": {
"node": ">=12"
},
@@ -3951,9 +3953,9 @@
}
},
"node_modules/ts-proto": {
"version": "2.11.5",
"resolved": "https://registry.npmjs.org/ts-proto/-/ts-proto-2.11.5.tgz",
"integrity": "sha512-Ua2Mu92MhCkOCQgOTn00jOZaOE51JHW/Zfmf65Gtp7mOe2DxUVjKvaedayjQ0SVrblyCVSKCm2tAX/2FWgiFzQ==",
"version": "2.11.8",
"resolved": "https://registry.npmjs.org/ts-proto/-/ts-proto-2.11.8.tgz",
"integrity": "sha512-+5hzECnyVB33jxjG1BIdzAHcRBm7hjnm8womdJVp2A7xJWihP0drHHVsXYTr9i/LpWNGfh80I+AVVNzFM5AwJw==",
"dev": true,
"license": "ISC",
"dependencies": {
@@ -4065,7 +4067,6 @@
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"dev": true,
"license": "Apache-2.0",
"peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@@ -4136,9 +4137,9 @@
}
},
"node_modules/vite": {
"version": "7.3.1",
"resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz",
"integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==",
"version": "7.3.3",
"resolved": "https://registry.npmjs.org/vite/-/vite-7.3.3.tgz",
"integrity": "sha512-/4XH147Ui7OGTjg3HbdWe5arnZQSbfuRzdr9Ec7TQi5I7R+ir0Rlc9GIvD4v0XZurELqA035KVXJXpR61xhiTA==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -4736,12 +4737,11 @@
}
},
"node_modules/vite/node_modules/picomatch": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
"dev": true,
"license": "MIT",
"peer": true,
"engines": {
"node": ">=12"
},
@@ -4823,9 +4823,9 @@
}
},
"node_modules/vitest/node_modules/picomatch": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
"dev": true,
"license": "MIT",
"engines": {

View File

@@ -103,6 +103,7 @@
"license": "MIT",
"dependencies": {
"@bufbuild/protobuf": "^2.0.0",
"fflate": "^0.8.2",
"meriyah": "^6.1.4"
},
"devDependencies": {

View File

@@ -528,7 +528,7 @@ export default class Session extends EventEmitter {
const buffer = BinarySerializer.serialize({
...session_data,
library_version: parseInt(packageInfo.version)
library_version: parseInt(packageInfo.version.split('.', 1)[0])
});
await cache.set('innertube_session_data', buffer);

View File

@@ -0,0 +1,14 @@
import { YTNode } from '../helpers.js';
import { Parser, type RawNode } from '../index.js';
import SectionHeaderView from './SectionHeaderView.js';
export default class HypeFanCreditsSectionView extends YTNode{
static type = 'HypeFanCreditsSectionView';
public header: SectionHeaderView | null;
constructor(data: RawNode) {
super();
this.header = Parser.parseItem(data.header, SectionHeaderView);
}
}

View File

@@ -12,6 +12,7 @@ import HowThisWasMadeSectionView from './HowThisWasMadeSectionView.js';
import ReelShelf from './ReelShelf.js';
import ExpandableMetadata from './ExpandableMetadata.js';
import MerchandiseShelf from './MerchandiseShelf.js';
import HypeFanCreditsSectionView from './HypeFanCreditsSectionView.js';
export default class StructuredDescriptionContent extends YTNode {
static type = 'StructuredDescriptionContent';
@@ -20,7 +21,7 @@ export default class StructuredDescriptionContent extends YTNode {
VideoDescriptionHeader | ExpandableVideoDescriptionBody | VideoDescriptionMusicSection |
VideoDescriptionInfocardsSection | VideoDescriptionTranscriptSection |
VideoDescriptionCourseSection | HorizontalCardList | ReelShelf | VideoAttributesSectionView |
HowThisWasMadeSectionView | ExpandableMetadata | MerchandiseShelf
HowThisWasMadeSectionView | ExpandableMetadata | MerchandiseShelf | HypeFanCreditsSectionView
>;
constructor(data: RawNode) {
@@ -29,7 +30,7 @@ export default class StructuredDescriptionContent extends YTNode {
VideoDescriptionHeader, ExpandableVideoDescriptionBody, VideoDescriptionMusicSection,
VideoDescriptionInfocardsSection, VideoDescriptionCourseSection, VideoDescriptionTranscriptSection,
VideoDescriptionTranscriptSection, HorizontalCardList, ReelShelf, VideoAttributesSectionView,
HowThisWasMadeSectionView, ExpandableMetadata, MerchandiseShelf
HowThisWasMadeSectionView, ExpandableMetadata, MerchandiseShelf, HypeFanCreditsSectionView
]);
}
}

View File

@@ -214,6 +214,7 @@ export { default as HorizontalCardList } from './classes/HorizontalCardList.js';
export { default as HorizontalList } from './classes/HorizontalList.js';
export { default as HorizontalMovieList } from './classes/HorizontalMovieList.js';
export { default as HowThisWasMadeSectionView } from './classes/HowThisWasMadeSectionView.js';
export { default as HypeFanCreditsSectionView } from './classes/HypeFanCreditsSectionView.js';
export { default as HypePointsFactoid } from './classes/HypePointsFactoid.js';
export { default as IconLink } from './classes/IconLink.js';
export { default as ImageBannerView } from './classes/ImageBannerView.js';

View File

@@ -141,7 +141,7 @@ export interface IStreamingData {
export type IPlayerResponse = Pick<IParsedResponse, 'captions' | 'cards' | 'endscreen' | 'microformat' | 'annotations' | 'playability_status' | 'streaming_data' | 'player_config' | 'playback_tracking' | 'storyboards' | 'video_details'>;
export type INextResponse = Pick<IParsedResponse, 'contents' | 'contents_memo' | 'continuation_contents' | 'continuation_contents_memo' | 'current_video_endpoint' | 'on_response_received_endpoints' | 'on_response_received_endpoints_memo' | 'player_overlays' | 'engagement_panels'>;
export type IBrowseResponse = Pick<IParsedResponse, 'background' | 'continuation_contents' | 'continuation_contents_memo' | 'on_response_received_actions' | 'on_response_received_actions_memo' | 'on_response_received_endpoints' | 'on_response_received_endpoints_memo' | 'contents' | 'contents_memo' | 'header' | 'header_memo' | 'metadata' | 'microformat' | 'alerts' | 'sidebar' | 'sidebar_memo'>;
export type ISearchResponse = Pick<IParsedResponse, 'header' | 'header_memo' | 'contents' | 'contents_memo' | 'on_response_received_commands' | 'continuation_contents' | 'continuation_contents_memo' | 'refinements' | 'estimated_results'>;
export type ISearchResponse = Pick<IParsedResponse, 'header' | 'header_memo' | 'contents' | 'contents_memo' | 'on_response_received_commands' | 'on_response_received_commands_memo' | 'continuation_contents' | 'continuation_contents_memo' | 'refinements' | 'estimated_results'>;
export type IResolveURLResponse = Pick<IParsedResponse, 'endpoint'>;
export type IGetTranscriptResponse = Pick<IParsedResponse, 'actions' | 'actions_memo'>;
export type IGetNotificationsMenuResponse = Pick<IParsedResponse, 'actions' | 'actions_memo'>;

View File

@@ -1,20 +1,19 @@
import Feed from '../../core/mixins/Feed.js';
import { InnertubeError } from '../../utils/Utils.js';
import HorizontalCardList from '../classes/HorizontalCardList.js';
import ItemSection from '../classes/ItemSection.js';
import SearchHeader from '../classes/SearchHeader.js';
import SearchRefinementCard from '../classes/SearchRefinementCard.js';
import SearchSubMenu from '../classes/SearchSubMenu.js';
import SectionList from '../classes/SectionList.js';
import UniversalWatchCard from '../classes/UniversalWatchCard.js';
import AppendContinuationItemsAction from '../classes/actions/AppendContinuationItemsAction.js';
import ChipCloudChip from '../classes/ChipCloudChip.js';
import type NavigationEndpoint from '../classes/NavigationEndpoint.js';
import { ReloadContinuationItemsCommand } from '../continuations.js';
import { observe } from '../helpers.js';
import type { ApiResponse, Actions } from '../../core/index.js';
import type { ObservedArray, YTNode } from '../helpers.js';
import type { ISearchResponse } from '../types/index.js';
import { ReloadContinuationItemsCommand } from '../index.js';
import AppendContinuationItemsAction from '../classes/actions/AppendContinuationItemsAction.js';
export default class Search extends Feed<ISearchResponse> {
public header?: SearchHeader;
@@ -23,23 +22,28 @@ export default class Search extends Feed<ISearchResponse> {
public estimated_results: number;
public sub_menu?: SearchSubMenu;
public watch_card?: UniversalWatchCard;
public refinement_cards?: HorizontalCardList | null;
constructor(actions: Actions, data: ApiResponse | ISearchResponse, already_parsed = false) {
super(actions, data, already_parsed);
const contents =
this.page.contents_memo?.getType(SectionList)[0].contents ||
this.page.on_response_received_commands_memo?.getType(SectionList)[0]?.contents ||
this.page.on_response_received_commands?.[0].as(AppendContinuationItemsAction, ReloadContinuationItemsCommand).contents;
if (!contents)
throw new InnertubeError('No contents found in search response');
if (this.page.header)
this.header = this.page.header.item().as(SearchHeader);
if (this.page.on_response_received_commands && !this.page.header) {
const headerSlot = this.page.on_response_received_commands.as(ReloadContinuationItemsCommand).find(
(command) => command.is(ReloadContinuationItemsCommand) && command.slot === 'RELOAD_CONTINUATION_SLOT_HEADER'
);
this.header = headerSlot?.contents?.firstOfType(SearchHeader);
} else {
this.header = this.page.header?.item().as(SearchHeader);
}
this.results = observe(contents.filterType(ItemSection).flatMap((section) => section.contents));
this.refinements = this.page.refinements || [];
this.estimated_results = this.page.estimated_results || 0;
@@ -47,39 +51,54 @@ export default class Search extends Feed<ISearchResponse> {
this.sub_menu = this.page.contents_memo.getType(SearchSubMenu)[0];
this.watch_card = this.page.contents_memo.getType(UniversalWatchCard)[0];
}
this.refinement_cards = this.results?.firstOfType(HorizontalCardList);
}
/**
* Applies given refinement card and returns a new {@link Search} object. Use {@link refinement_card_queries} to get a list of available refinement cards.
* Applies a refinement filter to the search results.
*
* Use {@link Search.refinement_filters} to get a list of available refinements.
*
* @example
* ```ts
* const results = await yt.search('PilotRedSun');
* // Narrow down to only YouTube Shorts
* const shortsOnly = await results.applyRefinement('Shorts');
* ```
* @param refinementFilter - The text label of the chip or the {@link ChipCloudChip} node itself.
*/
async selectRefinementCard(card: SearchRefinementCard | string): Promise<Search> {
let target_card: SearchRefinementCard | undefined;
async applyRefinement(refinementFilter: string | ChipCloudChip): Promise<Search> {
let endpoint: NavigationEndpoint | undefined;
if (typeof card === 'string') {
if (!this.refinement_cards) throw new InnertubeError('No refinement cards found.');
target_card = this.refinement_cards?.cards.find((refinement_card): refinement_card is SearchRefinementCard => {
return refinement_card.is(SearchRefinementCard) && refinement_card.query === card;
});
if (!target_card)
throw new InnertubeError(`Refinement card "${card}" not found`, { available_cards: this.refinement_card_queries });
} else if (card.type === 'SearchRefinementCard') {
target_card = card;
if (typeof refinementFilter === 'string') {
const chipBar = this.header?.chip_bar;
if (!chipBar) throw new InnertubeError('No chip bar found in search header');
const targetChip = chipBar.chips.find((chip) => chip.text === refinementFilter);
if (!targetChip) throw new InnertubeError(`Refinement filter "${refinementFilter}" not found`, { available_filters: this.refinement_filters });
endpoint = targetChip.endpoint;
if (!endpoint && targetChip.is_selected) return this; // The 'All' filter doesn't have an endpoint when it's selected.
} else if (refinementFilter.is(ChipCloudChip)) {
if (!refinementFilter.endpoint && refinementFilter.is_selected) return this;
endpoint = refinementFilter.endpoint;
} else {
throw new InnertubeError('Invalid refinement card!');
throw new InnertubeError('Invalid filter type');
}
const page = await target_card.endpoint.call<ISearchResponse>(this.actions, { parse: true });
if (!endpoint)
throw new InnertubeError('Could not find endpoint for the specified filter');
const page = await endpoint.call<ISearchResponse>(this.actions, { parse: true });
return new Search(this.actions, page, true);
}
/**
* Returns a list of refinement card queries.
* Returns a list of available refinement filters. Use {@link Search.applyRefinement} to apply a filter.
*/
get refinement_card_queries(): string[] {
return this.refinement_cards?.cards.as(SearchRefinementCard).map((card) => card.query) || [];
get refinement_filters(): string[] {
return this.header?.chip_bar?.chips.map((chip) => chip.text) || [];
}
/**

View File

@@ -1,30 +1,31 @@
import { compress, decompress } from './LZW.js';
import { gunzipSync, gzipSync } from 'fflate';
export const MAGIC_HEADER = 0x594254; // 'YTB' in hex...
export const VERSION = 1;
export const VERSION = 2;
export function serialize(data: any): ArrayBuffer {
const json_str = JSON.stringify(data);
const compressed = compress(json_str);
const compressed_bytes = new TextEncoder().encode(compressed);
const json = JSON.stringify(data);
const jsonBytes = new TextEncoder().encode(json);
const compressed = gzipSync(jsonBytes);
const buffer = new ArrayBuffer(12 + compressed_bytes.byteLength);
const buffer = new ArrayBuffer(12 + compressed.byteLength);
const view = new DataView(buffer);
view.setUint32(0, MAGIC_HEADER, true);
view.setUint32(4, VERSION, true);
view.setUint32(8, compressed_bytes.byteLength, true);
view.setUint32(8, compressed.byteLength, true);
new Uint8Array(buffer).set(compressed_bytes, 12);
new Uint8Array(buffer).set(compressed, 12);
return buffer;
}
export function deserialize<T>(buffer: Uint8Array): T {
if (buffer.byteLength < 12)
if (buffer.byteLength < 12) {
throw new Error('Invalid binary format: buffer too short');
}
const view = new DataView(buffer.buffer, buffer.byteOffset);
const view = new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength);
const magic = view.getUint32(0, true);
if (magic !== MAGIC_HEADER) {
@@ -36,11 +37,14 @@ export function deserialize<T>(buffer: Uint8Array): T {
throw new Error(`Unsupported binary format version: ${version}`);
}
const data_length = view.getUint32(8, true);
const compressed_data = buffer.slice(12, 12 + data_length);
const length = view.getUint32(8, true);
if (12 + length > buffer.byteLength) {
throw new Error('Invalid binary format: data length out of bounds');
}
const compressed = new TextDecoder().decode(compressed_data);
const json_str = decompress(compressed);
const compressed = buffer.subarray(12, 12 + length);
const decompressed = gunzipSync(compressed);
const json = new TextDecoder().decode(decompressed);
return JSON.parse(json_str);
return JSON.parse(json) as T;
}

View File

@@ -1,64 +0,0 @@
/**
* Compresses a string using the LZW compression algorithm.
* @param input - The data to compress.
*/
export function compress(input: string): string {
const output: number[] = [];
const dictionary: Record<string, number> = {};
for (let i = 0; i < 256; i++) {
dictionary[String.fromCharCode(i)] = i;
}
let current_string = '';
let dictionary_size = 256;
for (let i = 0; i < input.length; i++) {
const current_char = input[i];
const combined_string = current_string + current_char;
if (dictionary.hasOwnProperty(combined_string)) {
current_string = combined_string;
} else {
output.push(dictionary[current_string]);
dictionary[combined_string] = dictionary_size++;
current_string = current_char;
}
}
if (current_string !== '') {
output.push(dictionary[current_string]);
}
return output.map((code) => String.fromCharCode(code)).join('');
}
/**
* Decompresses data that was compressed using the LZW compression algorithm.
* @param input - The data to be decompressed.
*/
export function decompress(input: string): string {
const dictionary: Record<number, string> = {};
const input_data = input.split('');
const output: string[] = [ input_data.shift() as string ];
const input_length = input_data.length >>> 0; // Convert to unsigned 32-bit integer
let dictionary_code = 256;
let current_char = output[0];
let current_string = current_char;
for (let i = 0; i < input_length; ++i) {
const current_code = input_data[i].charCodeAt(0);
const entry =
current_code < 256 ? input_data[i] : (dictionary[current_code] ?
dictionary[current_code] : (current_string + current_char));
output.push(entry);
current_char = entry.charAt(0);
dictionary[dictionary_code++] = current_string + current_char;
current_string = entry;
}
return output.join('');
}

View File

@@ -13,7 +13,6 @@ export { Platform } from './Utils.js';
export * as Utils from './Utils.js';
export * as Log from './Log.js';
export * as LZW from './LZW.js';
export * as BinarySerializer from './BinarySerializer.js';
export * as ProtoUtils from './ProtoUtils.js';