Compare commits

...

20 Commits

Author SHA1 Message Date
LuanRT
6caa679df6 chore(release) v2.5.0 2022-11-25 01:36:50 -03:00
LuanRT
2a87f42b32 fix(Search): check if WatchCardHeroVideo is null before casting
Related #243
2022-11-25 01:25:02 -03:00
LuanRT
f7c1e0f249 fix(Music): search endpoint missing
Related: #242
2022-11-23 20:04:24 -03:00
LuanRT
fe4c5433cf feat: make Player instance optional (#240) 2022-11-16 03:17:59 -03:00
LuanRT
0e5e0c0fab feat(Channel): add support for filters (#237)
* feat: add support for filters

Also add `channel#getShorts()` and `channel#getLiveStreams()`

* docs: update API ref

* chore: add tests
2022-11-14 19:08:16 -03:00
LuanRT
f0fd6146c7 Merge branch 'main' of https://github.com/LuanRT/YouTube.js 2022-11-14 15:32:08 -03:00
LuanRT
43061970c6 fix: export Player & Session classes 2022-11-14 15:30:40 -03:00
LuanRT
746023d9bb chore(docs): fix typo' 2022-11-12 19:36:47 -03:00
LuanRT
3102479dd9 chore(release): v2.4.1
:]
2022-11-12 19:07:06 -03:00
LuanRT
c7a13c948c chore: remove unnecessary code 2022-11-12 19:02:40 -03:00
LuanRT
ec875ba321 chore(release): v2.4.0 2022-11-12 18:49:56 -03:00
LuanRT
db77bba802 fix(NotificationsCount): default to 0 2022-11-12 17:29:07 -03:00
LuanRT
5ea0a0ebf8 feat: add support for switching accounts (cookie based auth only) (#236)
* feat: add support for switching accounts

* style: lint
2022-11-12 16:26:02 -03:00
LuanRT
0130229236 fix(Actions): do not send undefined payloads 2022-11-12 15:38:29 -03:00
LuanRT
da517fe6d1 refactor: improve home feed parsing (#234)
* chore: update tests

* style: format code

* docs: update API ref
2022-11-12 01:31:11 -03:00
LuanRT
95ff1e6c5e refactor(Library): use memo to get target YTNodes 2022-11-11 19:00:12 -03:00
LuanRT
0f8adfd9b8 chore(parser): ignore AdSlot 2022-11-11 17:23:13 -03:00
LuanRT
b514765354 chore(docs): update examples 2022-11-11 17:05:24 -03:00
LuanRT
3cbcd71a3a feat: add support for topic/auto-generated channels and fix minor parsing errors (#233)
* dev: add support for topic channels

* dev(parser): do not try to parse empty nodes

* dev: add support for auto-generated game channels
2022-11-11 00:38:44 -03:00
Burhan Syed
4c00f15f55 fix: WatchCardHeroVideo accessibilityData parse error (#231)
* fix #230: WatchCardHeroVideo AccessibilityData Parser error

* add WatchCardHeroVideo test case
2022-11-10 19:18:08 -03:00
54 changed files with 912 additions and 541 deletions

View File

@@ -406,7 +406,32 @@ See [`./examples/comments`](https://github.com/LuanRT/YouTube.js/blob/main/examp
### getHomeFeed()
Retrieves YouTube's home feed.
**Returns**: `Promise.<FilterableFeed>`
**Returns**: `Promise.<HomeFeed>`
<details>
<summary>Methods & Getters</summary>
<p>
- `<home_feed>#videos`
- Returns all videos in the home feed.
- `<home_feed>#posts`
- Returns all posts in the home feed.
- `<home_feed>#shelves`
- Returns all shelves in the home feed.
- `<home_feed>#filters`
- Returns available filters.
- `<home_feed>#applyFilter(name | ChipCloudChip)`
- Applies given filter and returns a new HomeFeed instance.
- `<home_feed>#getContinuation()`
- Retrieves feed continuation.
</p>
</details>
<a name="getlibrary"></a>
### getLibrary()
@@ -472,11 +497,16 @@ Retrieves contents for a given channel.
<p>
- `<channel>#getVideos()`
- `<channel>#getShorts()`
- `<channel>#getLiveStreams()`
- `<channel>#getPlaylists()`
- `<channel>#getHome()`
- `<channel>#getCommunity()`
- `<channel>#getChannels()`
- `<channel>#getAbout()`
- `<channel>#getContinuation()`
- `<channel>#filters`
- `<channel>#page`
</p>
</details>
@@ -578,6 +608,7 @@ For example, you may want to call an endpoint directly, that can be achieved wit
// ...
const payload = {
// ...
videoId: 'jLTOuvBTLxA',
client: 'YTMUSIC', // InnerTube client, can be ANDROID, YTMUSIC, YTMUSIC_ANDROID, WEB or TV_EMBEDDED
parse: true // tells YouTube.js to parse the response, this is not sent to InnerTube.
@@ -593,10 +624,10 @@ Or maybe there's an interesting `NavigationEndpoint` in a parsed response and we
```ts
// ...
const artist = await yt.music.getArtist('UC52ZqHVQz5OoGhvbWiRal6g');
const albums = artist.sections[1].as(MusicCarouselShelf);
const albums = artist.sections[1].as(YTNodes.MusicCarouselShelf);
// Say we have a button and want to “click” it
const button = albums.as(MusicCarouselShelf).header?.more_content;
const button = albums.as(YTNodes.MusicCarouselShelf).header?.more_content;
if (button) {
// To do that, we can call its navigation endpoint:
@@ -609,24 +640,11 @@ if (button) {
If you're working on an extension for the library or just want to have nicely typed and sanitized InnerTube responses for a project then have a look at our powerful parser!
<details>
<summary>Example:</summary>
<p>
Example:
```ts
// See ./examples/parser
import { Parser } from 'youtubei.js';
import SectionList from 'youtubei.js/dist/src/parser/classes/SectionList';
import SingleColumnBrowseResults from 'youtubei.js/dist/src/parser/classes/SingleColumnBrowseResults';
import MusicVisualHeader from 'youtubei.js/dist/src/parser/classes/MusicVisualHeader';
import MusicImmersiveHeader from 'youtubei.js/dist/src/parser/classes/MusicImmersiveHeader';
import MusicCarouselShelf from 'youtubei.js/dist/src/parser/classes/MusicCarouselShelf';
import MusicDescriptionShelf from 'youtubei.js/dist/src/parser/classes/MusicDescriptionShelf';
import MusicShelf from 'youtubei.js/dist/src/parser/classes/MusicShelf';
import { Parser, YTNodes } from 'youtubei.js';
import { readFileSync } from 'fs';
// Artist page response from YouTube Music
@@ -634,7 +652,7 @@ const data = readFileSync('./artist.json').toString();
const page = Parser.parseResponse(JSON.parse(data));
const header = page.header.item().as(MusicImmersiveHeader, MusicVisualHeader);
const header = page.header?.item().as(YTNodes.MusicImmersiveHeader, YTNodes.MusicVisualHeader);
console.info('Header:', header);
@@ -642,7 +660,8 @@ console.info('Header:', header);
// A proxy intercepts access to the actual data, allowing
// the parser to add type safety and many utility methods
// that make working with InnerTube much easier.
const tab = page.contents.item().as(SingleColumnBrowseResults).tabs.get({ selected: false });
const tab = page.contents.item().as(YTNodes.SingleColumnBrowseResults).tabs.firstOfType(YTNodes.Tab);
if (!tab)
throw new Error('Target tab not found');
@@ -650,14 +669,11 @@ if (!tab)
if (!tab.content)
throw new Error('Target tab appears to be empty');
const sections = tab.content?.as(SectionList).contents.array().as(MusicCarouselShelf, MusicDescriptionShelf, MusicShelf);
const sections = tab.content?.as(YTNodes.SectionList).contents.array().as(YTNodes.MusicCarouselShelf, YTNodes.MusicDescriptionShelf, YTNodes.MusicShelf);
console.info('Sections:', sections);
```
</p>
</details>
Detailed documentation can be found [here](https://github.com/LuanRT/YouTube.js/blob/main/src/parser).
<!-- CONTRIBUTING -->
@@ -693,4 +709,4 @@ Distributed under the [MIT](https://choosealicense.com/licenses/mit/) License.
<p align=" right">
(<a href="#top">back to top</a>)
</p>
</p>

View File

@@ -6,4 +6,6 @@ export * from './src/utils';
export { YTNodes } from './src/parser/map';
export { default as Parser } from './src/parser';
export { default as Innertube } from './src/Innertube';
export { default as Session } from './src/core/Session';
export { default as Player } from './src/core/Player';
export default Innertube;

View File

@@ -1,12 +1,14 @@
import { Innertube, UniversalCache } from 'youtubei.js';
import { Innertube, UniversalCache, YTNodes } from 'youtubei.js';
(async () => {
const yt = await Innertube.create({ cache: new UniversalCache() });
const channel = await yt.getChannel('UCX6OQ3DkcsbYNE6H8uQQuVA');
console.info('Viewing channel:', channel.header.author.name);
console.info('Family Safe:', channel.metadata.is_family_safe);
if (channel.header?.is(YTNodes.C4TabbedHeader)) {
console.info('Viewing channel:', channel?.header?.author.name);
console.info('Family Safe:', channel.metadata.is_family_safe);
}
const about = await channel.getAbout();

View File

@@ -1,31 +1,25 @@
import { Innertube, UniversalCache } from 'youtubei.js';
import { Innertube, UniversalCache, YTNodes } from 'youtubei.js';
import { LiveChatContinuation } from 'youtubei.js/dist/src/parser';
import LiveChat, { ChatAction, LiveMetadata } from 'youtubei.js/dist/src/parser/youtube/LiveChat';
import Video from 'youtubei.js/dist/src/parser/classes/Video';
import AddChatItemAction from 'youtubei.js/dist/src/parser/classes/livechat/AddChatItemAction';
import MarkChatItemAsDeletedAction from 'youtubei.js/dist/src/parser/classes/livechat/MarkChatItemAsDeletedAction';
import LiveChatTextMessage from 'youtubei.js/dist/src/parser/classes/livechat/items/LiveChatTextMessage';
import LiveChatPaidMessage from 'youtubei.js/dist/src/parser/classes/livechat/items/LiveChatPaidMessage';
import { ChatAction, LiveMetadata } from 'youtubei.js/dist/src/parser/youtube/LiveChat';
(async () => {
const yt = await Innertube.create({ cache: new UniversalCache() });
const search = await yt.search('Lofi girl live');
const info = await yt.getInfo(search.videos[0].as(Video).id);
const info = await yt.getInfo(search.videos[0].as(YTNodes.Video).id);
const livechat = info.getLiveChat();
const livechat = await info.getLiveChat();
livechat.on('start', (initial_data: LiveChatContinuation) => {
/**
* Initial info is what you see when you first open a Live Chat — this is; inital actions (pinned messages, top donations..), account's info and so on.
*/
console.info(`Hey ${initial_data.viewer_name || 'N/A'}, welcome to Live Chat!`);
});
livechat.on('chat-update', (action: ChatAction) => {
/**
* An action represents what is being added to
@@ -35,28 +29,28 @@ import LiveChatPaidMessage from 'youtubei.js/dist/src/parser/classes/livechat/it
* Below are a few examples of how this can be used.
*/
if (action.is(AddChatItemAction)) {
const item = action.as(AddChatItemAction).item;
if (action.is(YTNodes.AddChatItemAction)) {
const item = action.as(YTNodes.AddChatItemAction).item;
if (!item)
return console.info('Action did not have an item.', action);
const hours = new Date(item.hasKey('timestamp') ? item.timestamp : Date.now()).toLocaleTimeString('en-US', {
hour: '2-digit',
minute: '2-digit'
});
switch (item.type) {
case 'LiveChatTextMessage':
console.info(
`${hours} - ${item.as(LiveChatTextMessage).author?.name.toString()}:\n` +
`${item.as(LiveChatTextMessage).message.toString()}\n`
`${hours} - ${item.as(YTNodes.LiveChatTextMessage).author?.name.toString()}:\n` +
`${item.as(YTNodes.LiveChatTextMessage).message.toString()}\n`
);
break;
case 'LiveChatPaidMessage':
console.info(
`${hours} - ${item.as(LiveChatPaidMessage).author.name.toString()}:\n` +
`${item.as(LiveChatPaidMessage).purchase_amount}\n`
`${hours} - ${item.as(YTNodes.LiveChatPaidMessage).author.name.toString()}:\n` +
`${item.as(YTNodes.LiveChatPaidMessage).purchase_amount}\n`
);
break;
default:
@@ -64,8 +58,8 @@ import LiveChatPaidMessage from 'youtubei.js/dist/src/parser/classes/livechat/it
break;
}
}
if (action.is(MarkChatItemAsDeletedAction)) {
if (action.is(YTNodes.MarkChatItemAsDeletedAction)) {
console.warn(`Message ${action.target_item_id} just got deleted and should be replaced with ${action.deleted_state_message.toString()}!`, '\n');
}
});

View File

@@ -1,22 +1,11 @@
import { Parser } from 'youtubei.js';
import SectionList from 'youtubei.js/dist/src/parser/classes/SectionList';
import SingleColumnBrowseResults from 'youtubei.js/dist/src/parser/classes/SingleColumnBrowseResults';
import MusicVisualHeader from 'youtubei.js/dist/src/parser/classes/MusicVisualHeader';
import MusicImmersiveHeader from 'youtubei.js/dist/src/parser/classes/MusicImmersiveHeader';
import MusicCarouselShelf from 'youtubei.js/dist/src/parser/classes/MusicCarouselShelf';
import MusicDescriptionShelf from 'youtubei.js/dist/src/parser/classes/MusicDescriptionShelf';
import MusicShelf from 'youtubei.js/dist/src/parser/classes/MusicShelf';
import { Parser, YTNodes } from 'youtubei.js';
import { readFileSync } from 'fs';
// Artist page response from YouTube Music
const data = readFileSync('./artist.json').toString();
const page = Parser.parseResponse(JSON.parse(data));
const header = page.header.item().as(MusicImmersiveHeader, MusicVisualHeader);
const header = page.header?.item().as(YTNodes.MusicImmersiveHeader, YTNodes.MusicVisualHeader);
console.info('Header:', header);
@@ -24,14 +13,14 @@ console.info('Header:', header);
// A proxy intercepts access to the actual data, allowing
// the parser to add type safety and many utility methods
// that make working with InnerTube much easier.
const tab = page.contents.item().as(SingleColumnBrowseResults).tabs.get({ selected: false });
const tab = page.contents.item().as(YTNodes.SingleColumnBrowseResults).tabs.firstOfType(YTNodes.Tab);
if (!tab)
throw new Error('Target tab not found');
if (!tab.content)
throw new Error('Target tab appears to be empty');
const sections = tab.content?.as(SectionList).contents.array().as(MusicCarouselShelf, MusicDescriptionShelf, MusicShelf);
const sections = tab.content?.as(YTNodes.SectionList).contents.array().as(YTNodes.MusicCarouselShelf, YTNodes.MusicDescriptionShelf, YTNodes.MusicShelf);
console.info('Sections:', sections);

View File

@@ -23,4 +23,6 @@ export * from './src/utils';
export { YTNodes } from './src/parser/map';
export { default as Parser } from './src/parser';
export { default as Innertube } from './src/Innertube';
export { default as Session } from './src/core/Session';
export { default as Player } from './src/core/Player';
export default Innertube;

439
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "youtubei.js",
"version": "2.3.3",
"version": "2.5.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "youtubei.js",
"version": "2.3.3",
"version": "2.5.0",
"funding": [
"https://github.com/sponsors/LuanRT"
],
@@ -106,9 +106,9 @@
}
},
"node_modules/@babel/generator": {
"version": "7.20.2",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.20.2.tgz",
"integrity": "sha512-SD75PMIK6i9H8G/tfGvB4KKl4Nw6Ssos9nGgYwxbgyTP0iX/Z55DveoH86rmUB/YHTQQ+ZC0F7xxaY8l2OF44Q==",
"version": "7.20.4",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.20.4.tgz",
"integrity": "sha512-luCf7yk/cm7yab6CAW1aiFnmEfBJplb/JojV56MYEK7ziWfGmFlTfmL9Ehwfy4gFhbjBfWO1wj7/TuSbVNEEtA==",
"dev": true,
"dependencies": {
"@babel/types": "^7.20.2",
@@ -385,9 +385,9 @@
}
},
"node_modules/@babel/parser": {
"version": "7.20.2",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.2.tgz",
"integrity": "sha512-afk318kh2uKbo7BEj2QtEi8HVCGrwHUffrYDy7dgVcSa2j9lY3LDjPzcyGdpX7xgm35aWqvciZJ4WKmdF/SxYg==",
"version": "7.20.3",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.3.tgz",
"integrity": "sha512-OP/s5a94frIPXwjzEcv5S/tpQfc6XhxYUnmWpgdqMWGgYCuErA3SzozaRAMQgSZWKeTJxht9aWAkUY+0UzvOFg==",
"dev": true,
"bin": {
"parser": "bin/babel-parser.js"
@@ -1202,15 +1202,15 @@
}
},
"node_modules/@protobuf-ts/plugin": {
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/@protobuf-ts/plugin/-/plugin-2.8.1.tgz",
"integrity": "sha512-lacRdXJ9TkTbI28U0KApsnVqnxeq1aZftOdq6LNPQJaIzBrVRxrMkqnnGWGQFYe0Tr93OKeW4A2lLjTkn32CuA==",
"version": "2.8.2",
"resolved": "https://registry.npmjs.org/@protobuf-ts/plugin/-/plugin-2.8.2.tgz",
"integrity": "sha512-rTPxaeKBfUar8ubKxbVdv4XL6AcGA0OOgHNHFyrfODP7Epy80omwuvgFJex1YpeNFJxm/FZXXj5Z+nHuhYEqJg==",
"dev": true,
"dependencies": {
"@protobuf-ts/plugin-framework": "^2.8.1",
"@protobuf-ts/protoc": "^2.8.1",
"@protobuf-ts/runtime": "^2.8.1",
"@protobuf-ts/runtime-rpc": "^2.8.1",
"@protobuf-ts/plugin-framework": "^2.8.2",
"@protobuf-ts/protoc": "^2.8.2",
"@protobuf-ts/runtime": "^2.8.2",
"@protobuf-ts/runtime-rpc": "^2.8.2",
"typescript": "^3.9"
},
"bin": {
@@ -1219,12 +1219,12 @@
}
},
"node_modules/@protobuf-ts/plugin-framework": {
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/@protobuf-ts/plugin-framework/-/plugin-framework-2.8.1.tgz",
"integrity": "sha512-hfLoYIyxCI6Ro6LY1BltNBDcXWL+36SwnWR/hcF4ttPHLE3rMIpYbz4IwQsfeU2SKbBeGFhZv9rcnDJB22cXog==",
"version": "2.8.2",
"resolved": "https://registry.npmjs.org/@protobuf-ts/plugin-framework/-/plugin-framework-2.8.2.tgz",
"integrity": "sha512-ivcJdNVB3Iee8044f8erZGBgmB6ZfQbbKyxRgDBXRVKYxsruLr432WcT5upw9autK9OnlSVLaebi8kDneFXd2g==",
"dev": true,
"dependencies": {
"@protobuf-ts/runtime": "^2.8.1",
"@protobuf-ts/runtime": "^2.8.2",
"typescript": "^3.9"
}
},
@@ -1255,26 +1255,26 @@
}
},
"node_modules/@protobuf-ts/protoc": {
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/@protobuf-ts/protoc/-/protoc-2.8.1.tgz",
"integrity": "sha512-6fehuL9bS22zCgPBBlESZjnoA4fxAUkOjMcaFMJSlVpN6CDN2O+c/Mo1pCXaNDO7FAidMPj5yhz48Kws4kOXEA==",
"version": "2.8.2",
"resolved": "https://registry.npmjs.org/@protobuf-ts/protoc/-/protoc-2.8.2.tgz",
"integrity": "sha512-1e+rOgp22ElyqRWunSc8bhatJcvRe90AGPceVn67IFYzybvfKl17vP1igHddeYkN0dzOucnOrwqn2v1jnDfE2w==",
"dev": true,
"bin": {
"protoc": "protoc.js"
}
},
"node_modules/@protobuf-ts/runtime": {
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/@protobuf-ts/runtime/-/runtime-2.8.1.tgz",
"integrity": "sha512-D9M5hSumYCovIfNllt7N6ODh4q+LrjiMWtNETvooaf+a2XheZJ7kgjFlsFghti0CFWwtA//of4JXQfw9hU+cCw=="
"version": "2.8.2",
"resolved": "https://registry.npmjs.org/@protobuf-ts/runtime/-/runtime-2.8.2.tgz",
"integrity": "sha512-PVxsH81y9kEbHldxxG/8Y3z2mTXWQytRl8zNS0mTPUjkEC+8GUX6gj6LsA8EFp25fAs9V0ruh+aNWmPccEI9MA=="
},
"node_modules/@protobuf-ts/runtime-rpc": {
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/@protobuf-ts/runtime-rpc/-/runtime-rpc-2.8.1.tgz",
"integrity": "sha512-hc+HJpoAu50by8aBS55UygcrzD8jAvRKWZMCRJ9XY3h9Gl2tciYysfzSH1SWtF6XOT/4b5CKnnmdMR3ad7uU5g==",
"version": "2.8.2",
"resolved": "https://registry.npmjs.org/@protobuf-ts/runtime-rpc/-/runtime-rpc-2.8.2.tgz",
"integrity": "sha512-vum/Y7AXdUTWGFu7dke/jCSB9dV3Oo3iVPcce3j7KudpzzWarDkEGvXjKv3Y8zJPj5waToyxwBNSb7eo5Vw5WA==",
"dev": true,
"dependencies": {
"@protobuf-ts/runtime": "^2.8.1"
"@protobuf-ts/runtime": "^2.8.2"
}
},
"node_modules/@sinclair/typebox": {
@@ -1284,9 +1284,9 @@
"dev": true
},
"node_modules/@sinonjs/commons": {
"version": "1.8.4",
"resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.4.tgz",
"integrity": "sha512-RpmQdHVo8hCEHDVpO39zToS9jOhR6nw+/lQAzRNq9ErrGV9IeHM71XCn68svVl/euFeVW6BWX4p35gkhbOcSIQ==",
"version": "1.8.5",
"resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.5.tgz",
"integrity": "sha512-rTpCA0wG1wUxglBSFdMMY0oTrKYvgf4fNgv/sXbfCVAdf+FnPBdKJR/7XbpTCwbCrvCbdPYnlWaUUYz4V2fPDA==",
"dev": true,
"dependencies": {
"type-detect": "4.0.8"
@@ -1302,9 +1302,9 @@
}
},
"node_modules/@types/babel__core": {
"version": "7.1.19",
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.19.tgz",
"integrity": "sha512-WEOTgRsbYkvA/KCsDwVEGkd7WAr1e3g31VHQ8zy5gul/V1qKullU/BU5I68X5v7V3GnB9eotmom4v5a5gjxorw==",
"version": "7.1.20",
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.20.tgz",
"integrity": "sha512-PVb6Bg2QuscZ30FvOU7z4guG6c926D9YRvOxEaelzndpMsvP+YM74Q/dAFASpg2l6+XLalxSGxcq/lrgYWZtyQ==",
"dev": true,
"dependencies": {
"@babel/parser": "^7.1.0",
@@ -1416,9 +1416,9 @@
"dev": true
},
"node_modules/@types/yargs": {
"version": "17.0.13",
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.13.tgz",
"integrity": "sha512-9sWaruZk2JGxIQU+IhI1fhPYRcQ0UuTNuKuCW9bR5fp7qi2Llf7WDzNa17Cy7TKnh3cdxDOiyTu6gaLS0eDatg==",
"version": "17.0.14",
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.14.tgz",
"integrity": "sha512-9Pj7abXoW1RSTcZaL2Hk6G2XyLMlp5ECdVC/Zf2p/KBjC3srijLGgRAXOBjtFrJoIrvxdTKyKDA14bEcbxBaWw==",
"dev": true,
"dependencies": {
"@types/yargs-parser": "*"
@@ -1431,14 +1431,14 @@
"dev": true
},
"node_modules/@typescript-eslint/eslint-plugin": {
"version": "5.42.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.42.0.tgz",
"integrity": "sha512-5TJh2AgL6+wpL8H/GTSjNb4WrjKoR2rqvFxR/DDTqYNk6uXn8BJMEcncLSpMbf/XV1aS0jAjYwn98uvVCiAywQ==",
"version": "5.44.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.44.0.tgz",
"integrity": "sha512-j5ULd7FmmekcyWeArx+i8x7sdRHzAtXTkmDPthE4amxZOWKFK7bomoJ4r7PJ8K7PoMzD16U8MmuZFAonr1ERvw==",
"dev": true,
"dependencies": {
"@typescript-eslint/scope-manager": "5.42.0",
"@typescript-eslint/type-utils": "5.42.0",
"@typescript-eslint/utils": "5.42.0",
"@typescript-eslint/scope-manager": "5.44.0",
"@typescript-eslint/type-utils": "5.44.0",
"@typescript-eslint/utils": "5.44.0",
"debug": "^4.3.4",
"ignore": "^5.2.0",
"natural-compare-lite": "^1.4.0",
@@ -1464,14 +1464,14 @@
}
},
"node_modules/@typescript-eslint/parser": {
"version": "5.42.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.42.0.tgz",
"integrity": "sha512-Ixh9qrOTDRctFg3yIwrLkgf33AHyEIn6lhyf5cCfwwiGtkWhNpVKlEZApi3inGQR/barWnY7qY8FbGKBO7p3JA==",
"version": "5.44.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.44.0.tgz",
"integrity": "sha512-H7LCqbZnKqkkgQHaKLGC6KUjt3pjJDx8ETDqmwncyb6PuoigYajyAwBGz08VU/l86dZWZgI4zm5k2VaKqayYyA==",
"dev": true,
"dependencies": {
"@typescript-eslint/scope-manager": "5.42.0",
"@typescript-eslint/types": "5.42.0",
"@typescript-eslint/typescript-estree": "5.42.0",
"@typescript-eslint/scope-manager": "5.44.0",
"@typescript-eslint/types": "5.44.0",
"@typescript-eslint/typescript-estree": "5.44.0",
"debug": "^4.3.4"
},
"engines": {
@@ -1491,13 +1491,13 @@
}
},
"node_modules/@typescript-eslint/scope-manager": {
"version": "5.42.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.42.0.tgz",
"integrity": "sha512-l5/3IBHLH0Bv04y+H+zlcLiEMEMjWGaCX6WyHE5Uk2YkSGAMlgdUPsT/ywTSKgu9D1dmmKMYgYZijObfA39Wow==",
"version": "5.44.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.44.0.tgz",
"integrity": "sha512-2pKml57KusI0LAhgLKae9kwWeITZ7IsZs77YxyNyIVOwQ1kToyXRaJLl+uDEXzMN5hnobKUOo2gKntK9H1YL8g==",
"dev": true,
"dependencies": {
"@typescript-eslint/types": "5.42.0",
"@typescript-eslint/visitor-keys": "5.42.0"
"@typescript-eslint/types": "5.44.0",
"@typescript-eslint/visitor-keys": "5.44.0"
},
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
@@ -1508,13 +1508,13 @@
}
},
"node_modules/@typescript-eslint/type-utils": {
"version": "5.42.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.42.0.tgz",
"integrity": "sha512-HW14TXC45dFVZxnVW8rnUGnvYyRC0E/vxXShFCthcC9VhVTmjqOmtqj6H5rm9Zxv+ORxKA/1aLGD7vmlLsdlOg==",
"version": "5.44.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.44.0.tgz",
"integrity": "sha512-A1u0Yo5wZxkXPQ7/noGkRhV4J9opcymcr31XQtOzcc5nO/IHN2E2TPMECKWYpM3e6olWEM63fq/BaL1wEYnt/w==",
"dev": true,
"dependencies": {
"@typescript-eslint/typescript-estree": "5.42.0",
"@typescript-eslint/utils": "5.42.0",
"@typescript-eslint/typescript-estree": "5.44.0",
"@typescript-eslint/utils": "5.44.0",
"debug": "^4.3.4",
"tsutils": "^3.21.0"
},
@@ -1535,9 +1535,9 @@
}
},
"node_modules/@typescript-eslint/types": {
"version": "5.42.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.42.0.tgz",
"integrity": "sha512-t4lzO9ZOAUcHY6bXQYRuu+3SSYdD9TS8ooApZft4WARt4/f2Cj/YpvbTe8A4GuhT4bNW72goDMOy7SW71mZwGw==",
"version": "5.44.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.44.0.tgz",
"integrity": "sha512-Tp+zDnHmGk4qKR1l+Y1rBvpjpm5tGXX339eAlRBDg+kgZkz9Bw+pqi4dyseOZMsGuSH69fYfPJCBKBrbPCxYFQ==",
"dev": true,
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
@@ -1548,13 +1548,13 @@
}
},
"node_modules/@typescript-eslint/typescript-estree": {
"version": "5.42.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.42.0.tgz",
"integrity": "sha512-2O3vSq794x3kZGtV7i4SCWZWCwjEtkWfVqX4m5fbUBomOsEOyd6OAD1qU2lbvV5S8tgy/luJnOYluNyYVeOTTg==",
"version": "5.44.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.44.0.tgz",
"integrity": "sha512-M6Jr+RM7M5zeRj2maSfsZK2660HKAJawv4Ud0xT+yauyvgrsHu276VtXlKDFnEmhG+nVEd0fYZNXGoAgxwDWJw==",
"dev": true,
"dependencies": {
"@typescript-eslint/types": "5.42.0",
"@typescript-eslint/visitor-keys": "5.42.0",
"@typescript-eslint/types": "5.44.0",
"@typescript-eslint/visitor-keys": "5.44.0",
"debug": "^4.3.4",
"globby": "^11.1.0",
"is-glob": "^4.0.3",
@@ -1575,16 +1575,16 @@
}
},
"node_modules/@typescript-eslint/utils": {
"version": "5.42.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.42.0.tgz",
"integrity": "sha512-JZ++3+h1vbeG1NUECXQZE3hg0kias9kOtcQr3+JVQ3whnjvKuMyktJAAIj6743OeNPnGBmjj7KEmiDL7qsdnCQ==",
"version": "5.44.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.44.0.tgz",
"integrity": "sha512-fMzA8LLQ189gaBjS0MZszw5HBdZgVwxVFShCO3QN+ws3GlPkcy9YuS3U4wkT6su0w+Byjq3mS3uamy9HE4Yfjw==",
"dev": true,
"dependencies": {
"@types/json-schema": "^7.0.9",
"@types/semver": "^7.3.12",
"@typescript-eslint/scope-manager": "5.42.0",
"@typescript-eslint/types": "5.42.0",
"@typescript-eslint/typescript-estree": "5.42.0",
"@typescript-eslint/scope-manager": "5.44.0",
"@typescript-eslint/types": "5.44.0",
"@typescript-eslint/typescript-estree": "5.44.0",
"eslint-scope": "^5.1.1",
"eslint-utils": "^3.0.0",
"semver": "^7.3.7"
@@ -1601,12 +1601,12 @@
}
},
"node_modules/@typescript-eslint/visitor-keys": {
"version": "5.42.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.42.0.tgz",
"integrity": "sha512-QHbu5Hf/2lOEOwy+IUw0GoSCuAzByTAWWrOTKzTzsotiUnWFpuKnXcAhC9YztAf2EElQ0VvIK+pHJUPkM0q7jg==",
"version": "5.44.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.44.0.tgz",
"integrity": "sha512-a48tLG8/4m62gPFbJ27FxwCOqPKxsb8KC3HkmYoq2As/4YyjQl1jDbRr1s63+g4FS/iIehjmN3L5UjmKva1HzQ==",
"dev": true,
"dependencies": {
"@typescript-eslint/types": "5.42.0",
"@typescript-eslint/types": "5.44.0",
"eslint-visitor-keys": "^3.3.0"
},
"engines": {
@@ -1705,9 +1705,9 @@
}
},
"node_modules/anymatch": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz",
"integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==",
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
"integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
"dev": true,
"dependencies": {
"normalize-path": "^3.0.0",
@@ -1941,9 +1941,9 @@
}
},
"node_modules/caniuse-lite": {
"version": "1.0.30001430",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001430.tgz",
"integrity": "sha512-IB1BXTZKPDVPM7cnV4iaKaHxckvdr/3xtctB3f7Hmenx3qYBhGtTZ//7EllK66aKXW98Lx0+7Yr0kxBtIt3tzg==",
"version": "1.0.30001434",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001434.tgz",
"integrity": "sha512-aOBHrLmTQw//WFa2rcF1If9fa3ypkC1wzqqiKHgfdrXTWcU8C4gKVZT77eQAPWN1APys3+uQ0Df07rKauXGEYA==",
"dev": true,
"funding": [
{
@@ -1982,10 +1982,13 @@
}
},
"node_modules/ci-info": {
"version": "3.5.0",
"resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.5.0.tgz",
"integrity": "sha512-yH4RezKOGlOhxkmhbeNuC4eYZKAUsEaGtBuBzDDP1eFUKiccDWzBABxBfOx31IDwDIXMTxWuwAxUGModvkbuVw==",
"dev": true
"version": "3.7.0",
"resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.7.0.tgz",
"integrity": "sha512-2CpRNYmImPx+RXKLq6jko/L07phmS9I02TyqkcNU20GCF/GgaWvc58hPtjxDX8lPpkdwc9sNh72V9k00S7ezog==",
"dev": true,
"engines": {
"node": ">=8"
}
},
"node_modules/cjs-module-lexer": {
"version": "1.2.2",
@@ -2651,9 +2654,9 @@
}
},
"node_modules/eslint": {
"version": "8.26.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.26.0.tgz",
"integrity": "sha512-kzJkpaw1Bfwheq4VXUezFriD1GxszX6dUekM7Z3aC2o4hju+tsR/XyTC3RcoSD7jmy9VkPU3+N6YjVU2e96Oyg==",
"version": "8.28.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.28.0.tgz",
"integrity": "sha512-S27Di+EVyMxcHiwDrFzk8dJYAaD+/5SoWKxL1ri/71CRHsnJnRDPNt2Kzj24+MT9FDupf4aqqyqPrvI8MvQ4VQ==",
"dev": true,
"dependencies": {
"@eslint/eslintrc": "^1.3.3",
@@ -3166,9 +3169,9 @@
}
},
"node_modules/globals": {
"version": "13.17.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-13.17.0.tgz",
"integrity": "sha512-1C+6nQRb1GwGMKm2dH/E7enFAMxGTmGI7/dEdhy/DNelv85w9B72t3uc5frtMNXIbzrarJJ/lTCjcaZwbLJmyw==",
"version": "13.18.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-13.18.0.tgz",
"integrity": "sha512-/mR4KI8Ps2spmoc0Ulu9L7agOF0du1CZNQ3dke8yItYlyKNmGrkONemBbd6V8UTc1Wgcqn21t3WYB7dbRmh6/A==",
"dev": true,
"dependencies": {
"type-fest": "^0.20.2"
@@ -3827,9 +3830,9 @@
}
},
"node_modules/jest-pnp-resolver": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz",
"integrity": "sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w==",
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz",
"integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==",
"dev": true,
"engines": {
"node": ">=6"
@@ -4133,10 +4136,14 @@
"dev": true
},
"node_modules/js-sdsl": {
"version": "4.1.5",
"resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.1.5.tgz",
"integrity": "sha512-08bOAKweV2NUC1wqTtf3qZlnpOX/R2DU9ikpjOHs0H+ibQv3zpncVQg6um4uYtRtrwIX8M4Nh3ytK4HGlYAq7Q==",
"dev": true
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.2.0.tgz",
"integrity": "sha512-dyBIzQBDkCqCu+0upx25Y2jGdbTGxE9fshMsCdK0ViOongpV+n5tXRcZY9v7CaVQ79AGS9KA1KHtojxiM7aXSQ==",
"dev": true,
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/js-sdsl"
}
},
"node_modules/js-tokens": {
"version": "4.0.0",
@@ -4236,9 +4243,9 @@
"dev": true
},
"node_modules/linkedom": {
"version": "0.14.19",
"resolved": "https://registry.npmjs.org/linkedom/-/linkedom-0.14.19.tgz",
"integrity": "sha512-sFNkQZlKBWpEaAcbsDIghTLE0hHbyvS6dZuM7IH+KTM09GaQ772PtDZAuFlN0oFgyAjUj8XS9FpoWSLmBjl8MA==",
"version": "0.14.21",
"resolved": "https://registry.npmjs.org/linkedom/-/linkedom-0.14.21.tgz",
"integrity": "sha512-V+c0AAFMTVJA2iAhrdd+u44lL0TjL6hBenVB061VQ6BHqTAHtXw1v5F1/CHGKtwg0OHm+hrGbepb9ZSFJ7lJkg==",
"dependencies": {
"css-select": "^5.1.0",
"cssom": "^0.5.0",
@@ -4992,9 +4999,9 @@
"dev": true
},
"node_modules/stack-utils": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.5.tgz",
"integrity": "sha512-xrQcmYhOsn/1kX+Vraq+7j4oE2j/6BFscZ0etmYg81xuM8Gq0022Pxb8+IqgOFUIaxHs0KaSb7T1+OegiNrNFA==",
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz",
"integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==",
"dev": true,
"dependencies": {
"escape-string-regexp": "^2.0.0"
@@ -5307,9 +5314,9 @@
}
},
"node_modules/typescript": {
"version": "4.8.4",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.4.tgz",
"integrity": "sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==",
"version": "4.9.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.3.tgz",
"integrity": "sha512-CIfGzTelbKNEnLpLdGFgdyKhG23CKdKgQPOBc+OUNrkJ2vr+KSzsSV5kq5iWhEQbok+quxgGzrAtGWCyU7tHnA==",
"dev": true,
"bin": {
"tsc": "bin/tsc",
@@ -5566,9 +5573,9 @@
}
},
"@babel/generator": {
"version": "7.20.2",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.20.2.tgz",
"integrity": "sha512-SD75PMIK6i9H8G/tfGvB4KKl4Nw6Ssos9nGgYwxbgyTP0iX/Z55DveoH86rmUB/YHTQQ+ZC0F7xxaY8l2OF44Q==",
"version": "7.20.4",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.20.4.tgz",
"integrity": "sha512-luCf7yk/cm7yab6CAW1aiFnmEfBJplb/JojV56MYEK7ziWfGmFlTfmL9Ehwfy4gFhbjBfWO1wj7/TuSbVNEEtA==",
"dev": true,
"requires": {
"@babel/types": "^7.20.2",
@@ -5782,9 +5789,9 @@
}
},
"@babel/parser": {
"version": "7.20.2",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.2.tgz",
"integrity": "sha512-afk318kh2uKbo7BEj2QtEi8HVCGrwHUffrYDy7dgVcSa2j9lY3LDjPzcyGdpX7xgm35aWqvciZJ4WKmdF/SxYg==",
"version": "7.20.3",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.3.tgz",
"integrity": "sha512-OP/s5a94frIPXwjzEcv5S/tpQfc6XhxYUnmWpgdqMWGgYCuErA3SzozaRAMQgSZWKeTJxht9aWAkUY+0UzvOFg==",
"dev": true
},
"@babel/plugin-syntax-async-generators": {
@@ -6408,15 +6415,15 @@
}
},
"@protobuf-ts/plugin": {
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/@protobuf-ts/plugin/-/plugin-2.8.1.tgz",
"integrity": "sha512-lacRdXJ9TkTbI28U0KApsnVqnxeq1aZftOdq6LNPQJaIzBrVRxrMkqnnGWGQFYe0Tr93OKeW4A2lLjTkn32CuA==",
"version": "2.8.2",
"resolved": "https://registry.npmjs.org/@protobuf-ts/plugin/-/plugin-2.8.2.tgz",
"integrity": "sha512-rTPxaeKBfUar8ubKxbVdv4XL6AcGA0OOgHNHFyrfODP7Epy80omwuvgFJex1YpeNFJxm/FZXXj5Z+nHuhYEqJg==",
"dev": true,
"requires": {
"@protobuf-ts/plugin-framework": "^2.8.1",
"@protobuf-ts/protoc": "^2.8.1",
"@protobuf-ts/runtime": "^2.8.1",
"@protobuf-ts/runtime-rpc": "^2.8.1",
"@protobuf-ts/plugin-framework": "^2.8.2",
"@protobuf-ts/protoc": "^2.8.2",
"@protobuf-ts/runtime": "^2.8.2",
"@protobuf-ts/runtime-rpc": "^2.8.2",
"typescript": "^3.9"
},
"dependencies": {
@@ -6429,12 +6436,12 @@
}
},
"@protobuf-ts/plugin-framework": {
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/@protobuf-ts/plugin-framework/-/plugin-framework-2.8.1.tgz",
"integrity": "sha512-hfLoYIyxCI6Ro6LY1BltNBDcXWL+36SwnWR/hcF4ttPHLE3rMIpYbz4IwQsfeU2SKbBeGFhZv9rcnDJB22cXog==",
"version": "2.8.2",
"resolved": "https://registry.npmjs.org/@protobuf-ts/plugin-framework/-/plugin-framework-2.8.2.tgz",
"integrity": "sha512-ivcJdNVB3Iee8044f8erZGBgmB6ZfQbbKyxRgDBXRVKYxsruLr432WcT5upw9autK9OnlSVLaebi8kDneFXd2g==",
"dev": true,
"requires": {
"@protobuf-ts/runtime": "^2.8.1",
"@protobuf-ts/runtime": "^2.8.2",
"typescript": "^3.9"
},
"dependencies": {
@@ -6447,23 +6454,23 @@
}
},
"@protobuf-ts/protoc": {
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/@protobuf-ts/protoc/-/protoc-2.8.1.tgz",
"integrity": "sha512-6fehuL9bS22zCgPBBlESZjnoA4fxAUkOjMcaFMJSlVpN6CDN2O+c/Mo1pCXaNDO7FAidMPj5yhz48Kws4kOXEA==",
"version": "2.8.2",
"resolved": "https://registry.npmjs.org/@protobuf-ts/protoc/-/protoc-2.8.2.tgz",
"integrity": "sha512-1e+rOgp22ElyqRWunSc8bhatJcvRe90AGPceVn67IFYzybvfKl17vP1igHddeYkN0dzOucnOrwqn2v1jnDfE2w==",
"dev": true
},
"@protobuf-ts/runtime": {
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/@protobuf-ts/runtime/-/runtime-2.8.1.tgz",
"integrity": "sha512-D9M5hSumYCovIfNllt7N6ODh4q+LrjiMWtNETvooaf+a2XheZJ7kgjFlsFghti0CFWwtA//of4JXQfw9hU+cCw=="
"version": "2.8.2",
"resolved": "https://registry.npmjs.org/@protobuf-ts/runtime/-/runtime-2.8.2.tgz",
"integrity": "sha512-PVxsH81y9kEbHldxxG/8Y3z2mTXWQytRl8zNS0mTPUjkEC+8GUX6gj6LsA8EFp25fAs9V0ruh+aNWmPccEI9MA=="
},
"@protobuf-ts/runtime-rpc": {
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/@protobuf-ts/runtime-rpc/-/runtime-rpc-2.8.1.tgz",
"integrity": "sha512-hc+HJpoAu50by8aBS55UygcrzD8jAvRKWZMCRJ9XY3h9Gl2tciYysfzSH1SWtF6XOT/4b5CKnnmdMR3ad7uU5g==",
"version": "2.8.2",
"resolved": "https://registry.npmjs.org/@protobuf-ts/runtime-rpc/-/runtime-rpc-2.8.2.tgz",
"integrity": "sha512-vum/Y7AXdUTWGFu7dke/jCSB9dV3Oo3iVPcce3j7KudpzzWarDkEGvXjKv3Y8zJPj5waToyxwBNSb7eo5Vw5WA==",
"dev": true,
"requires": {
"@protobuf-ts/runtime": "^2.8.1"
"@protobuf-ts/runtime": "^2.8.2"
}
},
"@sinclair/typebox": {
@@ -6473,9 +6480,9 @@
"dev": true
},
"@sinonjs/commons": {
"version": "1.8.4",
"resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.4.tgz",
"integrity": "sha512-RpmQdHVo8hCEHDVpO39zToS9jOhR6nw+/lQAzRNq9ErrGV9IeHM71XCn68svVl/euFeVW6BWX4p35gkhbOcSIQ==",
"version": "1.8.5",
"resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.5.tgz",
"integrity": "sha512-rTpCA0wG1wUxglBSFdMMY0oTrKYvgf4fNgv/sXbfCVAdf+FnPBdKJR/7XbpTCwbCrvCbdPYnlWaUUYz4V2fPDA==",
"dev": true,
"requires": {
"type-detect": "4.0.8"
@@ -6491,9 +6498,9 @@
}
},
"@types/babel__core": {
"version": "7.1.19",
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.19.tgz",
"integrity": "sha512-WEOTgRsbYkvA/KCsDwVEGkd7WAr1e3g31VHQ8zy5gul/V1qKullU/BU5I68X5v7V3GnB9eotmom4v5a5gjxorw==",
"version": "7.1.20",
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.20.tgz",
"integrity": "sha512-PVb6Bg2QuscZ30FvOU7z4guG6c926D9YRvOxEaelzndpMsvP+YM74Q/dAFASpg2l6+XLalxSGxcq/lrgYWZtyQ==",
"dev": true,
"requires": {
"@babel/parser": "^7.1.0",
@@ -6605,9 +6612,9 @@
"dev": true
},
"@types/yargs": {
"version": "17.0.13",
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.13.tgz",
"integrity": "sha512-9sWaruZk2JGxIQU+IhI1fhPYRcQ0UuTNuKuCW9bR5fp7qi2Llf7WDzNa17Cy7TKnh3cdxDOiyTu6gaLS0eDatg==",
"version": "17.0.14",
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.14.tgz",
"integrity": "sha512-9Pj7abXoW1RSTcZaL2Hk6G2XyLMlp5ECdVC/Zf2p/KBjC3srijLGgRAXOBjtFrJoIrvxdTKyKDA14bEcbxBaWw==",
"dev": true,
"requires": {
"@types/yargs-parser": "*"
@@ -6620,14 +6627,14 @@
"dev": true
},
"@typescript-eslint/eslint-plugin": {
"version": "5.42.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.42.0.tgz",
"integrity": "sha512-5TJh2AgL6+wpL8H/GTSjNb4WrjKoR2rqvFxR/DDTqYNk6uXn8BJMEcncLSpMbf/XV1aS0jAjYwn98uvVCiAywQ==",
"version": "5.44.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.44.0.tgz",
"integrity": "sha512-j5ULd7FmmekcyWeArx+i8x7sdRHzAtXTkmDPthE4amxZOWKFK7bomoJ4r7PJ8K7PoMzD16U8MmuZFAonr1ERvw==",
"dev": true,
"requires": {
"@typescript-eslint/scope-manager": "5.42.0",
"@typescript-eslint/type-utils": "5.42.0",
"@typescript-eslint/utils": "5.42.0",
"@typescript-eslint/scope-manager": "5.44.0",
"@typescript-eslint/type-utils": "5.44.0",
"@typescript-eslint/utils": "5.44.0",
"debug": "^4.3.4",
"ignore": "^5.2.0",
"natural-compare-lite": "^1.4.0",
@@ -6637,53 +6644,53 @@
}
},
"@typescript-eslint/parser": {
"version": "5.42.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.42.0.tgz",
"integrity": "sha512-Ixh9qrOTDRctFg3yIwrLkgf33AHyEIn6lhyf5cCfwwiGtkWhNpVKlEZApi3inGQR/barWnY7qY8FbGKBO7p3JA==",
"version": "5.44.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.44.0.tgz",
"integrity": "sha512-H7LCqbZnKqkkgQHaKLGC6KUjt3pjJDx8ETDqmwncyb6PuoigYajyAwBGz08VU/l86dZWZgI4zm5k2VaKqayYyA==",
"dev": true,
"requires": {
"@typescript-eslint/scope-manager": "5.42.0",
"@typescript-eslint/types": "5.42.0",
"@typescript-eslint/typescript-estree": "5.42.0",
"@typescript-eslint/scope-manager": "5.44.0",
"@typescript-eslint/types": "5.44.0",
"@typescript-eslint/typescript-estree": "5.44.0",
"debug": "^4.3.4"
}
},
"@typescript-eslint/scope-manager": {
"version": "5.42.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.42.0.tgz",
"integrity": "sha512-l5/3IBHLH0Bv04y+H+zlcLiEMEMjWGaCX6WyHE5Uk2YkSGAMlgdUPsT/ywTSKgu9D1dmmKMYgYZijObfA39Wow==",
"version": "5.44.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.44.0.tgz",
"integrity": "sha512-2pKml57KusI0LAhgLKae9kwWeITZ7IsZs77YxyNyIVOwQ1kToyXRaJLl+uDEXzMN5hnobKUOo2gKntK9H1YL8g==",
"dev": true,
"requires": {
"@typescript-eslint/types": "5.42.0",
"@typescript-eslint/visitor-keys": "5.42.0"
"@typescript-eslint/types": "5.44.0",
"@typescript-eslint/visitor-keys": "5.44.0"
}
},
"@typescript-eslint/type-utils": {
"version": "5.42.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.42.0.tgz",
"integrity": "sha512-HW14TXC45dFVZxnVW8rnUGnvYyRC0E/vxXShFCthcC9VhVTmjqOmtqj6H5rm9Zxv+ORxKA/1aLGD7vmlLsdlOg==",
"version": "5.44.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.44.0.tgz",
"integrity": "sha512-A1u0Yo5wZxkXPQ7/noGkRhV4J9opcymcr31XQtOzcc5nO/IHN2E2TPMECKWYpM3e6olWEM63fq/BaL1wEYnt/w==",
"dev": true,
"requires": {
"@typescript-eslint/typescript-estree": "5.42.0",
"@typescript-eslint/utils": "5.42.0",
"@typescript-eslint/typescript-estree": "5.44.0",
"@typescript-eslint/utils": "5.44.0",
"debug": "^4.3.4",
"tsutils": "^3.21.0"
}
},
"@typescript-eslint/types": {
"version": "5.42.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.42.0.tgz",
"integrity": "sha512-t4lzO9ZOAUcHY6bXQYRuu+3SSYdD9TS8ooApZft4WARt4/f2Cj/YpvbTe8A4GuhT4bNW72goDMOy7SW71mZwGw==",
"version": "5.44.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.44.0.tgz",
"integrity": "sha512-Tp+zDnHmGk4qKR1l+Y1rBvpjpm5tGXX339eAlRBDg+kgZkz9Bw+pqi4dyseOZMsGuSH69fYfPJCBKBrbPCxYFQ==",
"dev": true
},
"@typescript-eslint/typescript-estree": {
"version": "5.42.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.42.0.tgz",
"integrity": "sha512-2O3vSq794x3kZGtV7i4SCWZWCwjEtkWfVqX4m5fbUBomOsEOyd6OAD1qU2lbvV5S8tgy/luJnOYluNyYVeOTTg==",
"version": "5.44.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.44.0.tgz",
"integrity": "sha512-M6Jr+RM7M5zeRj2maSfsZK2660HKAJawv4Ud0xT+yauyvgrsHu276VtXlKDFnEmhG+nVEd0fYZNXGoAgxwDWJw==",
"dev": true,
"requires": {
"@typescript-eslint/types": "5.42.0",
"@typescript-eslint/visitor-keys": "5.42.0",
"@typescript-eslint/types": "5.44.0",
"@typescript-eslint/visitor-keys": "5.44.0",
"debug": "^4.3.4",
"globby": "^11.1.0",
"is-glob": "^4.0.3",
@@ -6692,28 +6699,28 @@
}
},
"@typescript-eslint/utils": {
"version": "5.42.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.42.0.tgz",
"integrity": "sha512-JZ++3+h1vbeG1NUECXQZE3hg0kias9kOtcQr3+JVQ3whnjvKuMyktJAAIj6743OeNPnGBmjj7KEmiDL7qsdnCQ==",
"version": "5.44.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.44.0.tgz",
"integrity": "sha512-fMzA8LLQ189gaBjS0MZszw5HBdZgVwxVFShCO3QN+ws3GlPkcy9YuS3U4wkT6su0w+Byjq3mS3uamy9HE4Yfjw==",
"dev": true,
"requires": {
"@types/json-schema": "^7.0.9",
"@types/semver": "^7.3.12",
"@typescript-eslint/scope-manager": "5.42.0",
"@typescript-eslint/types": "5.42.0",
"@typescript-eslint/typescript-estree": "5.42.0",
"@typescript-eslint/scope-manager": "5.44.0",
"@typescript-eslint/types": "5.44.0",
"@typescript-eslint/typescript-estree": "5.44.0",
"eslint-scope": "^5.1.1",
"eslint-utils": "^3.0.0",
"semver": "^7.3.7"
}
},
"@typescript-eslint/visitor-keys": {
"version": "5.42.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.42.0.tgz",
"integrity": "sha512-QHbu5Hf/2lOEOwy+IUw0GoSCuAzByTAWWrOTKzTzsotiUnWFpuKnXcAhC9YztAf2EElQ0VvIK+pHJUPkM0q7jg==",
"version": "5.44.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.44.0.tgz",
"integrity": "sha512-a48tLG8/4m62gPFbJ27FxwCOqPKxsb8KC3HkmYoq2As/4YyjQl1jDbRr1s63+g4FS/iIehjmN3L5UjmKva1HzQ==",
"dev": true,
"requires": {
"@typescript-eslint/types": "5.42.0",
"@typescript-eslint/types": "5.44.0",
"eslint-visitor-keys": "^3.3.0"
}
},
@@ -6774,9 +6781,9 @@
}
},
"anymatch": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz",
"integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==",
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
"integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
"dev": true,
"requires": {
"normalize-path": "^3.0.0",
@@ -6952,9 +6959,9 @@
"dev": true
},
"caniuse-lite": {
"version": "1.0.30001430",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001430.tgz",
"integrity": "sha512-IB1BXTZKPDVPM7cnV4iaKaHxckvdr/3xtctB3f7Hmenx3qYBhGtTZ//7EllK66aKXW98Lx0+7Yr0kxBtIt3tzg==",
"version": "1.0.30001434",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001434.tgz",
"integrity": "sha512-aOBHrLmTQw//WFa2rcF1If9fa3ypkC1wzqqiKHgfdrXTWcU8C4gKVZT77eQAPWN1APys3+uQ0Df07rKauXGEYA==",
"dev": true
},
"chalk": {
@@ -6974,9 +6981,9 @@
"dev": true
},
"ci-info": {
"version": "3.5.0",
"resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.5.0.tgz",
"integrity": "sha512-yH4RezKOGlOhxkmhbeNuC4eYZKAUsEaGtBuBzDDP1eFUKiccDWzBABxBfOx31IDwDIXMTxWuwAxUGModvkbuVw==",
"version": "3.7.0",
"resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.7.0.tgz",
"integrity": "sha512-2CpRNYmImPx+RXKLq6jko/L07phmS9I02TyqkcNU20GCF/GgaWvc58hPtjxDX8lPpkdwc9sNh72V9k00S7ezog==",
"dev": true
},
"cjs-module-lexer": {
@@ -7372,9 +7379,9 @@
"dev": true
},
"eslint": {
"version": "8.26.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.26.0.tgz",
"integrity": "sha512-kzJkpaw1Bfwheq4VXUezFriD1GxszX6dUekM7Z3aC2o4hju+tsR/XyTC3RcoSD7jmy9VkPU3+N6YjVU2e96Oyg==",
"version": "8.28.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.28.0.tgz",
"integrity": "sha512-S27Di+EVyMxcHiwDrFzk8dJYAaD+/5SoWKxL1ri/71CRHsnJnRDPNt2Kzj24+MT9FDupf4aqqyqPrvI8MvQ4VQ==",
"dev": true,
"requires": {
"@eslint/eslintrc": "^1.3.3",
@@ -7768,9 +7775,9 @@
}
},
"globals": {
"version": "13.17.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-13.17.0.tgz",
"integrity": "sha512-1C+6nQRb1GwGMKm2dH/E7enFAMxGTmGI7/dEdhy/DNelv85w9B72t3uc5frtMNXIbzrarJJ/lTCjcaZwbLJmyw==",
"version": "13.18.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-13.18.0.tgz",
"integrity": "sha512-/mR4KI8Ps2spmoc0Ulu9L7agOF0du1CZNQ3dke8yItYlyKNmGrkONemBbd6V8UTc1Wgcqn21t3WYB7dbRmh6/A==",
"dev": true,
"requires": {
"type-fest": "^0.20.2"
@@ -8252,9 +8259,9 @@
}
},
"jest-pnp-resolver": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz",
"integrity": "sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w==",
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz",
"integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==",
"dev": true,
"requires": {}
},
@@ -8499,9 +8506,9 @@
"dev": true
},
"js-sdsl": {
"version": "4.1.5",
"resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.1.5.tgz",
"integrity": "sha512-08bOAKweV2NUC1wqTtf3qZlnpOX/R2DU9ikpjOHs0H+ibQv3zpncVQg6um4uYtRtrwIX8M4Nh3ytK4HGlYAq7Q==",
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.2.0.tgz",
"integrity": "sha512-dyBIzQBDkCqCu+0upx25Y2jGdbTGxE9fshMsCdK0ViOongpV+n5tXRcZY9v7CaVQ79AGS9KA1KHtojxiM7aXSQ==",
"dev": true
},
"js-tokens": {
@@ -8578,9 +8585,9 @@
"dev": true
},
"linkedom": {
"version": "0.14.19",
"resolved": "https://registry.npmjs.org/linkedom/-/linkedom-0.14.19.tgz",
"integrity": "sha512-sFNkQZlKBWpEaAcbsDIghTLE0hHbyvS6dZuM7IH+KTM09GaQ772PtDZAuFlN0oFgyAjUj8XS9FpoWSLmBjl8MA==",
"version": "0.14.21",
"resolved": "https://registry.npmjs.org/linkedom/-/linkedom-0.14.21.tgz",
"integrity": "sha512-V+c0AAFMTVJA2iAhrdd+u44lL0TjL6hBenVB061VQ6BHqTAHtXw1v5F1/CHGKtwg0OHm+hrGbepb9ZSFJ7lJkg==",
"requires": {
"css-select": "^5.1.0",
"cssom": "^0.5.0",
@@ -9131,9 +9138,9 @@
"dev": true
},
"stack-utils": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.5.tgz",
"integrity": "sha512-xrQcmYhOsn/1kX+Vraq+7j4oE2j/6BFscZ0etmYg81xuM8Gq0022Pxb8+IqgOFUIaxHs0KaSb7T1+OegiNrNFA==",
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz",
"integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==",
"dev": true,
"requires": {
"escape-string-regexp": "^2.0.0"
@@ -9342,9 +9349,9 @@
"dev": true
},
"typescript": {
"version": "4.8.4",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.4.tgz",
"integrity": "sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==",
"version": "4.9.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.3.tgz",
"integrity": "sha512-CIfGzTelbKNEnLpLdGFgdyKhG23CKdKgQPOBc+OUNrkJ2vr+KSzsSV5kq5iWhEQbok+quxgGzrAtGWCyU7tHnA==",
"dev": true
},
"uhyphen": {

View File

@@ -1,7 +1,7 @@
{
"name": "youtubei.js",
"version": "2.3.3",
"description": "Full-featured wrapper around YouTube's private API.",
"version": "2.5.0",
"description": "Full-featured wrapper around YouTube's private API. Supports YouTube, YouTube Music and YouTube Studio (WIP).",
"main": "./dist/index.js",
"browser": "./bundle/browser.js",
"types": "./dist",
@@ -71,6 +71,7 @@
"youtube-dl",
"youtube-downloader",
"youtube-music",
"youtube-studio",
"innertubeapi",
"innertube",
"unofficial",

View File

@@ -24,11 +24,11 @@ import { YTNodeConstructor } from './helpers';
${import_list.join('\n')}
const map: Record<string, YTNodeConstructor> = {
export const YTNodes = {
${json.join(',\n ')}
};
export const YTNodes = map;
const map: Record<string, YTNodeConstructor> = YTNodes;
/**
* @param name - Name of the node to be parsed

View File

@@ -17,10 +17,10 @@ import { ActionsResponse } from './core/Actions';
import Feed from './core/Feed';
import YTMusic from './core/Music';
import Studio from './core/Studio';
import HomeFeed from './parser/youtube/HomeFeed';
import AccountManager from './core/AccountManager';
import PlaylistManager from './core/PlaylistManager';
import InteractionManager from './core/InteractionManager';
import FilterableFeed from './core/FilterableFeed';
import TabbedFeed from './core/TabbedFeed';
import Constants from './utils/Constants';
import Proto from './proto/index';
@@ -69,7 +69,7 @@ class Innertube {
async getInfo(video_id: string, client?: InnerTubeClient) {
const cpn = generateRandomString(16);
const initial_info = await this.actions.getVideoInfo(video_id, cpn, client);
const initial_info = this.actions.getVideoInfo(video_id, cpn, client);
const continuation = this.actions.execute('/next', { videoId: video_id });
const response = await Promise.all([ initial_info, continuation ]);
@@ -155,7 +155,7 @@ class Innertube {
*/
async getHomeFeed() {
const response = await this.actions.execute('/browse', { browseId: 'FEwhat_to_watch' });
return new FilterableFeed(this.actions, response.data);
return new HomeFeed(this.actions, response.data);
}
/**
@@ -212,9 +212,10 @@ class Innertube {
/**
* Retrieves unseen notifications count.
*/
async getUnseenNotificationsCount() {
async getUnseenNotificationsCount(): Promise<number> {
const response = await this.actions.execute('/notification/get_unseen_count');
return response.data.unseenCount;
// TODO: properly parse this
return response.data?.unseenCount || response.data?.actions?.[0].updateNotificationsUnseenCountAction?.unseenCount || 0;
}
/**

View File

@@ -21,7 +21,7 @@ class AccountManager {
*/
editName: (new_name: string) => {
if (!this.#actions.session.logged_in)
throw new InnertubeError('You are not signed in');
throw new InnertubeError('You must be signed in to perform this operation.');
return this.#actions.execute('/channel/edit_name', {
givenName: new_name,
@@ -34,7 +34,7 @@ class AccountManager {
*/
editDescription: (new_description: string) => {
if (!this.#actions.session.logged_in)
throw new InnertubeError('You are not signed in');
throw new InnertubeError('You must be signed in to perform this operation.');
return this.#actions.execute('/channel/edit_description', {
givenDescription: new_description,
@@ -53,7 +53,7 @@ class AccountManager {
*/
async getInfo() {
if (!this.#actions.session.logged_in)
throw new InnertubeError('You are not signed in');
throw new InnertubeError('You must be signed in to perform this operation.');
const response = await this.#actions.execute('/account/accounts_list', { client: 'ANDROID' });
return new AccountInfo(response);

View File

@@ -49,7 +49,7 @@ class Actions {
referer: 'https://www.youtube.com',
currentUrl: `/watch?v=${id}`,
autonavState: 'STATE_OFF',
signatureTimestamp: this.#session.player.sts,
signatureTimestamp: this.#session.player?.sts || 0,
autoCaptionsDefaultOn: false,
html5Preference: 'HTML5_PREF_WANTS',
lactMilliseconds: '-1'
@@ -122,7 +122,7 @@ class Actions {
if (Reflect.has(data, 'browseId')) {
if (this.#needsLogin(data.browseId) && !this.#session.logged_in)
throw new InnertubeError('You are not signed in');
throw new InnertubeError('You must be signed in to perform this operation.');
}
if (Reflect.has(data, 'override_endpoint'))
@@ -166,7 +166,7 @@ class Actions {
const response = await this.#session.http.fetch(endpoint, {
method: 'POST',
body: args?.protobuf ? data : JSON.stringify(data),
body: args?.protobuf ? data : JSON.stringify((data || {})),
headers: {
'Content-Type': args?.protobuf ?
'application/x-protobuf' :
@@ -188,6 +188,7 @@ class Actions {
'FEsubscriptions',
'FEmusic_listening_review',
'FEmusic_library_landing',
'SPaccount_overview',
'SPaccount_notifications',
'SPaccount_privacy',
'SPtime_watched'

View File

@@ -29,6 +29,7 @@ import AppendContinuationItemsAction from '../parser/classes/actions/AppendConti
import ContinuationItem from '../parser/classes/ContinuationItem';
import Video from '../parser/classes/Video';
import ReelItem from '../parser/classes/ReelItem';
class Feed {
#page: ParsedResponse;
@@ -63,9 +64,10 @@ class Feed {
* Get all videos on a given page via memo
*/
static getVideosFromMemo(memo: Memo) {
return memo.getType<Video | GridVideo | CompactVideo | PlaylistVideo | PlaylistPanelVideo | WatchCardCompactVideo>([
return memo.getType<Video | GridVideo | ReelItem | CompactVideo | PlaylistVideo | PlaylistPanelVideo | WatchCardCompactVideo>([
Video,
GridVideo,
ReelItem,
CompactVideo,
PlaylistVideo,
PlaylistPanelVideo,
@@ -115,7 +117,7 @@ class Feed {
/**
* Returns contents from the page.
*/
get contents() {
get page_contents() {
const tab_content = this.#memo.getType(Tab)?.[0]?.content;
const reload_continuation_items = this.#memo.getType(ReloadContinuationItemsCommand)?.[0];
const append_continuation_items = this.#memo.getType(AppendContinuationItemsAction)?.[0];

View File

@@ -13,7 +13,7 @@ class FilterableFeed extends Feed {
}
/**
* Get filters for the feed
* Returns the filter chips.
*/
get filter_chips() {
if (this.#chips)
@@ -30,6 +30,9 @@ class FilterableFeed extends Feed {
return this.#chips || [];
}
/**
* Returns available filters.
*/
get filters() {
return this.filter_chips.map((chip) => chip.text.toString()) || [];
}
@@ -42,9 +45,7 @@ class FilterableFeed extends Feed {
if (typeof filter === 'string') {
if (!this.filters.includes(filter))
throw new InnertubeError('Filter not found', {
available_filters: this.filters
});
throw new InnertubeError('Filter not found', { available_filters: this.filters });
target_filter = this.filter_chips.find((chip) => chip.text.toString() === filter);
} else if (filter.type === 'ChipCloudChip') {
target_filter = filter;
@@ -54,6 +55,7 @@ class FilterableFeed extends Feed {
if (!target_filter)
throw new InnertubeError('Filter not found');
if (target_filter.is_selected)
return this;

View File

@@ -17,7 +17,7 @@ class InteractionManager {
throwIfMissing({ video_id });
if (!this.#actions.session.logged_in)
throw new Error('You are not signed in');
throw new Error('You must be signed in to perform this operation.');
const action = await this.#actions.execute('/like/like', {
client: 'ANDROID',
@@ -37,7 +37,7 @@ class InteractionManager {
throwIfMissing({ video_id });
if (!this.#actions.session.logged_in)
throw new Error('You are not signed in');
throw new Error('You must be signed in to perform this operation.');
const action = await this.#actions.execute('/like/dislike', {
client: 'ANDROID',
@@ -57,7 +57,7 @@ class InteractionManager {
throwIfMissing({ video_id });
if (!this.#actions.session.logged_in)
throw new Error('You are not signed in');
throw new Error('You must be signed in to perform this operation.');
const action = await this.#actions.execute('/like/removelike', {
client: 'ANDROID',
@@ -77,7 +77,7 @@ class InteractionManager {
throwIfMissing({ channel_id });
if (!this.#actions.session.logged_in)
throw new Error('You are not signed in');
throw new Error('You must be signed in to perform this operation.');
const action = await this.#actions.execute('/subscription/subscribe', {
client: 'ANDROID',
@@ -96,7 +96,7 @@ class InteractionManager {
throwIfMissing({ channel_id });
if (!this.#actions.session.logged_in)
throw new Error('You are not signed in');
throw new Error('You must be signed in to perform this operation.');
const action = await this.#actions.execute('/subscription/unsubscribe', {
client: 'ANDROID',
@@ -116,7 +116,7 @@ class InteractionManager {
throwIfMissing({ video_id, text });
if (!this.#actions.session.logged_in)
throw new Error('You are not signed in');
throw new Error('You must be signed in to perform this operation.');
const action = await this.#actions.execute('/comment/create_comment', {
client: 'ANDROID',
@@ -163,7 +163,7 @@ class InteractionManager {
throwIfMissing({ channel_id, type });
if (!this.#actions.session.logged_in)
throw new Error('You are not signed in');
throw new Error('You must be signed in to perform this operation.');
const pref_types = {
PERSONALIZED: 1,

View File

@@ -61,7 +61,7 @@ class Music {
videoId: video_id,
playbackContext: {
contentPlaybackContext: {
signatureTimestamp: this.#session.player.sts
signatureTimestamp: this.#session.player?.sts || 0
}
}
});
@@ -89,7 +89,7 @@ class Music {
client: 'YTMUSIC',
playbackContext: {
contentPlaybackContext: {
signatureTimestamp: this.#session.player.sts
signatureTimestamp: this.#session.player?.sts || 0
}
}
});

View File

@@ -20,7 +20,7 @@ class PlaylistManager {
throwIfMissing({ title, video_ids });
if (!this.#actions.session.logged_in)
throw new InnertubeError('You are not signed in');
throw new InnertubeError('You must be signed in to perform this operation.');
const response = await this.#actions.execute('/playlist/create', {
title,
@@ -44,7 +44,7 @@ class PlaylistManager {
throwIfMissing({ playlist_id });
if (!this.#actions.session.logged_in)
throw new InnertubeError('You are not signed in');
throw new InnertubeError('You must be signed in to perform this operation.');
const response = await this.#actions.execute('playlist/delete', { playlistId: playlist_id });
@@ -65,7 +65,7 @@ class PlaylistManager {
throwIfMissing({ playlist_id, video_ids });
if (!this.#actions.session.logged_in)
throw new InnertubeError('You are not signed in');
throw new InnertubeError('You must be signed in to perform this operation.');
const response = await this.#actions.execute('/browse/edit_playlist', {
playlistId: playlist_id,
@@ -91,7 +91,7 @@ class PlaylistManager {
throwIfMissing({ playlist_id, video_ids });
if (!this.#actions.session.logged_in)
throw new InnertubeError('You are not signed in');
throw new InnertubeError('You must be signed in to perform this operation.');
const info = await this.#actions.execute('/browse', {
browseId: `VL${playlist_id}`,
@@ -150,7 +150,7 @@ class PlaylistManager {
throwIfMissing({ playlist_id, moved_video_id, predecessor_video_id });
if (!this.#actions.session.logged_in)
throw new InnertubeError('You are not signed in');
throw new InnertubeError('You must be signed in to perform this operation.');
const info = await this.#actions.execute('/browse', {
browseId: `VL${playlist_id}`,

View File

@@ -20,6 +20,10 @@ export interface Context {
hl: string;
gl: string;
remoteHost: string;
screenDensityFloat: number;
screenHeightPoints: number;
screenPixelDensity: number;
screenWidthPoints: number;
visitorData: string;
userAgent: string;
clientName: string;
@@ -52,6 +56,7 @@ export interface Context {
export interface SessionOptions {
lang?: string;
account_index?: number;
device_category?: DeviceCategory;
client_type?: ClientType;
timezone?: string;
@@ -64,6 +69,7 @@ export default class Session extends EventEmitterLike {
#api_version;
#key;
#context;
#account_index;
#player;
oauth;
@@ -72,9 +78,10 @@ export default class Session extends EventEmitterLike {
actions;
cache;
constructor(context: Context, api_key: string, api_version: string, player: Player, cookie?: string, fetch?: FetchFunction, cache?: UniversalCache) {
constructor(context: Context, api_key: string, api_version: string, account_index: number, player?: Player, cookie?: string, fetch?: FetchFunction, cache?: UniversalCache) {
super();
this.#context = context;
this.#account_index = account_index;
this.#key = api_key;
this.#api_version = api_version;
this.#player = player;
@@ -103,12 +110,20 @@ export default class Session extends EventEmitterLike {
}
static async create(options: SessionOptions = {}) {
const { context, api_key, api_version } = await Session.getSessionData(options.lang, options.device_category, options.client_type, options.timezone, options.fetch);
return new Session(context, api_key, api_version, await Player.create(options.cache, options.fetch), options.cookie, options.fetch, options.cache);
const { context, api_key, api_version, account_index } = await Session.getSessionData(
options.lang,
options.account_index,
options.device_category,
options.client_type,
options.timezone,
options.fetch
);
return new Session(context, api_key, api_version, account_index, await Player.create(options.cache, options.fetch), options.cookie, options.fetch, options.cache);
}
static async getSessionData(
lang = 'en-US',
account_index = 0,
device_category: DeviceCategory = 'desktop',
client_name: ClientType = ClientType.WEB,
tz: string = Intl.DateTimeFormat().resolvedOptions().timeZone,
@@ -144,6 +159,10 @@ export default class Session extends EventEmitterLike {
hl: device_info[0],
gl: device_info[2],
remoteHost: device_info[3],
screenDensityFloat: 1,
screenHeightPoints: 720,
screenPixelDensity: 1,
screenWidthPoints: 1280,
visitorData: device_info[13],
userAgent: device_info[14],
clientName: client_name,
@@ -169,7 +188,7 @@ export default class Session extends EventEmitterLike {
}
};
return { context, api_key, api_version };
return { context, api_key, api_version, account_index };
}
async signIn(credentials?: Credentials): Promise<void> {
@@ -205,7 +224,7 @@ export default class Session extends EventEmitterLike {
async signOut() {
if (!this.logged_in)
throw new InnertubeError('You are not signed in');
throw new InnertubeError('You must be signed in to perform this operation.');
const response = await this.oauth.revokeCredentials();
this.logged_in = false;
@@ -229,6 +248,10 @@ export default class Session extends EventEmitterLike {
return this.#context.client.clientName;
}
get account_index() {
return this.#account_index;
}
get context() {
return this.#context;
}

View File

@@ -52,7 +52,7 @@ class Studio {
*/
async setThumbnail(video_id: string, buffer: Uint8Array): Promise<ApiResponse> {
if (!this.#session.logged_in)
throw new InnertubeError('You are not signed in');
throw new InnertubeError('You must be signed in to perform this operation.');
if (!video_id || !buffer)
throw new MissingParamError('One or more parameters are missing.');
@@ -83,7 +83,7 @@ class Studio {
*/
async updateVideoMetadata(video_id: string, metadata: VideoMetadata) {
if (!this.#session.logged_in)
throw new InnertubeError('You are not signed in');
throw new InnertubeError('You must be signed in to perform this operation.');
const payload = Proto.encodeVideoMetadataPayload(video_id, metadata);
@@ -105,7 +105,7 @@ class Studio {
*/
async upload(file: BodyInit, metadata: UploadedVideoMetadata = {}): Promise<ApiResponse> {
if (!this.#session.logged_in)
throw new InnertubeError('You are not signed in');
throw new InnertubeError('You must be signed in to perform this operation.');
const initial_data = await this.#getInitialUploadData();
const upload_result = await this.#uploadVideo(initial_data.upload_url, file);

View File

@@ -0,0 +1,13 @@
import Parser from '..';
import { YTNode } from '../helpers';
export default class CarouselHeader extends YTNode {
static type = 'CarouselHeader';
contents: YTNode[];
constructor(data: any) {
super();
this.contents = Parser.parseArray(data.contents);
}
}

View File

@@ -0,0 +1,23 @@
import Parser from '..';
import { YTNode } from '../helpers';
import Thumbnail from './misc/Thumbnail';
export default class CarouselItem extends YTNode {
static type = 'CarouselItem';
items: YTNode[];
background_color: string;
layout_style: string;
pagination_thumbnails: Thumbnail[];
paginator_alignment: string;
constructor (data: any) {
super();
this.items = Parser.parseArray(data.carouselItems);
this.background_color = data.backgroundColor;
this.layout_style = data.layoutStyle;
this.pagination_thumbnails = Thumbnail.fromResponse(data.paginationThumbnails);
this.paginator_alignment = data.paginatorAlignment;
}
}

View File

@@ -0,0 +1,25 @@
import { YTNode } from '../helpers';
import Text from './misc/Text';
import Thumbnail from './misc/Thumbnail';
import NavigationEndpoint from './NavigationEndpoint';
export default class CompactStation extends YTNode {
static type = 'CompactStation';
title: Text;
description: Text;
video_count: Text;
endpoint: NavigationEndpoint;
thumbnail: Thumbnail[];
constructor(data: any) {
super();
this.title = new Text(data.title);
this.description = new Text(data.description);
this.video_count = new Text(data.videoCountText);
this.endpoint = new NavigationEndpoint(data.navigationEndpoint);
this.thumbnail = Thumbnail.fromResponse(data.thumbnail);
}
}

View File

@@ -0,0 +1,36 @@
import Parser from '..';
import { YTNode } from '../helpers';
import Text from './misc/Text';
import NavigationEndpoint from './NavigationEndpoint';
export default class DefaultPromoPanel extends YTNode {
static type = 'DefaultPromoPanel';
title: Text;
description: Text;
endpoint: NavigationEndpoint;
large_form_factor_background_thumbnail;
small_form_factor_background_thumbnail;
scrim_color_values: number[];
min_panel_display_duration_ms: number;
min_video_play_duration_ms: number;
scrim_duration: number;
metadata_order: string;
panel_layout: string;
constructor(data: any) {
super();
this.title = new Text(data.title);
this.description = new Text(data.description);
this.endpoint = new NavigationEndpoint(data.navigationEndpoint);
this.large_form_factor_background_thumbnail = Parser.parseItem(data.largeFormFactorBackgroundThumbnail);
this.small_form_factor_background_thumbnail = Parser.parseItem(data.smallFormFactorBackgroundThumbnail);
this.scrim_color_values = data.scrimColorValues;
this.min_panel_display_duration_ms = data.minPanelDisplayDurationMs;
this.min_video_play_duration_ms = data.minVideoPlayDurationMs;
this.scrim_duration = data.scrimDuration;
this.metadata_order = data.metadataOrder;
this.panel_layout = data.panelLayout;
}
}

View File

@@ -1,15 +1,14 @@
import Parser from '../index';
import { YTNode } from '../helpers';
import ChipCloudChip from './ChipCloudChip';
class FeedFilterChipBar extends YTNode {
export default class FeedFilterChipBar extends YTNode {
static type = 'FeedFilterChipBar';
contents;
constructor(data: any) {
super();
this.contents = Parser.parse(data.contents);
this.contents = Parser.parseArray<ChipCloudChip>(data.contents, ChipCloudChip);
}
}
export default FeedFilterChipBar;
}

View File

@@ -0,0 +1,13 @@
import Parser from '..';
import { YTNode } from '../helpers';
export default class GameCard extends YTNode {
static type = 'GameCard';
game;
constructor(data: any) {
super();
this.game = Parser.parseItem(data.game);
}
}

View File

@@ -0,0 +1,24 @@
import { YTNode } from '../helpers';
import Text from './misc/Text';
import Thumbnail from './misc/Thumbnail';
import NavigationEndpoint from './NavigationEndpoint';
export default class GameDetails extends YTNode {
static type = 'GameDetails';
title: Text;
box_art: Thumbnail[];
box_art_overlay_text: Text;
endpoint: NavigationEndpoint;
is_official_box_art: boolean;
constructor(data: any) {
super();
this.title = new Text(data.title);
this.box_art = Thumbnail.fromResponse(data.boxArt);
this.box_art_overlay_text = new Text(data.boxArtOverlayText);
this.endpoint = new NavigationEndpoint(data.endpoint);
this.is_official_box_art = data.isOfficialBoxArt;
}
}

View File

@@ -0,0 +1,36 @@
import Parser from '..';
import { ObservedArray, YTNode } from '../helpers';
import Text from './misc/Text';
import Thumbnail from './misc/Thumbnail';
import SubscribeButton from './SubscribeButton';
import MetadataBadge from './MetadataBadge';
import Button from './Button';
export default class InteractiveTabbedHeader extends YTNode {
static type = 'InteractiveTabbedHeader';
header_type: string;
title: Text;
description: Text;
metadata: Text;
badges: MetadataBadge[];
box_art: Thumbnail[];
banner: Thumbnail[];
buttons: ObservedArray<SubscribeButton | Button>;
auto_generated: Text;
constructor(data: any) {
super();
this.header_type = data.type;
this.title = new Text(data.title);
this.description = new Text(data.description);
this.metadata = new Text(data.metadata);
this.badges = Parser.parseArray<MetadataBadge>(data.badges, MetadataBadge);
this.box_art = Thumbnail.fromResponse(data.boxArt);
this.banner = Thumbnail.fromResponse(data.banner);
this.buttons = Parser.parseArray<SubscribeButton | Button>(data.buttons, [ SubscribeButton, Button ]);
this.auto_generated = new Text(data.autoGenerated);
}
}

View File

@@ -3,17 +3,18 @@ import ItemSectionHeader from './ItemSectionHeader';
import { YTNode } from '../helpers';
import ItemSectionTabbedHeader from './ItemSectionTabbedHeader';
import CommentsHeader from './comments/CommentsHeader';
class ItemSection extends YTNode {
static type = 'ItemSection';
header: ItemSectionHeader | ItemSectionTabbedHeader | null;
header: CommentsHeader | ItemSectionHeader | ItemSectionTabbedHeader | null;
contents;
target_id;
constructor(data: any) {
super();
this.header = Parser.parseItem<ItemSectionHeader | ItemSectionTabbedHeader>(data.header, [ ItemSectionHeader, ItemSectionTabbedHeader ]);
this.header = Parser.parseItem<CommentsHeader | ItemSectionHeader | ItemSectionTabbedHeader>(data.header);
this.contents = Parser.parse(data.contents, true);
if (data.targetId || data.sectionIdentifier) {

View File

@@ -76,6 +76,8 @@ class NavigationEndpoint extends YTNode {
return '/browse';
case 'watchEndpoint':
return '/player';
case 'searchEndpoint':
return '/search';
case 'watchPlaylistEndpoint':
return '/next';
case 'liveChatItemContextMenuEndpoint':

View File

@@ -0,0 +1,13 @@
import { YTNode } from '../helpers';
import Thumbnail from './misc/Thumbnail';
export default class PlaylistCustomThumbnail extends YTNode {
static type = 'PlaylistCustomThumbnail';
thumbnail: Thumbnail[];
constructor(data: any) {
super();
this.thumbnail = Thumbnail.fromResponse(data.thumbnail);
}
}

View File

@@ -0,0 +1,26 @@
import Parser from '..';
import { YTNode } from '../helpers';
import Button from './Button';
import Text from './misc/Text';
import Thumbnail from './misc/Thumbnail';
export default class RecognitionShelf extends YTNode {
static type = 'RecognitionShelf';
title: Text;
subtitle: Text;
avatars: Thumbnail[];
button: Button | null;
surface: string;
constructor(data: any) {
super();
this.title = new Text(data.title);
this.subtitle = new Text(data.subtitle);
this.avatars = data.avatars.map((avatar: any) => new Thumbnail(avatar));
this.button = Parser.parseItem<Button>(data.button, Button);
this.surface = data.surface;
}
}

View File

@@ -12,8 +12,8 @@ class RichGrid extends YTNode {
super();
// XXX: we don't parse the masthead since it is usually an advertisement
// XXX: reflowOptions aren't parsed, I think its only used internally for layout
this.header = Parser.parse(data.header);
this.contents = Parser.parse(data.contents);
this.header = Parser.parseItem(data.header);
this.contents = Parser.parseArray(data.contents);
}
}

View File

@@ -8,8 +8,7 @@ class RichItem extends YTNode {
constructor(data: any) {
super();
// TODO: check this
this.content = Parser.parse(data.content);
this.content = Parser.parseItem(data.content);
}
}

View File

@@ -5,12 +5,16 @@ class RichListHeader extends YTNode {
static type = 'RichListHeader';
title: Text;
subtitle: Text;
title_style: string | undefined;
icon_type: string;
constructor(data: any) {
super();
this.title = new Text(data.title);
this.icon_type = data.icon.iconType;
this.subtitle = new Text(data.subtitle);
this.title_style = data?.titleStyle?.style;
this.icon_type = data?.icon?.iconType;
}
}

View File

@@ -4,11 +4,11 @@ import { YTNode } from '../helpers';
class RichSection extends YTNode {
static type = 'RichSection';
contents;
content;
constructor(data: any) {
super();
this.contents = Parser.parse(data.content);
this.content = Parser.parseItem(data.content);
}
}

View File

@@ -13,7 +13,7 @@ class RichShelf extends YTNode {
constructor(data: any) {
super();
this.title = new Text(data.title);
this.contents = Parser.parse(data.contents);
this.contents = Parser.parseArray(data.contents);
this.endpoint = data.endpoint ? new NavigationEndpoint(data.endpoint) : null;
}
}

View File

@@ -15,6 +15,7 @@ class SectionList extends YTNode {
this.target_id = data.targetId;
}
// TODO: this should be Parser#parseArray
this.contents = Parser.parse(data.contents);
if (data.continuations) {

View File

@@ -0,0 +1,15 @@
import { YTNode } from '../helpers';
import Thumbnail from './misc/Thumbnail';
export default class ThumbnailLandscapePortrait extends YTNode {
static type = 'ThumbnailLandscapePortrait';
landscape: Thumbnail[];
portrait: Thumbnail[];
constructor (data: any) {
super();
this.landscape = Thumbnail.fromResponse(data.landscape);
this.portrait = Thumbnail.fromResponse(data.portrait);
}
}

View File

@@ -0,0 +1,27 @@
import Parser from '..';
import { YTNode } from '../helpers';
import Text from './misc/Text';
import Thumbnail from './misc/Thumbnail';
import NavigationEndpoint from './NavigationEndpoint';
import SubscribeButton from './SubscribeButton';
export default class TopicChannelDetails extends YTNode {
static type = 'TopicChannelDetails';
title: Text;
avatar: Thumbnail[];
subtitle: Text;
subscribe_button: SubscribeButton | null;
endpoint: NavigationEndpoint;
constructor (data: any) {
super();
this.title = new Text(data.title);
this.avatar = Thumbnail.fromResponse(data.thumbnail);
this.subtitle = new Text(data.title);
this.subscribe_button = Parser.parseItem<SubscribeButton>(data.subscribeButton, SubscribeButton);
this.endpoint = new NavigationEndpoint(data.navigationEndpoint);
}
}

View File

@@ -0,0 +1,9 @@
import Video from './Video';
export default class VideoCard extends Video {
static type = 'VideoCard';
constructor(data: any) {
super(data);
}
}

View File

@@ -15,7 +15,7 @@ class WatchCardHeroVideo extends YTNode {
this.endpoint = new NavigationEndpoint(data.navigationEndpoint);
this.call_to_action_button = Parser.parse(data.callToActionButton);
this.hero_image = Parser.parse(data.heroImage);
this.label = data.accessibility.accessibilityData.label;
this.label = data.lengthText.accessibility.accessibilityData.label;
}
}

View File

@@ -1,4 +1,5 @@
import Player from '../../../core/Player';
import { InnertubeError } from '../../../utils/Utils';
class Format {
itag: string;
@@ -73,7 +74,8 @@ class Format {
* Decipher the streaming url of the format.
* @returns Deciphered URL.
*/
decipher(player: Player): string {
decipher(player: Player | undefined): string {
if (!player) throw new InnertubeError('Cannot decipher format, this session appears to have no valid player.');
return player.decipher(this.url, this.signature_cipher, this.cipher);
}
}

View File

@@ -10,12 +10,12 @@ class NavigatableText extends Text {
super(node);
// TODO: is this needed? Text now supports this itself
this.endpoint =
node.runs?.[0]?.navigationEndpoint ?
new NavigationEndpoint(node.runs[0].navigationEndpoint) :
node.navigationEndpoint ?
new NavigationEndpoint(node.navigationEndpoint) :
node.titleNavigationEndpoint ?
new NavigationEndpoint(node.titleNavigationEndpoint) : null;
node?.runs?.[0]?.navigationEndpoint ?
new NavigationEndpoint(node?.runs[0].navigationEndpoint) :
node?.navigationEndpoint ?
new NavigationEndpoint(node?.navigationEndpoint) :
node?.titleNavigationEndpoint ?
new NavigationEndpoint(node?.titleNavigationEndpoint) : null;
}
toJSON(): NavigatableText {

View File

@@ -357,9 +357,13 @@ export default class Parser {
}
static parseItem<T extends YTNode = YTNode>(data: any, validTypes?: YTNodeConstructor<T> | YTNodeConstructor<T>[]) {
if (!data) return null;
if (!data ) return null;
const keys = Object.keys(data);
if (!keys.length)
return null;
const classname = this.sanitizeClassName(keys[0]);
if (!this.shouldIgnore(classname)) {
@@ -493,6 +497,7 @@ export default class Parser {
}
static ignore_list = new Set<string>([
'AdSlot',
'DisplayAd',
'SearchPyv',
'MealbarPromo',

View File

@@ -28,6 +28,8 @@ import { default as C4TabbedHeader } from './classes/C4TabbedHeader';
import { default as CallToActionButton } from './classes/CallToActionButton';
import { default as Card } from './classes/Card';
import { default as CardCollection } from './classes/CardCollection';
import { default as CarouselHeader } from './classes/CarouselHeader';
import { default as CarouselItem } from './classes/CarouselItem';
import { default as Channel } from './classes/Channel';
import { default as ChannelAboutFullMetadata } from './classes/ChannelAboutFullMetadata';
import { default as ChannelFeaturedContent } from './classes/ChannelFeaturedContent';
@@ -54,11 +56,13 @@ import { default as CommentThread } from './classes/comments/CommentThread';
import { default as CompactLink } from './classes/CompactLink';
import { default as CompactMix } from './classes/CompactMix';
import { default as CompactPlaylist } from './classes/CompactPlaylist';
import { default as CompactStation } from './classes/CompactStation';
import { default as CompactVideo } from './classes/CompactVideo';
import { default as ConfirmDialog } from './classes/ConfirmDialog';
import { default as ContinuationItem } from './classes/ContinuationItem';
import { default as CopyLink } from './classes/CopyLink';
import { default as CreatePlaylistDialog } from './classes/CreatePlaylistDialog';
import { default as DefaultPromoPanel } from './classes/DefaultPromoPanel';
import { default as DidYouMean } from './classes/DidYouMean';
import { default as DownloadButton } from './classes/DownloadButton';
import { default as Dropdown } from './classes/Dropdown';
@@ -73,6 +77,8 @@ import { default as ExpandableTab } from './classes/ExpandableTab';
import { default as ExpandedShelfContents } from './classes/ExpandedShelfContents';
import { default as FeedFilterChipBar } from './classes/FeedFilterChipBar';
import { default as FeedTabbedHeader } from './classes/FeedTabbedHeader';
import { default as GameCard } from './classes/GameCard';
import { default as GameDetails } from './classes/GameDetails';
import { default as Grid } from './classes/Grid';
import { default as GridChannel } from './classes/GridChannel';
import { default as GridHeader } from './classes/GridHeader';
@@ -83,6 +89,7 @@ import { default as HistorySuggestion } from './classes/HistorySuggestion';
import { default as HorizontalCardList } from './classes/HorizontalCardList';
import { default as HorizontalList } from './classes/HorizontalList';
import { default as IconLink } from './classes/IconLink';
import { default as InteractiveTabbedHeader } from './classes/InteractiveTabbedHeader';
import { default as ItemSection } from './classes/ItemSection';
import { default as ItemSectionHeader } from './classes/ItemSectionHeader';
import { default as ItemSectionTab } from './classes/ItemSectionTab';
@@ -189,6 +196,7 @@ import { default as PlayerOverlay } from './classes/PlayerOverlay';
import { default as PlayerOverlayAutoplay } from './classes/PlayerOverlayAutoplay';
import { default as PlayerStoryboardSpec } from './classes/PlayerStoryboardSpec';
import { default as Playlist } from './classes/Playlist';
import { default as PlaylistCustomThumbnail } from './classes/PlaylistCustomThumbnail';
import { default as PlaylistHeader } from './classes/PlaylistHeader';
import { default as PlaylistInfoCardContent } from './classes/PlaylistInfoCardContent';
import { default as PlaylistMetadata } from './classes/PlaylistMetadata';
@@ -207,6 +215,7 @@ import { default as ProfileColumn } from './classes/ProfileColumn';
import { default as ProfileColumnStats } from './classes/ProfileColumnStats';
import { default as ProfileColumnStatsEntry } from './classes/ProfileColumnStatsEntry';
import { default as ProfileColumnUserInfo } from './classes/ProfileColumnUserInfo';
import { default as RecognitionShelf } from './classes/RecognitionShelf';
import { default as ReelItem } from './classes/ReelItem';
import { default as ReelShelf } from './classes/ReelShelf';
import { default as RelatedChipCloud } from './classes/RelatedChipCloud';
@@ -245,6 +254,7 @@ import { default as Tab } from './classes/Tab';
import { default as Tabbed } from './classes/Tabbed';
import { default as TabbedSearchResults } from './classes/TabbedSearchResults';
import { default as TextHeader } from './classes/TextHeader';
import { default as ThumbnailLandscapePortrait } from './classes/ThumbnailLandscapePortrait';
import { default as ThumbnailOverlayBottomPanel } from './classes/ThumbnailOverlayBottomPanel';
import { default as ThumbnailOverlayEndorsement } from './classes/ThumbnailOverlayEndorsement';
import { default as ThumbnailOverlayHoverText } from './classes/ThumbnailOverlayHoverText';
@@ -261,6 +271,7 @@ import { default as TitleAndButtonListHeader } from './classes/TitleAndButtonLis
import { default as ToggleButton } from './classes/ToggleButton';
import { default as ToggleMenuServiceItem } from './classes/ToggleMenuServiceItem';
import { default as Tooltip } from './classes/Tooltip';
import { default as TopicChannelDetails } from './classes/TopicChannelDetails';
import { default as TwoColumnBrowseResults } from './classes/TwoColumnBrowseResults';
import { default as TwoColumnSearchResults } from './classes/TwoColumnSearchResults';
import { default as TwoColumnWatchNextResults } from './classes/TwoColumnWatchNextResults';
@@ -268,6 +279,7 @@ import { default as UniversalWatchCard } from './classes/UniversalWatchCard';
import { default as VerticalList } from './classes/VerticalList';
import { default as VerticalWatchCardList } from './classes/VerticalWatchCardList';
import { default as Video } from './classes/Video';
import { default as VideoCard } from './classes/VideoCard';
import { default as VideoInfoCardContent } from './classes/VideoInfoCardContent';
import { default as VideoOwner } from './classes/VideoOwner';
import { default as VideoPrimaryInfo } from './classes/VideoPrimaryInfo';
@@ -279,7 +291,7 @@ import { default as WatchCardSectionSequence } from './classes/WatchCardSectionS
import { default as WatchNextEndScreen } from './classes/WatchNextEndScreen';
import { default as WatchNextTabbedResults } from './classes/WatchNextTabbedResults';
const map: Record<string, YTNodeConstructor> = {
export const YTNodes = {
AccountChannel,
AccountItemSection,
AccountItemSectionHeader,
@@ -306,6 +318,8 @@ const map: Record<string, YTNodeConstructor> = {
CallToActionButton,
Card,
CardCollection,
CarouselHeader,
CarouselItem,
Channel,
ChannelAboutFullMetadata,
ChannelFeaturedContent,
@@ -332,11 +346,13 @@ const map: Record<string, YTNodeConstructor> = {
CompactLink,
CompactMix,
CompactPlaylist,
CompactStation,
CompactVideo,
ConfirmDialog,
ContinuationItem,
CopyLink,
CreatePlaylistDialog,
DefaultPromoPanel,
DidYouMean,
DownloadButton,
Dropdown,
@@ -351,6 +367,8 @@ const map: Record<string, YTNodeConstructor> = {
ExpandedShelfContents,
FeedFilterChipBar,
FeedTabbedHeader,
GameCard,
GameDetails,
Grid,
GridChannel,
GridHeader,
@@ -361,6 +379,7 @@ const map: Record<string, YTNodeConstructor> = {
HorizontalCardList,
HorizontalList,
IconLink,
InteractiveTabbedHeader,
ItemSection,
ItemSectionHeader,
ItemSectionTab,
@@ -467,6 +486,7 @@ const map: Record<string, YTNodeConstructor> = {
PlayerOverlayAutoplay,
PlayerStoryboardSpec,
Playlist,
PlaylistCustomThumbnail,
PlaylistHeader,
PlaylistInfoCardContent,
PlaylistMetadata,
@@ -485,6 +505,7 @@ const map: Record<string, YTNodeConstructor> = {
ProfileColumnStats,
ProfileColumnStatsEntry,
ProfileColumnUserInfo,
RecognitionShelf,
ReelItem,
ReelShelf,
RelatedChipCloud,
@@ -523,6 +544,7 @@ const map: Record<string, YTNodeConstructor> = {
Tabbed,
TabbedSearchResults,
TextHeader,
ThumbnailLandscapePortrait,
ThumbnailOverlayBottomPanel,
ThumbnailOverlayEndorsement,
ThumbnailOverlayHoverText,
@@ -539,6 +561,7 @@ const map: Record<string, YTNodeConstructor> = {
ToggleButton,
ToggleMenuServiceItem,
Tooltip,
TopicChannelDetails,
TwoColumnBrowseResults,
TwoColumnSearchResults,
TwoColumnWatchNextResults,
@@ -546,6 +569,7 @@ const map: Record<string, YTNodeConstructor> = {
VerticalList,
VerticalWatchCardList,
Video,
VideoCard,
VideoInfoCardContent,
VideoOwner,
VideoPrimaryInfo,
@@ -558,7 +582,7 @@ const map: Record<string, YTNodeConstructor> = {
WatchNextTabbedResults
};
export const YTNodes = map;
const map: Record<string, YTNodeConstructor> = YTNodes;
/**
* @param name - Name of the node to be parsed

View File

@@ -1,46 +1,96 @@
import Actions from '../../core/Actions';
import TabbedFeed from '../../core/TabbedFeed';
import C4TabbedHeader from '../classes/C4TabbedHeader';
import CarouselHeader from '../classes/CarouselHeader';
import InteractiveTabbedHeader from '../classes/InteractiveTabbedHeader';
import ChannelAboutFullMetadata from '../classes/ChannelAboutFullMetadata';
import ChannelMetadata from '../classes/ChannelMetadata';
import MicroformatData from '../classes/MicroformatData';
import SubscribeButton from '../classes/SubscribeButton';
import Tab from '../classes/Tab';
class Channel extends TabbedFeed {
import { InnertubeError } from '../../utils/Utils';
import FeedFilterChipBar from '../classes/FeedFilterChipBar';
import ChipCloudChip from '../classes/ChipCloudChip';
import FilterableFeed from '../../core/FilterableFeed';
import Feed from '../../core/Feed';
export default class Channel extends TabbedFeed {
header;
metadata;
sponsor_button;
subscribe_button;
current_tab;
constructor(actions: Actions, data: any, already_parsed = false) {
super(actions, data, already_parsed);
this.header = this.page.header?.item().as(C4TabbedHeader);
const metadata = this.page.metadata.item().as(ChannelMetadata);
this.header = this.page.header?.item()?.as(C4TabbedHeader, CarouselHeader, InteractiveTabbedHeader);
const metadata = this.page.metadata?.item().as(ChannelMetadata);
const microformat = this.page.microformat?.as(MicroformatData);
if (!metadata && !this.page.contents)
throw new InnertubeError('Invalid channel', this);
this.metadata = { ...metadata, ...(microformat || {}) };
this.sponsor_button = this.header?.sponsor_button;
this.subscribe_button = this.header?.subscribe_button;
this.subscribe_button = this.page.header_memo.getType(SubscribeButton)?.[0];
const tab = this.page.contents.item().key('tabs').parsed().array().filterType(Tab).get({ selected: true });
this.current_tab = tab;
}
/**
* Applies given filter to the list.
* @param filter - The filter to apply
*/
async applyFilter(filter: string | ChipCloudChip) {
let target_filter: ChipCloudChip | undefined;
const filter_chipbar = this.memo.getType(FeedFilterChipBar)?.[0];
if (typeof filter === 'string') {
target_filter = filter_chipbar?.contents.get({ text: filter });
if (!target_filter)
throw new InnertubeError(`Filter ${filter} not found`, { available_filters: this.filters });
} else if (filter instanceof ChipCloudChip) {
target_filter = filter;
}
if (!target_filter)
throw new InnertubeError('Invalid filter', filter);
const page = await target_filter.endpoint?.call(this.actions, { parse: true });
return new FilteredChannelList(this.actions, page, true);
}
get filters(): string[] {
return this.memo.getType(FeedFilterChipBar)?.[0]?.contents.filterType(ChipCloudChip).map((chip) => chip.text) || [];
}
async getHome() {
const tab = await this.getTab('Home');
return new Channel(this.actions, tab.page, true);
}
async getVideos() {
const tab = await this.getTab('Videos');
return new Channel(this.actions, tab.page, true);
}
async getPlaylists() {
const tab = await this.getTab('Playlists');
async getShorts() {
const tab = await this.getTab('Shorts');
return new Channel(this.actions, tab.page, true);
}
async getHome() {
const tab = await this.getTab('Home');
async getLiveStreams() {
const tab = await this.getTab('Live');
return new Channel(this.actions, tab.page, true);
}
async getPlaylists() {
const tab = await this.getTab('Playlists');
return new Channel(this.actions, tab.page, true);
}
@@ -62,6 +112,74 @@ class Channel extends TabbedFeed {
const tab = await this.getTab('About');
return tab.memo.getType(ChannelAboutFullMetadata)?.[0];
}
/**
* Retrives list continuation.
*/
async getContinuation() {
const page = await super.getContinuationData();
return new ChannelListContinuation(this.actions, page, true);
}
}
export default Channel;
export class ChannelListContinuation extends Feed {
contents;
constructor(actions: Actions, data: any, already_parsed = false) {
super(actions, data, already_parsed);
this.contents =
this.page.on_response_received_actions?.[0] ||
this.page.on_response_received_endpoints?.[0];
}
/**
* Retrieves list continuation.
*/
async getContinuation() {
const page = await super.getContinuationData();
return new ChannelListContinuation(this.actions, page, true);
}
}
export class FilteredChannelList extends FilterableFeed {
applied_filter: ChipCloudChip | undefined;
contents;
constructor(actions: Actions, data: any, already_parsed = false) {
super(actions, data, already_parsed);
this.applied_filter = this.memo.getType(ChipCloudChip).get({ is_selected: true });
// Removes the filter chipbar from the actions list
if (
this.page.on_response_received_actions &&
this.page.on_response_received_actions.length > 1
) {
this.page.on_response_received_actions.shift();
}
this.contents = this.page.on_response_received_actions?.[0];
}
/**
* Applies given filter to the list.
* @param filter - The filter to apply
*/
async applyFilter(filter: string | ChipCloudChip) {
const feed = await super.getFilteredFeed(filter);
return new FilteredChannelList(this.actions, feed.page, true);
}
/**
* Retrieves list continuation.
*/
async getContinuation() {
const page = await super.getContinuationData();
// Keep the filters
page?.on_response_received_actions_memo.set('FeedFilterChipBar', this.memo.getType(FeedFilterChipBar));
page?.on_response_received_actions_memo.set('ChipCloudChip', this.memo.getType(ChipCloudChip));
return new FilteredChannelList(this.actions, page, true);
}
}

View File

@@ -0,0 +1,42 @@
import Actions from '../../core/Actions';
import FilterableFeed from '../../core/FilterableFeed';
import ChipCloudChip from '../classes/ChipCloudChip';
import FeedTabbedHeader from '../classes/FeedTabbedHeader';
import RichGrid from '../classes/RichGrid';
import { ReloadContinuationItemsCommand, AppendContinuationItemsAction } from '..';
export default class HomeFeed extends FilterableFeed {
contents: RichGrid | AppendContinuationItemsAction | ReloadContinuationItemsCommand;
header: FeedTabbedHeader;
constructor(actions: Actions, data: any, already_parsed = false) {
super(actions, data, already_parsed);
this.header = this.memo.getType<FeedTabbedHeader>(FeedTabbedHeader)?.[0];
this.contents =
this.memo.getType<RichGrid>(RichGrid)?.[0] ||
this.page.on_response_received_actions?.[0];
}
/**
* Applies given filter to the feed.
* @param filter - Filter to apply.
*/
async applyFilter(filter: string | ChipCloudChip): Promise<HomeFeed> {
const feed = await super.getFilteredFeed(filter);
return new HomeFeed(this.actions, feed.page, true);
}
/**
* Retrieves next batch of contents.
*/
async getContinuation(): Promise<HomeFeed> {
const feed = await super.getContinuation();
// Keep the page header
feed.page.header = this.page.header;
feed.page.header_memo.set(this.header.type, [ this.header ]);
return new HomeFeed(this.actions, feed.page, true);
}
}

View File

@@ -5,16 +5,10 @@ import { InnertubeError } from '../../utils/Utils';
import Feed from '../../core/Feed';
import History from './History';
import Playlist from './Playlist';
import Tab from '../classes/Tab';
import Menu from '../classes/menus/Menu';
import Shelf from '../classes/Shelf';
import Button from '../classes/Button';
import SectionList from '../classes/SectionList';
import ItemSection from '../classes/ItemSection';
import TwoColumnBrowseResults from '../classes/TwoColumnBrowseResults';
import ProfileColumn from '../classes/ProfileColumn';
import ProfileColumnStats from '../classes/ProfileColumnStats';
import ProfileColumnUserInfo from '../classes/ProfileColumnUserInfo';
@@ -29,30 +23,17 @@ class Library {
this.#actions = actions;
this.#page = Parser.parseResponse(response);
const two_col = this.#page.contents.item().as(TwoColumnBrowseResults);
if (!two_col)
throw new InnertubeError('Response did not have a TwoColumnBrowseResults.');
const tab = two_col.tabs.array().as(Tab).get({ selected: true });
if (!tab)
throw new InnertubeError('Could not find target tab.');
const stats = two_col.secondary_contents.item().as(ProfileColumn).items.array().get({ type: 'ProfileColumnStats' })?.as(ProfileColumnStats) || null;
const user_info = two_col.secondary_contents.item().as(ProfileColumn).items.array().get({ type: 'ProfileColumnUserInfo' })?.as(ProfileColumnUserInfo) || null;
const stats = this.#page.contents_memo.getType(ProfileColumnStats)?.[0];
const user_info = this.#page.contents_memo.getType(ProfileColumnUserInfo)?.[0];
this.profile = { stats, user_info };
if (!tab.content)
throw new InnertubeError('Target tab did not have any content.');
const shelves = tab.content.as(SectionList).contents.array().as(ItemSection).map((is: ItemSection) => is.contents?.firstOfType(Shelf));
const shelves = this.#page.contents_memo.getType(Shelf);
this.sections = shelves.map((shelf: any) => ({
type: shelf.icon_type,
title: shelf.title,
contents: shelf.content?.item().items.array() || [],
contents: shelf.content?.item().items || [],
getAll: () => this.#getAll(shelf)
}));
}
@@ -61,7 +42,7 @@ class Library {
if (!shelf.menu?.item().as(Menu).hasKey('top_level_buttons'))
throw new InnertubeError(`The ${shelf.title.text} shelf doesn't have more items`);
const button = await shelf.menu.item().as(Menu).top_level_buttons.get({ text: 'See all' });
const button = shelf.menu.item().as(Menu).top_level_buttons.get({ text: 'See all' });
if (!button)
throw new InnertubeError('Did not find target button.');

View File

@@ -40,8 +40,8 @@ class Search extends Feed {
this.watch_card = {
header: universal_watch_card?.header.item() || null,
call_to_action: universal_watch_card?.call_to_action.item().as(WatchCardHeroVideo) || null,
sections: universal_watch_card?.sections.array().filterType(WatchCardSectionSequence) || []
call_to_action: universal_watch_card?.call_to_action?.item()?.as(WatchCardHeroVideo) || null,
sections: universal_watch_card?.sections?.array()?.filterType(WatchCardSectionSequence) || []
};
this.refinement_cards = {

View File

@@ -92,7 +92,7 @@ class VideoInfo {
* @param data - API response.
* @param cpn - Client Playback Nonce
*/
constructor(data: [ApiResponse, ApiResponse?], actions: Actions, player: Player, cpn: string) {
constructor(data: [ApiResponse, ApiResponse?], actions: Actions, player?: Player, cpn?: string) {
this.#actions = actions;
this.#player = player;
this.#cpn = cpn;
@@ -492,7 +492,7 @@ class VideoInfo {
throw new InnertubeError('Index and init ranges not available', { format });
const url = new URL(format.decipher(this.#player));
url.searchParams.set('cpn', this.#cpn);
url.searchParams.set('cpn', this.#cpn || '');
set.appendChild(this.#el(document, 'Representation', {
id: format.itag,
@@ -522,7 +522,7 @@ class VideoInfo {
throw new InnertubeError('Index and init ranges not available', { format });
const url = new URL(format.decipher(this.#player));
url.searchParams.set('cpn', this.#cpn);
url.searchParams.set('cpn', this.#cpn || '');
set.appendChild(this.#el(document, 'Representation', {
id: format.itag,

View File

@@ -458,36 +458,6 @@ export interface CreateCommentParams_Params {
*/
index: number;
}
/**
* @generated from protobuf message youtube.CreateCommentReplyParams
*/
export interface CreateCommentReplyParams {
/**
* @generated from protobuf field: string video_id = 2;
*/
videoId: string;
/**
* @generated from protobuf field: string comment_id = 4;
*/
commentId: string;
/**
* @generated from protobuf field: youtube.CreateCommentReplyParams.UnknownParams params = 5;
*/
params?: CreateCommentReplyParams_UnknownParams;
/**
* @generated from protobuf field: optional int32 unk_num = 10;
*/
unkNum?: number;
}
/**
* @generated from protobuf message youtube.CreateCommentReplyParams.UnknownParams
*/
export interface CreateCommentReplyParams_UnknownParams {
/**
* @generated from protobuf field: int32 unk_num = 1;
*/
unkNum: number;
}
/**
* @generated from protobuf message youtube.PeformCommentActionParams
*/
@@ -2402,121 +2372,6 @@ class CreateCommentParams_Params$Type extends MessageType<CreateCommentParams_Pa
*/
export const CreateCommentParams_Params = new CreateCommentParams_Params$Type();
// @generated message type with reflection information, may provide speed optimized methods
class CreateCommentReplyParams$Type extends MessageType<CreateCommentReplyParams> {
constructor() {
super("youtube.CreateCommentReplyParams", [
{ no: 2, name: "video_id", kind: "scalar", T: 9 /*ScalarType.STRING*/ },
{ no: 4, name: "comment_id", kind: "scalar", T: 9 /*ScalarType.STRING*/ },
{ no: 5, name: "params", kind: "message", T: () => CreateCommentReplyParams_UnknownParams },
{ no: 10, name: "unk_num", kind: "scalar", opt: true, T: 5 /*ScalarType.INT32*/ }
]);
}
create(value?: PartialMessage<CreateCommentReplyParams>): CreateCommentReplyParams {
const message = { videoId: "", commentId: "" };
globalThis.Object.defineProperty(message, MESSAGE_TYPE, { enumerable: false, value: this });
if (value !== undefined)
reflectionMergePartial<CreateCommentReplyParams>(this, message, value);
return message;
}
internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: CreateCommentReplyParams): CreateCommentReplyParams {
let message = target ?? this.create(), end = reader.pos + length;
while (reader.pos < end) {
let [fieldNo, wireType] = reader.tag();
switch (fieldNo) {
case /* string video_id */ 2:
message.videoId = reader.string();
break;
case /* string comment_id */ 4:
message.commentId = reader.string();
break;
case /* youtube.CreateCommentReplyParams.UnknownParams params */ 5:
message.params = CreateCommentReplyParams_UnknownParams.internalBinaryRead(reader, reader.uint32(), options, message.params);
break;
case /* optional int32 unk_num */ 10:
message.unkNum = reader.int32();
break;
default:
let u = options.readUnknownField;
if (u === "throw")
throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`);
let d = reader.skip(wireType);
if (u !== false)
(u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d);
}
}
return message;
}
internalBinaryWrite(message: CreateCommentReplyParams, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter {
/* string video_id = 2; */
if (message.videoId !== "")
writer.tag(2, WireType.LengthDelimited).string(message.videoId);
/* string comment_id = 4; */
if (message.commentId !== "")
writer.tag(4, WireType.LengthDelimited).string(message.commentId);
/* youtube.CreateCommentReplyParams.UnknownParams params = 5; */
if (message.params)
CreateCommentReplyParams_UnknownParams.internalBinaryWrite(message.params, writer.tag(5, WireType.LengthDelimited).fork(), options).join();
/* optional int32 unk_num = 10; */
if (message.unkNum !== undefined)
writer.tag(10, WireType.Varint).int32(message.unkNum);
let u = options.writeUnknownFields;
if (u !== false)
(u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
return writer;
}
}
/**
* @generated MessageType for protobuf message youtube.CreateCommentReplyParams
*/
export const CreateCommentReplyParams = new CreateCommentReplyParams$Type();
// @generated message type with reflection information, may provide speed optimized methods
class CreateCommentReplyParams_UnknownParams$Type extends MessageType<CreateCommentReplyParams_UnknownParams> {
constructor() {
super("youtube.CreateCommentReplyParams.UnknownParams", [
{ no: 1, name: "unk_num", kind: "scalar", T: 5 /*ScalarType.INT32*/ }
]);
}
create(value?: PartialMessage<CreateCommentReplyParams_UnknownParams>): CreateCommentReplyParams_UnknownParams {
const message = { unkNum: 0 };
globalThis.Object.defineProperty(message, MESSAGE_TYPE, { enumerable: false, value: this });
if (value !== undefined)
reflectionMergePartial<CreateCommentReplyParams_UnknownParams>(this, message, value);
return message;
}
internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: CreateCommentReplyParams_UnknownParams): CreateCommentReplyParams_UnknownParams {
let message = target ?? this.create(), end = reader.pos + length;
while (reader.pos < end) {
let [fieldNo, wireType] = reader.tag();
switch (fieldNo) {
case /* int32 unk_num */ 1:
message.unkNum = reader.int32();
break;
default:
let u = options.readUnknownField;
if (u === "throw")
throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`);
let d = reader.skip(wireType);
if (u !== false)
(u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d);
}
}
return message;
}
internalBinaryWrite(message: CreateCommentReplyParams_UnknownParams, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter {
/* int32 unk_num = 1; */
if (message.unkNum !== 0)
writer.tag(1, WireType.Varint).int32(message.unkNum);
let u = options.writeUnknownFields;
if (u !== false)
(u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
return writer;
}
}
/**
* @generated MessageType for protobuf message youtube.CreateCommentReplyParams.UnknownParams
*/
export const CreateCommentReplyParams_UnknownParams = new CreateCommentReplyParams_UnknownParams$Type();
// @generated message type with reflection information, may provide speed optimized methods
class PeformCommentActionParams$Type extends MessageType<PeformCommentActionParams> {
constructor() {
super("youtube.PeformCommentActionParams", [

View File

@@ -103,9 +103,12 @@ export default class HTTPClient {
if (this.#cookie) {
const papisid = getStringBetweenStrings(this.#cookie, 'PAPISID=', ';');
if (papisid) {
request_headers.set('authorization', await generateSidAuth(papisid));
request_headers.set('x-goog-authuser', this.#session.account_index.toString());
}
request_headers.set('cookie', this.#cookie);
}
}

View File

@@ -6,5 +6,16 @@ export const VIDEOS = [
{
ID: 'WSeNSzJ2-Jw',
QUERY: 'Scary Monsters and Nice Sprites Official Audio'
},
{
ID: 'I1qsF0WQy8c',
QUERY: 'mkbhd',
}
];
export const CHANNELS = [
{
ID: 'UC_x5XG1OV2P6uZZ5FSM9Ttw',
NAME: 'Linus Tech Tips'
}
];

View File

@@ -1,6 +1,6 @@
import fs from 'fs';
import Innertube from '..';
import { VIDEOS } from './constants';
import { CHANNELS, VIDEOS } from './constants';
import { streamToIterable } from '../src/utils/Utils';
describe('YouTube.js Tests', () => {
@@ -38,6 +38,14 @@ describe('YouTube.js Tests', () => {
expect(search.channels).toBeDefined();
expect(search.has_continuation).toBe(true);
});
it('should search with WatchCardHeroVideo parse', async () => {
search = await yt.search(VIDEOS[2].QUERY);
expect(search.results.length).toBeGreaterThanOrEqual(5);
expect(search.playlists).toBeDefined();
expect(search.channels).toBeDefined();
expect(search.has_continuation).toBe(true);
});
it('should retrieve search continuation', async () => {
const next = await search.getContinuation();
@@ -82,8 +90,22 @@ describe('YouTube.js Tests', () => {
expect(playlist.items.length).toBeLessThanOrEqual(100);
});
it('should retrieve channel', async () => {
const channel = await yt.getChannel(CHANNELS[0].ID);
expect(channel.videos.length).toBeGreaterThan(0);
expect(channel.shelves.length).toBeGreaterThan(0);
const videos_tab = await channel.getVideos();
expect(videos_tab.videos.length).toBeGreaterThan(0);
const filtered_list = await videos_tab.applyFilter('Popular');
expect(filtered_list.videos.length).toBeGreaterThan(0);
});
it('should retrieve home feed', async () => {
const homefeed = await yt.getHomeFeed();
expect(homefeed.header).toBeDefined();
expect(homefeed.contents).toBeDefined();
expect(homefeed.videos.length).toBeGreaterThan(0);
});