mirror of
https://github.com/LuanRT/YouTube.js.git
synced 2026-06-13 09:32:12 +00:00
Compare commits
13 Commits
03cb4d6801
...
4791deed28
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4791deed28 | ||
|
|
b8545f538d | ||
|
|
bd888f12aa | ||
|
|
4af77d4f5d | ||
|
|
f66d782276 | ||
|
|
717c67db67 | ||
|
|
1f47665e70 | ||
|
|
12d07c6b16 | ||
|
|
847863c4eb | ||
|
|
853a36307b | ||
|
|
358f4258bc | ||
|
|
f748b8b362 | ||
|
|
430fc70888 |
@@ -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.
|
||||
[](https://github.com/sponsors/absidue)
|
||||
|
||||
Several bug fixes, new features & improved MPD support.
|
||||
|
||||
@@ -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.
|
||||
@@ -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]
|
||||
[][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">
|
||||
|
||||
819
examples/browser/web/package-lock.json
generated
819
examples/browser/web/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -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
68
package-lock.json
generated
@@ -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": {
|
||||
|
||||
@@ -103,6 +103,7 @@
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@bufbuild/protobuf": "^2.0.0",
|
||||
"fflate": "^0.8.2",
|
||||
"meriyah": "^6.1.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -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);
|
||||
|
||||
14
src/parser/classes/HypeFanCreditsSectionView.ts
Normal file
14
src/parser/classes/HypeFanCreditsSectionView.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -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';
|
||||
|
||||
@@ -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'>;
|
||||
|
||||
@@ -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) || [];
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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('');
|
||||
}
|
||||
@@ -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';
|
||||
|
||||
Reference in New Issue
Block a user