Compare commits

...

29 Commits

Author SHA1 Message Date
github-actions[bot]
2b29244b41 chore(main): release 3.3.0 (#343)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-03-09 01:24:26 -03:00
LuanRT
f9754f5ac6 fix(ytmusic): use static visitor id to avoid empty API responses
Fixes #279
2023-03-09 01:21:13 -03:00
LuanRT
b2253df802 feat(parser): add ConversationBar node 2023-03-08 18:09:21 -03:00
LuanRT
f3517708ff fix(MultiMarkersPlayerBar): avoid observing undefined objects 2023-03-08 17:43:20 -03:00
Patrick Kan
0d35fe0ca5 feat(VideoInfo): support get by endpoint + more info (#342)
* feat(VideoInfo): get by endpoint + more info

* chore: fix param description for `getInfo()`
2023-03-08 16:42:41 -03:00
LuanRT
3e3dc351bb fix(SharedPost): import Menu node directly (oops) 2023-03-08 11:29:39 -03:00
github-actions[bot]
197bb759cd chore(main): release 3.2.0 (#334)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-03-08 07:20:21 -03:00
LuanRT
c76b24b3f4 chore(parser): import YTNodes directly to reduce web bundle size 2023-03-08 07:18:01 -03:00
absidue
574b67a1f7 feat: Add support for descriptive audio tracks (#338) 2023-03-08 05:36:01 -03:00
LuanRT
9b2738f128 fix(SegmentedLikeDislikeButton): like/dislike buttons can also be a simple Button 2023-03-07 05:43:32 -03:00
LuanRT
95f1d4077f fix(YouTube): fix warnings when retrieving members-only content (#341) 2023-03-07 05:15:46 -03:00
LuanRT
a511608f18 feat(YouTube/Search): add SearchSubMenu node (#340) 2023-03-07 04:17:58 -03:00
LuanRT
cf8a33c79f fix(ytmusic): export search filters type 2023-03-07 03:02:44 -03:00
Chinmay Kumar
cfc1a183e0 refactor(parser): type YTNodes' data arg as RawNode (wip) (#339)
* replaced YTNode's data arg as RawNode

* updated documentation

* removed unused import

---- Note that there are still many nodes that need to be updated, hence the WIP status.
2023-03-07 02:02:07 -03:00
Patrick Kan
95033e723e feat(parser): add banner to PlaylistHeader (#337) 2023-03-05 22:44:09 -03:00
Patrick Kan
2cc7b8bcd6 feat(yt): add getGuide() (#335)
* feat(yt): add `getGuide()`

* chore: lint

* fix(Guide): wrong prop

* fix(Guide): include subscription section

* fix(Guide): wrong import

* feat(Guide): add `page`
2023-03-04 06:23:17 -03:00
LuanRT
2d774e26aa feat: export FormatUtils' types 2023-03-04 04:06:19 -03:00
Nico K
214aa147ce feat(VideoInfo): add game_info and category (#333) 2023-03-03 03:47:20 -03:00
Daniel Wykerd
ce53ac1843 feat(parser): SharedPost (#332)
Add support for SharedPost in community tab.
Related to issue #331
2023-03-03 03:41:04 -03:00
github-actions[bot]
0ad26f28d9 chore(main): release 3.1.1 (#330)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-03-01 16:30:10 -03:00
ChunkyProgrammer
4c7b8a3403 fix(Channel): getting community continuations (#329) 2023-03-01 16:28:26 -03:00
github-actions[bot]
33a6e740d7 chore(main): release 3.1.0 (#318)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-26 20:35:20 -03:00
LuanRT
0b1840a62c chore(docs): update examples to reflect recent changes [skip ci] 2023-02-26 20:28:16 -03:00
LuanRT
f4e0f30e6e fix: send correct UA for Android requests
Related: #322
2023-02-26 19:21:41 -03:00
LuanRT
200632f374 fix(parser): export YTNodes individually so they can be used as types
Related: #321
2023-02-26 18:56:04 -03:00
LuanRT
f933cb45bc feat(VideoSecondaryInfo): add support for attributed descriptions (#325) 2023-02-26 16:47:47 -03:00
absidue
a0e6cef00f fix(PlayerMicroformat): Make the embed field optional (#320) 2023-02-25 12:11:03 -03:00
absidue
a0bfe16427 feat: Add upcoming and live info to playlist videos (#317) 2023-02-20 18:25:53 -03:00
Daniel Wykerd
9d352b58eb docs: update imports for platforms (#315)
* docs: fix browser import

* docs: add deno.land instructions

As mentioned in issue #314
2023-02-17 14:53:06 -03:00
148 changed files with 1451 additions and 272 deletions

View File

@@ -1,5 +1,62 @@
# Changelog
## [3.3.0](https://github.com/LuanRT/YouTube.js/compare/v3.2.0...v3.3.0) (2023-03-09)
### Features
* **parser:** add `ConversationBar` node ([b2253df](https://github.com/LuanRT/YouTube.js/commit/b2253df8022217dc486155d8cacbf22db04dd9c2))
* **VideoInfo:** support get by endpoint + more info ([#342](https://github.com/LuanRT/YouTube.js/issues/342)) ([0d35fe0](https://github.com/LuanRT/YouTube.js/commit/0d35fe0ca5e87a877b76cbb6cf3c92843eac5a99))
### Bug Fixes
* **MultiMarkersPlayerBar:** avoid observing undefined objects ([f351770](https://github.com/LuanRT/YouTube.js/commit/f3517708ff34093a544c09d6f5f1ec806130d5cc))
* **SharedPost:** import `Menu` node directly (oops) ([3e3dc35](https://github.com/LuanRT/YouTube.js/commit/3e3dc351bb44faec87616d9b922924d14a95f29f))
* **ytmusic:** use static visitor id to avoid empty API responses ([f9754f5](https://github.com/LuanRT/YouTube.js/commit/f9754f5ac61d0f11b025f37f93783f971560268b)), closes [#279](https://github.com/LuanRT/YouTube.js/issues/279)
## [3.2.0](https://github.com/LuanRT/YouTube.js/compare/v3.1.1...v3.2.0) (2023-03-08)
### Features
* Add support for descriptive audio tracks ([#338](https://github.com/LuanRT/YouTube.js/issues/338)) ([574b67a](https://github.com/LuanRT/YouTube.js/commit/574b67a1f707a32378586dd2fe7b2f36f4ab6ddb))
* export `FormatUtils`' types ([2d774e2](https://github.com/LuanRT/YouTube.js/commit/2d774e26aae79f3d1b115e0e85c148ae80985529))
* **parser:** add `banner` to `PlaylistHeader` ([#337](https://github.com/LuanRT/YouTube.js/issues/337)) ([95033e7](https://github.com/LuanRT/YouTube.js/commit/95033e723ef912706e4d176de6b2760f017184e1))
* **parser:** SharedPost ([#332](https://github.com/LuanRT/YouTube.js/issues/332)) ([ce53ac1](https://github.com/LuanRT/YouTube.js/commit/ce53ac18435cbcb20d6d4c4ab52fd156091e7592))
* **VideoInfo:** add `game_info` and `category` ([#333](https://github.com/LuanRT/YouTube.js/issues/333)) ([214aa14](https://github.com/LuanRT/YouTube.js/commit/214aa147ce6306e37a6bf860a7bed5635db4797e))
* **YouTube/Search:** add `SearchSubMenu` node ([#340](https://github.com/LuanRT/YouTube.js/issues/340)) ([a511608](https://github.com/LuanRT/YouTube.js/commit/a511608f18b37b0d9f2c7958ed5128330fabcfa0))
* **yt:** add `getGuide()` ([#335](https://github.com/LuanRT/YouTube.js/issues/335)) ([2cc7b8b](https://github.com/LuanRT/YouTube.js/commit/2cc7b8bcd6938c7fb3af4f854a1d78b86d153873))
### Bug Fixes
* **SegmentedLikeDislikeButton:** like/dislike buttons can also be a simple `Button` ([9b2738f](https://github.com/LuanRT/YouTube.js/commit/9b2738f1285b278c3e83541857651be9a6248288))
* **YouTube:** fix warnings when retrieving members-only content ([#341](https://github.com/LuanRT/YouTube.js/issues/341)) ([95f1d40](https://github.com/LuanRT/YouTube.js/commit/95f1d4077ff3775f36967dca786139a09e2830a2))
* **ytmusic:** export search filters type ([cf8a33c](https://github.com/LuanRT/YouTube.js/commit/cf8a33c79f5432136b865d535fd0ecedc2393382))
## [3.1.1](https://github.com/LuanRT/YouTube.js/compare/v3.1.0...v3.1.1) (2023-03-01)
### Bug Fixes
* **Channel:** getting community continuations ([#329](https://github.com/LuanRT/YouTube.js/issues/329)) ([4c7b8a3](https://github.com/LuanRT/YouTube.js/commit/4c7b8a34030effa26c4ea186d3e9509128aec31c))
## [3.1.0](https://github.com/LuanRT/YouTube.js/compare/v3.0.0...v3.1.0) (2023-02-26)
### Features
* Add upcoming and live info to playlist videos ([#317](https://github.com/LuanRT/YouTube.js/issues/317)) ([a0bfe16](https://github.com/LuanRT/YouTube.js/commit/a0bfe164279ec27b0c49c6b0c32222c1a92df5c3))
* **VideoSecondaryInfo:** add support for attributed descriptions ([#325](https://github.com/LuanRT/YouTube.js/issues/325)) ([f933cb4](https://github.com/LuanRT/YouTube.js/commit/f933cb45bcb92c07b3bc063d63869a51cbff4eb0))
### Bug Fixes
* **parser:** export YTNodes individually so they can be used as types ([200632f](https://github.com/LuanRT/YouTube.js/commit/200632f374d5e0e105b600d579a2665a6fb36e38)), closes [#321](https://github.com/LuanRT/YouTube.js/issues/321)
* **PlayerMicroformat:** Make the embed field optional ([#320](https://github.com/LuanRT/YouTube.js/issues/320)) ([a0e6cef](https://github.com/LuanRT/YouTube.js/commit/a0e6cef00fb9e3f52593cec22704f7ddc1f7553e))
* send correct UA for Android requests ([f4e0f30](https://github.com/LuanRT/YouTube.js/commit/f4e0f30e6e94b347b28d67d9a86284ea2d23ee15)), closes [#322](https://github.com/LuanRT/YouTube.js/issues/322)
## [3.0.0](https://github.com/LuanRT/YouTube.js/compare/v2.9.0...v3.0.0) (2023-02-17)

View File

@@ -3,16 +3,16 @@
Thank you for taking the time to contribute!
The following is a set of guidelines for contributing to YouTube.js.
___
* [Issues](#issues)
* [Create a new issue](#issue-1)
* [Solve an issue](#issue-2)
* [Make changes](#changes)
* [Commit your updates](#changes-1)
* [Create a PR](#changes-2)
* [Run tests](#test)
* [Lint your code](#lint)
* [Build](#build)
- [Contributing to YouTube.js](#contributing-to-youtubejs)
- [Issues](#issues)
- [Create a new issue](#create-a-new-issue)
- [Solve an issue](#solve-an-issue)
- [Make changes](#make-changes)
- [Commit your updates](#commit-your-updates)
- [Pull Request](#pull-request)
- [Test](#test)
- [Lint](#lint)
- [Build](#build)
## Issues
@@ -66,16 +66,26 @@ npm run lint:fix
#### Build
```bash
# Node
npm run build:node
# Browser
npm run build:browser
npm run build:browser:prod
# Build all
npm run build
# Protobuf
npm run build:proto
# Parser map
npm run build:parser-map
# Deno
npm run build:deno
# ES Module
npm run build:esm
# Node
npm run bundle:node
# Browser
npm run bundle:browser
npm run bundle:browser:prod
```

View File

@@ -112,7 +112,10 @@ yarn add youtubei.js@latest
npm install github:LuanRT/YouTube.js
```
**TODO:** Deno install instructions (deno.land)
When using Deno, you can import YouTube.js directly from deno.land:
```ts
import { Innertube } from 'https://deno.land/x/youtubei/deno.ts';
```
## Usage
Create an InnerTube instance:
@@ -128,8 +131,13 @@ To use YouTube.js in the browser you must proxy requests through your own server
You may provide your own fetch implementation to be used by YouTube.js. Which we will use here to modify and send the requests through our proxy. See [`examples/browser/web`](https://github.com/LuanRT/YouTube.js/tree/main/examples/browser/web) for a simple example using [Vite](https://vitejs.dev/).
```ts
// Pre-bundled version for the web
import { Innertube } from 'youtubei.js/bundle/browser';
// We provide multiple exports for the web.
// Unbundled ESM version
import { Innertube } from 'youtubei.js/web';
// Bundled ESM version
// import { Innertube } from 'youtubei.js/web.bundle';
// Production Bundled ESM version
// import { Innertube } from 'youtubei.js/web.bundle.min';
await Innertube.create({
fetch: async (input: RequestInfo | URL, init?: RequestInit) => {
// Modify the request
@@ -147,7 +155,7 @@ YouTube.js supports streaming of videos in the browser by converting YouTube's s
The example below uses [`dash.js`](https://github.com/Dash-Industry-Forum/dash.js) to play the video.
```ts
import { Innertube } from 'youtubei.js';
import { Innertube } from 'youtubei.js/web';
import dashjs from 'dashjs';
const youtube = await Innertube.create({ /* setup - see above */ });
@@ -202,7 +210,7 @@ Our cache uses the `node:fs` module in Node-like environments, `Deno.writeFile`
import { Innertube, UniversalCache } from 'youtubei.js';
// By default, cache stores files in the OS temp directory (or indexedDB in browsers).
const yt = await Innertube.create({
cache: new UniversalCache()
cache: new UniversalCache(false)
});
// You may wish to make the cache persistent (on Node and Deno)
@@ -240,12 +248,13 @@ const yt = await Innertube.create({
<summary>Methods</summary>
<p>
* [.getInfo(video_id, client?)](#getinfo)
* [.getInfo(target, client?)](#getinfo)
* [.getBasicInfo(video_id, client?)](#getbasicinfo)
* [.search(query, filters?)](#search)
* [.getSearchSuggestions(query)](#getsearchsuggestions)
* [.getComments(video_id, sort_by?)](#getcomments)
* [.getHomeFeed()](#gethomefeed)
* [.getGuide()](#getguide)
* [.getLibrary()](#getlibrary)
* [.getHistory()](#gethistory)
* [.getTrending()](#gettrending)
@@ -264,7 +273,7 @@ const yt = await Innertube.create({
</details>
<a name="getinfo"></a>
### getInfo(video_id, client?)
### getInfo(target, client?)
Retrieves video info, including playback data and even layout elements such as menus, buttons, etc — all nicely parsed.
@@ -272,7 +281,7 @@ Retrieves video info, including playback data and even layout elements such as m
| Param | Type | Description |
| --- | --- | --- |
| video_id | `string` | The id of the video |
| target | `string` \| `NavigationEndpoint` | If `string`, the id of the video. If `NavigationEndpoint`, the endpoint of watchable elements such as `Video`, `Mix` and `Playlist`. To clarify, valid endpoints have payloads containing at least `videoId` and optionally `playlistId`, `params` and `index`. |
| client? | `InnerTubeClient` | `WEB`, `ANDROID`, `YTMUSIC`, `YTMUSIC_ANDROID` or `TV_EMBEDDED` |
<details>
@@ -312,6 +321,9 @@ Retrieves video info, including playback data and even layout elements such as m
- `<info>#addToWatchHistory()`
- Adds the video to the watch history.
- `<info>#autoplay_video_endpoint`
- Returns the endpoint of the video for Autoplay.
- `<info>#page`
- Returns original InnerTube response (sanitized).
@@ -418,6 +430,12 @@ Retrieves YouTube's home feed.
</p>
</details>
<a name="getguide"></a>
### getGuide()
Retrieves YouTube's content guide.
**Returns**: `Promise<Guide>`
<a name="getlibrary"></a>
### getLibrary()
Retrieves the account's library.

View File

@@ -1,8 +1,9 @@
# Updating the parser
YouTube is constantly changing, so it is not rare to see YouTube crawlers/scrapers breaking every now and then.
YouTube is constantly changing, so it is not rare to see YouTube crawlers/scrapers breaking every now and then.
Our parser, on the other hand, was written so that it behaves similarly to an official client, parsing and mapping renderers (a.k.a YTNodes) dynamically without hard-coding their path in the response. This way, whenever a new renderer pops up (e.g; YouTube adds a new feature / minor UI changes) the library will print a warning similar to this:
Our parser, on the other hand, was written so that it behaves similarly to an official client, parsing and mapping renderers (a.k.a YTNodes) dynamically without hard-coding their path in the response. This way, whenever a new renderer pops up (e.g; YouTube adds a new feature / minor UI changes) the library will print a warning similar to this:
```
InnertubeError: SomeRenderer not found!
This is a bug, want to help us fix it? Follow the instructions at https://github.com/LuanRT/YouTube.js/blob/main/docs/updating-the-parser.md or report it at https://github.com/LuanRT/YouTube.js/issues!
@@ -26,17 +27,19 @@ Thanks to the modularity of the parser, a renderer can be implemented by simply
For example, say we found a new renderer named `verticalListRenderer`, to let the parser know it exists we would have to create a file with the following structure:
> `../classes/VerticalList.ts`
```ts
import Parser from '..';
import { YTNode } from '../helpers';
import type { RawNode } from '../index.js';
class VerticalList extends YTNode {
static type = 'VerticalList';
header;
contents;
constructor(data: any) {
constructor(data: RawNode) {
// parse the data here, ex;
this.header = Parser.parseItem(data.header);
this.contents = Parser.parseArray(data.contents);
@@ -47,8 +50,9 @@ export default VerticalList;
```
Then update the parser map:
```bash
npm run build:parser-map
```
And that's it!
And that's it!

View File

@@ -3,7 +3,7 @@ const { Innertube, UniversalCache } = require('youtubei.js');
(async () => {
const yt = await Innertube.create({
// required if you wish to use OAuth#cacheCredentials
cache: new UniversalCache()
cache: new UniversalCache(false)
});
// 'auth-pending' is fired with the info needed to sign in via OAuth.

View File

@@ -9,7 +9,7 @@ To use YouTube.js in the browser you must proxy requests through your own server
We'll use our own fetch implementation to proxy requests through our server. This is a simple example, but you can use any fetch implementation you want.
```ts
import { Innertube } from "youtubei.js/build/browser";
import { Innertube } from "youtubei.js/web.bundle.min";
const yt = await Innertube.create({
fetch: async (input, init) => {

View File

@@ -1,7 +1,7 @@
import { Innertube, UniversalCache, YTNodes } from 'youtubei.js';
(async () => {
const yt = await Innertube.create({ cache: new UniversalCache(), generate_session_locally: true });
const yt = await Innertube.create({ cache: new UniversalCache(false), generate_session_locally: true });
const channel = await yt.getChannel('UCX6OQ3DkcsbYNE6H8uQQuVA');

View File

@@ -1,7 +1,7 @@
import { Innertube, UniversalCache } from 'youtubei.js';
(async () => {
const yt = await Innertube.create({ cache: new UniversalCache(), generate_session_locally: true });
const yt = await Innertube.create({ cache: new UniversalCache(false), generate_session_locally: true });
const comment_section = await yt.getComments('a-rqu-hjobc');

View File

@@ -1,4 +1,4 @@
import { Innertube } from '../../bundle/browser.js';
import { Innertube } from 'https://deno.land/x/youtubei/deno.ts';
const yt = await Innertube.create();

View File

@@ -1,9 +1,8 @@
import { Innertube, UniversalCache } from 'youtubei.js';
import { readFileSync, existsSync, mkdirSync, createWriteStream } from 'fs';
import { streamToIterable } from 'youtubei.js/dist/src/utils/Utils';
import { Innertube, UniversalCache, Utils } from 'youtubei.js';
import { existsSync, mkdirSync, createWriteStream } from 'fs';
(async () => {
const yt = await Innertube.create({ cache: new UniversalCache(), generate_session_locally: true });
const yt = await Innertube.create({ cache: new UniversalCache(false), generate_session_locally: true });
const search = await yt.music.search('No Copyright Background Music', { type: 'album' });
@@ -34,7 +33,7 @@ import { streamToIterable } from 'youtubei.js/dist/src/utils/Utils';
const file = createWriteStream(`${dir}/${song.title?.replace(/\//g, '')}.m4a`);
for await (const chunk of streamToIterable(stream)) {
for await (const chunk of Utils.streamToIterable(stream)) {
file.write(chunk);
}

View File

@@ -1,9 +1,8 @@
import { Innertube, UniversalCache, YTNodes } from 'youtubei.js';
import { LiveChatContinuation } from 'youtubei.js/dist/src/parser';
import { Innertube, UniversalCache, YTNodes, LiveChatContinuation } from 'youtubei.js';
import { ChatAction, LiveMetadata } from 'youtubei.js/dist/src/parser/youtube/LiveChat';
(async () => {
const yt = await Innertube.create({ cache: new UniversalCache(), generate_session_locally: true });
const yt = await Innertube.create({ cache: new UniversalCache(false), generate_session_locally: true });
const search = await yt.search('lofi hip hop radio - beats to relax/study to');
const info = await yt.getInfo(search.videos[0].as(YTNodes.Video).id);

View File

@@ -5,7 +5,7 @@ const creds_path = './my_yt_creds.json';
const creds = existsSync(creds_path) ? JSON.parse(readFileSync(creds_path).toString()) : undefined;
(async () => {
const yt = await Innertube.create({ cache: new UniversalCache() });
const yt = await Innertube.create({ cache: new UniversalCache(false) });
yt.session.on('auth-pending', (data: any) => {
console.info(`Hello!\nOn your phone or computer, go to ${data.verification_url} and enter the code ${data.user_code}`);

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "youtubei.js",
"version": "3.0.0",
"version": "3.3.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "youtubei.js",
"version": "3.0.0",
"version": "3.3.0",
"funding": [
"https://github.com/sponsors/LuanRT"
],

View File

@@ -1,6 +1,6 @@
{
"name": "youtubei.js",
"version": "3.0.0",
"version": "3.3.0",
"description": "A wrapper around YouTube's private API. Supports YouTube, YouTube Music, YouTube Kids and YouTube Studio (WIP).",
"type": "module",
"types": "./dist/src/platform/lib.d.ts",

View File

@@ -19,7 +19,8 @@ glob.sync('../src/parser/classes/**/*.{js,ts}', { cwd: __dirname })
import_list.push(`import { default as ${class_name} } from './classes/${file}.js';`);
misc_exports.push(class_name);
} else {
import_list.push(`import { default as ${import_name} } from './classes/${file}.js';`);
import_list.push(`import { default as ${import_name} } from './classes/${file}.js';
export { ${import_name} };`);
json.push(import_name);
}
});
@@ -32,7 +33,7 @@ import { YTNodeConstructor } from './helpers.js';
${import_list.join('\n')}
export const YTNodes = {
const map: Record<string, YTNodeConstructor> = {
${json.join(',\n ')}
};
@@ -40,8 +41,6 @@ export const Misc = {
${misc_exports.join(',\n ')}
};
const map: Record<string, YTNodeConstructor> = YTNodes;
/**
* @param name - Name of the node to be parsed
*/

View File

@@ -21,6 +21,7 @@ import PlaylistManager from './core/PlaylistManager.js';
import YTStudio from './core/Studio.js';
import TabbedFeed from './core/TabbedFeed.js';
import HomeFeed from './parser/youtube/HomeFeed.js';
import Guide from './parser/youtube/Guide.js';
import Proto from './proto/index.js';
import Constants from './utils/Constants.js';
@@ -30,7 +31,7 @@ import type Format from './parser/classes/misc/Format.js';
import type { ApiResponse } from './core/Actions.js';
import type { IBrowseResponse, IParsedResponse } from './parser/types/index.js';
import type { DownloadOptions, FormatOptions } from './utils/FormatUtils.js';
import { generateRandomString, throwIfMissing } from './utils/Utils.js';
import { generateRandomString, InnertubeError, throwIfMissing } from './utils/Utils.js';
export type InnertubeConfig = SessionOptions;
@@ -71,16 +72,48 @@ class Innertube {
/**
* Retrieves video info.
* @param video_id - The video id.
* @param target - The video id or `NavigationEndpoint`.
* @param client - The client to use.
*/
async getInfo(video_id: string, client?: InnerTubeClient): Promise<VideoInfo> {
throwIfMissing({ video_id });
async getInfo(target: string | NavigationEndpoint, client?: InnerTubeClient): Promise<VideoInfo> {
throwIfMissing({ target });
let payload: {
videoId: string,
playlistId?: string,
params?: string,
playlistIndex?: number
};
if (target instanceof NavigationEndpoint) {
const video_id = target.payload?.videoId;
if (!video_id) {
throw new InnertubeError('Missing video id in endpoint payload.', target);
}
payload = {
videoId: video_id
};
if (target.payload.playlistId) {
payload.playlistId = target.payload.playlistId;
}
if (target.payload.params) {
payload.params = target.payload.params;
}
if (target.payload.index) {
payload.playlistIndex = target.payload.index;
}
} else if (typeof target === 'string') {
payload = {
videoId: target
};
} else {
throw new InnertubeError('Invalid target, expected either a video id or a valid NavigationEndpoint', target);
}
const cpn = generateRandomString(16);
const initial_info = this.actions.getVideoInfo(video_id, cpn, client);
const continuation = this.actions.execute('/next', { videoId: video_id });
const initial_info = this.actions.getVideoInfo(payload.videoId, cpn, client);
const continuation = this.actions.execute('/next', payload);
const response = await Promise.all([ initial_info, continuation ]);
return new VideoInfo(response, this.actions, this.session.player, cpn);
@@ -170,6 +203,14 @@ class Innertube {
return new HomeFeed(this.actions, response);
}
/**
* Retrieves YouTube's content guide.
*/
async getGuide(): Promise<Guide> {
const response = await this.actions.execute('/guide');
return new Guide(response.data);
}
/**
* Returns the account's library.
*/

View File

@@ -4,6 +4,7 @@ import { concatMemos, InnertubeError } from '../utils/Utils.js';
import type Actions from './Actions.js';
import BackstagePost from '../parser/classes/BackstagePost.js';
import SharedPost from '../parser/classes/SharedPost.js';
import Channel from '../parser/classes/Channel.js';
import CompactVideo from '../parser/classes/CompactVideo.js';
import GridChannel from '../parser/classes/GridChannel.js';
@@ -100,7 +101,7 @@ class Feed<T extends IParsedResponse = IParsedResponse> {
* Get all the community posts in the feed
*/
get posts() {
return this.#memo.getType<Post | BackstagePost>([ BackstagePost, Post ]);
return this.#memo.getType<Post | BackstagePost | SharedPost>([ BackstagePost, Post, SharedPost ]);
}
/**

View File

@@ -28,6 +28,10 @@ import type { ObservedArray, YTNode } from '../parser/helpers.js';
import type Actions from './Actions.js';
import type Session from './Session.js';
export type SearchFilters = {
type?: 'all' | 'song' | 'video' | 'album' | 'playlist' | 'artist';
};
class Music {
#session: Session;
#actions: Actions;
@@ -108,9 +112,7 @@ class Music {
* @param query - Search query.
* @param filters - Search filters.
*/
async search(query: string, filters: {
type?: 'all' | 'song' | 'video' | 'album' | 'playlist' | 'artist';
} = {}): Promise<Search> {
async search(query: string, filters: SearchFilters = {}): Promise<Search> {
throwIfMissing({ query });
const payload: {

View File

@@ -4,7 +4,7 @@ import Actions from './Actions.js';
import Player from './Player.js';
import HTTPClient from '../utils/HTTPClient.js';
import { Platform, DeviceCategory, generateRandomString, getRandomUserAgent, InnertubeError, SessionError } from '../utils/Utils.js';
import { Platform, DeviceCategory, getRandomUserAgent, InnertubeError, SessionError } from '../utils/Utils.js';
import OAuth, { Credentials, OAuthAuthErrorEventHandler, OAuthAuthEventHandler, OAuthAuthPendingEventHandler } from './OAuth.js';
import Proto from '../proto/index.js';
import { ICache } from '../types/Cache.js';
@@ -232,7 +232,7 @@ export default class Session extends EventEmitterLike {
'user-agent': getRandomUserAgent('desktop'),
'accept': '*/*',
'referer': 'https://www.youtube.com/sw.js',
'cookie': `PREF=tz=${options.time_zone.replace('/', '.')}`
'cookie': `PREF=tz=${options.time_zone.replace('/', '.')};VISITOR_INFO1_LIVE=${Constants.CLIENTS.WEB.STATIC_VISITOR_ID};`
}
});
@@ -294,7 +294,7 @@ export default class Session extends EventEmitterLike {
client_name: string;
enable_safety_mode: boolean
}): SessionData {
const id = generateRandomString(11);
const id = Constants.CLIENTS.WEB.STATIC_VISITOR_ID;
const timestamp = Math.floor(Date.now() / 1000);
const context: Context = {

View File

@@ -1,6 +1,7 @@
import Text from './misc/Text.js';
import NavigationEndpoint from './NavigationEndpoint.js';
import { YTNode } from '../helpers.js';
import type { RawNode } from '../index.js';
class AccountChannel extends YTNode {
static type = 'AccountChannel';
@@ -8,7 +9,7 @@ class AccountChannel extends YTNode {
title: Text;
endpoint: NavigationEndpoint;
constructor(data: any) {
constructor(data: RawNode) {
super();
this.title = new Text(data.title);
this.endpoint = new NavigationEndpoint(data.navigationEndpoint);

View File

@@ -6,6 +6,7 @@ import NavigationEndpoint from './NavigationEndpoint.js';
import AccountItemSectionHeader from './AccountItemSectionHeader.js';
import { YTNode } from '../helpers.js';
import type { RawNode } from '../index.js';
class AccountItem {
static type = 'AccountItem';
@@ -18,7 +19,7 @@ class AccountItem {
endpoint: NavigationEndpoint;
account_byline: Text;
constructor(data: any) {
constructor(data: RawNode) {
this.account_name = new Text(data.accountName);
this.account_photo = Thumbnail.fromResponse(data.accountPhoto);
this.is_selected = data.isSelected;
@@ -35,7 +36,7 @@ class AccountItemSection extends YTNode {
contents;
header;
constructor(data: any) {
constructor(data: RawNode) {
super();
this.contents = data.contents.map((ac: any) => new AccountItem(ac.accountItem));
this.header = Parser.parseItem<AccountItemSectionHeader>(data.header, AccountItemSectionHeader);

View File

@@ -1,12 +1,12 @@
import Text from './misc/Text.js';
import { YTNode } from '../helpers.js';
import type { RawNode } from '../index.js';
class AccountItemSectionHeader extends YTNode {
static type = 'AccountItemSectionHeader';
title: Text;
constructor(data: any) {
constructor(data: RawNode) {
super();
this.title = new Text(data.title);
}

View File

@@ -3,14 +3,14 @@ import AccountChannel from './AccountChannel.js';
import AccountItemSection from './AccountItemSection.js';
import { YTNode } from '../helpers.js';
import type { RawNode } from '../index.js';
class AccountSectionList extends YTNode {
static type = 'AccountSectionList';
contents;
footers;
constructor(data: any) {
constructor(data: RawNode) {
super();
this.contents = Parser.parseItem<AccountItemSection>(data.contents[0], AccountItemSection);
this.footers = Parser.parseItem<AccountChannel>(data.footers[0], AccountChannel);

View File

@@ -1,13 +1,13 @@
import Text from './misc/Text.js';
import { YTNode } from '../helpers.js';
import type { RawNode } from '../index.js';
class Alert extends YTNode {
static type = 'Alert';
text: Text;
alert_type: string;
constructor(data: any) {
constructor(data: RawNode) {
super();
this.text = new Text(data.text);
this.alert_type = data.type;

View File

@@ -1,11 +1,11 @@
import { YTNode } from '../helpers.js';
import type { RawNode } from '../index.js';
class AudioOnlyPlayability extends YTNode {
static type = 'AudioOnlyPlayability';
audio_only_availability: string;
constructor (data: any) {
constructor (data: RawNode) {
super();
this.audio_only_availability = data.audioOnlyAvailability;
}

View File

@@ -1,12 +1,12 @@
import { YTNode } from '../helpers.js';
import NavigationEndpoint from './NavigationEndpoint.js';
import type { RawNode } from '../index.js';
class AutomixPreviewVideo extends YTNode {
static type = 'AutomixPreviewVideo';
playlist_video?: { endpoint: NavigationEndpoint };
constructor(data: any) {
constructor(data: RawNode) {
super();
if (data?.content?.automixPlaylistVideoRenderer?.navigationEndpoint) {
this.playlist_video = {

View File

@@ -0,0 +1,16 @@
import { YTNode } from '../helpers.js';
import Parser, { RawNode } from '../index.js';
import Message from './Message.js';
class ConversationBar extends YTNode {
static type = 'ConversationBar';
availability_message: Message | null;
constructor(data: RawNode) {
super();
this.availability_message = Parser.parseItem<Message>(data.availabilityMessage, Message);
}
}
export default ConversationBar;

View File

@@ -2,6 +2,7 @@ import Parser from '../index.js';
import { YTNode } from '../helpers.js';
import type Button from './Button.js';
import type MultiMarkersPlayerBar from './MultiMarkersPlayerBar.js';
import type { RawNode } from '../index.js';
class DecoratedPlayerBar extends YTNode {
static type = 'DecoratedPlayerBar';
@@ -9,7 +10,7 @@ class DecoratedPlayerBar extends YTNode {
player_bar: MultiMarkersPlayerBar | null;
player_bar_action_button: Button | null;
constructor(data: any) {
constructor(data: RawNode) {
super();
this.player_bar = Parser.parseItem<MultiMarkersPlayerBar>(data.playerBar);
this.player_bar_action_button = Parser.parseItem<Button>(data.playerBarActionButton);

View File

@@ -0,0 +1,35 @@
import Text from './misc/Text.js';
import { YTNode } from '../helpers.js';
import Parser from '../parser.js';
class GuideCollapsibleEntry extends YTNode {
static type = 'GuideCollapsibleEntry';
expander_item: {
title: string,
icon_type: string
};
collapser_item: {
title: string,
icon_type: string
};
expandable_items;
constructor(data: any) {
super();
this.expander_item = {
title: new Text(data.expanderItem.guideEntryRenderer.formattedTitle).toString(),
icon_type: data.expanderItem.guideEntryRenderer.icon.iconType
};
this.collapser_item = {
title: new Text(data.collapserItem.guideEntryRenderer.formattedTitle).toString(),
icon_type: data.collapserItem.guideEntryRenderer.icon.iconType
};
this.expandable_items = Parser.parseArray(data.expandableItems);
}
}
export default GuideCollapsibleEntry;

View File

@@ -0,0 +1,23 @@
import { YTNode } from '../helpers.js';
import Parser from '../parser.js';
class GuideCollapsibleSectionEntry extends YTNode {
static type = 'GuideCollapsibleSectionEntry';
header_entry;
expander_icon: string;
collapser_icon: string;
section_items;
constructor(data: any) {
super();
this.header_entry = Parser.parseItem(data.headerEntry);
this.expander_icon = data.expanderIcon.iconType;
this.collapser_icon = data.collapserIcon.iconType;
this.section_items = Parser.parseArray(data.sectionItems);
}
}
export default GuideCollapsibleSectionEntry;

View File

@@ -0,0 +1,14 @@
import GuideEntry from './GuideEntry.js';
class GuideDownloadsEntry extends GuideEntry {
static type = 'GuideDownloadsEntry';
always_show: boolean;
constructor(data: any) {
super(data.entryRenderer.guideEntryRenderer);
this.always_show = !!data.alwaysShow;
}
}
export default GuideDownloadsEntry;

View File

@@ -0,0 +1,33 @@
import Text from './misc/Text.js';
import NavigationEndpoint from './NavigationEndpoint.js';
import { YTNode } from '../helpers.js';
import Thumbnail from './misc/Thumbnail.js';
class GuideEntry extends YTNode {
static type = 'GuideEntry';
title: Text;
endpoint: NavigationEndpoint;
icon_type?: string;
thumbnails?: Thumbnail[];
badges?: any;
is_primary: boolean;
constructor(data: any) {
super();
this.title = new Text(data.formattedTitle);
this.endpoint = new NavigationEndpoint(data.navigationEndpoint || data.serviceEndpoint);
if (data.icon?.iconType) {
this.icon_type = data.icon.iconType;
}
if (data.thumbnail) {
this.thumbnails = Thumbnail.fromResponse(data.thumbnail);
}
if (data.badges) {
this.badges = data.badges;
}
this.is_primary = !!data.isPrimary;
}
}
export default GuideEntry;

View File

@@ -0,0 +1,20 @@
import Text from './misc/Text.js';
import { YTNode } from '../helpers.js';
import Parser from '../parser.js';
class GuideSection extends YTNode {
static type = 'GuideSection';
title?: Text;
items;
constructor(data: any) {
super();
if (data.formattedTitle) {
this.title = new Text(data.formattedTitle);
}
this.items = Parser.parseArray(data.items);
}
}
export default GuideSection;

View File

@@ -0,0 +1,7 @@
import GuideSection from './GuideSection.js';
class GuideSubscriptionsSection extends GuideSection {
static type = 'GuideSubscriptionsSection';
}
export default GuideSubscriptionsSection;

View File

@@ -0,0 +1,19 @@
import { YTNode } from '../helpers.js';
import NavigationEndpoint from './NavigationEndpoint.js';
import Thumbnail from './misc/Thumbnail.js';
class HeroPlaylistThumbnail extends YTNode {
static type = 'HeroPlaylistThumbnail';
thumbnails: Thumbnail[];
on_tap_endpoint: NavigationEndpoint;
constructor(data: any) {
super();
this.thumbnails = Thumbnail.fromResponse(data.thumbnail);
this.on_tap_endpoint = new NavigationEndpoint(data.onTap);
}
}
export default HeroPlaylistThumbnail;

View File

@@ -1,6 +1,7 @@
import Parser from '../index.js';
import type Chapter from './Chapter.js';
import type Heatmap from './Heatmap.js';
import type { RawNode } from '../index.js';
import { observe, ObservedArray, YTNode } from '../helpers.js';
@@ -13,7 +14,7 @@ class Marker extends YTNode {
chapters?: Chapter[];
};
constructor (data: any) {
constructor (data: RawNode) {
super();
this.marker_key = data.key;
@@ -34,9 +35,12 @@ class MultiMarkersPlayerBar extends YTNode {
markers_map: ObservedArray<Marker>;
constructor(data: any) {
constructor(data: RawNode) {
super();
this.markers_map = observe(data.markersMap?.map((marker: { key: string; value: { [key: string ]: any }}) => new Marker(marker)));
this.markers_map = observe(data.markersMap?.map((marker: {
key: string;
value: { [key: string ]: any
}}) => new Marker(marker)) || []);
}
}

View File

@@ -21,7 +21,6 @@ class NavigationEndpoint extends YTNode {
constructor(data: any) {
super();
// This is only present in Android nav endpoints
if (Reflect.has(data || {}, 'innertubeCommand'))
data = data.innertubeCommand;

View File

@@ -0,0 +1,21 @@
import { YTNode } from '../helpers.js';
import type { RawNode } from '../index.js';
class PlayerLegacyDesktopYpcOffer extends YTNode {
static type = 'PlayerLegacyDesktopYpcOffer';
title: string;
thumbnail: string;
offer_description: string;
offer_id: string;
constructor(data: RawNode) {
super();
this.title = data.itemTitle;
this.thumbnail = data.itemThumbnail;
this.offer_description = data.offerDescription;
this.offer_id = data.offerId;
}
}
export default PlayerLegacyDesktopYpcOffer;

View File

@@ -16,7 +16,7 @@ class PlayerMicroformat extends YTNode {
// TODO: check these
width: any;
height: any;
};
} | null;
length_seconds: number;
@@ -42,13 +42,17 @@ class PlayerMicroformat extends YTNode {
this.description = new Text(data.description);
this.thumbnails = Thumbnail.fromResponse(data.thumbnail);
this.embed = {
iframe_url: data.embed.iframeUrl,
flash_url: data.embed.flashUrl,
flash_secure_url: data.embed.flashSecureUrl,
width: data.embed.width,
height: data.embed.height
};
if (data.embed) {
this.embed = {
iframe_url: data.embed.iframeUrl,
flash_url: data.embed.flashUrl,
flash_secure_url: data.embed.flashSecureUrl,
width: data.embed.width,
height: data.embed.height
};
} else {
this.embed = null;
}
this.length_seconds = parseInt(data.lengthSeconds);

View File

@@ -21,6 +21,7 @@ class PlaylistHeader extends YTNode {
save_button;
shuffle_play_button;
menu;
banner;
constructor(data: any) {
super();
@@ -39,6 +40,7 @@ class PlaylistHeader extends YTNode {
this.save_button = Parser.parse(data.saveButton);
this.shuffle_play_button = Parser.parse(data.shufflePlayButton);
this.menu = Parser.parse(data.moreActionsMenu);
this.banner = Parser.parseItem(data.playlistHeaderBanner);
}
}

View File

@@ -3,6 +3,7 @@ import Parser from '../index.js';
import Thumbnail from './misc/Thumbnail.js';
import PlaylistAuthor from './misc/PlaylistAuthor.js';
import NavigationEndpoint from './NavigationEndpoint.js';
import ThumbnailOverlayTimeStatus from './ThumbnailOverlayTimeStatus.js';
import type Menu from './menus/Menu.js';
import { YTNode } from '../helpers.js';
@@ -20,6 +21,7 @@ class PlaylistVideo extends YTNode {
endpoint: NavigationEndpoint;
is_playable: boolean;
menu: Menu | null;
upcoming;
duration: {
text: string;
@@ -33,16 +35,30 @@ class PlaylistVideo extends YTNode {
this.title = new Text(data.title);
this.author = new PlaylistAuthor(data.shortBylineText);
this.thumbnails = Thumbnail.fromResponse(data.thumbnail);
this.thumbnail_overlays = Parser.parse(data.thumbnailOverlays);
this.thumbnail_overlays = Parser.parseArray(data.thumbnailOverlays);
this.set_video_id = data?.setVideoId;
this.endpoint = new NavigationEndpoint(data.navigationEndpoint);
this.is_playable = data.isPlayable;
this.menu = Parser.parseItem<Menu>(data.menu);
const upcoming = data.upcomingEventData && Number(`${data.upcomingEventData.startTime}000`);
if (upcoming) {
this.upcoming = new Date(upcoming);
}
this.duration = {
text: new Text(data.lengthText).text,
seconds: parseInt(data.lengthSeconds)
};
}
get is_live(): boolean {
return this.thumbnail_overlays.firstOfType(ThumbnailOverlayTimeStatus)?.style === 'LIVE';
}
get is_upcoming(): boolean {
return this.thumbnail_overlays.firstOfType(ThumbnailOverlayTimeStatus)?.style === 'UPCOMING';
}
}
export default PlaylistVideo;

View File

@@ -0,0 +1,32 @@
import Text from './misc/Text.js';
import Thumbnail from './misc/Thumbnail.js';
import NavigationEndpoint from './NavigationEndpoint.js';
import { YTNode } from '../helpers.js';
class RichMetadata extends YTNode {
static type = 'RichMetadata';
thumbnail: Thumbnail[];
title: Text;
subtitle?: Text;
call_to_action: Text;
icon_type?: string;
endpoint: NavigationEndpoint;
constructor(data: any) {
super();
this.thumbnail = Thumbnail.fromResponse(data.thumbnail);
this.title = new Text(data.title);
this.subtitle = new Text(data.subtitle);
this.call_to_action = new Text(data.callToAction);
if (data.callToActionIcon?.iconType) {
this.icon_type = data.callToActionIcon?.iconType;
}
this.endpoint = new NavigationEndpoint(data.endpoint);
}
}
export default RichMetadata;

View File

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

View File

@@ -0,0 +1,22 @@
import { YTNode } from '../helpers.js';
import type { RawNode } from '../index.js';
import Text from './misc/Text.js';
import NavigationEndpoint from './NavigationEndpoint.js';
class SearchFilter extends YTNode {
static type = 'SearchFilter';
label: Text;
endpoint: NavigationEndpoint;
tooltip: string;
constructor(data: RawNode) {
super();
this.label = new Text(data.label);
this.endpoint = new NavigationEndpoint(data.endpoint);
this.tooltip = data.tooltip;
}
}
export default SearchFilter;

View File

@@ -0,0 +1,21 @@
import { ObservedArray, YTNode } from '../helpers.js';
import type { RawNode } from '../index.js';
import { Parser } from '../index.js';
import Text from './misc/Text.js';
import SearchFilter from './SearchFilter.js';
class SearchFilterGroup extends YTNode {
static type = 'SearchFilterGroup';
title: Text;
filters: ObservedArray<SearchFilter> | null;
constructor(data: RawNode) {
super();
this.title = new Text(data.title);
this.filters = Parser.parseArray(data.filters, SearchFilter);
}
}
export default SearchFilterGroup;

View File

@@ -0,0 +1,23 @@
import { ObservedArray, YTNode } from '../helpers.js';
import type { RawNode } from '../index.js';
import Parser from '../index.js';
import Text from './misc/Text.js';
import SearchFilterGroup from './SearchFilterGroup.js';
import ToggleButton from './ToggleButton.js';
class SearchSubMenu extends YTNode {
static type = 'SearchSubMenu';
title: Text;
groups: ObservedArray<SearchFilterGroup> | null;
button: ToggleButton | null;
constructor(data: RawNode) {
super();
this.title = new Text(data.title);
this.groups = Parser.parseArray(data.groups, SearchFilterGroup);
this.button = Parser.parseItem(data.button, ToggleButton);
}
}
export default SearchSubMenu;

View File

@@ -1,17 +1,19 @@
import Parser from '../index.js';
import ToggleButton from './ToggleButton.js';
import { YTNode } from '../helpers.js';
import type { RawNode } from '../index.js';
import Parser from '../index.js';
import Button from './Button.js';
import ToggleButton from './ToggleButton.js';
class SegmentedLikeDislikeButton extends YTNode {
static type = 'SegmentedLikeDislikeButton';
like_button: ToggleButton | null;
dislike_button: ToggleButton | null;
like_button: ToggleButton | Button | null;
dislike_button: ToggleButton | Button | null;
constructor (data: any) {
constructor (data: RawNode) {
super();
this.like_button = Parser.parseItem<ToggleButton>(data.likeButton, ToggleButton);
this.dislike_button = Parser.parseItem<ToggleButton>(data.dislikeButton, ToggleButton);
this.like_button = Parser.parseItem<ToggleButton | Button>(data.likeButton, [ ToggleButton, Button ]);
this.dislike_button = Parser.parseItem<ToggleButton | Button>(data.dislikeButton, [ ToggleButton, Button ]);
}
}

View File

@@ -0,0 +1,38 @@
import { YTNode } from '../helpers.js';
import Parser from '../parser.js';
import BackstagePost from './BackstagePost.js';
import Button from './Button.js';
import Menu from './menus/Menu.js';
import Author from './misc/Author.js';
import Text from './misc/Text.js';
import Thumbnail from './misc/Thumbnail.js';
import NavigationEndpoint from './NavigationEndpoint.js';
class SharedPost extends YTNode {
static type = 'SharedPost';
thumbnail: Thumbnail[];
content: Text;
published: Text;
menu: Menu | null;
original_post: BackstagePost | null;
id: string;
endpoint: NavigationEndpoint;
expand_button: Button | null;
author: Author;
constructor(data: any) {
super();
this.thumbnail = Thumbnail.fromResponse(data.thumbnail);
this.content = new Text(data.content);
this.published = new Text(data.publishedTimeText);
this.menu = Parser.parseItem(data.actionMenu, Menu);
this.original_post = Parser.parseItem(data.originalPost, BackstagePost);
this.id = data.postId;
this.endpoint = new NavigationEndpoint(data.navigationEndpoint);
this.expand_button = Parser.parseItem(data.expandButton, Button);
this.author = new Author(data.displayName, undefined);
}
}
export default SharedPost;

View File

@@ -5,10 +5,12 @@ class ThumbnailOverlayTimeStatus extends YTNode {
static type = 'ThumbnailOverlayTimeStatus';
text: string;
style: string;
constructor(data: any) {
super();
this.text = new Text(data.text).toString();
this.style = data.style;
}
}

View File

@@ -1,5 +1,15 @@
import Parser from '../index.js';
import { YTNode } from '../helpers.js';
import Text from './misc/Text.js';
import PlaylistAuthor from './misc/PlaylistAuthor.js';
import NavigationEndpoint from './NavigationEndpoint.js';
import type Menu from './menus/Menu.js';
type AutoplaySet = {
autoplay_video: NavigationEndpoint,
next_button_video?: NavigationEndpoint
};
class TwoColumnWatchNextResults extends YTNode {
static type = 'TwoColumnWatchNextResults';
@@ -7,12 +17,66 @@ class TwoColumnWatchNextResults extends YTNode {
results;
secondary_results;
conversation_bar;
playlist?: {
id: string,
title: string,
author: Text | PlaylistAuthor,
contents: YTNode[],
current_index: number,
is_infinite: boolean,
menu: Menu | null
};
autoplay?: {
sets: AutoplaySet[],
modified_sets?: AutoplaySet[],
count_down_secs?: number
};
constructor(data: any) {
super();
this.results = Parser.parseArray(data.results?.results.contents);
this.secondary_results = Parser.parseArray(data.secondaryResults?.secondaryResults.results);
this.conversation_bar = Parser.parseItem(data?.conversationBar);
const playlistData = data.playlist?.playlist;
if (playlistData) {
this.playlist = {
id: playlistData.playlistId,
title: playlistData.title,
author: playlistData.shortBylineText?.simpleText ?
new Text(playlistData.shortBylineText) :
new PlaylistAuthor(playlistData.longBylineText),
contents: Parser.parseArray(playlistData.contents),
current_index: playlistData.currentIndex,
is_infinite: !!playlistData.isInfinite,
menu: Parser.parseItem<Menu>(playlistData.menu)
};
}
const autoplayData = data.autoplay?.autoplay;
if (autoplayData) {
this.autoplay = {
sets: autoplayData.sets.map((set: any) => this.#parseAutoplaySet(set))
};
if (autoplayData.modifiedSets) {
this.autoplay.modified_sets = autoplayData.modifiedSets.map((set: any) => this.#parseAutoplaySet(set));
}
if (autoplayData.countDownSecs) {
this.autoplay.count_down_secs = autoplayData.countDownSecs;
}
}
}
#parseAutoplaySet(data: any): AutoplaySet {
const result = {
autoplay_video: new NavigationEndpoint(data.autoplayVideo)
} as AutoplaySet;
if (data.nextButtonVideo) {
result.next_button_video = new NavigationEndpoint(data.nextButtonVideo);
}
return result;
}
}

View File

@@ -1,6 +1,9 @@
import { YTNode } from '../helpers.js';
import type { RawNode } from '../index.js';
import type { ObservedArray } from '../helpers.js';
import Parser from '../index.js';
import Text from './misc/Text.js';
import { YTNode } from '../helpers.js';
import MetadataBadge from './MetadataBadge.js';
import Menu from './menus/Menu.js';
class VideoPrimaryInfo extends YTNode {
@@ -10,16 +13,20 @@ class VideoPrimaryInfo extends YTNode {
super_title_link: Text;
view_count: Text;
short_view_count: Text;
badges: ObservedArray<MetadataBadge>;
published: Text;
menu;
relative_date: Text;
menu: Menu | null;
constructor(data: any) {
constructor(data: RawNode) {
super();
this.title = new Text(data.title);
this.super_title_link = new Text(data.superTitleLink);
this.view_count = new Text(data.viewCount.videoViewCountRenderer.viewCount);
this.short_view_count = new Text(data.viewCount.videoViewCountRenderer.shortViewCount);
this.view_count = new Text(data.viewCount?.videoViewCountRenderer?.viewCount);
this.short_view_count = new Text(data.viewCount?.videoViewCountRenderer?.shortViewCount);
this.badges = Parser.parseArray(data.badges, MetadataBadge);
this.published = new Text(data.dateText);
this.relative_date = new Text(data.relativeDateText);
this.menu = Parser.parseItem(data.videoActions, Menu);
}
}

View File

@@ -1,4 +1,4 @@
import Parser from '../index.js';
import Parser, { RawNode } from '../index.js';
import Text from './misc/Text.js';
import Button from './Button.js';
import VideoOwner from './VideoOwner.js';
@@ -9,19 +9,24 @@ import { YTNode } from '../helpers.js';
class VideoSecondaryInfo extends YTNode {
static type = 'VideoSecondaryInfo';
owner: VideoOwner | null;// TODO: VideoOwner?
owner: VideoOwner | null;
description: Text;
subscribe_button;
subscribe_button: SubscribeButton | Button | null;
metadata: MetadataRowContainer | null;
show_more_text: string;
show_less_text: string;
default_expanded: string;
description_collapsed_lines: string;
constructor(data: any) {
constructor(data: RawNode) {
super();
this.owner = Parser.parseItem<VideoOwner>(data.owner);
this.description = new Text(data.description);
if (Reflect.has(data, 'attributedDescription')) {
this.description = new Text(this.#convertAttributedDescriptionToRuns(data.attributedDescription));
}
this.subscribe_button = Parser.parseItem<SubscribeButton | Button>(data.subscribeButton, [ SubscribeButton, Button ]);
this.metadata = Parser.parseItem<MetadataRowContainer>(data.metadataRowContainer, MetadataRowContainer);
this.show_more_text = data.showMoreText;
@@ -29,6 +34,74 @@ class VideoSecondaryInfo extends YTNode {
this.default_expanded = data.defaultExpanded;
this.description_collapsed_lines = data.descriptionCollapsedLines;
}
#convertAttributedDescriptionToRuns(description: RawNode) {
const runs: {
text: string,
navigationEndpoint?: RawNode,
attachment?: RawNode
}[] = [];
const content = description.content;
const command_runs = description.commandRuns;
let last_end_index = 0;
if (command_runs) {
for (const item of command_runs) {
const length: number = item.length;
const start_index: number = item.startIndex;
if (start_index > last_end_index) {
runs.push({
text: content.slice(last_end_index, start_index)
});
}
if (Reflect.has(item, 'onTap')) {
let attachment = null;
if (Reflect.has(description, 'attachmentRuns')) {
const attachment_runs = description.attachmentRuns;
for (const attatchment_run of attachment_runs) {
if ((attatchment_run.startIndex - 2) == start_index) {
attachment = attatchment_run;
break;
}
}
}
if (attachment) {
runs.push({
text: content.slice(start_index, start_index + length),
navigationEndpoint: item.onTap,
attachment
});
} else {
runs.push({
text: content.slice(start_index, start_index + length),
navigationEndpoint: item.onTap
});
}
}
last_end_index = start_index + length;
}
if (last_end_index < content.length) {
runs.push({
text: content.slice(last_end_index)
});
}
} else {
runs.push({
text: content
});
}
return { runs };
}
}
export default VideoSecondaryInfo;

View File

@@ -1,5 +1,6 @@
import Parser from '../../index.js';
import { YTNode } from '../../helpers.js';
import type { RawNode } from '../../index.js';
class AppendContinuationItemsAction extends YTNode {
static type = 'AppendContinuationItemsAction';
@@ -7,7 +8,7 @@ class AppendContinuationItemsAction extends YTNode {
items;
target: string;
constructor(data: any) {
constructor(data: RawNode) {
super();
this.items = Parser.parse(data.continuationItems);
this.target = data.target;

View File

@@ -1,5 +1,6 @@
import Parser from '../../index.js';
import { YTNode } from '../../helpers.js';
import type { RawNode } from '../../index.js';
class OpenPopupAction extends YTNode {
static type = 'OpenPopupAction';
@@ -7,7 +8,7 @@ class OpenPopupAction extends YTNode {
popup;
popup_type;
constructor(data: any) {
constructor(data: RawNode) {
super();
this.popup = Parser.parse(data.popup);
this.popup_type = data.popupType;

View File

@@ -1,5 +1,6 @@
import DataModelSection from './DataModelSection.js';
import { YTNode } from '../../helpers.js';
import type { RawNode } from '../../index.js';
class AnalyticsMainAppKeyMetrics extends YTNode {
static type = 'AnalyticsMainAppKeyMetrics';
@@ -7,7 +8,7 @@ class AnalyticsMainAppKeyMetrics extends YTNode {
period: string;
sections: DataModelSection[];
constructor(data: any) {
constructor(data: RawNode) {
super();
this.period = data.cardData.periodLabel;
const metrics_data = data.cardData.sections[0].analyticsKeyMetricsData;

View File

@@ -1,4 +1,5 @@
import { YTNode } from '../../helpers.js';
import type { RawNode } from '../../index.js';
class AnalyticsRoot extends YTNode {
static type = 'AnalyticsRoot';
@@ -19,7 +20,7 @@ class AnalyticsRoot extends YTNode {
}[];
}[];
constructor(data: any) {
constructor(data: RawNode) {
super();
const cards = data.analyticsTableCarouselData.data.tableCards;

View File

@@ -1,4 +1,5 @@
import { YTNode } from '../../helpers.js';
import type { RawNode } from '../../index.js';
import NavigationEndpoint from '../NavigationEndpoint.js';
class AnalyticsShortsCarouselCard extends YTNode {
@@ -11,7 +12,7 @@ class AnalyticsShortsCarouselCard extends YTNode {
endpoint: NavigationEndpoint;
}[];
constructor(data: any) {
constructor(data: RawNode) {
super();
this.title = data.title;
this.shorts = data.shortsCarouselData.shorts.map((short: any) => ({

View File

@@ -1,5 +1,6 @@
import Thumbnail from '../misc/Thumbnail.js';
import { YTNode } from '../../helpers.js';
import type { RawNode } from '../../index.js';
class AnalyticsVideo extends YTNode {
static type = 'AnalyticsVideo';
@@ -13,7 +14,7 @@ class AnalyticsVideo extends YTNode {
is_short: boolean;
};
constructor(data: any) {
constructor(data: RawNode) {
super();
this.title = data.videoTitle;

View File

@@ -1,5 +1,6 @@
import Video from './AnalyticsVideo.js';
import { YTNode } from '../../helpers.js';
import type { RawNode } from '../../index.js';
class AnalyticsVodCarouselCard extends YTNode {
static type = 'AnalyticsVodCarouselCard';
@@ -8,7 +9,7 @@ class AnalyticsVodCarouselCard extends YTNode {
videos: Video[] | null;
no_data_message?: string;
constructor(data: any) {
constructor(data: RawNode) {
super();
this.title = data.title;

View File

@@ -1,4 +1,5 @@
import { YTNode } from '../../helpers.js';
import type { RawNode } from '../../index.js';
class CtaGoToCreatorStudio extends YTNode {
static type = 'CtaGoToCreatorStudio';
@@ -6,7 +7,7 @@ class CtaGoToCreatorStudio extends YTNode {
title: string;
use_new_specs: boolean;
constructor(data: any) {
constructor(data: RawNode) {
super();
this.title = data.buttonLabel;
this.use_new_specs = data.useNewSpecs;

View File

@@ -1,4 +1,5 @@
import { YTNode } from '../../helpers.js';
import type { RawNode } from '../../index.js';
class DataModelSection extends YTNode {
static type = 'DataModelSection';
@@ -36,7 +37,7 @@ class DataModelSection extends YTNode {
}
};
constructor(data: any) {
constructor(data: RawNode) {
super();
this.title = data.title;

View File

@@ -1,6 +1,7 @@
import Text from '../misc/Text.js';
import { YTNode } from '../../helpers.js';
import type { RawNode } from '../../index.js';
class StatRow extends YTNode {
static type = 'StatRow';
@@ -8,7 +9,7 @@ class StatRow extends YTNode {
title: Text;
contents: Text;
constructor(data: any) {
constructor(data: RawNode) {
super();
this.title = new Text(data.title);
this.contents = new Text(data.contents);

View File

@@ -1,4 +1,5 @@
import { YTNode } from '../../helpers.js';
import type { RawNode } from '../../index.js';
class AuthorCommentBadge extends YTNode {
static type = 'AuthorCommentBadge';
@@ -9,7 +10,7 @@ class AuthorCommentBadge extends YTNode {
tooltip: string;
style?: string;
constructor(data: any) {
constructor(data: RawNode) {
super();
this.icon_type = data.icon?.iconType || null;

View File

@@ -16,6 +16,7 @@ import type Actions from '../../../core/Actions.js';
import Proto from '../../../proto/index.js';
import { InnertubeError } from '../../../utils/Utils.js';
import { YTNode, SuperParsedResult } from '../../helpers.js';
import type { RawNode } from '../../index.js';
class Comment extends YTNode {
static type = 'Comment';
@@ -44,7 +45,7 @@ class Comment extends YTNode {
is_pinned: boolean;
is_member: boolean;
constructor(data: any) {
constructor(data: RawNode) {
super();
this.content = new Text(data.contentText);
this.published = new Text(data.publishedTimeText);

View File

@@ -3,6 +3,7 @@ import type Button from '../Button.js';
import type ToggleButton from '../ToggleButton.js';
import type CreatorHeart from './CreatorHeart.js';
import { YTNode } from '../../helpers.js';
import type { RawNode } from '../../index.js';
class CommentActionButtons extends YTNode {
static type = 'CommentActionButtons';
@@ -12,7 +13,7 @@ class CommentActionButtons extends YTNode {
reply_button;
creator_heart;
constructor(data: any) {
constructor(data: RawNode) {
super();
this.like_button = Parser.parseItem<ToggleButton>(data.likeButton);
this.dislike_button = Parser.parseItem<ToggleButton>(data.dislikeButton);

View File

@@ -4,6 +4,7 @@ import Thumbnail from '../misc/Thumbnail.js';
import type Button from '../Button.js';
import type EmojiPicker from './EmojiPicker.js';
import { YTNode } from '../../helpers.js';
import type { RawNode } from '../../index.js';
class CommentDialog extends YTNode {
static type = 'CommentDialog';
@@ -16,7 +17,7 @@ class CommentDialog extends YTNode {
emoji_button: Button | null;
emoji_picker: any | null;
constructor(data: any) {
constructor(data: RawNode) {
super();
this.editable_text = new Text(data.editableText);
this.author_thumbnail = Thumbnail.fromResponse(data.authorThumbnail);

View File

@@ -2,7 +2,7 @@ import Parser from '../../index.js';
import Thumbnail from '../misc/Thumbnail.js';
import type Button from '../Button.js';
import { YTNode } from '../../helpers.js';
import type { RawNode } from '../../index.js';
class CommentReplies extends YTNode {
static type = 'CommentReplies';
@@ -12,7 +12,7 @@ class CommentReplies extends YTNode {
view_replies_creator_thumbnail: Thumbnail[];
has_channel_owner_replied: boolean;
constructor(data: any) {
constructor(data: RawNode) {
super();
this.contents = Parser.parseArray(data.contents);
this.view_replies = Parser.parseItem<Button>(data.viewReplies);

View File

@@ -3,6 +3,7 @@ import Thumbnail from '../misc/Thumbnail.js';
import Text from '../misc/Text.js';
import type Button from '../Button.js';
import { YTNode } from '../../helpers.js';
import type { RawNode } from '../../index.js';
class CommentReplyDialog extends YTNode {
static type = 'CommentReplyDialog';
@@ -13,7 +14,7 @@ class CommentReplyDialog extends YTNode {
placeholder: Text;
error_message: Text;
constructor(data: any) {
constructor(data: RawNode) {
super();
this.reply_button = Parser.parseItem<Button>(data.replyButton);
this.cancel_button = Parser.parseItem<Button>(data.cancelButton);

View File

@@ -3,6 +3,7 @@ import Thumbnail from '../misc/Thumbnail.js';
import Text from '../misc/Text.js';
import type Button from '../Button.js';
import { YTNode } from '../../helpers.js';
import type { RawNode } from '../../index.js';
class CommentSimplebox extends YTNode {
static type = 'CommentSimplebox';
@@ -13,7 +14,7 @@ class CommentSimplebox extends YTNode {
placeholder: Text;
avatar_size;
constructor(data: any) {
constructor(data: RawNode) {
super();
this.submit_button = Parser.parseItem<Button>(data.submitButton);
this.cancel_button = Parser.parseItem<Button>(data.cancelButton);

View File

@@ -7,6 +7,7 @@ import type Actions from '../../../core/Actions.js';
import type { ObservedArray } from '../../helpers.js';
import { InnertubeError } from '../../../utils/Utils.js';
import { observe, YTNode } from '../../helpers.js';
import type { RawNode } from '../../index.js';
class CommentThread extends YTNode {
static type = 'CommentThread';
@@ -20,7 +21,7 @@ class CommentThread extends YTNode {
is_moderated_elq_comment: boolean;
has_replies: boolean;
constructor(data: any) {
constructor(data: RawNode) {
super();
this.comment = Parser.parseItem<Comment>(data.comment, Comment);
this.comment_replies_data = Parser.parseItem<CommentReplies>(data.replies);

View File

@@ -1,6 +1,7 @@
import Text from '../misc/Text.js';
import Thumbnail from '../misc/Thumbnail.js';
import { YTNode } from '../../helpers.js';
import type { RawNode } from '../../index.js';
class CommentsEntryPointHeader extends YTNode {
static type = 'CommentsEntryPointHeader';
@@ -11,7 +12,7 @@ class CommentsEntryPointHeader extends YTNode {
teaser_content?: Text;
simplebox_placeholder?: Text;
constructor(data: any) {
constructor(data: RawNode) {
super();
if (data.header) {

View File

@@ -3,6 +3,7 @@ import Text from '../misc/Text.js';
import Thumbnail from '../misc/Thumbnail.js';
import type SortFilterSubMenu from '../SortFilterSubMenu.js';
import { YTNode } from '../../helpers.js';
import type { RawNode } from '../../index.js';
class CommentsHeader extends YTNode {
static type = 'CommentsHeader';
@@ -21,7 +22,7 @@ class CommentsHeader extends YTNode {
is_custom_emoji: boolean;
}[] | null;
constructor(data: any) {
constructor(data: RawNode) {
super();
this.title = new Text(data.titleText);
this.count = new Text(data.countText);

View File

@@ -1,5 +1,6 @@
import { YTNode } from '../../helpers.js';
import Thumbnail from '../misc/Thumbnail.js';
import type { RawNode } from '../../index.js';
class CreatorHeart extends YTNode {
static type = 'CreatorHeart';
@@ -16,7 +17,7 @@ class CreatorHeart extends YTNode {
is_enabled: boolean;
kennedy_heart_color_string: string;
constructor(data: any) {
constructor(data: RawNode) {
super();
this.creator_thumbnail = Thumbnail.fromResponse(data.creatorThumbnail);
this.heart_icon_type = data.heartIcon?.iconType;

View File

@@ -1,6 +1,7 @@
import Text from '../misc/Text.js';
import { YTNode } from '../../helpers.js';
import Parser from '../../index.js';
import type { RawNode } from '../../index.js';
class EmojiPicker extends YTNode {
static type = 'EmojiPicker';
@@ -19,7 +20,7 @@ class EmojiPicker extends YTNode {
skin_tone_medium_dark_label: string;
skin_tone_dark_label: string;
constructor(data: any) {
constructor(data: RawNode) {
super();
this.id = data.id;
this.categories = Parser.parseArray(data.categories);

View File

@@ -1,5 +1,6 @@
import Text from '../misc/Text.js';
import { YTNode } from '../../helpers.js';
import type { RawNode } from '../../index.js';
class PdgCommentChip extends YTNode {
static type = 'PdgCommentChip';
@@ -11,7 +12,7 @@ class PdgCommentChip extends YTNode {
};
icon_type: string;
constructor(data: any) {
constructor(data: RawNode) {
super();
this.text = new Text(data.chipText);
this.color_pallette = {

View File

@@ -1,5 +1,6 @@
import Thumbnail from '../misc/Thumbnail.js';
import { YTNode } from '../../helpers.js';
import type { RawNode } from '../../index.js';
class SponsorCommentBadge extends YTNode {
static type = 'SponsorCommentBadge';
@@ -7,7 +8,7 @@ class SponsorCommentBadge extends YTNode {
custom_badge: Thumbnail[];
tooltip: string;
constructor(data: any) {
constructor(data: RawNode) {
super();
this.custom_badge = Thumbnail.fromResponse(data.customBadge);
this.tooltip = data.tooltip;

View File

@@ -1,13 +1,14 @@
import Parser from '../../index.js';
import { YTNode } from '../../helpers.js';
import type LiveChatBanner from './items/LiveChatBanner.js';
import type { RawNode } from '../../index.js';
class AddBannerToLiveChatCommand extends YTNode {
static type = 'AddBannerToLiveChatCommand';
banner: LiveChatBanner | null;
constructor(data: any) {
constructor(data: RawNode) {
super();
this.banner = Parser.parseItem<LiveChatBanner>(data.bannerRenderer);
}

View File

@@ -1,5 +1,6 @@
import Parser from '../../index.js';
import { YTNode } from '../../helpers.js';
import type { RawNode } from '../../index.js';
class AddChatItemAction extends YTNode {
static type = 'AddChatItemAction';
@@ -7,7 +8,7 @@ class AddChatItemAction extends YTNode {
item;
client_id: string | null;
constructor(data: any) {
constructor(data: RawNode) {
super();
this.item = Parser.parseItem(data.item);
this.client_id = data.clientId || null;

View File

@@ -1,5 +1,6 @@
import Parser from '../../index.js';
import { YTNode } from '../../helpers.js';
import type { RawNode } from '../../index.js';
class AddLiveChatTickerItemAction extends YTNode {
static type = 'AddLiveChatTickerItemAction';
@@ -7,7 +8,7 @@ class AddLiveChatTickerItemAction extends YTNode {
item;
duration_sec: string; // TODO: check this assumption
constructor(data: any) {
constructor(data: RawNode) {
super();
this.item = Parser.parseItem(data.item);
this.duration_sec = data.durationSec;

View File

@@ -1,11 +1,11 @@
import { YTNode } from '../../helpers.js';
import type { RawNode } from '../../index.js';
class DimChatItemAction extends YTNode {
static type = 'DimChatItemAction';
client_assigned_id: string;
constructor(data: any) {
constructor(data: RawNode) {
super();
this.client_assigned_id = data.clientAssignedId;
}

View File

@@ -1,6 +1,6 @@
import Parser from '../../index.js';
import { YTNode } from '../../helpers.js';
import type { RawNode } from '../../index.js';
class LiveChatActionPanel extends YTNode {
static type = 'LiveChatActionPanel';
@@ -8,7 +8,7 @@ class LiveChatActionPanel extends YTNode {
contents;
target_id: string;
constructor(data: any) {
constructor(data: RawNode) {
super();
this.id = data.id;
this.contents = Parser.parse(data.contents);

View File

@@ -1,13 +1,13 @@
import Text from '../misc/Text.js';
import { YTNode } from '../../helpers.js';
import type { RawNode } from '../../index.js';
class MarkChatItemAsDeletedAction extends YTNode {
static type = 'MarkChatItemAsDeletedAction';
deleted_state_message: Text;
target_item_id: string;
constructor(data: any) {
constructor(data: RawNode) {
super();
this.deleted_state_message = new Text(data.deletedStateMessage);
this.target_item_id = data.targetItemId;

View File

@@ -1,13 +1,13 @@
import Text from '../misc/Text.js';
import { YTNode } from '../../helpers.js';
import type { RawNode } from '../../index.js';
class MarkChatItemsByAuthorAsDeletedAction extends YTNode {
static type = 'MarkChatItemsByAuthorAsDeletedAction';
deleted_state_message: Text;
channel_id: string;
constructor(data: any) {
constructor(data: RawNode) {
super();
this.deleted_state_message = new Text(data.deletedStateMessage);
this.channel_id = data.externalChannelId;

View File

@@ -1,11 +1,11 @@
import { YTNode } from '../../helpers.js';
import type { RawNode } from '../../index.js';
class RemoveBannerForLiveChatCommand extends YTNode {
static type = 'RemoveBannerForLiveChatCommand';
target_action_id: string;
constructor(data: any) {
constructor(data: RawNode) {
super();
this.target_action_id = data.targetActionId;
}

View File

@@ -1,11 +1,11 @@
import { YTNode } from '../../helpers.js';
import type { RawNode } from '../../index.js';
class RemoveChatItemAction extends YTNode {
static type = 'RemoveChatItemAction';
target_item_id: string;
constructor(data: any) {
constructor(data: RawNode) {
super();
this.target_item_id = data.targetItemId;
}

View File

@@ -1,11 +1,11 @@
import { YTNode } from '../../helpers.js';
import type { RawNode } from '../../index.js';
class RemoveChatItemByAuthorAction extends YTNode {
static type = 'RemoveChatItemByAuthorAction';
external_channel_id: string;
constructor(data: any) {
constructor(data: RawNode) {
super();
this.external_channel_id = data.externalChannelId;
}

View File

@@ -1,13 +1,13 @@
import Parser from '../../index.js';
import { YTNode } from '../../helpers.js';
import type { RawNode } from '../../index.js';
class ReplaceChatItemAction extends YTNode {
static type = 'ReplaceChatItemAction';
target_item_id: string;
replacement_item;
constructor(data: any) {
constructor(data: RawNode) {
super();
this.target_item_id = data.targetItemId;
this.replacement_item = Parser.parseItem(data.replacementItem);

View File

@@ -1,13 +1,13 @@
import Parser from '../../index.js';
import { YTNode } from '../../helpers.js';
import type { RawNode } from '../../index.js';
class ReplayChatItemAction extends YTNode {
static type = 'ReplayChatItemAction';
actions;
video_offset_time_msec: string; // Or number?
constructor(data: any) {
constructor(data: RawNode) {
super();
this.actions = Parser.parseArray(data.actions?.map((action: any) => {
delete action.clickTrackingParams;

View File

@@ -1,13 +1,13 @@
import Parser from '../../index.js';
import { YTNode } from '../../helpers.js';
import LiveChatActionPanel from './LiveChatActionPanel.js';
import type { RawNode } from '../../index.js';
class ShowLiveChatActionPanelAction extends YTNode {
static type = 'ShowLiveChatActionPanelAction';
panel_to_show: LiveChatActionPanel | null;
constructor(data: any) {
constructor(data: RawNode) {
super();
this.panel_to_show = Parser.parseItem<LiveChatActionPanel>(data.panelToShow, LiveChatActionPanel);
}

View File

@@ -1,12 +1,12 @@
import Parser from '../../index.js';
import { YTNode } from '../../helpers.js';
import type { RawNode } from '../../index.js';
class ShowLiveChatDialogAction extends YTNode {
static type = 'ShowLiveChatDialogAction';
dialog;
constructor(data: any) {
constructor(data: RawNode) {
super();
this.dialog = Parser.parseItem(data.dialog);
}

View File

@@ -1,12 +1,12 @@
import Parser from '../../index.js';
import { YTNode } from '../../helpers.js';
import type { RawNode } from '../../index.js';
class ShowLiveChatTooltipCommand extends YTNode {
static type = 'ShowLiveChatTooltipCommand';
tooltip;
constructor(data: any) {
constructor(data: RawNode) {
super();
this.tooltip = Parser.parseItem(data.tooltip);
}

View File

@@ -1,12 +1,12 @@
import Text from '../misc/Text.js';
import { YTNode } from '../../helpers.js';
import type { RawNode } from '../../index.js';
class UpdateDateTextAction extends YTNode {
static type = 'UpdateDateTextAction';
date_text: string;
constructor(data: any) {
constructor(data: RawNode) {
super();
this.date_text = new Text(data.dateText).toString();
}

View File

@@ -1,12 +1,12 @@
import Text from '../misc/Text.js';
import { YTNode } from '../../helpers.js';
import type { RawNode } from '../../index.js';
class UpdateDescriptionAction extends YTNode {
static type = 'UpdateDescriptionAction';
description: Text;
constructor(data: any) {
constructor(data: RawNode) {
super();
this.description = new Text(data.description);
}

View File

@@ -1,12 +1,12 @@
import Parser from '../../index.js';
import { YTNode } from '../../helpers.js';
import type { RawNode } from '../../index.js';
class UpdateLiveChatPollAction extends YTNode {
static type = 'UpdateLiveChatPollAction';
poll_to_update;
constructor(data: any) {
constructor(data: RawNode) {
super();
this.poll_to_update = Parser.parseItem(data.pollToUpdate);
}

View File

@@ -1,12 +1,12 @@
import Text from '../misc/Text.js';
import { YTNode } from '../../helpers.js';
import type { RawNode } from '../../index.js';
class UpdateTitleAction extends YTNode {
static type = 'UpdateTitleAction';
title: Text;
constructor(data: any) {
constructor(data: RawNode) {
super();
this.title = new Text(data.title);
}

View File

@@ -1,6 +1,6 @@
import Text from '../misc/Text.js';
import { YTNode } from '../../helpers.js';
import type { RawNode } from '../../index.js';
class UpdateToggleButtonTextAction extends YTNode {
static type = 'UpdateToggleButtonTextAction';
@@ -8,7 +8,7 @@ class UpdateToggleButtonTextAction extends YTNode {
toggled_text: string;
button_id: string;
constructor(data: any) {
constructor(data: RawNode) {
super();
this.default_text = new Text(data.defaultText).toString();
this.toggled_text = new Text(data.toggledText).toString();

View File

@@ -1,6 +1,6 @@
import Text from '../misc/Text.js';
import { YTNode } from '../../helpers.js';
import type { RawNode } from '../../index.js';
class UpdateViewershipAction extends YTNode {
static type = 'UpdateViewershipAction';
@@ -8,7 +8,7 @@ class UpdateViewershipAction extends YTNode {
extra_short_view_count: Text;
is_live: boolean;
constructor(data: any) {
constructor(data: RawNode) {
super();
const view_count_renderer = data.viewCount.videoViewCountRenderer;
this.view_count = new Text(view_count_renderer.viewCount);

View File

@@ -3,6 +3,7 @@ import Parser from '../../../index.js';
import Button from '../../Button.js';
import Text from '../../misc/Text.js';
import NavigationEndpoint from '../../NavigationEndpoint.js';
import type { RawNode } from '../../../index.js';
class LiveChatAutoModMessage extends YTNode {
static type = 'LiveChatAutoModMessage';
@@ -15,7 +16,7 @@ class LiveChatAutoModMessage extends YTNode {
timestamp: number;
id: string;
constructor(data: any) {
constructor(data: RawNode) {
super();
this.menu_endpoint = new NavigationEndpoint(data.contextMenuEndpoint);
this.moderation_buttons = Parser.parseArray<Button>(data.moderationButtons, [ Button ]);

View File

@@ -1,6 +1,7 @@
import { YTNode } from '../../../helpers.js';
import Parser from '../../../index.js';
import type LiveChatBannerHeader from './LiveChatBannerHeader.js';
import type { RawNode } from '../../../index.js';
class LiveChatBanner extends YTNode {
static type = 'LiveChatBanner';
@@ -13,7 +14,7 @@ class LiveChatBanner extends YTNode {
is_stackable: boolean;
background_type: string;
constructor(data: any) {
constructor(data: RawNode) {
super();
this.header = Parser.parseItem<LiveChatBannerHeader>(data.header);
this.contents = Parser.parseItem(data.contents);

View File

@@ -2,6 +2,7 @@ import { YTNode } from '../../../helpers.js';
import Parser from '../../../index.js';
import type Button from '../../Button.js';
import Text from '../../misc/Text.js';
import type { RawNode } from '../../../index.js';
class LiveChatBannerHeader extends YTNode {
static type = 'LiveChatBannerHeader';
@@ -10,7 +11,7 @@ class LiveChatBannerHeader extends YTNode {
icon_type: string;
context_menu_button: Button | null;
constructor(data: any) {
constructor(data: RawNode) {
super();
this.text = new Text(data.text).toString();
this.icon_type = data.icon?.iconType;

Some files were not shown because too many files have changed in this diff Show More