mirror of
https://github.com/LuanRT/YouTube.js.git
synced 2026-06-14 18:12:10 +00:00
Compare commits
29 Commits
v9.1.0-den
...
deno
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f21db117e8 | ||
|
|
cc09231db2 | ||
|
|
d46768dedb | ||
|
|
047157ff25 | ||
|
|
620067b773 | ||
|
|
a3aba1fd06 | ||
|
|
fc0ba30d1e | ||
|
|
ddc35db72e | ||
|
|
e94b715e7a | ||
|
|
06ec3075ea | ||
|
|
06bf822ada | ||
|
|
ad1a8818c0 | ||
|
|
adfb76d629 | ||
|
|
c52ed6ffde | ||
|
|
429b42695b | ||
|
|
12dbf35834 | ||
|
|
56e5b51092 | ||
|
|
fce8ae8dbd | ||
|
|
00c9ea8535 | ||
|
|
81224bc745 | ||
|
|
9b5c3bb223 | ||
|
|
742e7151fa | ||
|
|
843ecd28e3 | ||
|
|
8293af93fa | ||
|
|
f3d43bd395 | ||
|
|
09e0f91b66 | ||
|
|
28e766779e | ||
|
|
a482556187 | ||
|
|
b063b2f4a8 |
781
README.md
781
README.md
@@ -3,92 +3,30 @@
|
||||
[versions]: https://www.npmjs.com/package/youtubei.js?activeTab=versions
|
||||
[codefactor]: https://www.codefactor.io/repository/github/luanrt/youtube.js
|
||||
[actions]: https://github.com/LuanRT/YouTube.js/actions
|
||||
[collaborators]: https://github.com/LuanRT/YouTube.js/blob/main/COLLABORATORS.md
|
||||
|
||||
<!-- OTHER LINKS -->
|
||||
[project]: https://github.com/LuanRT/YouTube.js
|
||||
[twitter]: https://twitter.com/thesciencephile
|
||||
[discord]: https://discord.gg/syDu7Yks54
|
||||
|
||||
<h1 align=center>YouTube.js</h1>
|
||||
|
||||
<p align=center>A full-featured wrapper around the InnerTube API</p>
|
||||
|
||||
<div align="center">
|
||||
|
||||
[][actions]
|
||||
[][versions]
|
||||
[][codefactor]
|
||||
[][npm]
|
||||
[][discord]
|
||||
<br>
|
||||
[][collaborators]
|
||||
|
||||
</div>
|
||||
|
||||
<div align="center">
|
||||
<br/>
|
||||
<p>
|
||||
<sup>Special thanks to:</sup>
|
||||
<br>
|
||||
<br>
|
||||
<a href="https://serpapi.com" target="_blank">
|
||||
<img width="80" alt="SerpApi" src="https://luanrt.github.io/assets/img/serpapi.svg" />
|
||||
<br>
|
||||
<sub>
|
||||
API to get search engine results with ease.
|
||||
</sub>
|
||||
<a href="https://github.com/LuanRT/YouTube.js">
|
||||
<img src="https://luanrt.github.io/assets/img/ytjs.svg" alt="YouTube.js Logo" width="200" />
|
||||
</a>
|
||||
</p>
|
||||
<p>A JavaScript client for YouTube's private API</p>
|
||||
|
||||
[][discord]
|
||||
[][actions]
|
||||
[][versions]
|
||||
[][npm]
|
||||
[][codefactor]
|
||||
|
||||
</div>
|
||||
<br>
|
||||
<hr>
|
||||
<br>
|
||||
|
||||
## Table of Contents
|
||||
<ol>
|
||||
<li>
|
||||
<a href="#description">Description</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#getting-started">Getting Started</a>
|
||||
<ul>
|
||||
<li><a href="#prerequisites">Prerequisites</a></li>
|
||||
<li><a href="#installation">Installation</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#usage">Usage</a>
|
||||
<ul>
|
||||
<li><a href="#browser-usage">Browser Usage</a></li>
|
||||
<li><a href="#caching">Caching</a></li>
|
||||
<li><a href="#api">API</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><a href="#extending-the-library">Extending the library</a></li>
|
||||
<li><a href="#contributing">Contributing</a></li>
|
||||
<li><a href="#contact">Contact</a></li>
|
||||
<li><a href="#disclaimer">Disclaimer</a></li>
|
||||
<li><a href="#license">License</a></li>
|
||||
</ol>
|
||||
YouTube.js is a JavaScript client for YouTube's private API, known as "InnerTube". It allows you to interact with YouTube programmatically, providing access to videos, comments, live chats, streaming data and more. It works seamlessly across Node.js, Deno, and modern browsers.
|
||||
|
||||
## Description
|
||||
## Installation
|
||||
|
||||
InnerTube is an API used by all YouTube clients. It was created to simplify the deployment of new features and experiments across the platform [^1]. This library manages all low-level communication with InnerTube, providing a simple and efficient way to interact with YouTube programmatically. Its design aims to closely emulate an actual client, including the parsing of API responses.
|
||||
|
||||
If you have any questions or need help, feel free to reach out to us on our [Discord server][discord] or open an issue [here](https://github.com/LuanRT/YouTube.js/issues).
|
||||
|
||||
## Getting Started
|
||||
|
||||
### Prerequisites
|
||||
YouTube.js runs on Node.js, Deno, and modern browsers.
|
||||
|
||||
It requires a runtime with the following features:
|
||||
- [`fetch`](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API)
|
||||
- On Node, we use [undici](https://github.com/nodejs/undici)'s fetch implementation, which requires Node.js 16.8+. If you need to use an older version, you may provide your own fetch implementation. See [providing your own fetch implementation](#custom-fetch) for more information.
|
||||
- The `Response` object returned by fetch must thus be spec compliant and return a `ReadableStream` object if you want to use the `VideoInfo#download` method. (Implementations like `node-fetch` returns a non-standard `Readable` object.)
|
||||
- [`EventTarget`](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget) and [`CustomEvent`](https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent) are required.
|
||||
|
||||
### Installation
|
||||
Before installing, make sure your environment meets the [prerequisites](https://ytjs.dev/guide/getting-started.html#prerequisites).
|
||||
|
||||
```bash
|
||||
# NPM
|
||||
@@ -99,712 +37,41 @@ yarn add youtubei.js@latest
|
||||
|
||||
# Git (edge version)
|
||||
npm install github:LuanRT/YouTube.js
|
||||
|
||||
# Deno
|
||||
deno add npm:youtubei.js@latest
|
||||
```
|
||||
|
||||
When using Deno, you can import YouTube.js directly from deno.land:
|
||||
Deno (deprecated):
|
||||
```ts
|
||||
import { Innertube } from 'https://deno.land/x/youtubei/deno.ts';
|
||||
```
|
||||
|
||||
## Usage
|
||||
Create an InnerTube instance:
|
||||
```ts
|
||||
// const { Innertube } = require('youtubei.js');
|
||||
import { Innertube } from 'youtubei.js';
|
||||
const youtube = await Innertube.create(/* options */);
|
||||
```
|
||||
## Basic Usage
|
||||
|
||||
### Initialization Options
|
||||
<details>
|
||||
<summary>Click to expand</summary>
|
||||
|
||||
| Option | Type | Description | Default |
|
||||
| --- | --- | --- | --- |
|
||||
| `lang` | `string` | Language. | `en` |
|
||||
| `location` | `string` | Geolocation. | `US` |
|
||||
| `account_index` | `number` | The account index to use. This is useful if you have multiple accounts logged in. **NOTE:** Only works if you are signed in with cookies. | `0` |
|
||||
| `visitor_data` | `string` | Setting this to a valid and persistent visitor data string will allow YouTube to give this session tailored content even when not logged in. A good way to get a valid one is by either grabbing it from a browser or calling InnerTube's `/visitor_id` endpoint. | `undefined` |
|
||||
| `retrieve_player` | `boolean` | Specifies whether to retrieve the JS player. Disabling this will make session creation faster. **NOTE:** Deciphering formats is not possible without the JS player. | `true` |
|
||||
| `enable_safety_mode` | `boolean` | Specifies whether to enable safety mode. This will prevent the session from loading any potentially unsafe content. | `false` |
|
||||
| `generate_session_locally` | `boolean` | Specifies whether to generate the session data locally or retrieve it from YouTube. This can be useful if you need more performance. | `false` |
|
||||
| `device_category` | `DeviceCategory` | Platform to use for the session. | `DESKTOP` |
|
||||
| `client_type` | `ClientType` | InnerTube client type. | `WEB` |
|
||||
| `timezone` | `string` | The time zone. | `*` |
|
||||
| `cache` | `ICache` | Used to cache the deciphering functions from the JS player. | `undefined` |
|
||||
| `cookie` | `string` | YouTube cookies. | `undefined` |
|
||||
| `fetch` | `FetchFunction` | Fetch function to use. | `fetch` |
|
||||
|
||||
</details>
|
||||
|
||||
## Browser Usage
|
||||
To use YouTube.js in the browser, you must proxy requests through your own server. You can see our simple reference implementation in Deno at [`examples/browser/proxy/deno.ts`](https://github.com/LuanRT/YouTube.js/tree/main/examples/browser/proxy/deno.ts).
|
||||
|
||||
You may provide your own fetch implementation to be used by YouTube.js, which we will use to modify and send the requests through a 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
|
||||
// Multiple exports are available 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
|
||||
// and send it to the proxy
|
||||
|
||||
// fetch the URL
|
||||
return fetch(request, init);
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### Streaming
|
||||
YouTube.js supports streaming of videos in the browser by converting YouTube's streaming data into an MPEG-DASH manifest.
|
||||
|
||||
The example below uses [`dash.js`](https://github.com/Dash-Industry-Forum/dash.js) to play the video.
|
||||
|
||||
```ts
|
||||
import { Innertube } from 'youtubei.js/web';
|
||||
import dashjs from 'dashjs';
|
||||
|
||||
const youtube = await Innertube.create({ /* setup - see above */ });
|
||||
|
||||
// Get the video info
|
||||
const videoInfo = await youtube.getInfo('videoId');
|
||||
|
||||
// now convert to a dash manifest
|
||||
// again - to be able to stream the video in the browser - we must proxy the requests through our own server
|
||||
// to do this, we provide a method to transform the URLs before writing them to the manifest
|
||||
const manifest = await videoInfo.toDash(url => {
|
||||
// modify the url
|
||||
// and return it
|
||||
return url;
|
||||
});
|
||||
|
||||
const uri = "data:application/dash+xml;charset=utf-8;base64," + btoa(manifest);
|
||||
|
||||
const videoElement = document.getElementById('video_player');
|
||||
|
||||
const player = dashjs.MediaPlayer().create();
|
||||
player.initialize(videoElement, uri, true);
|
||||
```
|
||||
|
||||
A fully working example can be found in [`examples/browser/web`](https://github.com/LuanRT/YouTube.js/blob/main/examples/browser/web).
|
||||
|
||||
<a name="custom-fetch"></a>
|
||||
|
||||
## Providing your own fetch implementation
|
||||
You may provide your own fetch implementation to be used by YouTube.js. This can be useful in some cases to modify the requests before they are sent and transform the responses before they are returned (eg. for proxies).
|
||||
```ts
|
||||
// provide a fetch implementation
|
||||
const yt = await Innertube.create({
|
||||
fetch: async (input: RequestInfo | URL, init?: RequestInit) => {
|
||||
// make the request with your own fetch implementation
|
||||
// and return the response
|
||||
return new Response(
|
||||
/* ... */
|
||||
);
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
<a name="caching"></a>
|
||||
|
||||
## Caching
|
||||
Caching the transformed player instance can greatly improve the performance. Our `UniversalCache` implementation uses different caching methods depending on the environment.
|
||||
|
||||
In Node.js, we use the `node:fs` module, `Deno.writeFile()` in Deno, and `indexedDB` in browsers.
|
||||
|
||||
By default, the cache stores data in the operating system's temporary directory (or `indexedDB` in browsers). You can make this cache persistent by specifying the path to the cache directory, which will be created if it doesn't exist.
|
||||
|
||||
```ts
|
||||
import { Innertube, UniversalCache } from 'youtubei.js';
|
||||
// Create a cache that stores files in the OS temp directory (or indexedDB in browsers) by default.
|
||||
const yt = await Innertube.create({
|
||||
cache: new UniversalCache(false)
|
||||
});
|
||||
|
||||
// You may want to create a persistent cache instead (on Node and Deno).
|
||||
const yt = await Innertube.create({
|
||||
cache: new UniversalCache(
|
||||
// Enables persistent caching
|
||||
true,
|
||||
// Path to the cache directory. The directory will be created if it doesn't exist
|
||||
'./.cache'
|
||||
)
|
||||
});
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
* `Innertube`
|
||||
|
||||
<details>
|
||||
<summary>Properties</summary>
|
||||
<p>
|
||||
|
||||
* [.session](https://github.com/LuanRT/YouTube.js/blob/main/docs/API/session.md)
|
||||
* [.account](https://github.com/LuanRT/YouTube.js/blob/main/docs/API/account.md)
|
||||
* [.interact](https://github.com/LuanRT/YouTube.js/blob/main/docs/API/interaction-manager.md)
|
||||
* [.playlist](https://github.com/LuanRT/YouTube.js/blob/main/docs/API/playlist.md)
|
||||
* [.music](https://github.com/LuanRT/YouTube.js/blob/main/docs/API/music.md)
|
||||
* [.studio](https://github.com/LuanRT/YouTube.js/blob/main/docs/API/studio.md)
|
||||
* [.kids](https://github.com/LuanRT/YouTube.js/blob/main/docs/API/kids.md)
|
||||
|
||||
</p>
|
||||
</details>
|
||||
|
||||
|
||||
<details>
|
||||
<summary>Methods</summary>
|
||||
<p>
|
||||
|
||||
* [.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)
|
||||
* [.getSubscriptionsFeed()](#getsubscriptionsfeed)
|
||||
* [.getChannel(id)](#getchannel)
|
||||
* [.getNotifications()](#getnotifications)
|
||||
* [.getUnseenNotificationsCount()](#getunseennotificationscount)
|
||||
* [.getPlaylist(id)](#getplaylist)
|
||||
* [.getHashtag(hashtag)](#gethashtag)
|
||||
* [.getStreamingData(video_id, options)](#getstreamingdata)
|
||||
* [.download(video_id, options?)](#download)
|
||||
* [.resolveURL(url)](#resolveurl)
|
||||
* [.call(endpoint, args?)](#call)
|
||||
|
||||
</p>
|
||||
</details>
|
||||
|
||||
<a name="getinfo"></a>
|
||||
### `getInfo(target, client?)`
|
||||
|
||||
Retrieves video info.
|
||||
|
||||
**Returns**: `Promise<VideoInfo>`
|
||||
|
||||
| Param | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| 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>
|
||||
<summary>Methods & Getters</summary>
|
||||
<p>
|
||||
|
||||
- `<info>#like()`
|
||||
- Likes the video.
|
||||
|
||||
- `<info>#dislike()`
|
||||
- Dislikes the video.
|
||||
|
||||
- `<info>#removeRating()`
|
||||
- Removes like/dislike.
|
||||
|
||||
- `<info>#getLiveChat()`
|
||||
- Returns a LiveChat instance.
|
||||
|
||||
- `<info>#getTrailerInfo()`
|
||||
- Returns trailer info in a new `VideoInfo` instance, or `null` if none. Typically available for non-purchased movies or films.
|
||||
|
||||
- `<info>#chooseFormat(options)`
|
||||
- Used to choose streaming data formats.
|
||||
|
||||
- `<info>#toDash(url_transformer?, format_filter?)`
|
||||
- Converts streaming data to an MPEG-DASH manifest.
|
||||
|
||||
- `<info>#download(options)`
|
||||
- Downloads the video. See [download](#download).
|
||||
|
||||
- `<info>#getTranscript()`
|
||||
- Retrieves the video's transcript.
|
||||
|
||||
- `<info>#filters`
|
||||
- Returns filters that can be applied to the watch next feed.
|
||||
|
||||
- `<info>#selectFilter(name)`
|
||||
- Applies the given filter to the watch next feed and returns a new instance of [`VideoInfo`](https://github.com/LuanRT/YouTube.js/blob/main/src/parser/youtube/VideoInfo.ts).
|
||||
|
||||
- `<info>#getWatchNextContinuation()`
|
||||
- Retrieves the next batch of items for the watch next feed.
|
||||
|
||||
- `<info>#addToWatchHistory()`
|
||||
- Adds the video to the watch history.
|
||||
|
||||
- `<info>#autoplay_video_endpoint`
|
||||
- Returns the endpoint of the video for Autoplay.
|
||||
|
||||
- `<info>#has_trailer`
|
||||
- Checks if trailer is available.
|
||||
|
||||
- `<info>#page`
|
||||
- Returns original InnerTube response (sanitized).
|
||||
|
||||
</p>
|
||||
</details>
|
||||
|
||||
<a name="getbasicinfo"></a>
|
||||
### `getBasicInfo(video_id, client?)`
|
||||
|
||||
Suitable for cases where you only need basic video metadata. Also, it is faster than [`getInfo()`](#getinfo).
|
||||
|
||||
**Returns**: `Promise<VideoInfo>`
|
||||
|
||||
| Param | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| video_id | `string` | The id of the video |
|
||||
| client? | `InnerTubeClient` | `WEB`, `ANDROID`, `YTMUSIC_ANDROID`, `YTMUSIC`, `TV_EMBEDDED` |
|
||||
|
||||
<a name="search"></a>
|
||||
### `search(query, filters?)`
|
||||
|
||||
Searches the given query on YouTube.
|
||||
|
||||
**Returns**: `Promise<Search>`
|
||||
|
||||
> **Note**
|
||||
> `Search` extends the [`Feed`](https://github.com/LuanRT/YouTube.js/blob/main/docs/API/feed.md) class.
|
||||
|
||||
| Param | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| query | `string` | The search query |
|
||||
| filters? | `SearchFilters` | Search filters |
|
||||
|
||||
|
||||
<details>
|
||||
<summary>Search Filters</summary>
|
||||
|
||||
| Filter | Type | Value | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| upload_date | `string` | `all` \| `hour` \| `today` \| `week` \| `month` \| `year` | Filter by upload date |
|
||||
| type | `string` | `all` \| `video` \| `channel` \| `playlist` \| `movie` | Filter by type |
|
||||
| duration | `string` | `all` \| `short` \| `medium` \| `long` | Filter by duration |
|
||||
| sort_by | `string` | `relevance` \| `rating` \| `upload_date` \| `view_count` | Sort by |
|
||||
| features | `string[]` | `hd` \| `subtitles` \| `creative_commons` \| `3d` \| `live` \| `purchased` \| `4k` \| `360` \| `location` \| `hdr` \| `vr180` | Filter by features |
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Methods & Getters</summary>
|
||||
<p>
|
||||
|
||||
- `<search>#selectRefinementCard(SearchRefinementCard | string)`
|
||||
- Applies given refinement card and returns a new Search instance.
|
||||
|
||||
- `<search>#refinement_card_queries`
|
||||
- Returns available refinement cards, this is a simplified version of the `refinement_cards` object.
|
||||
|
||||
- `<search>#getContinuation()`
|
||||
- Retrieves next batch of results.
|
||||
|
||||
</p>
|
||||
</details>
|
||||
|
||||
<a name="getsearchsuggestions"></a>
|
||||
### `getSearchSuggestions(query)`
|
||||
Retrieves search suggestions for given query.
|
||||
|
||||
**Returns**: `Promise<string[]>`
|
||||
|
||||
| Param | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| query | `string` | The search query |
|
||||
|
||||
<a name="getcomments"></a>
|
||||
### `getComments(video_id, sort_by?)`
|
||||
Retrieves comments for given video.
|
||||
|
||||
**Returns**: `Promise<Comments>`
|
||||
|
||||
| Param | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| video_id | `string` | The video id |
|
||||
| sort_by | `string` | Can be: `TOP_COMMENTS` or `NEWEST_FIRST` |
|
||||
|
||||
See [`./examples/comments`](https://github.com/LuanRT/YouTube.js/blob/main/examples/comments) for examples.
|
||||
|
||||
<a name="gethomefeed"></a>
|
||||
### `getHomeFeed()`
|
||||
Retrieves YouTube's home feed.
|
||||
|
||||
**Returns**: `Promise<HomeFeed>`
|
||||
|
||||
> **Note**
|
||||
> `HomeFeed` extends the [`FilterableFeed`](https://github.com/LuanRT/YouTube.js/blob/main/docs/API/filterable-feed.md) class.
|
||||
|
||||
<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="getguide"></a>
|
||||
### `getGuide()`
|
||||
Retrieves YouTube's content guide.
|
||||
|
||||
**Returns**: `Promise<Guide>`
|
||||
|
||||
<a name="getlibrary"></a>
|
||||
### `getLibrary()`
|
||||
Retrieves the account's library.
|
||||
|
||||
**Returns**: `Promise<Library>`
|
||||
|
||||
> **Note**
|
||||
> `Library` extends the [`Feed`](https://github.com/LuanRT/YouTube.js/blob/main/docs/API/feed.md) class.
|
||||
|
||||
<details>
|
||||
<summary>Methods & Getters</summary>
|
||||
<p>
|
||||
|
||||
- `<library>#history`
|
||||
- `<library>#watch_later`
|
||||
- `<library>#liked_videos`
|
||||
- `<library>#playlists_section`
|
||||
- `<library>#clips`
|
||||
|
||||
</p>
|
||||
</details>
|
||||
|
||||
<a name="gethistory"></a>
|
||||
### `getHistory()`
|
||||
Retrieves watch history.
|
||||
|
||||
**Returns**: `Promise<History>`
|
||||
|
||||
> **Note**
|
||||
> `History` extends the [`Feed`](https://github.com/LuanRT/YouTube.js/blob/main/docs/API/feed.md) class.
|
||||
|
||||
<details>
|
||||
<summary>Methods & Getters</summary>
|
||||
<p>
|
||||
|
||||
- `<history>#getContinuation()`
|
||||
- Retrieves next batch of contents.
|
||||
|
||||
</p>
|
||||
</details>
|
||||
|
||||
<a name="gettrending"></a>
|
||||
### `getTrending()`
|
||||
Retrieves trending content.
|
||||
|
||||
**Returns**: `Promise<TabbedFeed<IBrowseResponse>>`
|
||||
|
||||
<a name="getsubscriptionsfeed"></a>
|
||||
### `getSubscriptionsFeed()`
|
||||
Retrieves the subscriptions feed.
|
||||
|
||||
**Returns**: `Promise<Feed<IBrowseResponse>>`
|
||||
|
||||
<a name="getchannel"></a>
|
||||
### `getChannel(id)`
|
||||
Retrieves contents for a given channel.
|
||||
|
||||
**Returns**: `Promise<Channel>`
|
||||
|
||||
> **Note**
|
||||
> `Channel` extends the [`TabbedFeed`](https://github.com/LuanRT/YouTube.js/blob/main/docs/API/tabbed-feed.md) class.
|
||||
|
||||
| Param | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| id | `string` | Channel id |
|
||||
|
||||
<details>
|
||||
<summary>Methods & Getters</summary>
|
||||
<p>
|
||||
|
||||
- `<channel>#getVideos()`
|
||||
- `<channel>#getShorts()`
|
||||
- `<channel>#getLiveStreams()`
|
||||
- `<channel>#getReleases()`
|
||||
- `<channel>#getPodcasts()`
|
||||
- `<channel>#getPlaylists()`
|
||||
- `<channel>#getHome()`
|
||||
- `<channel>#getCommunity()`
|
||||
- `<channel>#getChannels()`
|
||||
- `<channel>#getAbout()`
|
||||
- `<channel>#search(query)`
|
||||
- `<channel>#applyFilter(filter)`
|
||||
- `<channel>#applyContentTypeFilter(content_type_filter)`
|
||||
- `<channel>#applySort(sort)`
|
||||
- `<channel>#getContinuation()`
|
||||
- `<channel>#filters`
|
||||
- `<channel>#content_type_filters`
|
||||
- `<channel>#sort_filters`
|
||||
- `<channel>#page`
|
||||
|
||||
</p>
|
||||
</details>
|
||||
|
||||
See [`./examples/channel`](https://github.com/LuanRT/YouTube.js/blob/main/examples/channel) for examples.
|
||||
|
||||
<a name="getnotifications"></a>
|
||||
### `getNotifications()`
|
||||
Retrieves notifications.
|
||||
|
||||
**Returns**: `Promise<NotificationsMenu>`
|
||||
|
||||
<details>
|
||||
<summary>Methods & Getter</summary>
|
||||
<p>
|
||||
|
||||
- `<notifications>#getContinuation()`
|
||||
- Retrieves next batch of notifications.
|
||||
|
||||
</p>
|
||||
</details>
|
||||
|
||||
<a name="getunseennotificationscount"></a>
|
||||
### `getUnseenNotificationsCount()`
|
||||
Retrieves unseen notifications count.
|
||||
|
||||
**Returns**: `Promise<number>`
|
||||
|
||||
<a name="getplaylist"></a>
|
||||
### `getPlaylist(id)`
|
||||
Retrieves playlist contents.
|
||||
|
||||
**Returns**: `Promise<Playlist>`
|
||||
|
||||
> **Note**
|
||||
> `Playlist` extends the [`Feed`](https://github.com/LuanRT/YouTube.js/blob/main/docs/API/feed.md) class.
|
||||
|
||||
| Param | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| id | `string` | Playlist id |
|
||||
|
||||
<details>
|
||||
<summary>Methods & Getter</summary>
|
||||
<p>
|
||||
|
||||
- `<playlist>#items`
|
||||
- Returns the items of the playlist.
|
||||
|
||||
</p>
|
||||
</details>
|
||||
|
||||
<a name="gethashtag"></a>
|
||||
### `getHashtag(hashtag)`
|
||||
Retrieves a given hashtag's page.
|
||||
|
||||
**Returns**: `Promise<HashtagFeed>`
|
||||
|
||||
> **Note**
|
||||
> `HashtagFeed` extends the [`FilterableFeed`](https://github.com/LuanRT/YouTube.js/blob/main/docs/API/filterable-feed.md) class.
|
||||
|
||||
| Param | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| hashtag | `string` | The hashtag |
|
||||
|
||||
<details>
|
||||
<summary>Methods & Getter</summary>
|
||||
<p>
|
||||
|
||||
- `<hashtag>#applyFilter(filter)`
|
||||
- Applies given filter and returns a new `HashtagFeed` instance.
|
||||
- `<hashtag>#getContinuation()`
|
||||
- Retrieves next batch of contents.
|
||||
|
||||
</p>
|
||||
</details>
|
||||
|
||||
<a name="getstreamingdata"></a>
|
||||
### `getStreamingData(video_id, options)`
|
||||
Returns deciphered streaming data.
|
||||
|
||||
> **Note**
|
||||
> This method will be deprecated in the future. We recommend retrieving streaming data from a `VideoInfo` or `TrackInfo` object instead if you want to select formats manually. Please refer to the following example:
|
||||
|
||||
```ts
|
||||
const info = await yt.getBasicInfo('somevideoid');
|
||||
|
||||
const url = info.streaming_data?.formats[0].decipher(yt.session.player);
|
||||
console.info('Playback url:', url);
|
||||
|
||||
// or:
|
||||
const format = info.chooseFormat({ type: 'audio', quality: 'best' });
|
||||
const url = format?.decipher(yt.session.player);
|
||||
console.info('Playback url:', url);
|
||||
```
|
||||
|
||||
**Returns**: `Promise<object>`
|
||||
|
||||
| Param | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| video_id | `string` | Video id |
|
||||
| options | `FormatOptions` | Format options |
|
||||
|
||||
<a name="download"></a>
|
||||
### `download(video_id, options?)`
|
||||
Downloads a given video.
|
||||
|
||||
**Returns**: `Promise<ReadableStream<Uint8Array>>`
|
||||
|
||||
| Param | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| video_id | `string` | Video id |
|
||||
| options | `DownloadOptions` | Download options |
|
||||
|
||||
See [`./examples/download`](https://github.com/LuanRT/YouTube.js/blob/main/examples/download) for examples.
|
||||
|
||||
<a name="resolveurl"></a>
|
||||
### `resolveURL(url)`
|
||||
Resolves a given url.
|
||||
|
||||
**Returns**: `Promise<NavigationEndpoint>`
|
||||
|
||||
| Param | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| url | `string` | Url to resolve |
|
||||
|
||||
<a name="call"></a>
|
||||
### `call(endpoint, args?)`
|
||||
Utility to call navigation endpoints.
|
||||
|
||||
**Returns**: `Promise<T extends IParsedResponse | IParsedResponse | ApiResponse>`
|
||||
|
||||
| Param | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| endpoint | `NavigationEndpoint` | The target endpoint |
|
||||
| args? | `object` | Additional payload arguments |
|
||||
|
||||
## Extending the library
|
||||
|
||||
YouTube.js is modular and easy to extend. Most of the methods, classes, and utilities used internally are exposed and can be used to implement your own extensions without having to modify the library's source code.
|
||||
|
||||
For example, let's say we want to implement a method to retrieve video info. We can do that by using an instance of the `Actions` class:
|
||||
```ts
|
||||
import { Innertube } from 'youtubei.js';
|
||||
|
||||
(async () => {
|
||||
const yt = await Innertube.create();
|
||||
|
||||
async function getVideoInfo(videoId: string) {
|
||||
const videoInfo = await yt.actions.execute('/player', {
|
||||
// You can add any additional payloads here, and they'll merge with the default payload sent to InnerTube.
|
||||
videoId,
|
||||
client: 'YTMUSIC', // InnerTube client options: ANDROID, YTMUSIC, YTMUSIC_ANDROID, WEB, or TV_EMBEDDED.
|
||||
parse: true // tells YouTube.js to parse the response (not sent to InnerTube).
|
||||
});
|
||||
|
||||
return videoInfo;
|
||||
}
|
||||
|
||||
const videoInfo = await getVideoInfo('jLTOuvBTLxA');
|
||||
console.info(videoInfo);
|
||||
})();
|
||||
const innertube = await Innertube.create(/* options */);
|
||||
```
|
||||
|
||||
Alternatively, suppose we locate a `NavigationEndpoint` in a parsed response and want to see what happens when we call it:
|
||||
```ts
|
||||
import { Innertube, YTNodes } from 'youtubei.js';
|
||||
|
||||
(async () => {
|
||||
const yt = await Innertube.create();
|
||||
|
||||
const artist = await yt.music.getArtist('UC52ZqHVQz5OoGhvbWiRal6g');
|
||||
const albums = artist.sections[1].as(YTNodes.MusicCarouselShelf);
|
||||
|
||||
// Let's imagine that we wish to click on the “More” button:
|
||||
const button = albums.as(YTNodes.MusicCarouselShelf).header?.more_content;
|
||||
|
||||
if (button) {
|
||||
// Having ensured that it exists, we can then call its navigation endpoint using the following code:
|
||||
const page = await button.endpoint.call(yt.actions, { parse: true });
|
||||
console.info(page);
|
||||
}
|
||||
})();
|
||||
```
|
||||
|
||||
### Parser
|
||||
|
||||
YouTube.js' parser enables you to parse InnerTube responses and convert their nodes into strongly-typed objects that are simple to manipulate. Additionally, it provides numerous utility methods that make working with InnerTube a breeze.
|
||||
|
||||
Here's an example of its usage:
|
||||
```ts
|
||||
// See ./examples/parser
|
||||
|
||||
import { Parser, YTNodes } from 'youtubei.js';
|
||||
import { readFileSync } from 'fs';
|
||||
|
||||
// YouTube Music's artist page response
|
||||
const data = readFileSync('./artist.json').toString();
|
||||
|
||||
const page = Parser.parseResponse(JSON.parse(data));
|
||||
|
||||
const header = page.header?.item().as(YTNodes.MusicImmersiveHeader, YTNodes.MusicVisualHeader);
|
||||
|
||||
console.info('Header:', header);
|
||||
|
||||
// The parser uses a proxy object to add type safety and utility methods for working with InnerTube's data arrays:
|
||||
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(YTNodes.SectionList).contents.as(YTNodes.MusicCarouselShelf, YTNodes.MusicDescriptionShelf, YTNodes.MusicShelf);
|
||||
|
||||
console.info('Sections:', sections);
|
||||
```
|
||||
|
||||
Documentation for the parser can be found [here](https://github.com/LuanRT/YouTube.js/blob/main/src/parser).
|
||||
For detailed usage, check out the [YouTube.js Guide and API Documentation](https://ytjs.dev).
|
||||
|
||||
## Contributing
|
||||
We welcome all contributions, issues and feature requests, whether small or large. If you want to contribute, feel free to check out our [issues page](https://github.com/LuanRT/YouTube.js/issues) and our [guidelines](https://github.com/LuanRT/YouTube.js/blob/main/CONTRIBUTING.md).
|
||||
|
||||
We are immensely grateful to all the wonderful people who have contributed to this project. A special shoutout to all our contributors! 🎉
|
||||
## Contributors
|
||||
<a href="https://github.com/LuanRT/YouTube.js/graphs/contributors">
|
||||
<img src="https://contrib.rocks/image?repo=LuanRT/YouTube.js" />
|
||||
</a>
|
||||
|
||||
## Contact
|
||||
|
||||
LuanRT - [@thesciencephile][twitter] - luanrt@thatsciencephile.com
|
||||
|
||||
Project Link: [https://github.com/LuanRT/YouTube.js][project]
|
||||
|
||||
## Disclaimer
|
||||
This project is not affiliated with, endorsed, or sponsored by YouTube or any of its affiliates or subsidiaries. All trademarks, logos, and brand names used in this project are the property of their respective owners and are used solely to describe the services provided.
|
||||
|
||||
As such, any usage of trademarks to refer to such services is considered nominative use. If you have any questions or concerns, please contact me directly via email.
|
||||
|
||||
[^1]: https://gizmodo.com/how-project-innertube-helped-pull-youtube-out-of-the-gu-1704946491
|
||||
As such, any usage of trademarks to refer to such services is considered nominative use. If you have any questions or concerns, please contact me.
|
||||
|
||||
## License
|
||||
Distributed under the [MIT](https://choosealicense.com/licenses/mit/) License.
|
||||
|
||||
<p align=" right">
|
||||
<p align="right">
|
||||
(<a href="#top">back to top</a>)
|
||||
</p>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "youtubei.js",
|
||||
"version": "9.1.0",
|
||||
"description": "A wrapper around YouTube's private API. Supports YouTube, YouTube Music, YouTube Kids and YouTube Studio (WIP).",
|
||||
"version": "17.0.1",
|
||||
"description": "A JavaScript client for YouTube's private API, known as InnerTube.",
|
||||
"type": "module",
|
||||
"types": "./dist/src/platform/lib.d.ts",
|
||||
"typesVersions": {
|
||||
@@ -12,25 +12,33 @@
|
||||
"web": [
|
||||
"./dist/src/platform/lib.d.ts"
|
||||
],
|
||||
"react-native": [
|
||||
"./dist/src/platform/lib.d.ts"
|
||||
],
|
||||
"web.bundle": [
|
||||
"./dist/src/platform/lib.d.ts"
|
||||
],
|
||||
"web.bundle.min": [
|
||||
"./dist/src/platform/lib.d.ts"
|
||||
],
|
||||
"cf-worker": [
|
||||
"./dist/src/platform/lib.d.ts"
|
||||
]
|
||||
}
|
||||
},
|
||||
"exports": {
|
||||
".": {
|
||||
"deno": "./dist/src/platform/deno.js",
|
||||
"node": {
|
||||
"import": "./dist/src/platform/node.js",
|
||||
"require": "./bundle/node.cjs"
|
||||
"default": "./dist/src/platform/node.js"
|
||||
},
|
||||
"deno": "./dist/src/platform/deno.js",
|
||||
"types": "./dist/src/platform/lib.d.ts",
|
||||
"browser": "./dist/src/platform/web.js",
|
||||
"react-native": "./dist/src/platform/react-native.js",
|
||||
"default": "./dist/src/platform/web.js"
|
||||
},
|
||||
"./package.json": "./package.json",
|
||||
"./agnostic": {
|
||||
"types": "./dist/src/platform/lib.d.ts",
|
||||
"default": "./dist/src/platform/lib.js"
|
||||
@@ -39,13 +47,17 @@
|
||||
"types": "./dist/src/platform/lib.d.ts",
|
||||
"default": "./dist/src/platform/web.js"
|
||||
},
|
||||
"./react-native": {
|
||||
"types": "./dist/src/platform/lib.d.ts",
|
||||
"default": "./dist/src/platform/react-native.js"
|
||||
},
|
||||
"./web.bundle": {
|
||||
"types": "./dist/src/platform/lib.d.ts",
|
||||
"default": "./bundle/browser.js"
|
||||
},
|
||||
"./web.bundle.min": {
|
||||
"./cf-worker": {
|
||||
"types": "./dist/src/platform/lib.d.ts",
|
||||
"default": "./bundle/browser.min.js"
|
||||
"default": "./dist/src/platform/cf-worker.js"
|
||||
}
|
||||
},
|
||||
"author": "LuanRT <luan.lrt4@gmail.com> (https://github.com/LuanRT)",
|
||||
@@ -59,81 +71,73 @@
|
||||
"akkadaska (https://github.com/akkadaska)",
|
||||
"Absidue (https://github.com/absidue)"
|
||||
],
|
||||
"directories": {
|
||||
"test": "./test",
|
||||
"examples": "./examples",
|
||||
"dist": "./dist"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "npx jest --verbose",
|
||||
"lint": "npx eslint ./src",
|
||||
"lint:fix": "npx eslint --fix ./src",
|
||||
"build": "npm run build:parser-map && npm run build:proto && npm run build:esm && npm run bundle:node && npm run bundle:browser && npm run bundle:browser:prod",
|
||||
"build:parser-map": "node ./scripts/gen-parser-map.mjs",
|
||||
"build:proto": "npx pb-gen-ts --entry-path=\"src/proto\" --out-dir=\"src/proto/generated\" --ext-in-import=\".js\"",
|
||||
"build:esm": "npx tspc",
|
||||
"build:deno": "npx cpy ./src ./deno && npx esbuild ./src/utils/DashManifest.tsx --keep-names --format=esm --platform=neutral --target=es2020 --outfile=./deno/src/utils/DashManifest.js && npx cpy ./package.json ./deno && npx replace \".ts';\" \".ts';\" ./deno -r && npx replace '.js\";' '.ts\";' ./deno -r && npx replace \"'./DashManifest.js';\" \"'./DashManifest.js';\" ./deno -r && npx replace \"'https://esm.sh/jintr';\" \"'https://esm.sh/jintr';\" ./deno -r",
|
||||
"bundle:node": "npx esbuild ./dist/src/platform/node.js --bundle --target=node10 --keep-names --format=cjs --platform=node --outfile=./bundle/node.cjs --external:jintr --external:undici --external:linkedom --external:tslib --sourcemap --banner:js=\"/* eslint-disable */\"",
|
||||
"bundle:browser": "npx esbuild ./dist/src/platform/web.js --banner:js=\"/* eslint-disable */\" --bundle --target=chrome58 --keep-names --format=esm --sourcemap --define:global=globalThis --conditions=module --outfile=./bundle/browser.js --platform=browser",
|
||||
"bundle:browser:prod": "npm run bundle:browser -- --outfile=./bundle/browser.min.js --minify",
|
||||
"test": "vitest run --reporter verbose",
|
||||
"lint": "eslint ./src",
|
||||
"lint:fix": "eslint --fix ./src",
|
||||
"clean:source-maps": "rimraf ./bundle/browser.js.map ./bundle/cf-worker.js.map ./bundle/react-native.js.map",
|
||||
"clean:build-output": "rimraf ./dist ./bundle/browser.js ./bundle/cf-worker.js ./bundle/react-native.js ./deno",
|
||||
"build": "npm run clean:build-output && npm run clean:source-maps && npm run build:parser-map && npm run build:esm && npm run bundle:browser && npm run bundle:cf-worker && npm run bundle:react-native",
|
||||
"build:esm": "tspc",
|
||||
"build:deno": "cpy ./src ./deno && cpy ./protos ./deno && esbuild ./src/utils/DashManifest.tsx --keep-names --format=esm --platform=neutral --target=es2020 --outfile=./deno/src/utils/DashManifest.js && cpy ./package.json ./deno && replace \".ts';\" \".ts';\" ./deno -r && replace '.js\";' '.ts\";' ./deno -r && replace \"'./DashManifest.js';\" \"'./DashManifest.js';\" ./deno -r && replace \"'jsr:@luanrt/jintr';\" \"'jsr:@luanrt/jintr';\" ./deno -r && replace \"https://esm.sh/@bufbuild/protobuf@2.0.0/wire\" \"https://esm.sh/@bufbuild/protobuf@2.0.0/wire\" ./deno -r",
|
||||
"build:proto": "rimraf ./protos/generated && node ./dev-scripts/generate-proto.mjs",
|
||||
"build:parser-map": "node ./dev-scripts/gen-parser-map.mjs",
|
||||
"bundle:browser": "esbuild ./dist/src/platform/web.js --banner:js=\"/* eslint-disable */\" --bundle --sourcemap --target=chrome70 --keep-names --format=esm --define:global=globalThis --conditions=module --outfile=./bundle/browser.js --platform=browser",
|
||||
"bundle:react-native": "esbuild ./dist/src/platform/react-native.js --bundle --sourcemap --target=es2020 --keep-names --format=esm --platform=neutral --define:global=globalThis --conditions=module --outfile=./bundle/react-native.js",
|
||||
"bundle:cf-worker": "esbuild ./dist/src/platform/cf-worker.js --banner:js=\"/* eslint-disable */\" --bundle --sourcemap --target=es2020 --keep-names --format=esm --define:global=globalThis --conditions=module --outfile=./bundle/cf-worker.js --platform=node",
|
||||
"build:docs": "typedoc",
|
||||
"prepare": "npm run build",
|
||||
"watch": "npx tsc --watch"
|
||||
"watch": "tspc --watch"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/LuanRT/YouTube.js.git"
|
||||
},
|
||||
"files": [
|
||||
"dist/",
|
||||
"bundle/",
|
||||
"package.json",
|
||||
"README.md",
|
||||
"LICENSE"
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"jintr": "^1.1.0",
|
||||
"tslib": "^2.5.0",
|
||||
"undici": "^5.19.1"
|
||||
},
|
||||
"overrides": {
|
||||
"typescript": "^5.0.0"
|
||||
"@bufbuild/protobuf": "^2.0.0",
|
||||
"meriyah": "^6.1.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/glob": "^8.1.0",
|
||||
"@types/jest": "^28.1.7",
|
||||
"@types/node": "^17.0.45",
|
||||
"@typescript-eslint/eslint-plugin": "^5.30.6",
|
||||
"@typescript-eslint/parser": "^5.30.6",
|
||||
"cpy-cli": "^4.2.0",
|
||||
"esbuild": "^0.14.49",
|
||||
"eslint": "^8.19.0",
|
||||
"eslint-plugin-tsdoc": "^0.2.16",
|
||||
"glob": "^8.0.3",
|
||||
"jest": "^28.1.3",
|
||||
"pbkit": "^0.0.59",
|
||||
"@eslint/js": "^9.37.0",
|
||||
"@types/estree": "^1.0.6",
|
||||
"@types/node": "^25.0.3",
|
||||
"@typescript-eslint/eslint-plugin": "^8.46.0",
|
||||
"@typescript-eslint/parser": "^8.46.0",
|
||||
"cpy-cli": "^6.0.0",
|
||||
"esbuild": "^0.25.6",
|
||||
"eslint": "^9.37.0",
|
||||
"globals": "^17.0.0",
|
||||
"replace": "^1.2.2",
|
||||
"ts-jest": "^28.0.8",
|
||||
"rimraf": "^6.0.1",
|
||||
"ts-patch": "^3.0.2",
|
||||
"ts-transformer-inline-file": "^0.2.0",
|
||||
"typescript": "^5.0.0"
|
||||
"ts-proto": "^2.2.0",
|
||||
"typedoc": "^0.28.14",
|
||||
"typedoc-plugin-markdown": "^4.9.0",
|
||||
"typescript": "^5.9.3",
|
||||
"typescript-eslint": "^8.46.0",
|
||||
"vitest": "^3.2.4"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/LuanRT/YouTube.js/issues"
|
||||
},
|
||||
"homepage": "https://github.com/LuanRT/YouTube.js#readme",
|
||||
"keywords": [
|
||||
"yt",
|
||||
"dl",
|
||||
"ytdl",
|
||||
"api",
|
||||
"youtube",
|
||||
"youtubedl",
|
||||
"youtube-dl",
|
||||
"youtube-downloader",
|
||||
"youtube-music",
|
||||
"youtube-studio",
|
||||
"innertube",
|
||||
"unofficial",
|
||||
"downloader",
|
||||
"livechat",
|
||||
"studio",
|
||||
"upload",
|
||||
"ytmusic",
|
||||
"search",
|
||||
"music",
|
||||
"api"
|
||||
"youtube-music",
|
||||
"ytdl",
|
||||
"youtube-studio",
|
||||
"downloader",
|
||||
"ytmusic"
|
||||
]
|
||||
}
|
||||
|
||||
344
deno/protos/generated/misc/common.ts
Normal file
344
deno/protos/generated/misc/common.ts
Normal file
@@ -0,0 +1,344 @@
|
||||
// Code generated by protoc-gen-ts_proto. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-ts_proto v2.7.7
|
||||
// protoc v6.33.5
|
||||
// source: misc/common.proto
|
||||
|
||||
/* eslint-disable */
|
||||
import { BinaryReader, BinaryWriter } from "https://esm.sh/@bufbuild/protobuf@2.0.0/wire";
|
||||
|
||||
export const protobufPackage = "misc";
|
||||
|
||||
export interface HttpHeader {
|
||||
name?: string | undefined;
|
||||
value?: string | undefined;
|
||||
}
|
||||
|
||||
export interface FormatId {
|
||||
itag?: number | undefined;
|
||||
lastModified?: number | undefined;
|
||||
xtags?: string | undefined;
|
||||
}
|
||||
|
||||
export interface InitRange {
|
||||
start?: number | undefined;
|
||||
end?: number | undefined;
|
||||
}
|
||||
|
||||
export interface IndexRange {
|
||||
start?: number | undefined;
|
||||
end?: number | undefined;
|
||||
}
|
||||
|
||||
export interface KeyValuePair {
|
||||
key?: string | undefined;
|
||||
value?: string | undefined;
|
||||
}
|
||||
|
||||
export interface FormatXTags {
|
||||
xtags: KeyValuePair[];
|
||||
}
|
||||
|
||||
function createBaseHttpHeader(): HttpHeader {
|
||||
return { name: undefined, value: undefined };
|
||||
}
|
||||
|
||||
export const HttpHeader: MessageFns<HttpHeader> = {
|
||||
encode(message: HttpHeader, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
|
||||
if (message.name !== undefined) {
|
||||
writer.uint32(10).string(message.name);
|
||||
}
|
||||
if (message.value !== undefined) {
|
||||
writer.uint32(18).string(message.value);
|
||||
}
|
||||
return writer;
|
||||
},
|
||||
|
||||
decode(input: BinaryReader | Uint8Array, length?: number): HttpHeader {
|
||||
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
|
||||
const end = length === undefined ? reader.len : reader.pos + length;
|
||||
const message = createBaseHttpHeader();
|
||||
while (reader.pos < end) {
|
||||
const tag = reader.uint32();
|
||||
switch (tag >>> 3) {
|
||||
case 1: {
|
||||
if (tag !== 10) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.name = reader.string();
|
||||
continue;
|
||||
}
|
||||
case 2: {
|
||||
if (tag !== 18) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.value = reader.string();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if ((tag & 7) === 4 || tag === 0) {
|
||||
break;
|
||||
}
|
||||
reader.skip(tag & 7);
|
||||
}
|
||||
return message;
|
||||
},
|
||||
};
|
||||
|
||||
function createBaseFormatId(): FormatId {
|
||||
return { itag: undefined, lastModified: undefined, xtags: undefined };
|
||||
}
|
||||
|
||||
export const FormatId: MessageFns<FormatId> = {
|
||||
encode(message: FormatId, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
|
||||
if (message.itag !== undefined) {
|
||||
writer.uint32(8).int32(message.itag);
|
||||
}
|
||||
if (message.lastModified !== undefined) {
|
||||
writer.uint32(16).uint64(message.lastModified);
|
||||
}
|
||||
if (message.xtags !== undefined) {
|
||||
writer.uint32(26).string(message.xtags);
|
||||
}
|
||||
return writer;
|
||||
},
|
||||
|
||||
decode(input: BinaryReader | Uint8Array, length?: number): FormatId {
|
||||
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
|
||||
const end = length === undefined ? reader.len : reader.pos + length;
|
||||
const message = createBaseFormatId();
|
||||
while (reader.pos < end) {
|
||||
const tag = reader.uint32();
|
||||
switch (tag >>> 3) {
|
||||
case 1: {
|
||||
if (tag !== 8) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.itag = reader.int32();
|
||||
continue;
|
||||
}
|
||||
case 2: {
|
||||
if (tag !== 16) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.lastModified = longToNumber(reader.uint64());
|
||||
continue;
|
||||
}
|
||||
case 3: {
|
||||
if (tag !== 26) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.xtags = reader.string();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if ((tag & 7) === 4 || tag === 0) {
|
||||
break;
|
||||
}
|
||||
reader.skip(tag & 7);
|
||||
}
|
||||
return message;
|
||||
},
|
||||
};
|
||||
|
||||
function createBaseInitRange(): InitRange {
|
||||
return { start: undefined, end: undefined };
|
||||
}
|
||||
|
||||
export const InitRange: MessageFns<InitRange> = {
|
||||
encode(message: InitRange, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
|
||||
if (message.start !== undefined) {
|
||||
writer.uint32(8).int32(message.start);
|
||||
}
|
||||
if (message.end !== undefined) {
|
||||
writer.uint32(16).int32(message.end);
|
||||
}
|
||||
return writer;
|
||||
},
|
||||
|
||||
decode(input: BinaryReader | Uint8Array, length?: number): InitRange {
|
||||
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
|
||||
const end = length === undefined ? reader.len : reader.pos + length;
|
||||
const message = createBaseInitRange();
|
||||
while (reader.pos < end) {
|
||||
const tag = reader.uint32();
|
||||
switch (tag >>> 3) {
|
||||
case 1: {
|
||||
if (tag !== 8) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.start = reader.int32();
|
||||
continue;
|
||||
}
|
||||
case 2: {
|
||||
if (tag !== 16) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.end = reader.int32();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if ((tag & 7) === 4 || tag === 0) {
|
||||
break;
|
||||
}
|
||||
reader.skip(tag & 7);
|
||||
}
|
||||
return message;
|
||||
},
|
||||
};
|
||||
|
||||
function createBaseIndexRange(): IndexRange {
|
||||
return { start: undefined, end: undefined };
|
||||
}
|
||||
|
||||
export const IndexRange: MessageFns<IndexRange> = {
|
||||
encode(message: IndexRange, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
|
||||
if (message.start !== undefined) {
|
||||
writer.uint32(8).int32(message.start);
|
||||
}
|
||||
if (message.end !== undefined) {
|
||||
writer.uint32(16).int32(message.end);
|
||||
}
|
||||
return writer;
|
||||
},
|
||||
|
||||
decode(input: BinaryReader | Uint8Array, length?: number): IndexRange {
|
||||
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
|
||||
const end = length === undefined ? reader.len : reader.pos + length;
|
||||
const message = createBaseIndexRange();
|
||||
while (reader.pos < end) {
|
||||
const tag = reader.uint32();
|
||||
switch (tag >>> 3) {
|
||||
case 1: {
|
||||
if (tag !== 8) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.start = reader.int32();
|
||||
continue;
|
||||
}
|
||||
case 2: {
|
||||
if (tag !== 16) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.end = reader.int32();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if ((tag & 7) === 4 || tag === 0) {
|
||||
break;
|
||||
}
|
||||
reader.skip(tag & 7);
|
||||
}
|
||||
return message;
|
||||
},
|
||||
};
|
||||
|
||||
function createBaseKeyValuePair(): KeyValuePair {
|
||||
return { key: undefined, value: undefined };
|
||||
}
|
||||
|
||||
export const KeyValuePair: MessageFns<KeyValuePair> = {
|
||||
encode(message: KeyValuePair, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
|
||||
if (message.key !== undefined) {
|
||||
writer.uint32(10).string(message.key);
|
||||
}
|
||||
if (message.value !== undefined) {
|
||||
writer.uint32(18).string(message.value);
|
||||
}
|
||||
return writer;
|
||||
},
|
||||
|
||||
decode(input: BinaryReader | Uint8Array, length?: number): KeyValuePair {
|
||||
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
|
||||
const end = length === undefined ? reader.len : reader.pos + length;
|
||||
const message = createBaseKeyValuePair();
|
||||
while (reader.pos < end) {
|
||||
const tag = reader.uint32();
|
||||
switch (tag >>> 3) {
|
||||
case 1: {
|
||||
if (tag !== 10) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.key = reader.string();
|
||||
continue;
|
||||
}
|
||||
case 2: {
|
||||
if (tag !== 18) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.value = reader.string();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if ((tag & 7) === 4 || tag === 0) {
|
||||
break;
|
||||
}
|
||||
reader.skip(tag & 7);
|
||||
}
|
||||
return message;
|
||||
},
|
||||
};
|
||||
|
||||
function createBaseFormatXTags(): FormatXTags {
|
||||
return { xtags: [] };
|
||||
}
|
||||
|
||||
export const FormatXTags: MessageFns<FormatXTags> = {
|
||||
encode(message: FormatXTags, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
|
||||
for (const v of message.xtags) {
|
||||
KeyValuePair.encode(v!, writer.uint32(10).fork()).join();
|
||||
}
|
||||
return writer;
|
||||
},
|
||||
|
||||
decode(input: BinaryReader | Uint8Array, length?: number): FormatXTags {
|
||||
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
|
||||
const end = length === undefined ? reader.len : reader.pos + length;
|
||||
const message = createBaseFormatXTags();
|
||||
while (reader.pos < end) {
|
||||
const tag = reader.uint32();
|
||||
switch (tag >>> 3) {
|
||||
case 1: {
|
||||
if (tag !== 10) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.xtags.push(KeyValuePair.decode(reader, reader.uint32()));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if ((tag & 7) === 4 || tag === 0) {
|
||||
break;
|
||||
}
|
||||
reader.skip(tag & 7);
|
||||
}
|
||||
return message;
|
||||
},
|
||||
};
|
||||
|
||||
function longToNumber(int64: { toString(): string }): number {
|
||||
const num = globalThis.Number(int64.toString());
|
||||
if (num > globalThis.Number.MAX_SAFE_INTEGER) {
|
||||
throw new globalThis.Error("Value is larger than Number.MAX_SAFE_INTEGER");
|
||||
}
|
||||
if (num < globalThis.Number.MIN_SAFE_INTEGER) {
|
||||
throw new globalThis.Error("Value is smaller than Number.MIN_SAFE_INTEGER");
|
||||
}
|
||||
return num;
|
||||
}
|
||||
|
||||
export interface MessageFns<T> {
|
||||
encode(message: T, writer?: BinaryWriter): BinaryWriter;
|
||||
decode(input: BinaryReader | Uint8Array, length?: number): T;
|
||||
}
|
||||
2617
deno/protos/generated/misc/params.ts
Normal file
2617
deno/protos/generated/misc/params.ts
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,187 @@
|
||||
// Code generated by protoc-gen-ts_proto. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-ts_proto v2.7.7
|
||||
// protoc v6.33.5
|
||||
// source: youtube/api/pfiinnertube/attestation_response_data.proto
|
||||
|
||||
/* eslint-disable */
|
||||
import { BinaryReader, BinaryWriter } from "https://esm.sh/@bufbuild/protobuf@2.0.0/wire";
|
||||
|
||||
export const protobufPackage = "youtube.api.pfiinnertube";
|
||||
|
||||
export interface AttestationResponseData {
|
||||
challenge?: string | undefined;
|
||||
webResponse?: string | undefined;
|
||||
androidResponse?: string | undefined;
|
||||
iosResponse?: Uint8Array | undefined;
|
||||
error?: number | undefined;
|
||||
adblockReporting?: AttestationResponseData_AdblockReporting | undefined;
|
||||
}
|
||||
|
||||
export interface AttestationResponseData_AdblockReporting {
|
||||
reportingStatus?: number | undefined;
|
||||
broadSpectrumDetectionResult?: number | undefined;
|
||||
}
|
||||
|
||||
function createBaseAttestationResponseData(): AttestationResponseData {
|
||||
return {
|
||||
challenge: undefined,
|
||||
webResponse: undefined,
|
||||
androidResponse: undefined,
|
||||
iosResponse: undefined,
|
||||
error: undefined,
|
||||
adblockReporting: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
export const AttestationResponseData: MessageFns<AttestationResponseData> = {
|
||||
encode(message: AttestationResponseData, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
|
||||
if (message.challenge !== undefined) {
|
||||
writer.uint32(10).string(message.challenge);
|
||||
}
|
||||
if (message.webResponse !== undefined) {
|
||||
writer.uint32(18).string(message.webResponse);
|
||||
}
|
||||
if (message.androidResponse !== undefined) {
|
||||
writer.uint32(26).string(message.androidResponse);
|
||||
}
|
||||
if (message.iosResponse !== undefined) {
|
||||
writer.uint32(34).bytes(message.iosResponse);
|
||||
}
|
||||
if (message.error !== undefined) {
|
||||
writer.uint32(40).int32(message.error);
|
||||
}
|
||||
if (message.adblockReporting !== undefined) {
|
||||
AttestationResponseData_AdblockReporting.encode(message.adblockReporting, writer.uint32(50).fork()).join();
|
||||
}
|
||||
return writer;
|
||||
},
|
||||
|
||||
decode(input: BinaryReader | Uint8Array, length?: number): AttestationResponseData {
|
||||
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
|
||||
const end = length === undefined ? reader.len : reader.pos + length;
|
||||
const message = createBaseAttestationResponseData();
|
||||
while (reader.pos < end) {
|
||||
const tag = reader.uint32();
|
||||
switch (tag >>> 3) {
|
||||
case 1: {
|
||||
if (tag !== 10) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.challenge = reader.string();
|
||||
continue;
|
||||
}
|
||||
case 2: {
|
||||
if (tag !== 18) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.webResponse = reader.string();
|
||||
continue;
|
||||
}
|
||||
case 3: {
|
||||
if (tag !== 26) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.androidResponse = reader.string();
|
||||
continue;
|
||||
}
|
||||
case 4: {
|
||||
if (tag !== 34) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.iosResponse = reader.bytes();
|
||||
continue;
|
||||
}
|
||||
case 5: {
|
||||
if (tag !== 40) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.error = reader.int32();
|
||||
continue;
|
||||
}
|
||||
case 6: {
|
||||
if (tag !== 50) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.adblockReporting = AttestationResponseData_AdblockReporting.decode(reader, reader.uint32());
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if ((tag & 7) === 4 || tag === 0) {
|
||||
break;
|
||||
}
|
||||
reader.skip(tag & 7);
|
||||
}
|
||||
return message;
|
||||
},
|
||||
};
|
||||
|
||||
function createBaseAttestationResponseData_AdblockReporting(): AttestationResponseData_AdblockReporting {
|
||||
return { reportingStatus: undefined, broadSpectrumDetectionResult: undefined };
|
||||
}
|
||||
|
||||
export const AttestationResponseData_AdblockReporting: MessageFns<AttestationResponseData_AdblockReporting> = {
|
||||
encode(message: AttestationResponseData_AdblockReporting, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
|
||||
if (message.reportingStatus !== undefined) {
|
||||
writer.uint32(8).uint64(message.reportingStatus);
|
||||
}
|
||||
if (message.broadSpectrumDetectionResult !== undefined) {
|
||||
writer.uint32(16).uint64(message.broadSpectrumDetectionResult);
|
||||
}
|
||||
return writer;
|
||||
},
|
||||
|
||||
decode(input: BinaryReader | Uint8Array, length?: number): AttestationResponseData_AdblockReporting {
|
||||
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
|
||||
const end = length === undefined ? reader.len : reader.pos + length;
|
||||
const message = createBaseAttestationResponseData_AdblockReporting();
|
||||
while (reader.pos < end) {
|
||||
const tag = reader.uint32();
|
||||
switch (tag >>> 3) {
|
||||
case 1: {
|
||||
if (tag !== 8) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.reportingStatus = longToNumber(reader.uint64());
|
||||
continue;
|
||||
}
|
||||
case 2: {
|
||||
if (tag !== 16) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.broadSpectrumDetectionResult = longToNumber(reader.uint64());
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if ((tag & 7) === 4 || tag === 0) {
|
||||
break;
|
||||
}
|
||||
reader.skip(tag & 7);
|
||||
}
|
||||
return message;
|
||||
},
|
||||
};
|
||||
|
||||
function longToNumber(int64: { toString(): string }): number {
|
||||
const num = globalThis.Number(int64.toString());
|
||||
if (num > globalThis.Number.MAX_SAFE_INTEGER) {
|
||||
throw new globalThis.Error("Value is larger than Number.MAX_SAFE_INTEGER");
|
||||
}
|
||||
if (num < globalThis.Number.MIN_SAFE_INTEGER) {
|
||||
throw new globalThis.Error("Value is smaller than Number.MIN_SAFE_INTEGER");
|
||||
}
|
||||
return num;
|
||||
}
|
||||
|
||||
export interface MessageFns<T> {
|
||||
encode(message: T, writer?: BinaryWriter): BinaryWriter;
|
||||
decode(input: BinaryReader | Uint8Array, length?: number): T;
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
// Code generated by protoc-gen-ts_proto. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-ts_proto v2.7.7
|
||||
// protoc v6.33.5
|
||||
// source: youtube/api/pfiinnertube/capability_info.proto
|
||||
|
||||
/* eslint-disable */
|
||||
import { BinaryReader, BinaryWriter } from "https://esm.sh/@bufbuild/protobuf@2.0.0/wire";
|
||||
|
||||
export const protobufPackage = "youtube.api.pfiinnertube";
|
||||
|
||||
export interface CapabilityInfo {
|
||||
profile?: string | undefined;
|
||||
supportedCapabilities: InnerTubeCapability[];
|
||||
disabledCapabilities: InnerTubeCapability[];
|
||||
snapshot?: string | undefined;
|
||||
}
|
||||
|
||||
export interface InnerTubeCapability {
|
||||
capability?: number | undefined;
|
||||
features?: number | undefined;
|
||||
experimentFlags?: string | undefined;
|
||||
}
|
||||
|
||||
function createBaseCapabilityInfo(): CapabilityInfo {
|
||||
return { profile: undefined, supportedCapabilities: [], disabledCapabilities: [], snapshot: undefined };
|
||||
}
|
||||
|
||||
export const CapabilityInfo: MessageFns<CapabilityInfo> = {
|
||||
encode(message: CapabilityInfo, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
|
||||
if (message.profile !== undefined) {
|
||||
writer.uint32(10).string(message.profile);
|
||||
}
|
||||
for (const v of message.supportedCapabilities) {
|
||||
InnerTubeCapability.encode(v!, writer.uint32(18).fork()).join();
|
||||
}
|
||||
for (const v of message.disabledCapabilities) {
|
||||
InnerTubeCapability.encode(v!, writer.uint32(26).fork()).join();
|
||||
}
|
||||
if (message.snapshot !== undefined) {
|
||||
writer.uint32(42).string(message.snapshot);
|
||||
}
|
||||
return writer;
|
||||
},
|
||||
|
||||
decode(input: BinaryReader | Uint8Array, length?: number): CapabilityInfo {
|
||||
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
|
||||
const end = length === undefined ? reader.len : reader.pos + length;
|
||||
const message = createBaseCapabilityInfo();
|
||||
while (reader.pos < end) {
|
||||
const tag = reader.uint32();
|
||||
switch (tag >>> 3) {
|
||||
case 1: {
|
||||
if (tag !== 10) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.profile = reader.string();
|
||||
continue;
|
||||
}
|
||||
case 2: {
|
||||
if (tag !== 18) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.supportedCapabilities.push(InnerTubeCapability.decode(reader, reader.uint32()));
|
||||
continue;
|
||||
}
|
||||
case 3: {
|
||||
if (tag !== 26) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.disabledCapabilities.push(InnerTubeCapability.decode(reader, reader.uint32()));
|
||||
continue;
|
||||
}
|
||||
case 5: {
|
||||
if (tag !== 42) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.snapshot = reader.string();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if ((tag & 7) === 4 || tag === 0) {
|
||||
break;
|
||||
}
|
||||
reader.skip(tag & 7);
|
||||
}
|
||||
return message;
|
||||
},
|
||||
};
|
||||
|
||||
function createBaseInnerTubeCapability(): InnerTubeCapability {
|
||||
return { capability: undefined, features: undefined, experimentFlags: undefined };
|
||||
}
|
||||
|
||||
export const InnerTubeCapability: MessageFns<InnerTubeCapability> = {
|
||||
encode(message: InnerTubeCapability, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
|
||||
if (message.capability !== undefined) {
|
||||
writer.uint32(8).uint32(message.capability);
|
||||
}
|
||||
if (message.features !== undefined) {
|
||||
writer.uint32(16).uint32(message.features);
|
||||
}
|
||||
if (message.experimentFlags !== undefined) {
|
||||
writer.uint32(50).string(message.experimentFlags);
|
||||
}
|
||||
return writer;
|
||||
},
|
||||
|
||||
decode(input: BinaryReader | Uint8Array, length?: number): InnerTubeCapability {
|
||||
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
|
||||
const end = length === undefined ? reader.len : reader.pos + length;
|
||||
const message = createBaseInnerTubeCapability();
|
||||
while (reader.pos < end) {
|
||||
const tag = reader.uint32();
|
||||
switch (tag >>> 3) {
|
||||
case 1: {
|
||||
if (tag !== 8) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.capability = reader.uint32();
|
||||
continue;
|
||||
}
|
||||
case 2: {
|
||||
if (tag !== 16) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.features = reader.uint32();
|
||||
continue;
|
||||
}
|
||||
case 6: {
|
||||
if (tag !== 50) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.experimentFlags = reader.string();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if ((tag & 7) === 4 || tag === 0) {
|
||||
break;
|
||||
}
|
||||
reader.skip(tag & 7);
|
||||
}
|
||||
return message;
|
||||
},
|
||||
};
|
||||
|
||||
export interface MessageFns<T> {
|
||||
encode(message: T, writer?: BinaryWriter): BinaryWriter;
|
||||
decode(input: BinaryReader | Uint8Array, length?: number): T;
|
||||
}
|
||||
2862
deno/protos/generated/youtube/api/pfiinnertube/client_info.ts
Normal file
2862
deno/protos/generated/youtube/api/pfiinnertube/client_info.ts
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,96 @@
|
||||
// Code generated by protoc-gen-ts_proto. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-ts_proto v2.7.7
|
||||
// protoc v6.33.5
|
||||
// source: youtube/api/pfiinnertube/get_watch_request.proto
|
||||
|
||||
/* eslint-disable */
|
||||
import { BinaryReader, BinaryWriter } from "https://esm.sh/@bufbuild/protobuf@2.0.0/wire";
|
||||
import { InnerTubeContext } from "./innertube_context.ts";
|
||||
import { PlayerRequest } from "./player_request.ts";
|
||||
import { ReelItemWatchRequest } from "./reel_item_watch_request.ts";
|
||||
import { WatchNextRequest } from "./watch_next_request.ts";
|
||||
|
||||
export const protobufPackage = "youtube.api.pfiinnertube";
|
||||
|
||||
export interface GetWatchRequest {
|
||||
context?: InnerTubeContext | undefined;
|
||||
playerRequest?: PlayerRequest | undefined;
|
||||
watchNextRequest?: WatchNextRequest | undefined;
|
||||
reelItemWatchRequest?: ReelItemWatchRequest | undefined;
|
||||
}
|
||||
|
||||
function createBaseGetWatchRequest(): GetWatchRequest {
|
||||
return { context: undefined, playerRequest: undefined, watchNextRequest: undefined, reelItemWatchRequest: undefined };
|
||||
}
|
||||
|
||||
export const GetWatchRequest: MessageFns<GetWatchRequest> = {
|
||||
encode(message: GetWatchRequest, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
|
||||
if (message.context !== undefined) {
|
||||
InnerTubeContext.encode(message.context, writer.uint32(10).fork()).join();
|
||||
}
|
||||
if (message.playerRequest !== undefined) {
|
||||
PlayerRequest.encode(message.playerRequest, writer.uint32(18).fork()).join();
|
||||
}
|
||||
if (message.watchNextRequest !== undefined) {
|
||||
WatchNextRequest.encode(message.watchNextRequest, writer.uint32(26).fork()).join();
|
||||
}
|
||||
if (message.reelItemWatchRequest !== undefined) {
|
||||
ReelItemWatchRequest.encode(message.reelItemWatchRequest, writer.uint32(34).fork()).join();
|
||||
}
|
||||
return writer;
|
||||
},
|
||||
|
||||
decode(input: BinaryReader | Uint8Array, length?: number): GetWatchRequest {
|
||||
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
|
||||
const end = length === undefined ? reader.len : reader.pos + length;
|
||||
const message = createBaseGetWatchRequest();
|
||||
while (reader.pos < end) {
|
||||
const tag = reader.uint32();
|
||||
switch (tag >>> 3) {
|
||||
case 1: {
|
||||
if (tag !== 10) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.context = InnerTubeContext.decode(reader, reader.uint32());
|
||||
continue;
|
||||
}
|
||||
case 2: {
|
||||
if (tag !== 18) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.playerRequest = PlayerRequest.decode(reader, reader.uint32());
|
||||
continue;
|
||||
}
|
||||
case 3: {
|
||||
if (tag !== 26) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.watchNextRequest = WatchNextRequest.decode(reader, reader.uint32());
|
||||
continue;
|
||||
}
|
||||
case 4: {
|
||||
if (tag !== 34) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.reelItemWatchRequest = ReelItemWatchRequest.decode(reader, reader.uint32());
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if ((tag & 7) === 4 || tag === 0) {
|
||||
break;
|
||||
}
|
||||
reader.skip(tag & 7);
|
||||
}
|
||||
return message;
|
||||
},
|
||||
};
|
||||
|
||||
export interface MessageFns<T> {
|
||||
encode(message: T, writer?: BinaryWriter): BinaryWriter;
|
||||
decode(input: BinaryReader | Uint8Array, length?: number): T;
|
||||
}
|
||||
@@ -0,0 +1,438 @@
|
||||
// Code generated by protoc-gen-ts_proto. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-ts_proto v2.7.7
|
||||
// protoc v6.33.5
|
||||
// source: youtube/api/pfiinnertube/innertube_context.proto
|
||||
|
||||
/* eslint-disable */
|
||||
import { BinaryReader, BinaryWriter } from "https://esm.sh/@bufbuild/protobuf@2.0.0/wire";
|
||||
import { KeyValuePair } from "../../../misc/common.ts";
|
||||
import { CapabilityInfo } from "./capability_info.ts";
|
||||
import { ClientInfo } from "./client_info.ts";
|
||||
import { RequestInfo } from "./request_info.ts";
|
||||
import { ThirdPartyInfo } from "./third_party_info.ts";
|
||||
import { UserInfo } from "./user_info.ts";
|
||||
|
||||
export const protobufPackage = "youtube.api.pfiinnertube";
|
||||
|
||||
export interface InnerTubeContext {
|
||||
client?: ClientInfo | undefined;
|
||||
user?: UserInfo | undefined;
|
||||
capabilities?: CapabilityInfo | undefined;
|
||||
request?: RequestInfo | undefined;
|
||||
clickTracking?: InnerTubeContext_ClickTrackingInfo | undefined;
|
||||
thirdParty?: ThirdPartyInfo | undefined;
|
||||
remoteClient?: ClientInfo | undefined;
|
||||
adSignalsInfo?: InnerTubeContext_AdSignalsInfo | undefined;
|
||||
experimentalData?: InnerTubeContext_ExperimentalData | undefined;
|
||||
clientScreenNonce?: string | undefined;
|
||||
activePlayers: InnerTubeContext_ActivePlayerInfo[];
|
||||
}
|
||||
|
||||
export interface InnerTubeContext_ExperimentalData {
|
||||
params: KeyValuePair[];
|
||||
}
|
||||
|
||||
export interface InnerTubeContext_ActivePlayerInfo {
|
||||
playerContextParams?: Uint8Array | undefined;
|
||||
}
|
||||
|
||||
export interface InnerTubeContext_ClickTrackingInfo {
|
||||
clickTrackingParams?: Uint8Array | undefined;
|
||||
}
|
||||
|
||||
export interface InnerTubeContext_AdSignalsInfo {
|
||||
params: KeyValuePair[];
|
||||
bid?: string | undefined;
|
||||
mutsuId?: string | undefined;
|
||||
consentBumpState?: string | undefined;
|
||||
advertisingId?: string | undefined;
|
||||
limitAdTracking?: boolean | undefined;
|
||||
attributionOsSupportedVersion?: string | undefined;
|
||||
}
|
||||
|
||||
function createBaseInnerTubeContext(): InnerTubeContext {
|
||||
return {
|
||||
client: undefined,
|
||||
user: undefined,
|
||||
capabilities: undefined,
|
||||
request: undefined,
|
||||
clickTracking: undefined,
|
||||
thirdParty: undefined,
|
||||
remoteClient: undefined,
|
||||
adSignalsInfo: undefined,
|
||||
experimentalData: undefined,
|
||||
clientScreenNonce: undefined,
|
||||
activePlayers: [],
|
||||
};
|
||||
}
|
||||
|
||||
export const InnerTubeContext: MessageFns<InnerTubeContext> = {
|
||||
encode(message: InnerTubeContext, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
|
||||
if (message.client !== undefined) {
|
||||
ClientInfo.encode(message.client, writer.uint32(10).fork()).join();
|
||||
}
|
||||
if (message.user !== undefined) {
|
||||
UserInfo.encode(message.user, writer.uint32(26).fork()).join();
|
||||
}
|
||||
if (message.capabilities !== undefined) {
|
||||
CapabilityInfo.encode(message.capabilities, writer.uint32(34).fork()).join();
|
||||
}
|
||||
if (message.request !== undefined) {
|
||||
RequestInfo.encode(message.request, writer.uint32(42).fork()).join();
|
||||
}
|
||||
if (message.clickTracking !== undefined) {
|
||||
InnerTubeContext_ClickTrackingInfo.encode(message.clickTracking, writer.uint32(50).fork()).join();
|
||||
}
|
||||
if (message.thirdParty !== undefined) {
|
||||
ThirdPartyInfo.encode(message.thirdParty, writer.uint32(58).fork()).join();
|
||||
}
|
||||
if (message.remoteClient !== undefined) {
|
||||
ClientInfo.encode(message.remoteClient, writer.uint32(66).fork()).join();
|
||||
}
|
||||
if (message.adSignalsInfo !== undefined) {
|
||||
InnerTubeContext_AdSignalsInfo.encode(message.adSignalsInfo, writer.uint32(74).fork()).join();
|
||||
}
|
||||
if (message.experimentalData !== undefined) {
|
||||
InnerTubeContext_ExperimentalData.encode(message.experimentalData, writer.uint32(82).fork()).join();
|
||||
}
|
||||
if (message.clientScreenNonce !== undefined) {
|
||||
writer.uint32(90).string(message.clientScreenNonce);
|
||||
}
|
||||
for (const v of message.activePlayers) {
|
||||
InnerTubeContext_ActivePlayerInfo.encode(v!, writer.uint32(98).fork()).join();
|
||||
}
|
||||
return writer;
|
||||
},
|
||||
|
||||
decode(input: BinaryReader | Uint8Array, length?: number): InnerTubeContext {
|
||||
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
|
||||
const end = length === undefined ? reader.len : reader.pos + length;
|
||||
const message = createBaseInnerTubeContext();
|
||||
while (reader.pos < end) {
|
||||
const tag = reader.uint32();
|
||||
switch (tag >>> 3) {
|
||||
case 1: {
|
||||
if (tag !== 10) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.client = ClientInfo.decode(reader, reader.uint32());
|
||||
continue;
|
||||
}
|
||||
case 3: {
|
||||
if (tag !== 26) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.user = UserInfo.decode(reader, reader.uint32());
|
||||
continue;
|
||||
}
|
||||
case 4: {
|
||||
if (tag !== 34) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.capabilities = CapabilityInfo.decode(reader, reader.uint32());
|
||||
continue;
|
||||
}
|
||||
case 5: {
|
||||
if (tag !== 42) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.request = RequestInfo.decode(reader, reader.uint32());
|
||||
continue;
|
||||
}
|
||||
case 6: {
|
||||
if (tag !== 50) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.clickTracking = InnerTubeContext_ClickTrackingInfo.decode(reader, reader.uint32());
|
||||
continue;
|
||||
}
|
||||
case 7: {
|
||||
if (tag !== 58) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.thirdParty = ThirdPartyInfo.decode(reader, reader.uint32());
|
||||
continue;
|
||||
}
|
||||
case 8: {
|
||||
if (tag !== 66) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.remoteClient = ClientInfo.decode(reader, reader.uint32());
|
||||
continue;
|
||||
}
|
||||
case 9: {
|
||||
if (tag !== 74) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.adSignalsInfo = InnerTubeContext_AdSignalsInfo.decode(reader, reader.uint32());
|
||||
continue;
|
||||
}
|
||||
case 10: {
|
||||
if (tag !== 82) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.experimentalData = InnerTubeContext_ExperimentalData.decode(reader, reader.uint32());
|
||||
continue;
|
||||
}
|
||||
case 11: {
|
||||
if (tag !== 90) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.clientScreenNonce = reader.string();
|
||||
continue;
|
||||
}
|
||||
case 12: {
|
||||
if (tag !== 98) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.activePlayers.push(InnerTubeContext_ActivePlayerInfo.decode(reader, reader.uint32()));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if ((tag & 7) === 4 || tag === 0) {
|
||||
break;
|
||||
}
|
||||
reader.skip(tag & 7);
|
||||
}
|
||||
return message;
|
||||
},
|
||||
};
|
||||
|
||||
function createBaseInnerTubeContext_ExperimentalData(): InnerTubeContext_ExperimentalData {
|
||||
return { params: [] };
|
||||
}
|
||||
|
||||
export const InnerTubeContext_ExperimentalData: MessageFns<InnerTubeContext_ExperimentalData> = {
|
||||
encode(message: InnerTubeContext_ExperimentalData, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
|
||||
for (const v of message.params) {
|
||||
KeyValuePair.encode(v!, writer.uint32(10).fork()).join();
|
||||
}
|
||||
return writer;
|
||||
},
|
||||
|
||||
decode(input: BinaryReader | Uint8Array, length?: number): InnerTubeContext_ExperimentalData {
|
||||
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
|
||||
const end = length === undefined ? reader.len : reader.pos + length;
|
||||
const message = createBaseInnerTubeContext_ExperimentalData();
|
||||
while (reader.pos < end) {
|
||||
const tag = reader.uint32();
|
||||
switch (tag >>> 3) {
|
||||
case 1: {
|
||||
if (tag !== 10) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.params.push(KeyValuePair.decode(reader, reader.uint32()));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if ((tag & 7) === 4 || tag === 0) {
|
||||
break;
|
||||
}
|
||||
reader.skip(tag & 7);
|
||||
}
|
||||
return message;
|
||||
},
|
||||
};
|
||||
|
||||
function createBaseInnerTubeContext_ActivePlayerInfo(): InnerTubeContext_ActivePlayerInfo {
|
||||
return { playerContextParams: undefined };
|
||||
}
|
||||
|
||||
export const InnerTubeContext_ActivePlayerInfo: MessageFns<InnerTubeContext_ActivePlayerInfo> = {
|
||||
encode(message: InnerTubeContext_ActivePlayerInfo, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
|
||||
if (message.playerContextParams !== undefined) {
|
||||
writer.uint32(10).bytes(message.playerContextParams);
|
||||
}
|
||||
return writer;
|
||||
},
|
||||
|
||||
decode(input: BinaryReader | Uint8Array, length?: number): InnerTubeContext_ActivePlayerInfo {
|
||||
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
|
||||
const end = length === undefined ? reader.len : reader.pos + length;
|
||||
const message = createBaseInnerTubeContext_ActivePlayerInfo();
|
||||
while (reader.pos < end) {
|
||||
const tag = reader.uint32();
|
||||
switch (tag >>> 3) {
|
||||
case 1: {
|
||||
if (tag !== 10) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.playerContextParams = reader.bytes();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if ((tag & 7) === 4 || tag === 0) {
|
||||
break;
|
||||
}
|
||||
reader.skip(tag & 7);
|
||||
}
|
||||
return message;
|
||||
},
|
||||
};
|
||||
|
||||
function createBaseInnerTubeContext_ClickTrackingInfo(): InnerTubeContext_ClickTrackingInfo {
|
||||
return { clickTrackingParams: undefined };
|
||||
}
|
||||
|
||||
export const InnerTubeContext_ClickTrackingInfo: MessageFns<InnerTubeContext_ClickTrackingInfo> = {
|
||||
encode(message: InnerTubeContext_ClickTrackingInfo, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
|
||||
if (message.clickTrackingParams !== undefined) {
|
||||
writer.uint32(18).bytes(message.clickTrackingParams);
|
||||
}
|
||||
return writer;
|
||||
},
|
||||
|
||||
decode(input: BinaryReader | Uint8Array, length?: number): InnerTubeContext_ClickTrackingInfo {
|
||||
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
|
||||
const end = length === undefined ? reader.len : reader.pos + length;
|
||||
const message = createBaseInnerTubeContext_ClickTrackingInfo();
|
||||
while (reader.pos < end) {
|
||||
const tag = reader.uint32();
|
||||
switch (tag >>> 3) {
|
||||
case 2: {
|
||||
if (tag !== 18) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.clickTrackingParams = reader.bytes();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if ((tag & 7) === 4 || tag === 0) {
|
||||
break;
|
||||
}
|
||||
reader.skip(tag & 7);
|
||||
}
|
||||
return message;
|
||||
},
|
||||
};
|
||||
|
||||
function createBaseInnerTubeContext_AdSignalsInfo(): InnerTubeContext_AdSignalsInfo {
|
||||
return {
|
||||
params: [],
|
||||
bid: undefined,
|
||||
mutsuId: undefined,
|
||||
consentBumpState: undefined,
|
||||
advertisingId: undefined,
|
||||
limitAdTracking: undefined,
|
||||
attributionOsSupportedVersion: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
export const InnerTubeContext_AdSignalsInfo: MessageFns<InnerTubeContext_AdSignalsInfo> = {
|
||||
encode(message: InnerTubeContext_AdSignalsInfo, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
|
||||
for (const v of message.params) {
|
||||
KeyValuePair.encode(v!, writer.uint32(10).fork()).join();
|
||||
}
|
||||
if (message.bid !== undefined) {
|
||||
writer.uint32(18).string(message.bid);
|
||||
}
|
||||
if (message.mutsuId !== undefined) {
|
||||
writer.uint32(26).string(message.mutsuId);
|
||||
}
|
||||
if (message.consentBumpState !== undefined) {
|
||||
writer.uint32(34).string(message.consentBumpState);
|
||||
}
|
||||
if (message.advertisingId !== undefined) {
|
||||
writer.uint32(58).string(message.advertisingId);
|
||||
}
|
||||
if (message.limitAdTracking !== undefined) {
|
||||
writer.uint32(72).bool(message.limitAdTracking);
|
||||
}
|
||||
if (message.attributionOsSupportedVersion !== undefined) {
|
||||
writer.uint32(82).string(message.attributionOsSupportedVersion);
|
||||
}
|
||||
return writer;
|
||||
},
|
||||
|
||||
decode(input: BinaryReader | Uint8Array, length?: number): InnerTubeContext_AdSignalsInfo {
|
||||
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
|
||||
const end = length === undefined ? reader.len : reader.pos + length;
|
||||
const message = createBaseInnerTubeContext_AdSignalsInfo();
|
||||
while (reader.pos < end) {
|
||||
const tag = reader.uint32();
|
||||
switch (tag >>> 3) {
|
||||
case 1: {
|
||||
if (tag !== 10) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.params.push(KeyValuePair.decode(reader, reader.uint32()));
|
||||
continue;
|
||||
}
|
||||
case 2: {
|
||||
if (tag !== 18) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.bid = reader.string();
|
||||
continue;
|
||||
}
|
||||
case 3: {
|
||||
if (tag !== 26) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.mutsuId = reader.string();
|
||||
continue;
|
||||
}
|
||||
case 4: {
|
||||
if (tag !== 34) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.consentBumpState = reader.string();
|
||||
continue;
|
||||
}
|
||||
case 7: {
|
||||
if (tag !== 58) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.advertisingId = reader.string();
|
||||
continue;
|
||||
}
|
||||
case 9: {
|
||||
if (tag !== 72) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.limitAdTracking = reader.bool();
|
||||
continue;
|
||||
}
|
||||
case 10: {
|
||||
if (tag !== 82) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.attributionOsSupportedVersion = reader.string();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if ((tag & 7) === 4 || tag === 0) {
|
||||
break;
|
||||
}
|
||||
reader.skip(tag & 7);
|
||||
}
|
||||
return message;
|
||||
},
|
||||
};
|
||||
|
||||
export interface MessageFns<T> {
|
||||
encode(message: T, writer?: BinaryWriter): BinaryWriter;
|
||||
decode(input: BinaryReader | Uint8Array, length?: number): T;
|
||||
}
|
||||
@@ -0,0 +1,903 @@
|
||||
// Code generated by protoc-gen-ts_proto. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-ts_proto v2.7.7
|
||||
// protoc v6.33.5
|
||||
// source: youtube/api/pfiinnertube/metadata_update_request.proto
|
||||
|
||||
/* eslint-disable */
|
||||
import { BinaryReader, BinaryWriter } from "https://esm.sh/@bufbuild/protobuf@2.0.0/wire";
|
||||
import { InnerTubeContext } from "./innertube_context.ts";
|
||||
|
||||
export const protobufPackage = "youtube.api.pfiinnertube";
|
||||
|
||||
export interface MetadataUpdateRequest {
|
||||
context?: InnerTubeContext | undefined;
|
||||
encryptedVideoId?: string | undefined;
|
||||
title?: MetadataUpdateRequest_MdeTitleUpdateRequest | undefined;
|
||||
description?: MetadataUpdateRequest_MdeDescriptionUpdateRequest | undefined;
|
||||
privacy?: MetadataUpdateRequest_MdePrivacyUpdateRequest | undefined;
|
||||
tags?: MetadataUpdateRequest_MdeTagsUpdateRequest | undefined;
|
||||
category?: MetadataUpdateRequest_MdeCategoryUpdateRequest | undefined;
|
||||
license?: MetadataUpdateRequest_MdeLicenseUpdateRequest | undefined;
|
||||
ageRestriction?: MetadataUpdateRequest_MdeAgeRestrictionUpdateRequest | undefined;
|
||||
videoStill?: MetadataUpdateRequest_MdeVideoStillRequestParams | undefined;
|
||||
madeForKids?: MetadataUpdateRequest_MdeMadeForKidsUpdateRequestParams | undefined;
|
||||
racy?: MetadataUpdateRequest_MdeRacyRequestParams | undefined;
|
||||
}
|
||||
|
||||
export interface MetadataUpdateRequest_MdeTitleUpdateRequest {
|
||||
newTitle?: string | undefined;
|
||||
}
|
||||
|
||||
export interface MetadataUpdateRequest_MdeDescriptionUpdateRequest {
|
||||
newDescription?: string | undefined;
|
||||
}
|
||||
|
||||
export interface MetadataUpdateRequest_MdePrivacyUpdateRequest {
|
||||
newPrivacy?: number | undefined;
|
||||
clearPrivacyDraft?: boolean | undefined;
|
||||
}
|
||||
|
||||
export interface MetadataUpdateRequest_MdeTagsUpdateRequest {
|
||||
newTags: string[];
|
||||
}
|
||||
|
||||
export interface MetadataUpdateRequest_MdeCategoryUpdateRequest {
|
||||
newCategoryId?: number | undefined;
|
||||
}
|
||||
|
||||
export interface MetadataUpdateRequest_MdeLicenseUpdateRequest {
|
||||
newLicenseId?: string | undefined;
|
||||
}
|
||||
|
||||
export interface MetadataUpdateRequest_MdeMadeForKidsUpdateRequestParams {
|
||||
operation?: number | undefined;
|
||||
newMfk?: number | undefined;
|
||||
}
|
||||
|
||||
export interface MetadataUpdateRequest_MdeRacyRequestParams {
|
||||
operation?: number | undefined;
|
||||
newRacy?: number | undefined;
|
||||
}
|
||||
|
||||
export interface MetadataUpdateRequest_MdeAgeRestrictionUpdateRequest {
|
||||
newIsAgeRestricted?: boolean | undefined;
|
||||
}
|
||||
|
||||
export interface MetadataUpdateRequest_MdeVideoStillRequestParams {
|
||||
operation?: number | undefined;
|
||||
newStillId?: number | undefined;
|
||||
image?: MetadataUpdateRequest_MdeVideoStillRequestParams_CustomThumbnailImage | undefined;
|
||||
testImage?: MetadataUpdateRequest_MdeVideoStillRequestParams_CustomThumbnailImage | undefined;
|
||||
experimentImage: MetadataUpdateRequest_MdeVideoStillRequestParams_ThumbnailExperimentImageData[];
|
||||
}
|
||||
|
||||
export interface MetadataUpdateRequest_MdeVideoStillRequestParams_ThumbnailExperimentImageData {
|
||||
image?: MetadataUpdateRequest_MdeVideoStillRequestParams_CustomThumbnailImage | undefined;
|
||||
}
|
||||
|
||||
export interface MetadataUpdateRequest_MdeVideoStillRequestParams_CustomThumbnailImage {
|
||||
rawBytes?: Uint8Array | undefined;
|
||||
dataUri?: string | undefined;
|
||||
frameTimestampUs?: number | undefined;
|
||||
isVertical?: boolean | undefined;
|
||||
}
|
||||
|
||||
function createBaseMetadataUpdateRequest(): MetadataUpdateRequest {
|
||||
return {
|
||||
context: undefined,
|
||||
encryptedVideoId: undefined,
|
||||
title: undefined,
|
||||
description: undefined,
|
||||
privacy: undefined,
|
||||
tags: undefined,
|
||||
category: undefined,
|
||||
license: undefined,
|
||||
ageRestriction: undefined,
|
||||
videoStill: undefined,
|
||||
madeForKids: undefined,
|
||||
racy: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
export const MetadataUpdateRequest: MessageFns<MetadataUpdateRequest> = {
|
||||
encode(message: MetadataUpdateRequest, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
|
||||
if (message.context !== undefined) {
|
||||
InnerTubeContext.encode(message.context, writer.uint32(10).fork()).join();
|
||||
}
|
||||
if (message.encryptedVideoId !== undefined) {
|
||||
writer.uint32(18).string(message.encryptedVideoId);
|
||||
}
|
||||
if (message.title !== undefined) {
|
||||
MetadataUpdateRequest_MdeTitleUpdateRequest.encode(message.title, writer.uint32(26).fork()).join();
|
||||
}
|
||||
if (message.description !== undefined) {
|
||||
MetadataUpdateRequest_MdeDescriptionUpdateRequest.encode(message.description, writer.uint32(34).fork()).join();
|
||||
}
|
||||
if (message.privacy !== undefined) {
|
||||
MetadataUpdateRequest_MdePrivacyUpdateRequest.encode(message.privacy, writer.uint32(42).fork()).join();
|
||||
}
|
||||
if (message.tags !== undefined) {
|
||||
MetadataUpdateRequest_MdeTagsUpdateRequest.encode(message.tags, writer.uint32(50).fork()).join();
|
||||
}
|
||||
if (message.category !== undefined) {
|
||||
MetadataUpdateRequest_MdeCategoryUpdateRequest.encode(message.category, writer.uint32(58).fork()).join();
|
||||
}
|
||||
if (message.license !== undefined) {
|
||||
MetadataUpdateRequest_MdeLicenseUpdateRequest.encode(message.license, writer.uint32(66).fork()).join();
|
||||
}
|
||||
if (message.ageRestriction !== undefined) {
|
||||
MetadataUpdateRequest_MdeAgeRestrictionUpdateRequest.encode(message.ageRestriction, writer.uint32(90).fork())
|
||||
.join();
|
||||
}
|
||||
if (message.videoStill !== undefined) {
|
||||
MetadataUpdateRequest_MdeVideoStillRequestParams.encode(message.videoStill, writer.uint32(162).fork()).join();
|
||||
}
|
||||
if (message.madeForKids !== undefined) {
|
||||
MetadataUpdateRequest_MdeMadeForKidsUpdateRequestParams.encode(message.madeForKids, writer.uint32(546).fork())
|
||||
.join();
|
||||
}
|
||||
if (message.racy !== undefined) {
|
||||
MetadataUpdateRequest_MdeRacyRequestParams.encode(message.racy, writer.uint32(554).fork()).join();
|
||||
}
|
||||
return writer;
|
||||
},
|
||||
|
||||
decode(input: BinaryReader | Uint8Array, length?: number): MetadataUpdateRequest {
|
||||
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
|
||||
const end = length === undefined ? reader.len : reader.pos + length;
|
||||
const message = createBaseMetadataUpdateRequest();
|
||||
while (reader.pos < end) {
|
||||
const tag = reader.uint32();
|
||||
switch (tag >>> 3) {
|
||||
case 1: {
|
||||
if (tag !== 10) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.context = InnerTubeContext.decode(reader, reader.uint32());
|
||||
continue;
|
||||
}
|
||||
case 2: {
|
||||
if (tag !== 18) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.encryptedVideoId = reader.string();
|
||||
continue;
|
||||
}
|
||||
case 3: {
|
||||
if (tag !== 26) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.title = MetadataUpdateRequest_MdeTitleUpdateRequest.decode(reader, reader.uint32());
|
||||
continue;
|
||||
}
|
||||
case 4: {
|
||||
if (tag !== 34) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.description = MetadataUpdateRequest_MdeDescriptionUpdateRequest.decode(reader, reader.uint32());
|
||||
continue;
|
||||
}
|
||||
case 5: {
|
||||
if (tag !== 42) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.privacy = MetadataUpdateRequest_MdePrivacyUpdateRequest.decode(reader, reader.uint32());
|
||||
continue;
|
||||
}
|
||||
case 6: {
|
||||
if (tag !== 50) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.tags = MetadataUpdateRequest_MdeTagsUpdateRequest.decode(reader, reader.uint32());
|
||||
continue;
|
||||
}
|
||||
case 7: {
|
||||
if (tag !== 58) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.category = MetadataUpdateRequest_MdeCategoryUpdateRequest.decode(reader, reader.uint32());
|
||||
continue;
|
||||
}
|
||||
case 8: {
|
||||
if (tag !== 66) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.license = MetadataUpdateRequest_MdeLicenseUpdateRequest.decode(reader, reader.uint32());
|
||||
continue;
|
||||
}
|
||||
case 11: {
|
||||
if (tag !== 90) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.ageRestriction = MetadataUpdateRequest_MdeAgeRestrictionUpdateRequest.decode(reader, reader.uint32());
|
||||
continue;
|
||||
}
|
||||
case 20: {
|
||||
if (tag !== 162) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.videoStill = MetadataUpdateRequest_MdeVideoStillRequestParams.decode(reader, reader.uint32());
|
||||
continue;
|
||||
}
|
||||
case 68: {
|
||||
if (tag !== 546) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.madeForKids = MetadataUpdateRequest_MdeMadeForKidsUpdateRequestParams.decode(reader, reader.uint32());
|
||||
continue;
|
||||
}
|
||||
case 69: {
|
||||
if (tag !== 554) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.racy = MetadataUpdateRequest_MdeRacyRequestParams.decode(reader, reader.uint32());
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if ((tag & 7) === 4 || tag === 0) {
|
||||
break;
|
||||
}
|
||||
reader.skip(tag & 7);
|
||||
}
|
||||
return message;
|
||||
},
|
||||
};
|
||||
|
||||
function createBaseMetadataUpdateRequest_MdeTitleUpdateRequest(): MetadataUpdateRequest_MdeTitleUpdateRequest {
|
||||
return { newTitle: undefined };
|
||||
}
|
||||
|
||||
export const MetadataUpdateRequest_MdeTitleUpdateRequest: MessageFns<MetadataUpdateRequest_MdeTitleUpdateRequest> = {
|
||||
encode(
|
||||
message: MetadataUpdateRequest_MdeTitleUpdateRequest,
|
||||
writer: BinaryWriter = new BinaryWriter(),
|
||||
): BinaryWriter {
|
||||
if (message.newTitle !== undefined) {
|
||||
writer.uint32(10).string(message.newTitle);
|
||||
}
|
||||
return writer;
|
||||
},
|
||||
|
||||
decode(input: BinaryReader | Uint8Array, length?: number): MetadataUpdateRequest_MdeTitleUpdateRequest {
|
||||
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
|
||||
const end = length === undefined ? reader.len : reader.pos + length;
|
||||
const message = createBaseMetadataUpdateRequest_MdeTitleUpdateRequest();
|
||||
while (reader.pos < end) {
|
||||
const tag = reader.uint32();
|
||||
switch (tag >>> 3) {
|
||||
case 1: {
|
||||
if (tag !== 10) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.newTitle = reader.string();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if ((tag & 7) === 4 || tag === 0) {
|
||||
break;
|
||||
}
|
||||
reader.skip(tag & 7);
|
||||
}
|
||||
return message;
|
||||
},
|
||||
};
|
||||
|
||||
function createBaseMetadataUpdateRequest_MdeDescriptionUpdateRequest(): MetadataUpdateRequest_MdeDescriptionUpdateRequest {
|
||||
return { newDescription: undefined };
|
||||
}
|
||||
|
||||
export const MetadataUpdateRequest_MdeDescriptionUpdateRequest: MessageFns<
|
||||
MetadataUpdateRequest_MdeDescriptionUpdateRequest
|
||||
> = {
|
||||
encode(
|
||||
message: MetadataUpdateRequest_MdeDescriptionUpdateRequest,
|
||||
writer: BinaryWriter = new BinaryWriter(),
|
||||
): BinaryWriter {
|
||||
if (message.newDescription !== undefined) {
|
||||
writer.uint32(10).string(message.newDescription);
|
||||
}
|
||||
return writer;
|
||||
},
|
||||
|
||||
decode(input: BinaryReader | Uint8Array, length?: number): MetadataUpdateRequest_MdeDescriptionUpdateRequest {
|
||||
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
|
||||
const end = length === undefined ? reader.len : reader.pos + length;
|
||||
const message = createBaseMetadataUpdateRequest_MdeDescriptionUpdateRequest();
|
||||
while (reader.pos < end) {
|
||||
const tag = reader.uint32();
|
||||
switch (tag >>> 3) {
|
||||
case 1: {
|
||||
if (tag !== 10) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.newDescription = reader.string();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if ((tag & 7) === 4 || tag === 0) {
|
||||
break;
|
||||
}
|
||||
reader.skip(tag & 7);
|
||||
}
|
||||
return message;
|
||||
},
|
||||
};
|
||||
|
||||
function createBaseMetadataUpdateRequest_MdePrivacyUpdateRequest(): MetadataUpdateRequest_MdePrivacyUpdateRequest {
|
||||
return { newPrivacy: undefined, clearPrivacyDraft: undefined };
|
||||
}
|
||||
|
||||
export const MetadataUpdateRequest_MdePrivacyUpdateRequest: MessageFns<MetadataUpdateRequest_MdePrivacyUpdateRequest> =
|
||||
{
|
||||
encode(
|
||||
message: MetadataUpdateRequest_MdePrivacyUpdateRequest,
|
||||
writer: BinaryWriter = new BinaryWriter(),
|
||||
): BinaryWriter {
|
||||
if (message.newPrivacy !== undefined) {
|
||||
writer.uint32(8).int32(message.newPrivacy);
|
||||
}
|
||||
if (message.clearPrivacyDraft !== undefined) {
|
||||
writer.uint32(16).bool(message.clearPrivacyDraft);
|
||||
}
|
||||
return writer;
|
||||
},
|
||||
|
||||
decode(input: BinaryReader | Uint8Array, length?: number): MetadataUpdateRequest_MdePrivacyUpdateRequest {
|
||||
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
|
||||
const end = length === undefined ? reader.len : reader.pos + length;
|
||||
const message = createBaseMetadataUpdateRequest_MdePrivacyUpdateRequest();
|
||||
while (reader.pos < end) {
|
||||
const tag = reader.uint32();
|
||||
switch (tag >>> 3) {
|
||||
case 1: {
|
||||
if (tag !== 8) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.newPrivacy = reader.int32();
|
||||
continue;
|
||||
}
|
||||
case 2: {
|
||||
if (tag !== 16) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.clearPrivacyDraft = reader.bool();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if ((tag & 7) === 4 || tag === 0) {
|
||||
break;
|
||||
}
|
||||
reader.skip(tag & 7);
|
||||
}
|
||||
return message;
|
||||
},
|
||||
};
|
||||
|
||||
function createBaseMetadataUpdateRequest_MdeTagsUpdateRequest(): MetadataUpdateRequest_MdeTagsUpdateRequest {
|
||||
return { newTags: [] };
|
||||
}
|
||||
|
||||
export const MetadataUpdateRequest_MdeTagsUpdateRequest: MessageFns<MetadataUpdateRequest_MdeTagsUpdateRequest> = {
|
||||
encode(message: MetadataUpdateRequest_MdeTagsUpdateRequest, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
|
||||
for (const v of message.newTags) {
|
||||
writer.uint32(10).string(v!);
|
||||
}
|
||||
return writer;
|
||||
},
|
||||
|
||||
decode(input: BinaryReader | Uint8Array, length?: number): MetadataUpdateRequest_MdeTagsUpdateRequest {
|
||||
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
|
||||
const end = length === undefined ? reader.len : reader.pos + length;
|
||||
const message = createBaseMetadataUpdateRequest_MdeTagsUpdateRequest();
|
||||
while (reader.pos < end) {
|
||||
const tag = reader.uint32();
|
||||
switch (tag >>> 3) {
|
||||
case 1: {
|
||||
if (tag !== 10) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.newTags.push(reader.string());
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if ((tag & 7) === 4 || tag === 0) {
|
||||
break;
|
||||
}
|
||||
reader.skip(tag & 7);
|
||||
}
|
||||
return message;
|
||||
},
|
||||
};
|
||||
|
||||
function createBaseMetadataUpdateRequest_MdeCategoryUpdateRequest(): MetadataUpdateRequest_MdeCategoryUpdateRequest {
|
||||
return { newCategoryId: undefined };
|
||||
}
|
||||
|
||||
export const MetadataUpdateRequest_MdeCategoryUpdateRequest: MessageFns<
|
||||
MetadataUpdateRequest_MdeCategoryUpdateRequest
|
||||
> = {
|
||||
encode(
|
||||
message: MetadataUpdateRequest_MdeCategoryUpdateRequest,
|
||||
writer: BinaryWriter = new BinaryWriter(),
|
||||
): BinaryWriter {
|
||||
if (message.newCategoryId !== undefined) {
|
||||
writer.uint32(8).int32(message.newCategoryId);
|
||||
}
|
||||
return writer;
|
||||
},
|
||||
|
||||
decode(input: BinaryReader | Uint8Array, length?: number): MetadataUpdateRequest_MdeCategoryUpdateRequest {
|
||||
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
|
||||
const end = length === undefined ? reader.len : reader.pos + length;
|
||||
const message = createBaseMetadataUpdateRequest_MdeCategoryUpdateRequest();
|
||||
while (reader.pos < end) {
|
||||
const tag = reader.uint32();
|
||||
switch (tag >>> 3) {
|
||||
case 1: {
|
||||
if (tag !== 8) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.newCategoryId = reader.int32();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if ((tag & 7) === 4 || tag === 0) {
|
||||
break;
|
||||
}
|
||||
reader.skip(tag & 7);
|
||||
}
|
||||
return message;
|
||||
},
|
||||
};
|
||||
|
||||
function createBaseMetadataUpdateRequest_MdeLicenseUpdateRequest(): MetadataUpdateRequest_MdeLicenseUpdateRequest {
|
||||
return { newLicenseId: undefined };
|
||||
}
|
||||
|
||||
export const MetadataUpdateRequest_MdeLicenseUpdateRequest: MessageFns<MetadataUpdateRequest_MdeLicenseUpdateRequest> =
|
||||
{
|
||||
encode(
|
||||
message: MetadataUpdateRequest_MdeLicenseUpdateRequest,
|
||||
writer: BinaryWriter = new BinaryWriter(),
|
||||
): BinaryWriter {
|
||||
if (message.newLicenseId !== undefined) {
|
||||
writer.uint32(10).string(message.newLicenseId);
|
||||
}
|
||||
return writer;
|
||||
},
|
||||
|
||||
decode(input: BinaryReader | Uint8Array, length?: number): MetadataUpdateRequest_MdeLicenseUpdateRequest {
|
||||
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
|
||||
const end = length === undefined ? reader.len : reader.pos + length;
|
||||
const message = createBaseMetadataUpdateRequest_MdeLicenseUpdateRequest();
|
||||
while (reader.pos < end) {
|
||||
const tag = reader.uint32();
|
||||
switch (tag >>> 3) {
|
||||
case 1: {
|
||||
if (tag !== 10) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.newLicenseId = reader.string();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if ((tag & 7) === 4 || tag === 0) {
|
||||
break;
|
||||
}
|
||||
reader.skip(tag & 7);
|
||||
}
|
||||
return message;
|
||||
},
|
||||
};
|
||||
|
||||
function createBaseMetadataUpdateRequest_MdeMadeForKidsUpdateRequestParams(): MetadataUpdateRequest_MdeMadeForKidsUpdateRequestParams {
|
||||
return { operation: undefined, newMfk: undefined };
|
||||
}
|
||||
|
||||
export const MetadataUpdateRequest_MdeMadeForKidsUpdateRequestParams: MessageFns<
|
||||
MetadataUpdateRequest_MdeMadeForKidsUpdateRequestParams
|
||||
> = {
|
||||
encode(
|
||||
message: MetadataUpdateRequest_MdeMadeForKidsUpdateRequestParams,
|
||||
writer: BinaryWriter = new BinaryWriter(),
|
||||
): BinaryWriter {
|
||||
if (message.operation !== undefined) {
|
||||
writer.uint32(8).int32(message.operation);
|
||||
}
|
||||
if (message.newMfk !== undefined) {
|
||||
writer.uint32(16).int32(message.newMfk);
|
||||
}
|
||||
return writer;
|
||||
},
|
||||
|
||||
decode(input: BinaryReader | Uint8Array, length?: number): MetadataUpdateRequest_MdeMadeForKidsUpdateRequestParams {
|
||||
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
|
||||
const end = length === undefined ? reader.len : reader.pos + length;
|
||||
const message = createBaseMetadataUpdateRequest_MdeMadeForKidsUpdateRequestParams();
|
||||
while (reader.pos < end) {
|
||||
const tag = reader.uint32();
|
||||
switch (tag >>> 3) {
|
||||
case 1: {
|
||||
if (tag !== 8) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.operation = reader.int32();
|
||||
continue;
|
||||
}
|
||||
case 2: {
|
||||
if (tag !== 16) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.newMfk = reader.int32();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if ((tag & 7) === 4 || tag === 0) {
|
||||
break;
|
||||
}
|
||||
reader.skip(tag & 7);
|
||||
}
|
||||
return message;
|
||||
},
|
||||
};
|
||||
|
||||
function createBaseMetadataUpdateRequest_MdeRacyRequestParams(): MetadataUpdateRequest_MdeRacyRequestParams {
|
||||
return { operation: undefined, newRacy: undefined };
|
||||
}
|
||||
|
||||
export const MetadataUpdateRequest_MdeRacyRequestParams: MessageFns<MetadataUpdateRequest_MdeRacyRequestParams> = {
|
||||
encode(message: MetadataUpdateRequest_MdeRacyRequestParams, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
|
||||
if (message.operation !== undefined) {
|
||||
writer.uint32(8).int32(message.operation);
|
||||
}
|
||||
if (message.newRacy !== undefined) {
|
||||
writer.uint32(16).int32(message.newRacy);
|
||||
}
|
||||
return writer;
|
||||
},
|
||||
|
||||
decode(input: BinaryReader | Uint8Array, length?: number): MetadataUpdateRequest_MdeRacyRequestParams {
|
||||
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
|
||||
const end = length === undefined ? reader.len : reader.pos + length;
|
||||
const message = createBaseMetadataUpdateRequest_MdeRacyRequestParams();
|
||||
while (reader.pos < end) {
|
||||
const tag = reader.uint32();
|
||||
switch (tag >>> 3) {
|
||||
case 1: {
|
||||
if (tag !== 8) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.operation = reader.int32();
|
||||
continue;
|
||||
}
|
||||
case 2: {
|
||||
if (tag !== 16) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.newRacy = reader.int32();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if ((tag & 7) === 4 || tag === 0) {
|
||||
break;
|
||||
}
|
||||
reader.skip(tag & 7);
|
||||
}
|
||||
return message;
|
||||
},
|
||||
};
|
||||
|
||||
function createBaseMetadataUpdateRequest_MdeAgeRestrictionUpdateRequest(): MetadataUpdateRequest_MdeAgeRestrictionUpdateRequest {
|
||||
return { newIsAgeRestricted: undefined };
|
||||
}
|
||||
|
||||
export const MetadataUpdateRequest_MdeAgeRestrictionUpdateRequest: MessageFns<
|
||||
MetadataUpdateRequest_MdeAgeRestrictionUpdateRequest
|
||||
> = {
|
||||
encode(
|
||||
message: MetadataUpdateRequest_MdeAgeRestrictionUpdateRequest,
|
||||
writer: BinaryWriter = new BinaryWriter(),
|
||||
): BinaryWriter {
|
||||
if (message.newIsAgeRestricted !== undefined) {
|
||||
writer.uint32(8).bool(message.newIsAgeRestricted);
|
||||
}
|
||||
return writer;
|
||||
},
|
||||
|
||||
decode(input: BinaryReader | Uint8Array, length?: number): MetadataUpdateRequest_MdeAgeRestrictionUpdateRequest {
|
||||
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
|
||||
const end = length === undefined ? reader.len : reader.pos + length;
|
||||
const message = createBaseMetadataUpdateRequest_MdeAgeRestrictionUpdateRequest();
|
||||
while (reader.pos < end) {
|
||||
const tag = reader.uint32();
|
||||
switch (tag >>> 3) {
|
||||
case 1: {
|
||||
if (tag !== 8) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.newIsAgeRestricted = reader.bool();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if ((tag & 7) === 4 || tag === 0) {
|
||||
break;
|
||||
}
|
||||
reader.skip(tag & 7);
|
||||
}
|
||||
return message;
|
||||
},
|
||||
};
|
||||
|
||||
function createBaseMetadataUpdateRequest_MdeVideoStillRequestParams(): MetadataUpdateRequest_MdeVideoStillRequestParams {
|
||||
return { operation: undefined, newStillId: undefined, image: undefined, testImage: undefined, experimentImage: [] };
|
||||
}
|
||||
|
||||
export const MetadataUpdateRequest_MdeVideoStillRequestParams: MessageFns<
|
||||
MetadataUpdateRequest_MdeVideoStillRequestParams
|
||||
> = {
|
||||
encode(
|
||||
message: MetadataUpdateRequest_MdeVideoStillRequestParams,
|
||||
writer: BinaryWriter = new BinaryWriter(),
|
||||
): BinaryWriter {
|
||||
if (message.operation !== undefined) {
|
||||
writer.uint32(8).int32(message.operation);
|
||||
}
|
||||
if (message.newStillId !== undefined) {
|
||||
writer.uint32(16).int32(message.newStillId);
|
||||
}
|
||||
if (message.image !== undefined) {
|
||||
MetadataUpdateRequest_MdeVideoStillRequestParams_CustomThumbnailImage.encode(
|
||||
message.image,
|
||||
writer.uint32(26).fork(),
|
||||
).join();
|
||||
}
|
||||
if (message.testImage !== undefined) {
|
||||
MetadataUpdateRequest_MdeVideoStillRequestParams_CustomThumbnailImage.encode(
|
||||
message.testImage,
|
||||
writer.uint32(34).fork(),
|
||||
).join();
|
||||
}
|
||||
for (const v of message.experimentImage) {
|
||||
MetadataUpdateRequest_MdeVideoStillRequestParams_ThumbnailExperimentImageData.encode(v!, writer.uint32(50).fork())
|
||||
.join();
|
||||
}
|
||||
return writer;
|
||||
},
|
||||
|
||||
decode(input: BinaryReader | Uint8Array, length?: number): MetadataUpdateRequest_MdeVideoStillRequestParams {
|
||||
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
|
||||
const end = length === undefined ? reader.len : reader.pos + length;
|
||||
const message = createBaseMetadataUpdateRequest_MdeVideoStillRequestParams();
|
||||
while (reader.pos < end) {
|
||||
const tag = reader.uint32();
|
||||
switch (tag >>> 3) {
|
||||
case 1: {
|
||||
if (tag !== 8) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.operation = reader.int32();
|
||||
continue;
|
||||
}
|
||||
case 2: {
|
||||
if (tag !== 16) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.newStillId = reader.int32();
|
||||
continue;
|
||||
}
|
||||
case 3: {
|
||||
if (tag !== 26) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.image = MetadataUpdateRequest_MdeVideoStillRequestParams_CustomThumbnailImage.decode(
|
||||
reader,
|
||||
reader.uint32(),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
case 4: {
|
||||
if (tag !== 34) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.testImage = MetadataUpdateRequest_MdeVideoStillRequestParams_CustomThumbnailImage.decode(
|
||||
reader,
|
||||
reader.uint32(),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
case 6: {
|
||||
if (tag !== 50) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.experimentImage.push(
|
||||
MetadataUpdateRequest_MdeVideoStillRequestParams_ThumbnailExperimentImageData.decode(
|
||||
reader,
|
||||
reader.uint32(),
|
||||
),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if ((tag & 7) === 4 || tag === 0) {
|
||||
break;
|
||||
}
|
||||
reader.skip(tag & 7);
|
||||
}
|
||||
return message;
|
||||
},
|
||||
};
|
||||
|
||||
function createBaseMetadataUpdateRequest_MdeVideoStillRequestParams_ThumbnailExperimentImageData(): MetadataUpdateRequest_MdeVideoStillRequestParams_ThumbnailExperimentImageData {
|
||||
return { image: undefined };
|
||||
}
|
||||
|
||||
export const MetadataUpdateRequest_MdeVideoStillRequestParams_ThumbnailExperimentImageData: MessageFns<
|
||||
MetadataUpdateRequest_MdeVideoStillRequestParams_ThumbnailExperimentImageData
|
||||
> = {
|
||||
encode(
|
||||
message: MetadataUpdateRequest_MdeVideoStillRequestParams_ThumbnailExperimentImageData,
|
||||
writer: BinaryWriter = new BinaryWriter(),
|
||||
): BinaryWriter {
|
||||
if (message.image !== undefined) {
|
||||
MetadataUpdateRequest_MdeVideoStillRequestParams_CustomThumbnailImage.encode(
|
||||
message.image,
|
||||
writer.uint32(10).fork(),
|
||||
).join();
|
||||
}
|
||||
return writer;
|
||||
},
|
||||
|
||||
decode(
|
||||
input: BinaryReader | Uint8Array,
|
||||
length?: number,
|
||||
): MetadataUpdateRequest_MdeVideoStillRequestParams_ThumbnailExperimentImageData {
|
||||
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
|
||||
const end = length === undefined ? reader.len : reader.pos + length;
|
||||
const message = createBaseMetadataUpdateRequest_MdeVideoStillRequestParams_ThumbnailExperimentImageData();
|
||||
while (reader.pos < end) {
|
||||
const tag = reader.uint32();
|
||||
switch (tag >>> 3) {
|
||||
case 1: {
|
||||
if (tag !== 10) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.image = MetadataUpdateRequest_MdeVideoStillRequestParams_CustomThumbnailImage.decode(
|
||||
reader,
|
||||
reader.uint32(),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if ((tag & 7) === 4 || tag === 0) {
|
||||
break;
|
||||
}
|
||||
reader.skip(tag & 7);
|
||||
}
|
||||
return message;
|
||||
},
|
||||
};
|
||||
|
||||
function createBaseMetadataUpdateRequest_MdeVideoStillRequestParams_CustomThumbnailImage(): MetadataUpdateRequest_MdeVideoStillRequestParams_CustomThumbnailImage {
|
||||
return { rawBytes: undefined, dataUri: undefined, frameTimestampUs: undefined, isVertical: undefined };
|
||||
}
|
||||
|
||||
export const MetadataUpdateRequest_MdeVideoStillRequestParams_CustomThumbnailImage: MessageFns<
|
||||
MetadataUpdateRequest_MdeVideoStillRequestParams_CustomThumbnailImage
|
||||
> = {
|
||||
encode(
|
||||
message: MetadataUpdateRequest_MdeVideoStillRequestParams_CustomThumbnailImage,
|
||||
writer: BinaryWriter = new BinaryWriter(),
|
||||
): BinaryWriter {
|
||||
if (message.rawBytes !== undefined) {
|
||||
writer.uint32(10).bytes(message.rawBytes);
|
||||
}
|
||||
if (message.dataUri !== undefined) {
|
||||
writer.uint32(18).string(message.dataUri);
|
||||
}
|
||||
if (message.frameTimestampUs !== undefined) {
|
||||
writer.uint32(32).int64(message.frameTimestampUs);
|
||||
}
|
||||
if (message.isVertical !== undefined) {
|
||||
writer.uint32(40).bool(message.isVertical);
|
||||
}
|
||||
return writer;
|
||||
},
|
||||
|
||||
decode(
|
||||
input: BinaryReader | Uint8Array,
|
||||
length?: number,
|
||||
): MetadataUpdateRequest_MdeVideoStillRequestParams_CustomThumbnailImage {
|
||||
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
|
||||
const end = length === undefined ? reader.len : reader.pos + length;
|
||||
const message = createBaseMetadataUpdateRequest_MdeVideoStillRequestParams_CustomThumbnailImage();
|
||||
while (reader.pos < end) {
|
||||
const tag = reader.uint32();
|
||||
switch (tag >>> 3) {
|
||||
case 1: {
|
||||
if (tag !== 10) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.rawBytes = reader.bytes();
|
||||
continue;
|
||||
}
|
||||
case 2: {
|
||||
if (tag !== 18) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.dataUri = reader.string();
|
||||
continue;
|
||||
}
|
||||
case 4: {
|
||||
if (tag !== 32) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.frameTimestampUs = longToNumber(reader.int64());
|
||||
continue;
|
||||
}
|
||||
case 5: {
|
||||
if (tag !== 40) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.isVertical = reader.bool();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if ((tag & 7) === 4 || tag === 0) {
|
||||
break;
|
||||
}
|
||||
reader.skip(tag & 7);
|
||||
}
|
||||
return message;
|
||||
},
|
||||
};
|
||||
|
||||
function longToNumber(int64: { toString(): string }): number {
|
||||
const num = globalThis.Number(int64.toString());
|
||||
if (num > globalThis.Number.MAX_SAFE_INTEGER) {
|
||||
throw new globalThis.Error("Value is larger than Number.MAX_SAFE_INTEGER");
|
||||
}
|
||||
if (num < globalThis.Number.MIN_SAFE_INTEGER) {
|
||||
throw new globalThis.Error("Value is smaller than Number.MIN_SAFE_INTEGER");
|
||||
}
|
||||
return num;
|
||||
}
|
||||
|
||||
export interface MessageFns<T> {
|
||||
encode(message: T, writer?: BinaryWriter): BinaryWriter;
|
||||
decode(input: BinaryReader | Uint8Array, length?: number): T;
|
||||
}
|
||||
@@ -0,0 +1,521 @@
|
||||
// Code generated by protoc-gen-ts_proto. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-ts_proto v2.7.7
|
||||
// protoc v6.33.5
|
||||
// source: youtube/api/pfiinnertube/playback_context.proto
|
||||
|
||||
/* eslint-disable */
|
||||
import { BinaryReader, BinaryWriter } from "https://esm.sh/@bufbuild/protobuf@2.0.0/wire";
|
||||
|
||||
export const protobufPackage = "youtube.api.pfiinnertube";
|
||||
|
||||
export interface PlaybackContext {
|
||||
contentPlaybackContext?: PlaybackContext_ContentPlaybackContext | undefined;
|
||||
}
|
||||
|
||||
export interface PlaybackContext_ContentPlaybackContext {
|
||||
deviceSignals?: string | undefined;
|
||||
revShareClientId?: string | undefined;
|
||||
timeSinceLastAdSeconds?: number | undefined;
|
||||
lactMilliseconds?: number | undefined;
|
||||
autoplaysSinceLastAd?: number | undefined;
|
||||
vis?: number | undefined;
|
||||
fling?: boolean | undefined;
|
||||
splay?: boolean | undefined;
|
||||
autoplay?: boolean | undefined;
|
||||
timeOfLastInstreamPrerollAd?: number | undefined;
|
||||
currentUrl?: string | undefined;
|
||||
referer?: string | undefined;
|
||||
loadAnnotationsByDemand?: boolean | undefined;
|
||||
autoCaptionsDefaultOn?:
|
||||
| boolean
|
||||
| undefined;
|
||||
/** optional ForceAdParameters force_ad_parameters = 25; */
|
||||
slicedBread?: boolean | undefined;
|
||||
autonav?: boolean | undefined;
|
||||
trailer?:
|
||||
| boolean
|
||||
| undefined;
|
||||
/**
|
||||
* optional MdxPlaybackContext mdx_context = 31;
|
||||
* optional LivePlaybackContext live_context = 32;
|
||||
*/
|
||||
playerWidthPixels?: number | undefined;
|
||||
playerHeightPixels?:
|
||||
| number
|
||||
| undefined;
|
||||
/** optional Html5Preference html5_preference = 36; */
|
||||
snd?: number | undefined;
|
||||
vnd?:
|
||||
| number
|
||||
| undefined;
|
||||
/** optional UnpluggedContentPlaybackContext unplugged_context = 40; */
|
||||
uao?: number | undefined;
|
||||
mutedAutoplay?:
|
||||
| boolean
|
||||
| undefined;
|
||||
/** optional AutonavSettingsState autonav_state = 45; */
|
||||
enablePrivacyFilter?: boolean | undefined;
|
||||
isLivingRoomDeeplink?: boolean | undefined;
|
||||
signatureTimestamp?:
|
||||
| number
|
||||
| undefined;
|
||||
/** optional TrailerPlaybackContext trailer_context = 49; */
|
||||
isInlinePlaybackNoAd?: boolean | undefined;
|
||||
isInlineUnmutedPlayback?:
|
||||
| boolean
|
||||
| undefined;
|
||||
/**
|
||||
* optional CustomTabContext custom_tab_context = 52;
|
||||
* optional VideoPlaybackPosition player_playback_position = 54;
|
||||
*/
|
||||
playPackageVersion?:
|
||||
| number
|
||||
| undefined;
|
||||
/**
|
||||
* optional CoWatchPlaybackContext co_watch_context = 56;
|
||||
* optional WatchAmbientModePlaybackContext watch_ambient_mode_context = 57;
|
||||
* optional CompositeVideoPlaybackContext composite_video_context = 58;
|
||||
*/
|
||||
isSequenceEntry?: boolean | undefined;
|
||||
}
|
||||
|
||||
function createBasePlaybackContext(): PlaybackContext {
|
||||
return { contentPlaybackContext: undefined };
|
||||
}
|
||||
|
||||
export const PlaybackContext: MessageFns<PlaybackContext> = {
|
||||
encode(message: PlaybackContext, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
|
||||
if (message.contentPlaybackContext !== undefined) {
|
||||
PlaybackContext_ContentPlaybackContext.encode(message.contentPlaybackContext, writer.uint32(10).fork()).join();
|
||||
}
|
||||
return writer;
|
||||
},
|
||||
|
||||
decode(input: BinaryReader | Uint8Array, length?: number): PlaybackContext {
|
||||
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
|
||||
const end = length === undefined ? reader.len : reader.pos + length;
|
||||
const message = createBasePlaybackContext();
|
||||
while (reader.pos < end) {
|
||||
const tag = reader.uint32();
|
||||
switch (tag >>> 3) {
|
||||
case 1: {
|
||||
if (tag !== 10) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.contentPlaybackContext = PlaybackContext_ContentPlaybackContext.decode(reader, reader.uint32());
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if ((tag & 7) === 4 || tag === 0) {
|
||||
break;
|
||||
}
|
||||
reader.skip(tag & 7);
|
||||
}
|
||||
return message;
|
||||
},
|
||||
};
|
||||
|
||||
function createBasePlaybackContext_ContentPlaybackContext(): PlaybackContext_ContentPlaybackContext {
|
||||
return {
|
||||
deviceSignals: undefined,
|
||||
revShareClientId: undefined,
|
||||
timeSinceLastAdSeconds: undefined,
|
||||
lactMilliseconds: undefined,
|
||||
autoplaysSinceLastAd: undefined,
|
||||
vis: undefined,
|
||||
fling: undefined,
|
||||
splay: undefined,
|
||||
autoplay: undefined,
|
||||
timeOfLastInstreamPrerollAd: undefined,
|
||||
currentUrl: undefined,
|
||||
referer: undefined,
|
||||
loadAnnotationsByDemand: undefined,
|
||||
autoCaptionsDefaultOn: undefined,
|
||||
slicedBread: undefined,
|
||||
autonav: undefined,
|
||||
trailer: undefined,
|
||||
playerWidthPixels: undefined,
|
||||
playerHeightPixels: undefined,
|
||||
snd: undefined,
|
||||
vnd: undefined,
|
||||
uao: undefined,
|
||||
mutedAutoplay: undefined,
|
||||
enablePrivacyFilter: undefined,
|
||||
isLivingRoomDeeplink: undefined,
|
||||
signatureTimestamp: undefined,
|
||||
isInlinePlaybackNoAd: undefined,
|
||||
isInlineUnmutedPlayback: undefined,
|
||||
playPackageVersion: undefined,
|
||||
isSequenceEntry: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
export const PlaybackContext_ContentPlaybackContext: MessageFns<PlaybackContext_ContentPlaybackContext> = {
|
||||
encode(message: PlaybackContext_ContentPlaybackContext, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
|
||||
if (message.deviceSignals !== undefined) {
|
||||
writer.uint32(10).string(message.deviceSignals);
|
||||
}
|
||||
if (message.revShareClientId !== undefined) {
|
||||
writer.uint32(26).string(message.revShareClientId);
|
||||
}
|
||||
if (message.timeSinceLastAdSeconds !== undefined) {
|
||||
writer.uint32(32).uint32(message.timeSinceLastAdSeconds);
|
||||
}
|
||||
if (message.lactMilliseconds !== undefined) {
|
||||
writer.uint32(40).int64(message.lactMilliseconds);
|
||||
}
|
||||
if (message.autoplaysSinceLastAd !== undefined) {
|
||||
writer.uint32(48).uint32(message.autoplaysSinceLastAd);
|
||||
}
|
||||
if (message.vis !== undefined) {
|
||||
writer.uint32(64).uint32(message.vis);
|
||||
}
|
||||
if (message.fling !== undefined) {
|
||||
writer.uint32(72).bool(message.fling);
|
||||
}
|
||||
if (message.splay !== undefined) {
|
||||
writer.uint32(80).bool(message.splay);
|
||||
}
|
||||
if (message.autoplay !== undefined) {
|
||||
writer.uint32(88).bool(message.autoplay);
|
||||
}
|
||||
if (message.timeOfLastInstreamPrerollAd !== undefined) {
|
||||
writer.uint32(104).uint64(message.timeOfLastInstreamPrerollAd);
|
||||
}
|
||||
if (message.currentUrl !== undefined) {
|
||||
writer.uint32(122).string(message.currentUrl);
|
||||
}
|
||||
if (message.referer !== undefined) {
|
||||
writer.uint32(130).string(message.referer);
|
||||
}
|
||||
if (message.loadAnnotationsByDemand !== undefined) {
|
||||
writer.uint32(184).bool(message.loadAnnotationsByDemand);
|
||||
}
|
||||
if (message.autoCaptionsDefaultOn !== undefined) {
|
||||
writer.uint32(192).bool(message.autoCaptionsDefaultOn);
|
||||
}
|
||||
if (message.slicedBread !== undefined) {
|
||||
writer.uint32(216).bool(message.slicedBread);
|
||||
}
|
||||
if (message.autonav !== undefined) {
|
||||
writer.uint32(232).bool(message.autonav);
|
||||
}
|
||||
if (message.trailer !== undefined) {
|
||||
writer.uint32(240).bool(message.trailer);
|
||||
}
|
||||
if (message.playerWidthPixels !== undefined) {
|
||||
writer.uint32(272).int32(message.playerWidthPixels);
|
||||
}
|
||||
if (message.playerHeightPixels !== undefined) {
|
||||
writer.uint32(280).int32(message.playerHeightPixels);
|
||||
}
|
||||
if (message.snd !== undefined) {
|
||||
writer.uint32(296).int32(message.snd);
|
||||
}
|
||||
if (message.vnd !== undefined) {
|
||||
writer.uint32(304).int32(message.vnd);
|
||||
}
|
||||
if (message.uao !== undefined) {
|
||||
writer.uint32(328).int32(message.uao);
|
||||
}
|
||||
if (message.mutedAutoplay !== undefined) {
|
||||
writer.uint32(352).bool(message.mutedAutoplay);
|
||||
}
|
||||
if (message.enablePrivacyFilter !== undefined) {
|
||||
writer.uint32(368).bool(message.enablePrivacyFilter);
|
||||
}
|
||||
if (message.isLivingRoomDeeplink !== undefined) {
|
||||
writer.uint32(376).bool(message.isLivingRoomDeeplink);
|
||||
}
|
||||
if (message.signatureTimestamp !== undefined) {
|
||||
writer.uint32(384).uint32(message.signatureTimestamp);
|
||||
}
|
||||
if (message.isInlinePlaybackNoAd !== undefined) {
|
||||
writer.uint32(400).bool(message.isInlinePlaybackNoAd);
|
||||
}
|
||||
if (message.isInlineUnmutedPlayback !== undefined) {
|
||||
writer.uint32(408).bool(message.isInlineUnmutedPlayback);
|
||||
}
|
||||
if (message.playPackageVersion !== undefined) {
|
||||
writer.uint32(440).int64(message.playPackageVersion);
|
||||
}
|
||||
if (message.isSequenceEntry !== undefined) {
|
||||
writer.uint32(480).bool(message.isSequenceEntry);
|
||||
}
|
||||
return writer;
|
||||
},
|
||||
|
||||
decode(input: BinaryReader | Uint8Array, length?: number): PlaybackContext_ContentPlaybackContext {
|
||||
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
|
||||
const end = length === undefined ? reader.len : reader.pos + length;
|
||||
const message = createBasePlaybackContext_ContentPlaybackContext();
|
||||
while (reader.pos < end) {
|
||||
const tag = reader.uint32();
|
||||
switch (tag >>> 3) {
|
||||
case 1: {
|
||||
if (tag !== 10) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.deviceSignals = reader.string();
|
||||
continue;
|
||||
}
|
||||
case 3: {
|
||||
if (tag !== 26) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.revShareClientId = reader.string();
|
||||
continue;
|
||||
}
|
||||
case 4: {
|
||||
if (tag !== 32) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.timeSinceLastAdSeconds = reader.uint32();
|
||||
continue;
|
||||
}
|
||||
case 5: {
|
||||
if (tag !== 40) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.lactMilliseconds = longToNumber(reader.int64());
|
||||
continue;
|
||||
}
|
||||
case 6: {
|
||||
if (tag !== 48) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.autoplaysSinceLastAd = reader.uint32();
|
||||
continue;
|
||||
}
|
||||
case 8: {
|
||||
if (tag !== 64) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.vis = reader.uint32();
|
||||
continue;
|
||||
}
|
||||
case 9: {
|
||||
if (tag !== 72) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.fling = reader.bool();
|
||||
continue;
|
||||
}
|
||||
case 10: {
|
||||
if (tag !== 80) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.splay = reader.bool();
|
||||
continue;
|
||||
}
|
||||
case 11: {
|
||||
if (tag !== 88) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.autoplay = reader.bool();
|
||||
continue;
|
||||
}
|
||||
case 13: {
|
||||
if (tag !== 104) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.timeOfLastInstreamPrerollAd = longToNumber(reader.uint64());
|
||||
continue;
|
||||
}
|
||||
case 15: {
|
||||
if (tag !== 122) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.currentUrl = reader.string();
|
||||
continue;
|
||||
}
|
||||
case 16: {
|
||||
if (tag !== 130) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.referer = reader.string();
|
||||
continue;
|
||||
}
|
||||
case 23: {
|
||||
if (tag !== 184) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.loadAnnotationsByDemand = reader.bool();
|
||||
continue;
|
||||
}
|
||||
case 24: {
|
||||
if (tag !== 192) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.autoCaptionsDefaultOn = reader.bool();
|
||||
continue;
|
||||
}
|
||||
case 27: {
|
||||
if (tag !== 216) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.slicedBread = reader.bool();
|
||||
continue;
|
||||
}
|
||||
case 29: {
|
||||
if (tag !== 232) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.autonav = reader.bool();
|
||||
continue;
|
||||
}
|
||||
case 30: {
|
||||
if (tag !== 240) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.trailer = reader.bool();
|
||||
continue;
|
||||
}
|
||||
case 34: {
|
||||
if (tag !== 272) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.playerWidthPixels = reader.int32();
|
||||
continue;
|
||||
}
|
||||
case 35: {
|
||||
if (tag !== 280) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.playerHeightPixels = reader.int32();
|
||||
continue;
|
||||
}
|
||||
case 37: {
|
||||
if (tag !== 296) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.snd = reader.int32();
|
||||
continue;
|
||||
}
|
||||
case 38: {
|
||||
if (tag !== 304) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.vnd = reader.int32();
|
||||
continue;
|
||||
}
|
||||
case 41: {
|
||||
if (tag !== 328) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.uao = reader.int32();
|
||||
continue;
|
||||
}
|
||||
case 44: {
|
||||
if (tag !== 352) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.mutedAutoplay = reader.bool();
|
||||
continue;
|
||||
}
|
||||
case 46: {
|
||||
if (tag !== 368) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.enablePrivacyFilter = reader.bool();
|
||||
continue;
|
||||
}
|
||||
case 47: {
|
||||
if (tag !== 376) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.isLivingRoomDeeplink = reader.bool();
|
||||
continue;
|
||||
}
|
||||
case 48: {
|
||||
if (tag !== 384) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.signatureTimestamp = reader.uint32();
|
||||
continue;
|
||||
}
|
||||
case 50: {
|
||||
if (tag !== 400) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.isInlinePlaybackNoAd = reader.bool();
|
||||
continue;
|
||||
}
|
||||
case 51: {
|
||||
if (tag !== 408) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.isInlineUnmutedPlayback = reader.bool();
|
||||
continue;
|
||||
}
|
||||
case 55: {
|
||||
if (tag !== 440) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.playPackageVersion = longToNumber(reader.int64());
|
||||
continue;
|
||||
}
|
||||
case 60: {
|
||||
if (tag !== 480) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.isSequenceEntry = reader.bool();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if ((tag & 7) === 4 || tag === 0) {
|
||||
break;
|
||||
}
|
||||
reader.skip(tag & 7);
|
||||
}
|
||||
return message;
|
||||
},
|
||||
};
|
||||
|
||||
function longToNumber(int64: { toString(): string }): number {
|
||||
const num = globalThis.Number(int64.toString());
|
||||
if (num > globalThis.Number.MAX_SAFE_INTEGER) {
|
||||
throw new globalThis.Error("Value is larger than Number.MAX_SAFE_INTEGER");
|
||||
}
|
||||
if (num < globalThis.Number.MIN_SAFE_INTEGER) {
|
||||
throw new globalThis.Error("Value is smaller than Number.MIN_SAFE_INTEGER");
|
||||
}
|
||||
return num;
|
||||
}
|
||||
|
||||
export interface MessageFns<T> {
|
||||
encode(message: T, writer?: BinaryWriter): BinaryWriter;
|
||||
decode(input: BinaryReader | Uint8Array, length?: number): T;
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
// Code generated by protoc-gen-ts_proto. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-ts_proto v2.7.7
|
||||
// protoc v6.33.5
|
||||
// source: youtube/api/pfiinnertube/player_attestation_request_data.proto
|
||||
|
||||
/* eslint-disable */
|
||||
import { BinaryReader, BinaryWriter } from "https://esm.sh/@bufbuild/protobuf@2.0.0/wire";
|
||||
|
||||
export const protobufPackage = "youtube.api.pfiinnertube";
|
||||
|
||||
export interface PlayerAttestationRequestData {
|
||||
iosguardRequest?: PlayerAttestationRequestData_IosguardChallengeRequestData | undefined;
|
||||
omitBotguardData?: boolean | undefined;
|
||||
}
|
||||
|
||||
export interface PlayerAttestationRequestData_IosguardChallengeRequestData {
|
||||
challengeRequest?: Uint8Array | undefined;
|
||||
}
|
||||
|
||||
function createBasePlayerAttestationRequestData(): PlayerAttestationRequestData {
|
||||
return { iosguardRequest: undefined, omitBotguardData: undefined };
|
||||
}
|
||||
|
||||
export const PlayerAttestationRequestData: MessageFns<PlayerAttestationRequestData> = {
|
||||
encode(message: PlayerAttestationRequestData, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
|
||||
if (message.iosguardRequest !== undefined) {
|
||||
PlayerAttestationRequestData_IosguardChallengeRequestData.encode(
|
||||
message.iosguardRequest,
|
||||
writer.uint32(10).fork(),
|
||||
).join();
|
||||
}
|
||||
if (message.omitBotguardData !== undefined) {
|
||||
writer.uint32(16).bool(message.omitBotguardData);
|
||||
}
|
||||
return writer;
|
||||
},
|
||||
|
||||
decode(input: BinaryReader | Uint8Array, length?: number): PlayerAttestationRequestData {
|
||||
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
|
||||
const end = length === undefined ? reader.len : reader.pos + length;
|
||||
const message = createBasePlayerAttestationRequestData();
|
||||
while (reader.pos < end) {
|
||||
const tag = reader.uint32();
|
||||
switch (tag >>> 3) {
|
||||
case 1: {
|
||||
if (tag !== 10) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.iosguardRequest = PlayerAttestationRequestData_IosguardChallengeRequestData.decode(
|
||||
reader,
|
||||
reader.uint32(),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
case 2: {
|
||||
if (tag !== 16) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.omitBotguardData = reader.bool();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if ((tag & 7) === 4 || tag === 0) {
|
||||
break;
|
||||
}
|
||||
reader.skip(tag & 7);
|
||||
}
|
||||
return message;
|
||||
},
|
||||
};
|
||||
|
||||
function createBasePlayerAttestationRequestData_IosguardChallengeRequestData(): PlayerAttestationRequestData_IosguardChallengeRequestData {
|
||||
return { challengeRequest: undefined };
|
||||
}
|
||||
|
||||
export const PlayerAttestationRequestData_IosguardChallengeRequestData: MessageFns<
|
||||
PlayerAttestationRequestData_IosguardChallengeRequestData
|
||||
> = {
|
||||
encode(
|
||||
message: PlayerAttestationRequestData_IosguardChallengeRequestData,
|
||||
writer: BinaryWriter = new BinaryWriter(),
|
||||
): BinaryWriter {
|
||||
if (message.challengeRequest !== undefined) {
|
||||
writer.uint32(10).bytes(message.challengeRequest);
|
||||
}
|
||||
return writer;
|
||||
},
|
||||
|
||||
decode(input: BinaryReader | Uint8Array, length?: number): PlayerAttestationRequestData_IosguardChallengeRequestData {
|
||||
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
|
||||
const end = length === undefined ? reader.len : reader.pos + length;
|
||||
const message = createBasePlayerAttestationRequestData_IosguardChallengeRequestData();
|
||||
while (reader.pos < end) {
|
||||
const tag = reader.uint32();
|
||||
switch (tag >>> 3) {
|
||||
case 1: {
|
||||
if (tag !== 10) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.challengeRequest = reader.bytes();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if ((tag & 7) === 4 || tag === 0) {
|
||||
break;
|
||||
}
|
||||
reader.skip(tag & 7);
|
||||
}
|
||||
return message;
|
||||
},
|
||||
};
|
||||
|
||||
export interface MessageFns<T> {
|
||||
encode(message: T, writer?: BinaryWriter): BinaryWriter;
|
||||
decode(input: BinaryReader | Uint8Array, length?: number): T;
|
||||
}
|
||||
365
deno/protos/generated/youtube/api/pfiinnertube/player_request.ts
Normal file
365
deno/protos/generated/youtube/api/pfiinnertube/player_request.ts
Normal file
@@ -0,0 +1,365 @@
|
||||
// Code generated by protoc-gen-ts_proto. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-ts_proto v2.7.7
|
||||
// protoc v6.33.5
|
||||
// source: youtube/api/pfiinnertube/player_request.proto
|
||||
|
||||
/* eslint-disable */
|
||||
import { BinaryReader, BinaryWriter } from "https://esm.sh/@bufbuild/protobuf@2.0.0/wire";
|
||||
import { InnerTubeContext } from "./innertube_context.ts";
|
||||
import { PlaybackContext } from "./playback_context.ts";
|
||||
import { PlayerAttestationRequestData } from "./player_attestation_request_data.ts";
|
||||
import { PlayerRequestCaptionParams } from "./player_request_caption_params.ts";
|
||||
import { ServiceIntegrityDimensions } from "./service_integrity_dimensions.ts";
|
||||
|
||||
export const protobufPackage = "youtube.api.pfiinnertube";
|
||||
|
||||
export interface PlayerRequest {
|
||||
context?: InnerTubeContext | undefined;
|
||||
videoId?: string | undefined;
|
||||
contentCheckOk?: boolean | undefined;
|
||||
playbackContext?: PlaybackContext | undefined;
|
||||
racyCheckOk?: boolean | undefined;
|
||||
id?: string | undefined;
|
||||
t?: string | undefined;
|
||||
forOffline?: boolean | undefined;
|
||||
playlistId?: string | undefined;
|
||||
playlistIndex?: number | undefined;
|
||||
startTimeSecs?: number | undefined;
|
||||
params?: string | undefined;
|
||||
offlineSharingWrappedKey?: Uint8Array | undefined;
|
||||
attestationRequest?: PlayerAttestationRequestData | undefined;
|
||||
referringApp?: string | undefined;
|
||||
referrer?: string | undefined;
|
||||
serializedThirdPartyEmbedConfig?: string | undefined;
|
||||
proxiedByOnesie?: boolean | undefined;
|
||||
hostAppToken?: string | undefined;
|
||||
cpn?: string | undefined;
|
||||
overrideMutedAtStart?: boolean | undefined;
|
||||
captionParams?: PlayerRequestCaptionParams | undefined;
|
||||
serviceIntegrityDimensions?:
|
||||
| ServiceIntegrityDimensions
|
||||
| undefined;
|
||||
/** optional VideoQualitySetting video_quality_setting_params = 28; */
|
||||
deferredPlayerToken?: Uint8Array | undefined;
|
||||
}
|
||||
|
||||
function createBasePlayerRequest(): PlayerRequest {
|
||||
return {
|
||||
context: undefined,
|
||||
videoId: undefined,
|
||||
contentCheckOk: undefined,
|
||||
playbackContext: undefined,
|
||||
racyCheckOk: undefined,
|
||||
id: undefined,
|
||||
t: undefined,
|
||||
forOffline: undefined,
|
||||
playlistId: undefined,
|
||||
playlistIndex: undefined,
|
||||
startTimeSecs: undefined,
|
||||
params: undefined,
|
||||
offlineSharingWrappedKey: undefined,
|
||||
attestationRequest: undefined,
|
||||
referringApp: undefined,
|
||||
referrer: undefined,
|
||||
serializedThirdPartyEmbedConfig: undefined,
|
||||
proxiedByOnesie: undefined,
|
||||
hostAppToken: undefined,
|
||||
cpn: undefined,
|
||||
overrideMutedAtStart: undefined,
|
||||
captionParams: undefined,
|
||||
serviceIntegrityDimensions: undefined,
|
||||
deferredPlayerToken: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
export const PlayerRequest: MessageFns<PlayerRequest> = {
|
||||
encode(message: PlayerRequest, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
|
||||
if (message.context !== undefined) {
|
||||
InnerTubeContext.encode(message.context, writer.uint32(10).fork()).join();
|
||||
}
|
||||
if (message.videoId !== undefined) {
|
||||
writer.uint32(18).string(message.videoId);
|
||||
}
|
||||
if (message.contentCheckOk !== undefined) {
|
||||
writer.uint32(24).bool(message.contentCheckOk);
|
||||
}
|
||||
if (message.playbackContext !== undefined) {
|
||||
PlaybackContext.encode(message.playbackContext, writer.uint32(34).fork()).join();
|
||||
}
|
||||
if (message.racyCheckOk !== undefined) {
|
||||
writer.uint32(40).bool(message.racyCheckOk);
|
||||
}
|
||||
if (message.id !== undefined) {
|
||||
writer.uint32(50).string(message.id);
|
||||
}
|
||||
if (message.t !== undefined) {
|
||||
writer.uint32(58).string(message.t);
|
||||
}
|
||||
if (message.forOffline !== undefined) {
|
||||
writer.uint32(64).bool(message.forOffline);
|
||||
}
|
||||
if (message.playlistId !== undefined) {
|
||||
writer.uint32(74).string(message.playlistId);
|
||||
}
|
||||
if (message.playlistIndex !== undefined) {
|
||||
writer.uint32(80).int32(message.playlistIndex);
|
||||
}
|
||||
if (message.startTimeSecs !== undefined) {
|
||||
writer.uint32(88).uint32(message.startTimeSecs);
|
||||
}
|
||||
if (message.params !== undefined) {
|
||||
writer.uint32(98).string(message.params);
|
||||
}
|
||||
if (message.offlineSharingWrappedKey !== undefined) {
|
||||
writer.uint32(114).bytes(message.offlineSharingWrappedKey);
|
||||
}
|
||||
if (message.attestationRequest !== undefined) {
|
||||
PlayerAttestationRequestData.encode(message.attestationRequest, writer.uint32(130).fork()).join();
|
||||
}
|
||||
if (message.referringApp !== undefined) {
|
||||
writer.uint32(138).string(message.referringApp);
|
||||
}
|
||||
if (message.referrer !== undefined) {
|
||||
writer.uint32(146).string(message.referrer);
|
||||
}
|
||||
if (message.serializedThirdPartyEmbedConfig !== undefined) {
|
||||
writer.uint32(154).string(message.serializedThirdPartyEmbedConfig);
|
||||
}
|
||||
if (message.proxiedByOnesie !== undefined) {
|
||||
writer.uint32(160).bool(message.proxiedByOnesie);
|
||||
}
|
||||
if (message.hostAppToken !== undefined) {
|
||||
writer.uint32(178).string(message.hostAppToken);
|
||||
}
|
||||
if (message.cpn !== undefined) {
|
||||
writer.uint32(186).string(message.cpn);
|
||||
}
|
||||
if (message.overrideMutedAtStart !== undefined) {
|
||||
writer.uint32(200).bool(message.overrideMutedAtStart);
|
||||
}
|
||||
if (message.captionParams !== undefined) {
|
||||
PlayerRequestCaptionParams.encode(message.captionParams, writer.uint32(210).fork()).join();
|
||||
}
|
||||
if (message.serviceIntegrityDimensions !== undefined) {
|
||||
ServiceIntegrityDimensions.encode(message.serviceIntegrityDimensions, writer.uint32(218).fork()).join();
|
||||
}
|
||||
if (message.deferredPlayerToken !== undefined) {
|
||||
writer.uint32(234).bytes(message.deferredPlayerToken);
|
||||
}
|
||||
return writer;
|
||||
},
|
||||
|
||||
decode(input: BinaryReader | Uint8Array, length?: number): PlayerRequest {
|
||||
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
|
||||
const end = length === undefined ? reader.len : reader.pos + length;
|
||||
const message = createBasePlayerRequest();
|
||||
while (reader.pos < end) {
|
||||
const tag = reader.uint32();
|
||||
switch (tag >>> 3) {
|
||||
case 1: {
|
||||
if (tag !== 10) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.context = InnerTubeContext.decode(reader, reader.uint32());
|
||||
continue;
|
||||
}
|
||||
case 2: {
|
||||
if (tag !== 18) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.videoId = reader.string();
|
||||
continue;
|
||||
}
|
||||
case 3: {
|
||||
if (tag !== 24) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.contentCheckOk = reader.bool();
|
||||
continue;
|
||||
}
|
||||
case 4: {
|
||||
if (tag !== 34) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.playbackContext = PlaybackContext.decode(reader, reader.uint32());
|
||||
continue;
|
||||
}
|
||||
case 5: {
|
||||
if (tag !== 40) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.racyCheckOk = reader.bool();
|
||||
continue;
|
||||
}
|
||||
case 6: {
|
||||
if (tag !== 50) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.id = reader.string();
|
||||
continue;
|
||||
}
|
||||
case 7: {
|
||||
if (tag !== 58) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.t = reader.string();
|
||||
continue;
|
||||
}
|
||||
case 8: {
|
||||
if (tag !== 64) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.forOffline = reader.bool();
|
||||
continue;
|
||||
}
|
||||
case 9: {
|
||||
if (tag !== 74) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.playlistId = reader.string();
|
||||
continue;
|
||||
}
|
||||
case 10: {
|
||||
if (tag !== 80) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.playlistIndex = reader.int32();
|
||||
continue;
|
||||
}
|
||||
case 11: {
|
||||
if (tag !== 88) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.startTimeSecs = reader.uint32();
|
||||
continue;
|
||||
}
|
||||
case 12: {
|
||||
if (tag !== 98) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.params = reader.string();
|
||||
continue;
|
||||
}
|
||||
case 14: {
|
||||
if (tag !== 114) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.offlineSharingWrappedKey = reader.bytes();
|
||||
continue;
|
||||
}
|
||||
case 16: {
|
||||
if (tag !== 130) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.attestationRequest = PlayerAttestationRequestData.decode(reader, reader.uint32());
|
||||
continue;
|
||||
}
|
||||
case 17: {
|
||||
if (tag !== 138) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.referringApp = reader.string();
|
||||
continue;
|
||||
}
|
||||
case 18: {
|
||||
if (tag !== 146) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.referrer = reader.string();
|
||||
continue;
|
||||
}
|
||||
case 19: {
|
||||
if (tag !== 154) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.serializedThirdPartyEmbedConfig = reader.string();
|
||||
continue;
|
||||
}
|
||||
case 20: {
|
||||
if (tag !== 160) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.proxiedByOnesie = reader.bool();
|
||||
continue;
|
||||
}
|
||||
case 22: {
|
||||
if (tag !== 178) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.hostAppToken = reader.string();
|
||||
continue;
|
||||
}
|
||||
case 23: {
|
||||
if (tag !== 186) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.cpn = reader.string();
|
||||
continue;
|
||||
}
|
||||
case 25: {
|
||||
if (tag !== 200) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.overrideMutedAtStart = reader.bool();
|
||||
continue;
|
||||
}
|
||||
case 26: {
|
||||
if (tag !== 210) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.captionParams = PlayerRequestCaptionParams.decode(reader, reader.uint32());
|
||||
continue;
|
||||
}
|
||||
case 27: {
|
||||
if (tag !== 218) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.serviceIntegrityDimensions = ServiceIntegrityDimensions.decode(reader, reader.uint32());
|
||||
continue;
|
||||
}
|
||||
case 29: {
|
||||
if (tag !== 234) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.deferredPlayerToken = reader.bytes();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if ((tag & 7) === 4 || tag === 0) {
|
||||
break;
|
||||
}
|
||||
reader.skip(tag & 7);
|
||||
}
|
||||
return message;
|
||||
},
|
||||
};
|
||||
|
||||
export interface MessageFns<T> {
|
||||
encode(message: T, writer?: BinaryWriter): BinaryWriter;
|
||||
decode(input: BinaryReader | Uint8Array, length?: number): T;
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
// Code generated by protoc-gen-ts_proto. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-ts_proto v2.7.7
|
||||
// protoc v6.33.5
|
||||
// source: youtube/api/pfiinnertube/player_request_caption_params.proto
|
||||
|
||||
/* eslint-disable */
|
||||
import { BinaryReader, BinaryWriter } from "https://esm.sh/@bufbuild/protobuf@2.0.0/wire";
|
||||
|
||||
export const protobufPackage = "youtube.api.pfiinnertube";
|
||||
|
||||
export interface PlayerRequestCaptionParams {
|
||||
deviceCaptionsOn?: boolean | undefined;
|
||||
deviceCaptionsLangPref?: string | undefined;
|
||||
viewerSelectedCaptionLangs?: string | undefined;
|
||||
ccLangPref?: string | undefined;
|
||||
ccLoadPolicyOn?: boolean | undefined;
|
||||
}
|
||||
|
||||
function createBasePlayerRequestCaptionParams(): PlayerRequestCaptionParams {
|
||||
return {
|
||||
deviceCaptionsOn: undefined,
|
||||
deviceCaptionsLangPref: undefined,
|
||||
viewerSelectedCaptionLangs: undefined,
|
||||
ccLangPref: undefined,
|
||||
ccLoadPolicyOn: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
export const PlayerRequestCaptionParams: MessageFns<PlayerRequestCaptionParams> = {
|
||||
encode(message: PlayerRequestCaptionParams, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
|
||||
if (message.deviceCaptionsOn !== undefined) {
|
||||
writer.uint32(8).bool(message.deviceCaptionsOn);
|
||||
}
|
||||
if (message.deviceCaptionsLangPref !== undefined) {
|
||||
writer.uint32(18).string(message.deviceCaptionsLangPref);
|
||||
}
|
||||
if (message.viewerSelectedCaptionLangs !== undefined) {
|
||||
writer.uint32(26).string(message.viewerSelectedCaptionLangs);
|
||||
}
|
||||
if (message.ccLangPref !== undefined) {
|
||||
writer.uint32(34).string(message.ccLangPref);
|
||||
}
|
||||
if (message.ccLoadPolicyOn !== undefined) {
|
||||
writer.uint32(40).bool(message.ccLoadPolicyOn);
|
||||
}
|
||||
return writer;
|
||||
},
|
||||
|
||||
decode(input: BinaryReader | Uint8Array, length?: number): PlayerRequestCaptionParams {
|
||||
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
|
||||
const end = length === undefined ? reader.len : reader.pos + length;
|
||||
const message = createBasePlayerRequestCaptionParams();
|
||||
while (reader.pos < end) {
|
||||
const tag = reader.uint32();
|
||||
switch (tag >>> 3) {
|
||||
case 1: {
|
||||
if (tag !== 8) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.deviceCaptionsOn = reader.bool();
|
||||
continue;
|
||||
}
|
||||
case 2: {
|
||||
if (tag !== 18) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.deviceCaptionsLangPref = reader.string();
|
||||
continue;
|
||||
}
|
||||
case 3: {
|
||||
if (tag !== 26) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.viewerSelectedCaptionLangs = reader.string();
|
||||
continue;
|
||||
}
|
||||
case 4: {
|
||||
if (tag !== 34) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.ccLangPref = reader.string();
|
||||
continue;
|
||||
}
|
||||
case 5: {
|
||||
if (tag !== 40) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.ccLoadPolicyOn = reader.bool();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if ((tag & 7) === 4 || tag === 0) {
|
||||
break;
|
||||
}
|
||||
reader.skip(tag & 7);
|
||||
}
|
||||
return message;
|
||||
},
|
||||
};
|
||||
|
||||
export interface MessageFns<T> {
|
||||
encode(message: T, writer?: BinaryWriter): BinaryWriter;
|
||||
decode(input: BinaryReader | Uint8Array, length?: number): T;
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
// Code generated by protoc-gen-ts_proto. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-ts_proto v2.7.7
|
||||
// protoc v6.33.5
|
||||
// source: youtube/api/pfiinnertube/reel_item_watch_request.proto
|
||||
|
||||
/* eslint-disable */
|
||||
import { BinaryReader, BinaryWriter } from "https://esm.sh/@bufbuild/protobuf@2.0.0/wire";
|
||||
import { InnerTubeContext } from "./innertube_context.ts";
|
||||
import { PlayerRequest } from "./player_request.ts";
|
||||
|
||||
export const protobufPackage = "youtube.api.pfiinnertube";
|
||||
|
||||
export interface ReelItemWatchRequest {
|
||||
context?: InnerTubeContext | undefined;
|
||||
playerRequest?: PlayerRequest | undefined;
|
||||
params?: string | undefined;
|
||||
disablePlayerResponse?: boolean | undefined;
|
||||
}
|
||||
|
||||
function createBaseReelItemWatchRequest(): ReelItemWatchRequest {
|
||||
return { context: undefined, playerRequest: undefined, params: undefined, disablePlayerResponse: undefined };
|
||||
}
|
||||
|
||||
export const ReelItemWatchRequest: MessageFns<ReelItemWatchRequest> = {
|
||||
encode(message: ReelItemWatchRequest, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
|
||||
if (message.context !== undefined) {
|
||||
InnerTubeContext.encode(message.context, writer.uint32(10).fork()).join();
|
||||
}
|
||||
if (message.playerRequest !== undefined) {
|
||||
PlayerRequest.encode(message.playerRequest, writer.uint32(18).fork()).join();
|
||||
}
|
||||
if (message.params !== undefined) {
|
||||
writer.uint32(26).string(message.params);
|
||||
}
|
||||
if (message.disablePlayerResponse !== undefined) {
|
||||
writer.uint32(32).bool(message.disablePlayerResponse);
|
||||
}
|
||||
return writer;
|
||||
},
|
||||
|
||||
decode(input: BinaryReader | Uint8Array, length?: number): ReelItemWatchRequest {
|
||||
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
|
||||
const end = length === undefined ? reader.len : reader.pos + length;
|
||||
const message = createBaseReelItemWatchRequest();
|
||||
while (reader.pos < end) {
|
||||
const tag = reader.uint32();
|
||||
switch (tag >>> 3) {
|
||||
case 1: {
|
||||
if (tag !== 10) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.context = InnerTubeContext.decode(reader, reader.uint32());
|
||||
continue;
|
||||
}
|
||||
case 2: {
|
||||
if (tag !== 18) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.playerRequest = PlayerRequest.decode(reader, reader.uint32());
|
||||
continue;
|
||||
}
|
||||
case 3: {
|
||||
if (tag !== 26) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.params = reader.string();
|
||||
continue;
|
||||
}
|
||||
case 4: {
|
||||
if (tag !== 32) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.disablePlayerResponse = reader.bool();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if ((tag & 7) === 4 || tag === 0) {
|
||||
break;
|
||||
}
|
||||
reader.skip(tag & 7);
|
||||
}
|
||||
return message;
|
||||
},
|
||||
};
|
||||
|
||||
export interface MessageFns<T> {
|
||||
encode(message: T, writer?: BinaryWriter): BinaryWriter;
|
||||
decode(input: BinaryReader | Uint8Array, length?: number): T;
|
||||
}
|
||||
440
deno/protos/generated/youtube/api/pfiinnertube/request_info.ts
Normal file
440
deno/protos/generated/youtube/api/pfiinnertube/request_info.ts
Normal file
@@ -0,0 +1,440 @@
|
||||
// Code generated by protoc-gen-ts_proto. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-ts_proto v2.7.7
|
||||
// protoc v6.33.5
|
||||
// source: youtube/api/pfiinnertube/request_info.proto
|
||||
|
||||
/* eslint-disable */
|
||||
import { BinaryReader, BinaryWriter } from "https://esm.sh/@bufbuild/protobuf@2.0.0/wire";
|
||||
import { KeyValuePair } from "../../../misc/common.ts";
|
||||
import { AttestationResponseData } from "./attestation_response_data.ts";
|
||||
|
||||
export const protobufPackage = "youtube.api.pfiinnertube";
|
||||
|
||||
export interface RequestInfo {
|
||||
thirdPartyDigest?: string | undefined;
|
||||
useSsl?: boolean | undefined;
|
||||
returnErrorDetail?:
|
||||
| boolean
|
||||
| undefined;
|
||||
/** "If-None-Match" HTTP header? */
|
||||
ifNoneMatch?: string | undefined;
|
||||
returnLogEntry?: boolean | undefined;
|
||||
isPrefetch?: boolean | undefined;
|
||||
internalExperimentFlags: KeyValuePair[];
|
||||
returnDebugData?: boolean | undefined;
|
||||
innertubez?: string | undefined;
|
||||
traceProto?: boolean | undefined;
|
||||
returnLogEntryJson?: boolean | undefined;
|
||||
sherlogUsername?:
|
||||
| string
|
||||
| undefined;
|
||||
/** repeated ConsistencyTokenJar consistency_token_jars = 26; */
|
||||
reauthRequestInfo?: RequestInfo_ReauthRequestInfo | undefined;
|
||||
sessionInfo?: RequestInfo_SessionInfo | undefined;
|
||||
returnLogEntryProto?:
|
||||
| boolean
|
||||
| undefined;
|
||||
/** External pre-request context as a string */
|
||||
externalPrequestContext?:
|
||||
| string
|
||||
| undefined;
|
||||
/**
|
||||
* repeated InnerTubeTokenJar innertube_token_jar = 33;
|
||||
* Would a botguard/droidguard response here allow playback?
|
||||
*/
|
||||
attestationResponseData?: AttestationResponseData | undefined;
|
||||
eats?: Uint8Array | undefined;
|
||||
requestQos?: RequestInfo_RequestQoS | undefined;
|
||||
}
|
||||
|
||||
export enum RequestInfo_Criticality {
|
||||
CRITICALITY_UNSPECIFIED = 0,
|
||||
CRITICALITY_CRITICAL = 1,
|
||||
CRITICALITY_NON_CRITICAL = 2,
|
||||
UNRECOGNIZED = -1,
|
||||
}
|
||||
|
||||
/** Quality of Service? */
|
||||
export interface RequestInfo_RequestQoS {
|
||||
criticality?: RequestInfo_Criticality | undefined;
|
||||
}
|
||||
|
||||
export interface RequestInfo_SessionInfo {
|
||||
token?: string | undefined;
|
||||
}
|
||||
|
||||
export interface RequestInfo_ReauthRequestInfo {
|
||||
encodedReauthProofToken?: string | undefined;
|
||||
}
|
||||
|
||||
function createBaseRequestInfo(): RequestInfo {
|
||||
return {
|
||||
thirdPartyDigest: undefined,
|
||||
useSsl: undefined,
|
||||
returnErrorDetail: undefined,
|
||||
ifNoneMatch: undefined,
|
||||
returnLogEntry: undefined,
|
||||
isPrefetch: undefined,
|
||||
internalExperimentFlags: [],
|
||||
returnDebugData: undefined,
|
||||
innertubez: undefined,
|
||||
traceProto: undefined,
|
||||
returnLogEntryJson: undefined,
|
||||
sherlogUsername: undefined,
|
||||
reauthRequestInfo: undefined,
|
||||
sessionInfo: undefined,
|
||||
returnLogEntryProto: undefined,
|
||||
externalPrequestContext: undefined,
|
||||
attestationResponseData: undefined,
|
||||
eats: undefined,
|
||||
requestQos: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
export const RequestInfo: MessageFns<RequestInfo> = {
|
||||
encode(message: RequestInfo, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
|
||||
if (message.thirdPartyDigest !== undefined) {
|
||||
writer.uint32(50).string(message.thirdPartyDigest);
|
||||
}
|
||||
if (message.useSsl !== undefined) {
|
||||
writer.uint32(56).bool(message.useSsl);
|
||||
}
|
||||
if (message.returnErrorDetail !== undefined) {
|
||||
writer.uint32(72).bool(message.returnErrorDetail);
|
||||
}
|
||||
if (message.ifNoneMatch !== undefined) {
|
||||
writer.uint32(98).string(message.ifNoneMatch);
|
||||
}
|
||||
if (message.returnLogEntry !== undefined) {
|
||||
writer.uint32(104).bool(message.returnLogEntry);
|
||||
}
|
||||
if (message.isPrefetch !== undefined) {
|
||||
writer.uint32(112).bool(message.isPrefetch);
|
||||
}
|
||||
for (const v of message.internalExperimentFlags) {
|
||||
KeyValuePair.encode(v!, writer.uint32(122).fork()).join();
|
||||
}
|
||||
if (message.returnDebugData !== undefined) {
|
||||
writer.uint32(128).bool(message.returnDebugData);
|
||||
}
|
||||
if (message.innertubez !== undefined) {
|
||||
writer.uint32(146).string(message.innertubez);
|
||||
}
|
||||
if (message.traceProto !== undefined) {
|
||||
writer.uint32(184).bool(message.traceProto);
|
||||
}
|
||||
if (message.returnLogEntryJson !== undefined) {
|
||||
writer.uint32(192).bool(message.returnLogEntryJson);
|
||||
}
|
||||
if (message.sherlogUsername !== undefined) {
|
||||
writer.uint32(202).string(message.sherlogUsername);
|
||||
}
|
||||
if (message.reauthRequestInfo !== undefined) {
|
||||
RequestInfo_ReauthRequestInfo.encode(message.reauthRequestInfo, writer.uint32(234).fork()).join();
|
||||
}
|
||||
if (message.sessionInfo !== undefined) {
|
||||
RequestInfo_SessionInfo.encode(message.sessionInfo, writer.uint32(242).fork()).join();
|
||||
}
|
||||
if (message.returnLogEntryProto !== undefined) {
|
||||
writer.uint32(248).bool(message.returnLogEntryProto);
|
||||
}
|
||||
if (message.externalPrequestContext !== undefined) {
|
||||
writer.uint32(258).string(message.externalPrequestContext);
|
||||
}
|
||||
if (message.attestationResponseData !== undefined) {
|
||||
AttestationResponseData.encode(message.attestationResponseData, writer.uint32(274).fork()).join();
|
||||
}
|
||||
if (message.eats !== undefined) {
|
||||
writer.uint32(282).bytes(message.eats);
|
||||
}
|
||||
if (message.requestQos !== undefined) {
|
||||
RequestInfo_RequestQoS.encode(message.requestQos, writer.uint32(290).fork()).join();
|
||||
}
|
||||
return writer;
|
||||
},
|
||||
|
||||
decode(input: BinaryReader | Uint8Array, length?: number): RequestInfo {
|
||||
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
|
||||
const end = length === undefined ? reader.len : reader.pos + length;
|
||||
const message = createBaseRequestInfo();
|
||||
while (reader.pos < end) {
|
||||
const tag = reader.uint32();
|
||||
switch (tag >>> 3) {
|
||||
case 6: {
|
||||
if (tag !== 50) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.thirdPartyDigest = reader.string();
|
||||
continue;
|
||||
}
|
||||
case 7: {
|
||||
if (tag !== 56) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.useSsl = reader.bool();
|
||||
continue;
|
||||
}
|
||||
case 9: {
|
||||
if (tag !== 72) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.returnErrorDetail = reader.bool();
|
||||
continue;
|
||||
}
|
||||
case 12: {
|
||||
if (tag !== 98) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.ifNoneMatch = reader.string();
|
||||
continue;
|
||||
}
|
||||
case 13: {
|
||||
if (tag !== 104) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.returnLogEntry = reader.bool();
|
||||
continue;
|
||||
}
|
||||
case 14: {
|
||||
if (tag !== 112) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.isPrefetch = reader.bool();
|
||||
continue;
|
||||
}
|
||||
case 15: {
|
||||
if (tag !== 122) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.internalExperimentFlags.push(KeyValuePair.decode(reader, reader.uint32()));
|
||||
continue;
|
||||
}
|
||||
case 16: {
|
||||
if (tag !== 128) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.returnDebugData = reader.bool();
|
||||
continue;
|
||||
}
|
||||
case 18: {
|
||||
if (tag !== 146) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.innertubez = reader.string();
|
||||
continue;
|
||||
}
|
||||
case 23: {
|
||||
if (tag !== 184) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.traceProto = reader.bool();
|
||||
continue;
|
||||
}
|
||||
case 24: {
|
||||
if (tag !== 192) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.returnLogEntryJson = reader.bool();
|
||||
continue;
|
||||
}
|
||||
case 25: {
|
||||
if (tag !== 202) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.sherlogUsername = reader.string();
|
||||
continue;
|
||||
}
|
||||
case 29: {
|
||||
if (tag !== 234) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.reauthRequestInfo = RequestInfo_ReauthRequestInfo.decode(reader, reader.uint32());
|
||||
continue;
|
||||
}
|
||||
case 30: {
|
||||
if (tag !== 242) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.sessionInfo = RequestInfo_SessionInfo.decode(reader, reader.uint32());
|
||||
continue;
|
||||
}
|
||||
case 31: {
|
||||
if (tag !== 248) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.returnLogEntryProto = reader.bool();
|
||||
continue;
|
||||
}
|
||||
case 32: {
|
||||
if (tag !== 258) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.externalPrequestContext = reader.string();
|
||||
continue;
|
||||
}
|
||||
case 34: {
|
||||
if (tag !== 274) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.attestationResponseData = AttestationResponseData.decode(reader, reader.uint32());
|
||||
continue;
|
||||
}
|
||||
case 35: {
|
||||
if (tag !== 282) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.eats = reader.bytes();
|
||||
continue;
|
||||
}
|
||||
case 36: {
|
||||
if (tag !== 290) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.requestQos = RequestInfo_RequestQoS.decode(reader, reader.uint32());
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if ((tag & 7) === 4 || tag === 0) {
|
||||
break;
|
||||
}
|
||||
reader.skip(tag & 7);
|
||||
}
|
||||
return message;
|
||||
},
|
||||
};
|
||||
|
||||
function createBaseRequestInfo_RequestQoS(): RequestInfo_RequestQoS {
|
||||
return { criticality: undefined };
|
||||
}
|
||||
|
||||
export const RequestInfo_RequestQoS: MessageFns<RequestInfo_RequestQoS> = {
|
||||
encode(message: RequestInfo_RequestQoS, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
|
||||
if (message.criticality !== undefined) {
|
||||
writer.uint32(8).int32(message.criticality);
|
||||
}
|
||||
return writer;
|
||||
},
|
||||
|
||||
decode(input: BinaryReader | Uint8Array, length?: number): RequestInfo_RequestQoS {
|
||||
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
|
||||
const end = length === undefined ? reader.len : reader.pos + length;
|
||||
const message = createBaseRequestInfo_RequestQoS();
|
||||
while (reader.pos < end) {
|
||||
const tag = reader.uint32();
|
||||
switch (tag >>> 3) {
|
||||
case 1: {
|
||||
if (tag !== 8) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.criticality = reader.int32() as any;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if ((tag & 7) === 4 || tag === 0) {
|
||||
break;
|
||||
}
|
||||
reader.skip(tag & 7);
|
||||
}
|
||||
return message;
|
||||
},
|
||||
};
|
||||
|
||||
function createBaseRequestInfo_SessionInfo(): RequestInfo_SessionInfo {
|
||||
return { token: undefined };
|
||||
}
|
||||
|
||||
export const RequestInfo_SessionInfo: MessageFns<RequestInfo_SessionInfo> = {
|
||||
encode(message: RequestInfo_SessionInfo, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
|
||||
if (message.token !== undefined) {
|
||||
writer.uint32(10).string(message.token);
|
||||
}
|
||||
return writer;
|
||||
},
|
||||
|
||||
decode(input: BinaryReader | Uint8Array, length?: number): RequestInfo_SessionInfo {
|
||||
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
|
||||
const end = length === undefined ? reader.len : reader.pos + length;
|
||||
const message = createBaseRequestInfo_SessionInfo();
|
||||
while (reader.pos < end) {
|
||||
const tag = reader.uint32();
|
||||
switch (tag >>> 3) {
|
||||
case 1: {
|
||||
if (tag !== 10) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.token = reader.string();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if ((tag & 7) === 4 || tag === 0) {
|
||||
break;
|
||||
}
|
||||
reader.skip(tag & 7);
|
||||
}
|
||||
return message;
|
||||
},
|
||||
};
|
||||
|
||||
function createBaseRequestInfo_ReauthRequestInfo(): RequestInfo_ReauthRequestInfo {
|
||||
return { encodedReauthProofToken: undefined };
|
||||
}
|
||||
|
||||
export const RequestInfo_ReauthRequestInfo: MessageFns<RequestInfo_ReauthRequestInfo> = {
|
||||
encode(message: RequestInfo_ReauthRequestInfo, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
|
||||
if (message.encodedReauthProofToken !== undefined) {
|
||||
writer.uint32(10).string(message.encodedReauthProofToken);
|
||||
}
|
||||
return writer;
|
||||
},
|
||||
|
||||
decode(input: BinaryReader | Uint8Array, length?: number): RequestInfo_ReauthRequestInfo {
|
||||
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
|
||||
const end = length === undefined ? reader.len : reader.pos + length;
|
||||
const message = createBaseRequestInfo_ReauthRequestInfo();
|
||||
while (reader.pos < end) {
|
||||
const tag = reader.uint32();
|
||||
switch (tag >>> 3) {
|
||||
case 1: {
|
||||
if (tag !== 10) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.encodedReauthProofToken = reader.string();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if ((tag & 7) === 4 || tag === 0) {
|
||||
break;
|
||||
}
|
||||
reader.skip(tag & 7);
|
||||
}
|
||||
return message;
|
||||
},
|
||||
};
|
||||
|
||||
export interface MessageFns<T> {
|
||||
encode(message: T, writer?: BinaryWriter): BinaryWriter;
|
||||
decode(input: BinaryReader | Uint8Array, length?: number): T;
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
// Code generated by protoc-gen-ts_proto. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-ts_proto v2.7.7
|
||||
// protoc v6.33.5
|
||||
// source: youtube/api/pfiinnertube/service_integrity_dimensions.proto
|
||||
|
||||
/* eslint-disable */
|
||||
import { BinaryReader, BinaryWriter } from "https://esm.sh/@bufbuild/protobuf@2.0.0/wire";
|
||||
|
||||
export const protobufPackage = "youtube.api.pfiinnertube";
|
||||
|
||||
export interface ServiceIntegrityDimensions {
|
||||
poToken?: Uint8Array | undefined;
|
||||
}
|
||||
|
||||
function createBaseServiceIntegrityDimensions(): ServiceIntegrityDimensions {
|
||||
return { poToken: undefined };
|
||||
}
|
||||
|
||||
export const ServiceIntegrityDimensions: MessageFns<ServiceIntegrityDimensions> = {
|
||||
encode(message: ServiceIntegrityDimensions, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
|
||||
if (message.poToken !== undefined) {
|
||||
writer.uint32(10).bytes(message.poToken);
|
||||
}
|
||||
return writer;
|
||||
},
|
||||
|
||||
decode(input: BinaryReader | Uint8Array, length?: number): ServiceIntegrityDimensions {
|
||||
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
|
||||
const end = length === undefined ? reader.len : reader.pos + length;
|
||||
const message = createBaseServiceIntegrityDimensions();
|
||||
while (reader.pos < end) {
|
||||
const tag = reader.uint32();
|
||||
switch (tag >>> 3) {
|
||||
case 1: {
|
||||
if (tag !== 10) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.poToken = reader.bytes();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if ((tag & 7) === 4 || tag === 0) {
|
||||
break;
|
||||
}
|
||||
reader.skip(tag & 7);
|
||||
}
|
||||
return message;
|
||||
},
|
||||
};
|
||||
|
||||
export interface MessageFns<T> {
|
||||
encode(message: T, writer?: BinaryWriter): BinaryWriter;
|
||||
decode(input: BinaryReader | Uint8Array, length?: number): T;
|
||||
}
|
||||
@@ -0,0 +1,188 @@
|
||||
// Code generated by protoc-gen-ts_proto. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-ts_proto v2.7.7
|
||||
// protoc v6.33.5
|
||||
// source: youtube/api/pfiinnertube/third_party_info.proto
|
||||
|
||||
/* eslint-disable */
|
||||
import { BinaryReader, BinaryWriter } from "https://esm.sh/@bufbuild/protobuf@2.0.0/wire";
|
||||
|
||||
export const protobufPackage = "youtube.api.pfiinnertube";
|
||||
|
||||
export interface ThirdPartyInfo {
|
||||
developerKey?: string | undefined;
|
||||
appName?: string | undefined;
|
||||
appPublisher?: string | undefined;
|
||||
embedUrl?: string | undefined;
|
||||
appVersion?: string | undefined;
|
||||
embeddedPlayerContext?: ThirdPartyInfo_EmbeddedPlayerContext | undefined;
|
||||
}
|
||||
|
||||
export interface ThirdPartyInfo_EmbeddedPlayerContext {
|
||||
ancestorOrigins?: string | undefined;
|
||||
embeddedPlayerEncryptedContext?: string | undefined;
|
||||
ancestorOriginsSupported?: boolean | undefined;
|
||||
}
|
||||
|
||||
function createBaseThirdPartyInfo(): ThirdPartyInfo {
|
||||
return {
|
||||
developerKey: undefined,
|
||||
appName: undefined,
|
||||
appPublisher: undefined,
|
||||
embedUrl: undefined,
|
||||
appVersion: undefined,
|
||||
embeddedPlayerContext: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
export const ThirdPartyInfo: MessageFns<ThirdPartyInfo> = {
|
||||
encode(message: ThirdPartyInfo, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
|
||||
if (message.developerKey !== undefined) {
|
||||
writer.uint32(10).string(message.developerKey);
|
||||
}
|
||||
if (message.appName !== undefined) {
|
||||
writer.uint32(18).string(message.appName);
|
||||
}
|
||||
if (message.appPublisher !== undefined) {
|
||||
writer.uint32(26).string(message.appPublisher);
|
||||
}
|
||||
if (message.embedUrl !== undefined) {
|
||||
writer.uint32(34).string(message.embedUrl);
|
||||
}
|
||||
if (message.appVersion !== undefined) {
|
||||
writer.uint32(50).string(message.appVersion);
|
||||
}
|
||||
if (message.embeddedPlayerContext !== undefined) {
|
||||
ThirdPartyInfo_EmbeddedPlayerContext.encode(message.embeddedPlayerContext, writer.uint32(58).fork()).join();
|
||||
}
|
||||
return writer;
|
||||
},
|
||||
|
||||
decode(input: BinaryReader | Uint8Array, length?: number): ThirdPartyInfo {
|
||||
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
|
||||
const end = length === undefined ? reader.len : reader.pos + length;
|
||||
const message = createBaseThirdPartyInfo();
|
||||
while (reader.pos < end) {
|
||||
const tag = reader.uint32();
|
||||
switch (tag >>> 3) {
|
||||
case 1: {
|
||||
if (tag !== 10) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.developerKey = reader.string();
|
||||
continue;
|
||||
}
|
||||
case 2: {
|
||||
if (tag !== 18) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.appName = reader.string();
|
||||
continue;
|
||||
}
|
||||
case 3: {
|
||||
if (tag !== 26) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.appPublisher = reader.string();
|
||||
continue;
|
||||
}
|
||||
case 4: {
|
||||
if (tag !== 34) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.embedUrl = reader.string();
|
||||
continue;
|
||||
}
|
||||
case 6: {
|
||||
if (tag !== 50) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.appVersion = reader.string();
|
||||
continue;
|
||||
}
|
||||
case 7: {
|
||||
if (tag !== 58) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.embeddedPlayerContext = ThirdPartyInfo_EmbeddedPlayerContext.decode(reader, reader.uint32());
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if ((tag & 7) === 4 || tag === 0) {
|
||||
break;
|
||||
}
|
||||
reader.skip(tag & 7);
|
||||
}
|
||||
return message;
|
||||
},
|
||||
};
|
||||
|
||||
function createBaseThirdPartyInfo_EmbeddedPlayerContext(): ThirdPartyInfo_EmbeddedPlayerContext {
|
||||
return { ancestorOrigins: undefined, embeddedPlayerEncryptedContext: undefined, ancestorOriginsSupported: undefined };
|
||||
}
|
||||
|
||||
export const ThirdPartyInfo_EmbeddedPlayerContext: MessageFns<ThirdPartyInfo_EmbeddedPlayerContext> = {
|
||||
encode(message: ThirdPartyInfo_EmbeddedPlayerContext, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
|
||||
if (message.ancestorOrigins !== undefined) {
|
||||
writer.uint32(10).string(message.ancestorOrigins);
|
||||
}
|
||||
if (message.embeddedPlayerEncryptedContext !== undefined) {
|
||||
writer.uint32(18).string(message.embeddedPlayerEncryptedContext);
|
||||
}
|
||||
if (message.ancestorOriginsSupported !== undefined) {
|
||||
writer.uint32(24).bool(message.ancestorOriginsSupported);
|
||||
}
|
||||
return writer;
|
||||
},
|
||||
|
||||
decode(input: BinaryReader | Uint8Array, length?: number): ThirdPartyInfo_EmbeddedPlayerContext {
|
||||
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
|
||||
const end = length === undefined ? reader.len : reader.pos + length;
|
||||
const message = createBaseThirdPartyInfo_EmbeddedPlayerContext();
|
||||
while (reader.pos < end) {
|
||||
const tag = reader.uint32();
|
||||
switch (tag >>> 3) {
|
||||
case 1: {
|
||||
if (tag !== 10) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.ancestorOrigins = reader.string();
|
||||
continue;
|
||||
}
|
||||
case 2: {
|
||||
if (tag !== 18) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.embeddedPlayerEncryptedContext = reader.string();
|
||||
continue;
|
||||
}
|
||||
case 3: {
|
||||
if (tag !== 24) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.ancestorOriginsSupported = reader.bool();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if ((tag & 7) === 4 || tag === 0) {
|
||||
break;
|
||||
}
|
||||
reader.skip(tag & 7);
|
||||
}
|
||||
return message;
|
||||
},
|
||||
};
|
||||
|
||||
export interface MessageFns<T> {
|
||||
encode(message: T, writer?: BinaryWriter): BinaryWriter;
|
||||
decode(input: BinaryReader | Uint8Array, length?: number): T;
|
||||
}
|
||||
278
deno/protos/generated/youtube/api/pfiinnertube/user_info.ts
Normal file
278
deno/protos/generated/youtube/api/pfiinnertube/user_info.ts
Normal file
@@ -0,0 +1,278 @@
|
||||
// Code generated by protoc-gen-ts_proto. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-ts_proto v2.7.7
|
||||
// protoc v6.33.5
|
||||
// source: youtube/api/pfiinnertube/user_info.proto
|
||||
|
||||
/* eslint-disable */
|
||||
import { BinaryReader, BinaryWriter } from "https://esm.sh/@bufbuild/protobuf@2.0.0/wire";
|
||||
|
||||
export const protobufPackage = "youtube.api.pfiinnertube";
|
||||
|
||||
export interface UserInfo {
|
||||
onBehalfOfUser?: string | undefined;
|
||||
enableSafetyMode?: boolean | undefined;
|
||||
credentialTransferTokens: UserInfo_CredentialTransferToken[];
|
||||
delegatePurchases?: UserInfo_DelegatePurchases | undefined;
|
||||
kidsParent?: UserInfo_KidsParent | undefined;
|
||||
isIncognito?: boolean | undefined;
|
||||
lockedSafetyMode?: boolean | undefined;
|
||||
delegationContext?: UserInfo_DelegationContext | undefined;
|
||||
serializedDelegationContext?: string | undefined;
|
||||
}
|
||||
|
||||
export interface UserInfo_KidsParent {
|
||||
}
|
||||
|
||||
export interface UserInfo_DelegatePurchases {
|
||||
}
|
||||
|
||||
export interface UserInfo_DelegationContext {
|
||||
}
|
||||
|
||||
export interface UserInfo_CredentialTransferToken {
|
||||
}
|
||||
|
||||
function createBaseUserInfo(): UserInfo {
|
||||
return {
|
||||
onBehalfOfUser: undefined,
|
||||
enableSafetyMode: undefined,
|
||||
credentialTransferTokens: [],
|
||||
delegatePurchases: undefined,
|
||||
kidsParent: undefined,
|
||||
isIncognito: undefined,
|
||||
lockedSafetyMode: undefined,
|
||||
delegationContext: undefined,
|
||||
serializedDelegationContext: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
export const UserInfo: MessageFns<UserInfo> = {
|
||||
encode(message: UserInfo, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
|
||||
if (message.onBehalfOfUser !== undefined) {
|
||||
writer.uint32(26).string(message.onBehalfOfUser);
|
||||
}
|
||||
if (message.enableSafetyMode !== undefined) {
|
||||
writer.uint32(56).bool(message.enableSafetyMode);
|
||||
}
|
||||
for (const v of message.credentialTransferTokens) {
|
||||
UserInfo_CredentialTransferToken.encode(v!, writer.uint32(98).fork()).join();
|
||||
}
|
||||
if (message.delegatePurchases !== undefined) {
|
||||
UserInfo_DelegatePurchases.encode(message.delegatePurchases, writer.uint32(106).fork()).join();
|
||||
}
|
||||
if (message.kidsParent !== undefined) {
|
||||
UserInfo_KidsParent.encode(message.kidsParent, writer.uint32(114).fork()).join();
|
||||
}
|
||||
if (message.isIncognito !== undefined) {
|
||||
writer.uint32(120).bool(message.isIncognito);
|
||||
}
|
||||
if (message.lockedSafetyMode !== undefined) {
|
||||
writer.uint32(128).bool(message.lockedSafetyMode);
|
||||
}
|
||||
if (message.delegationContext !== undefined) {
|
||||
UserInfo_DelegationContext.encode(message.delegationContext, writer.uint32(138).fork()).join();
|
||||
}
|
||||
if (message.serializedDelegationContext !== undefined) {
|
||||
writer.uint32(146).string(message.serializedDelegationContext);
|
||||
}
|
||||
return writer;
|
||||
},
|
||||
|
||||
decode(input: BinaryReader | Uint8Array, length?: number): UserInfo {
|
||||
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
|
||||
const end = length === undefined ? reader.len : reader.pos + length;
|
||||
const message = createBaseUserInfo();
|
||||
while (reader.pos < end) {
|
||||
const tag = reader.uint32();
|
||||
switch (tag >>> 3) {
|
||||
case 3: {
|
||||
if (tag !== 26) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.onBehalfOfUser = reader.string();
|
||||
continue;
|
||||
}
|
||||
case 7: {
|
||||
if (tag !== 56) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.enableSafetyMode = reader.bool();
|
||||
continue;
|
||||
}
|
||||
case 12: {
|
||||
if (tag !== 98) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.credentialTransferTokens.push(UserInfo_CredentialTransferToken.decode(reader, reader.uint32()));
|
||||
continue;
|
||||
}
|
||||
case 13: {
|
||||
if (tag !== 106) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.delegatePurchases = UserInfo_DelegatePurchases.decode(reader, reader.uint32());
|
||||
continue;
|
||||
}
|
||||
case 14: {
|
||||
if (tag !== 114) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.kidsParent = UserInfo_KidsParent.decode(reader, reader.uint32());
|
||||
continue;
|
||||
}
|
||||
case 15: {
|
||||
if (tag !== 120) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.isIncognito = reader.bool();
|
||||
continue;
|
||||
}
|
||||
case 16: {
|
||||
if (tag !== 128) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.lockedSafetyMode = reader.bool();
|
||||
continue;
|
||||
}
|
||||
case 17: {
|
||||
if (tag !== 138) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.delegationContext = UserInfo_DelegationContext.decode(reader, reader.uint32());
|
||||
continue;
|
||||
}
|
||||
case 18: {
|
||||
if (tag !== 146) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.serializedDelegationContext = reader.string();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if ((tag & 7) === 4 || tag === 0) {
|
||||
break;
|
||||
}
|
||||
reader.skip(tag & 7);
|
||||
}
|
||||
return message;
|
||||
},
|
||||
};
|
||||
|
||||
function createBaseUserInfo_KidsParent(): UserInfo_KidsParent {
|
||||
return {};
|
||||
}
|
||||
|
||||
export const UserInfo_KidsParent: MessageFns<UserInfo_KidsParent> = {
|
||||
encode(_: UserInfo_KidsParent, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
|
||||
return writer;
|
||||
},
|
||||
|
||||
decode(input: BinaryReader | Uint8Array, length?: number): UserInfo_KidsParent {
|
||||
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
|
||||
const end = length === undefined ? reader.len : reader.pos + length;
|
||||
const message = createBaseUserInfo_KidsParent();
|
||||
while (reader.pos < end) {
|
||||
const tag = reader.uint32();
|
||||
switch (tag >>> 3) {
|
||||
}
|
||||
if ((tag & 7) === 4 || tag === 0) {
|
||||
break;
|
||||
}
|
||||
reader.skip(tag & 7);
|
||||
}
|
||||
return message;
|
||||
},
|
||||
};
|
||||
|
||||
function createBaseUserInfo_DelegatePurchases(): UserInfo_DelegatePurchases {
|
||||
return {};
|
||||
}
|
||||
|
||||
export const UserInfo_DelegatePurchases: MessageFns<UserInfo_DelegatePurchases> = {
|
||||
encode(_: UserInfo_DelegatePurchases, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
|
||||
return writer;
|
||||
},
|
||||
|
||||
decode(input: BinaryReader | Uint8Array, length?: number): UserInfo_DelegatePurchases {
|
||||
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
|
||||
const end = length === undefined ? reader.len : reader.pos + length;
|
||||
const message = createBaseUserInfo_DelegatePurchases();
|
||||
while (reader.pos < end) {
|
||||
const tag = reader.uint32();
|
||||
switch (tag >>> 3) {
|
||||
}
|
||||
if ((tag & 7) === 4 || tag === 0) {
|
||||
break;
|
||||
}
|
||||
reader.skip(tag & 7);
|
||||
}
|
||||
return message;
|
||||
},
|
||||
};
|
||||
|
||||
function createBaseUserInfo_DelegationContext(): UserInfo_DelegationContext {
|
||||
return {};
|
||||
}
|
||||
|
||||
export const UserInfo_DelegationContext: MessageFns<UserInfo_DelegationContext> = {
|
||||
encode(_: UserInfo_DelegationContext, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
|
||||
return writer;
|
||||
},
|
||||
|
||||
decode(input: BinaryReader | Uint8Array, length?: number): UserInfo_DelegationContext {
|
||||
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
|
||||
const end = length === undefined ? reader.len : reader.pos + length;
|
||||
const message = createBaseUserInfo_DelegationContext();
|
||||
while (reader.pos < end) {
|
||||
const tag = reader.uint32();
|
||||
switch (tag >>> 3) {
|
||||
}
|
||||
if ((tag & 7) === 4 || tag === 0) {
|
||||
break;
|
||||
}
|
||||
reader.skip(tag & 7);
|
||||
}
|
||||
return message;
|
||||
},
|
||||
};
|
||||
|
||||
function createBaseUserInfo_CredentialTransferToken(): UserInfo_CredentialTransferToken {
|
||||
return {};
|
||||
}
|
||||
|
||||
export const UserInfo_CredentialTransferToken: MessageFns<UserInfo_CredentialTransferToken> = {
|
||||
encode(_: UserInfo_CredentialTransferToken, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
|
||||
return writer;
|
||||
},
|
||||
|
||||
decode(input: BinaryReader | Uint8Array, length?: number): UserInfo_CredentialTransferToken {
|
||||
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
|
||||
const end = length === undefined ? reader.len : reader.pos + length;
|
||||
const message = createBaseUserInfo_CredentialTransferToken();
|
||||
while (reader.pos < end) {
|
||||
const tag = reader.uint32();
|
||||
switch (tag >>> 3) {
|
||||
}
|
||||
if ((tag & 7) === 4 || tag === 0) {
|
||||
break;
|
||||
}
|
||||
reader.skip(tag & 7);
|
||||
}
|
||||
return message;
|
||||
},
|
||||
};
|
||||
|
||||
export interface MessageFns<T> {
|
||||
encode(message: T, writer?: BinaryWriter): BinaryWriter;
|
||||
decode(input: BinaryReader | Uint8Array, length?: number): T;
|
||||
}
|
||||
@@ -0,0 +1,423 @@
|
||||
// Code generated by protoc-gen-ts_proto. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-ts_proto v2.7.7
|
||||
// protoc v6.33.5
|
||||
// source: youtube/api/pfiinnertube/watch_next_request.proto
|
||||
|
||||
/* eslint-disable */
|
||||
import { BinaryReader, BinaryWriter } from "https://esm.sh/@bufbuild/protobuf@2.0.0/wire";
|
||||
import { InnerTubeContext } from "./innertube_context.ts";
|
||||
|
||||
export const protobufPackage = "youtube.api.pfiinnertube";
|
||||
|
||||
export interface WatchNextRequest {
|
||||
context?: InnerTubeContext | undefined;
|
||||
videoId?: string | undefined;
|
||||
playlistId?: string | undefined;
|
||||
params?: string | undefined;
|
||||
continuation?: string | undefined;
|
||||
isAdPlayback?: boolean | undefined;
|
||||
mdxUseDevServer?: boolean | undefined;
|
||||
referrer?: string | undefined;
|
||||
referringApp?: string | undefined;
|
||||
adParams?: string | undefined;
|
||||
requestMusicSequence?: boolean | undefined;
|
||||
enableMdxAutoplay?: boolean | undefined;
|
||||
isMdxPlayback?: boolean | undefined;
|
||||
racyCheckOk?: boolean | undefined;
|
||||
contentCheckOk?: boolean | undefined;
|
||||
isAudioOnly?: boolean | undefined;
|
||||
autonavEnabled?: boolean | undefined;
|
||||
enablePersistentPlaylistPanel?: boolean | undefined;
|
||||
playlistSetVideoId?: string | undefined;
|
||||
showRuInvalidTokenMessage?: boolean | undefined;
|
||||
serializedThirdPartyEmbedConfig?: string | undefined;
|
||||
showContentOwnerOnly?: boolean | undefined;
|
||||
isEmbedPreview?: boolean | undefined;
|
||||
lastScrubbedInlinePlaybackVideoId?: string | undefined;
|
||||
lastAudioTurnedOnInlinePlaybackVideoId?: string | undefined;
|
||||
lastAudioTurnedOffInlinePlaybackVideoId?: string | undefined;
|
||||
captionsRequested?: boolean | undefined;
|
||||
queueContextParams?: Uint8Array | undefined;
|
||||
showShortsOnly?: boolean | undefined;
|
||||
}
|
||||
|
||||
function createBaseWatchNextRequest(): WatchNextRequest {
|
||||
return {
|
||||
context: undefined,
|
||||
videoId: undefined,
|
||||
playlistId: undefined,
|
||||
params: undefined,
|
||||
continuation: undefined,
|
||||
isAdPlayback: undefined,
|
||||
mdxUseDevServer: undefined,
|
||||
referrer: undefined,
|
||||
referringApp: undefined,
|
||||
adParams: undefined,
|
||||
requestMusicSequence: undefined,
|
||||
enableMdxAutoplay: undefined,
|
||||
isMdxPlayback: undefined,
|
||||
racyCheckOk: undefined,
|
||||
contentCheckOk: undefined,
|
||||
isAudioOnly: undefined,
|
||||
autonavEnabled: undefined,
|
||||
enablePersistentPlaylistPanel: undefined,
|
||||
playlistSetVideoId: undefined,
|
||||
showRuInvalidTokenMessage: undefined,
|
||||
serializedThirdPartyEmbedConfig: undefined,
|
||||
showContentOwnerOnly: undefined,
|
||||
isEmbedPreview: undefined,
|
||||
lastScrubbedInlinePlaybackVideoId: undefined,
|
||||
lastAudioTurnedOnInlinePlaybackVideoId: undefined,
|
||||
lastAudioTurnedOffInlinePlaybackVideoId: undefined,
|
||||
captionsRequested: undefined,
|
||||
queueContextParams: undefined,
|
||||
showShortsOnly: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
export const WatchNextRequest: MessageFns<WatchNextRequest> = {
|
||||
encode(message: WatchNextRequest, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
|
||||
if (message.context !== undefined) {
|
||||
InnerTubeContext.encode(message.context, writer.uint32(10).fork()).join();
|
||||
}
|
||||
if (message.videoId !== undefined) {
|
||||
writer.uint32(18).string(message.videoId);
|
||||
}
|
||||
if (message.playlistId !== undefined) {
|
||||
writer.uint32(34).string(message.playlistId);
|
||||
}
|
||||
if (message.params !== undefined) {
|
||||
writer.uint32(50).string(message.params);
|
||||
}
|
||||
if (message.continuation !== undefined) {
|
||||
writer.uint32(66).string(message.continuation);
|
||||
}
|
||||
if (message.isAdPlayback !== undefined) {
|
||||
writer.uint32(72).bool(message.isAdPlayback);
|
||||
}
|
||||
if (message.mdxUseDevServer !== undefined) {
|
||||
writer.uint32(80).bool(message.mdxUseDevServer);
|
||||
}
|
||||
if (message.referrer !== undefined) {
|
||||
writer.uint32(98).string(message.referrer);
|
||||
}
|
||||
if (message.referringApp !== undefined) {
|
||||
writer.uint32(106).string(message.referringApp);
|
||||
}
|
||||
if (message.adParams !== undefined) {
|
||||
writer.uint32(130).string(message.adParams);
|
||||
}
|
||||
if (message.requestMusicSequence !== undefined) {
|
||||
writer.uint32(144).bool(message.requestMusicSequence);
|
||||
}
|
||||
if (message.enableMdxAutoplay !== undefined) {
|
||||
writer.uint32(168).bool(message.enableMdxAutoplay);
|
||||
}
|
||||
if (message.isMdxPlayback !== undefined) {
|
||||
writer.uint32(176).bool(message.isMdxPlayback);
|
||||
}
|
||||
if (message.racyCheckOk !== undefined) {
|
||||
writer.uint32(192).bool(message.racyCheckOk);
|
||||
}
|
||||
if (message.contentCheckOk !== undefined) {
|
||||
writer.uint32(200).bool(message.contentCheckOk);
|
||||
}
|
||||
if (message.isAudioOnly !== undefined) {
|
||||
writer.uint32(208).bool(message.isAudioOnly);
|
||||
}
|
||||
if (message.autonavEnabled !== undefined) {
|
||||
writer.uint32(216).bool(message.autonavEnabled);
|
||||
}
|
||||
if (message.enablePersistentPlaylistPanel !== undefined) {
|
||||
writer.uint32(240).bool(message.enablePersistentPlaylistPanel);
|
||||
}
|
||||
if (message.playlistSetVideoId !== undefined) {
|
||||
writer.uint32(250).string(message.playlistSetVideoId);
|
||||
}
|
||||
if (message.showRuInvalidTokenMessage !== undefined) {
|
||||
writer.uint32(280).bool(message.showRuInvalidTokenMessage);
|
||||
}
|
||||
if (message.serializedThirdPartyEmbedConfig !== undefined) {
|
||||
writer.uint32(298).string(message.serializedThirdPartyEmbedConfig);
|
||||
}
|
||||
if (message.showContentOwnerOnly !== undefined) {
|
||||
writer.uint32(304).bool(message.showContentOwnerOnly);
|
||||
}
|
||||
if (message.isEmbedPreview !== undefined) {
|
||||
writer.uint32(336).bool(message.isEmbedPreview);
|
||||
}
|
||||
if (message.lastScrubbedInlinePlaybackVideoId !== undefined) {
|
||||
writer.uint32(346).string(message.lastScrubbedInlinePlaybackVideoId);
|
||||
}
|
||||
if (message.lastAudioTurnedOnInlinePlaybackVideoId !== undefined) {
|
||||
writer.uint32(354).string(message.lastAudioTurnedOnInlinePlaybackVideoId);
|
||||
}
|
||||
if (message.lastAudioTurnedOffInlinePlaybackVideoId !== undefined) {
|
||||
writer.uint32(362).string(message.lastAudioTurnedOffInlinePlaybackVideoId);
|
||||
}
|
||||
if (message.captionsRequested !== undefined) {
|
||||
writer.uint32(376).bool(message.captionsRequested);
|
||||
}
|
||||
if (message.queueContextParams !== undefined) {
|
||||
writer.uint32(402).bytes(message.queueContextParams);
|
||||
}
|
||||
if (message.showShortsOnly !== undefined) {
|
||||
writer.uint32(440).bool(message.showShortsOnly);
|
||||
}
|
||||
return writer;
|
||||
},
|
||||
|
||||
decode(input: BinaryReader | Uint8Array, length?: number): WatchNextRequest {
|
||||
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
|
||||
const end = length === undefined ? reader.len : reader.pos + length;
|
||||
const message = createBaseWatchNextRequest();
|
||||
while (reader.pos < end) {
|
||||
const tag = reader.uint32();
|
||||
switch (tag >>> 3) {
|
||||
case 1: {
|
||||
if (tag !== 10) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.context = InnerTubeContext.decode(reader, reader.uint32());
|
||||
continue;
|
||||
}
|
||||
case 2: {
|
||||
if (tag !== 18) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.videoId = reader.string();
|
||||
continue;
|
||||
}
|
||||
case 4: {
|
||||
if (tag !== 34) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.playlistId = reader.string();
|
||||
continue;
|
||||
}
|
||||
case 6: {
|
||||
if (tag !== 50) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.params = reader.string();
|
||||
continue;
|
||||
}
|
||||
case 8: {
|
||||
if (tag !== 66) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.continuation = reader.string();
|
||||
continue;
|
||||
}
|
||||
case 9: {
|
||||
if (tag !== 72) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.isAdPlayback = reader.bool();
|
||||
continue;
|
||||
}
|
||||
case 10: {
|
||||
if (tag !== 80) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.mdxUseDevServer = reader.bool();
|
||||
continue;
|
||||
}
|
||||
case 12: {
|
||||
if (tag !== 98) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.referrer = reader.string();
|
||||
continue;
|
||||
}
|
||||
case 13: {
|
||||
if (tag !== 106) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.referringApp = reader.string();
|
||||
continue;
|
||||
}
|
||||
case 16: {
|
||||
if (tag !== 130) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.adParams = reader.string();
|
||||
continue;
|
||||
}
|
||||
case 18: {
|
||||
if (tag !== 144) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.requestMusicSequence = reader.bool();
|
||||
continue;
|
||||
}
|
||||
case 21: {
|
||||
if (tag !== 168) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.enableMdxAutoplay = reader.bool();
|
||||
continue;
|
||||
}
|
||||
case 22: {
|
||||
if (tag !== 176) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.isMdxPlayback = reader.bool();
|
||||
continue;
|
||||
}
|
||||
case 24: {
|
||||
if (tag !== 192) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.racyCheckOk = reader.bool();
|
||||
continue;
|
||||
}
|
||||
case 25: {
|
||||
if (tag !== 200) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.contentCheckOk = reader.bool();
|
||||
continue;
|
||||
}
|
||||
case 26: {
|
||||
if (tag !== 208) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.isAudioOnly = reader.bool();
|
||||
continue;
|
||||
}
|
||||
case 27: {
|
||||
if (tag !== 216) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.autonavEnabled = reader.bool();
|
||||
continue;
|
||||
}
|
||||
case 30: {
|
||||
if (tag !== 240) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.enablePersistentPlaylistPanel = reader.bool();
|
||||
continue;
|
||||
}
|
||||
case 31: {
|
||||
if (tag !== 250) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.playlistSetVideoId = reader.string();
|
||||
continue;
|
||||
}
|
||||
case 35: {
|
||||
if (tag !== 280) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.showRuInvalidTokenMessage = reader.bool();
|
||||
continue;
|
||||
}
|
||||
case 37: {
|
||||
if (tag !== 298) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.serializedThirdPartyEmbedConfig = reader.string();
|
||||
continue;
|
||||
}
|
||||
case 38: {
|
||||
if (tag !== 304) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.showContentOwnerOnly = reader.bool();
|
||||
continue;
|
||||
}
|
||||
case 42: {
|
||||
if (tag !== 336) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.isEmbedPreview = reader.bool();
|
||||
continue;
|
||||
}
|
||||
case 43: {
|
||||
if (tag !== 346) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.lastScrubbedInlinePlaybackVideoId = reader.string();
|
||||
continue;
|
||||
}
|
||||
case 44: {
|
||||
if (tag !== 354) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.lastAudioTurnedOnInlinePlaybackVideoId = reader.string();
|
||||
continue;
|
||||
}
|
||||
case 45: {
|
||||
if (tag !== 362) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.lastAudioTurnedOffInlinePlaybackVideoId = reader.string();
|
||||
continue;
|
||||
}
|
||||
case 47: {
|
||||
if (tag !== 376) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.captionsRequested = reader.bool();
|
||||
continue;
|
||||
}
|
||||
case 50: {
|
||||
if (tag !== 402) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.queueContextParams = reader.bytes();
|
||||
continue;
|
||||
}
|
||||
case 55: {
|
||||
if (tag !== 440) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.showShortsOnly = reader.bool();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if ((tag & 7) === 4 || tag === 0) {
|
||||
break;
|
||||
}
|
||||
reader.skip(tag & 7);
|
||||
}
|
||||
return message;
|
||||
},
|
||||
};
|
||||
|
||||
export interface MessageFns<T> {
|
||||
encode(message: T, writer?: BinaryWriter): BinaryWriter;
|
||||
decode(input: BinaryReader | Uint8Array, length?: number): T;
|
||||
}
|
||||
32
deno/protos/misc/common.proto
Normal file
32
deno/protos/misc/common.proto
Normal file
@@ -0,0 +1,32 @@
|
||||
syntax = "proto3";
|
||||
package misc;
|
||||
|
||||
message HttpHeader {
|
||||
optional string name = 1;
|
||||
optional string value = 2;
|
||||
}
|
||||
|
||||
message FormatId {
|
||||
optional int32 itag = 1;
|
||||
optional uint64 last_modified = 2;
|
||||
optional string xtags = 3;
|
||||
}
|
||||
|
||||
message InitRange {
|
||||
optional int32 start = 1;
|
||||
optional int32 end = 2;
|
||||
}
|
||||
|
||||
message IndexRange {
|
||||
optional int32 start = 1;
|
||||
optional int32 end = 2;
|
||||
}
|
||||
|
||||
message KeyValuePair {
|
||||
optional string key = 1;
|
||||
optional string value = 2;
|
||||
}
|
||||
|
||||
message FormatXTags {
|
||||
repeated KeyValuePair xtags = 1;
|
||||
}
|
||||
268
deno/protos/misc/params.proto
Normal file
268
deno/protos/misc/params.proto
Normal file
@@ -0,0 +1,268 @@
|
||||
syntax = "proto3";
|
||||
package misc;
|
||||
|
||||
message VisitorData {
|
||||
string id = 1;
|
||||
int32 timestamp = 5;
|
||||
}
|
||||
|
||||
message SearchFilter {
|
||||
optional Prioritize prioritize = 1;
|
||||
|
||||
enum Prioritize {
|
||||
RELEVANCE = 0;
|
||||
POPULARITY = 3;
|
||||
}
|
||||
|
||||
message Filters {
|
||||
optional UploadDate upload_date = 1;
|
||||
optional SearchType type = 2;
|
||||
optional Duration duration = 3;
|
||||
optional MusicSearchType music_search_type = 17;
|
||||
|
||||
optional bool features_hd = 4;
|
||||
optional bool features_subtitles = 5;
|
||||
optional bool features_creative_commons = 6;
|
||||
optional bool features_3d = 7;
|
||||
optional bool features_live = 8;
|
||||
optional bool features_purchased = 9;
|
||||
optional bool features_4k = 14;
|
||||
optional bool features_360 = 15;
|
||||
optional bool features_location = 23;
|
||||
optional bool features_hdr = 25;
|
||||
optional bool features_vr180 = 26;
|
||||
|
||||
enum UploadDate {
|
||||
ANY_DATE = 0;
|
||||
TODAY = 2;
|
||||
WEEK = 3;
|
||||
MONTH = 4;
|
||||
YEAR = 5;
|
||||
}
|
||||
|
||||
enum SearchType {
|
||||
ANY_TYPE = 0;
|
||||
VIDEO = 1;
|
||||
CHANNEL = 2;
|
||||
PLAYLIST = 3;
|
||||
MOVIE = 4;
|
||||
SHORTS = 9;
|
||||
}
|
||||
|
||||
enum Duration {
|
||||
ANY_DURATION = 0;
|
||||
OVER_TWENTY_MINS = 2;
|
||||
UNDER_THREE_MINS = 4;
|
||||
THREE_TO_TWENTY_MINS = 5;
|
||||
}
|
||||
|
||||
message MusicSearchType {
|
||||
optional bool song = 1;
|
||||
optional bool video = 2;
|
||||
optional bool album = 3;
|
||||
optional bool artist = 4;
|
||||
optional bool playlist = 5;
|
||||
}
|
||||
}
|
||||
|
||||
optional Filters filters = 2;
|
||||
}
|
||||
|
||||
message ChannelAnalytics {
|
||||
message Params {
|
||||
string channel_id = 1001;
|
||||
}
|
||||
|
||||
Params params = 32;
|
||||
}
|
||||
|
||||
message SoundInfoParams {
|
||||
message Sound {
|
||||
message Params {
|
||||
message Ids {
|
||||
string id_1 = 1;
|
||||
string id_2 = 2;
|
||||
string id_3 = 3;
|
||||
}
|
||||
Ids ids = 2;
|
||||
}
|
||||
Params params = 1;
|
||||
}
|
||||
|
||||
Sound sound = 94;
|
||||
}
|
||||
|
||||
message NotificationPreferences {
|
||||
string channel_id = 1;
|
||||
|
||||
message Preference {
|
||||
int32 index = 1;
|
||||
}
|
||||
|
||||
Preference pref_id = 2;
|
||||
|
||||
optional int32 number_0 = 3;
|
||||
optional int32 number_1 = 4;
|
||||
}
|
||||
|
||||
message LiveMessageParams {
|
||||
message Params {
|
||||
message Ids {
|
||||
string channel_id = 1;
|
||||
string video_id = 2;
|
||||
}
|
||||
Ids ids = 5;
|
||||
}
|
||||
|
||||
Params params = 1;
|
||||
|
||||
optional int32 number_0 = 2;
|
||||
optional int32 number_1 = 3;
|
||||
}
|
||||
|
||||
message GetCommentsSectionParams {
|
||||
message Context {
|
||||
string video_id = 2;
|
||||
}
|
||||
Context ctx = 2;
|
||||
|
||||
int32 unk_param = 3;
|
||||
|
||||
message Params {
|
||||
optional string unk_token = 1;
|
||||
|
||||
message Options {
|
||||
string video_id = 4;
|
||||
int32 sort_by = 6;
|
||||
int32 type = 15;
|
||||
optional string comment_id = 16;
|
||||
}
|
||||
|
||||
message RepliesOptions {
|
||||
string comment_id = 2;
|
||||
|
||||
message UnkOpts {
|
||||
int32 unk_param = 1;
|
||||
}
|
||||
UnkOpts unkopts = 4;
|
||||
|
||||
optional string channel_id = 5;
|
||||
string video_id = 6;
|
||||
|
||||
int32 unk_param_1 = 8;
|
||||
int32 unk_param_2 = 9;
|
||||
}
|
||||
|
||||
optional Options opts = 4;
|
||||
optional RepliesOptions replies_opts = 3;
|
||||
|
||||
optional int32 page = 5;
|
||||
string target = 8;
|
||||
}
|
||||
|
||||
Params params = 6;
|
||||
}
|
||||
|
||||
message CreateCommentParams {
|
||||
string video_id = 2;
|
||||
message Params {
|
||||
int32 index = 1;
|
||||
}
|
||||
Params params = 5;
|
||||
int32 number = 10;
|
||||
}
|
||||
|
||||
message PeformCommentActionParams {
|
||||
int32 type = 1;
|
||||
string comment_id = 3;
|
||||
string video_id = 5;
|
||||
|
||||
optional int32 unk_num = 2;
|
||||
optional string channel_id = 23;
|
||||
|
||||
message TranslateCommentParams {
|
||||
message Params {
|
||||
message Comment {
|
||||
string text = 1;
|
||||
}
|
||||
Comment comment = 1;
|
||||
}
|
||||
Params params = 3;
|
||||
|
||||
string comment_id = 2;
|
||||
string target_language = 4;
|
||||
}
|
||||
|
||||
optional TranslateCommentParams translate_comment_params = 31;
|
||||
}
|
||||
|
||||
message Hashtag {
|
||||
message Params {
|
||||
string hashtag = 1;
|
||||
int32 type = 3;
|
||||
}
|
||||
|
||||
Params params = 93;
|
||||
}
|
||||
|
||||
message ReelSequence {
|
||||
string short_id = 1;
|
||||
message Params {
|
||||
int32 number = 3;
|
||||
}
|
||||
|
||||
Params params = 5;
|
||||
int32 feature_2 = 10;
|
||||
int32 feature_3 = 13;
|
||||
}
|
||||
|
||||
message ShortsParam {
|
||||
message Field1 {
|
||||
optional int32 p1 = 1;
|
||||
}
|
||||
optional Field1 f1 = 1;
|
||||
optional int32 p59 = 59;
|
||||
}
|
||||
|
||||
message NextParams {
|
||||
repeated string video_id = 5;
|
||||
optional string playlist_title = 6;
|
||||
}
|
||||
|
||||
message CommunityPostParams {
|
||||
message Field1 {
|
||||
string ucid1 = 2;
|
||||
string post_id = 3;
|
||||
string ucid2 = 11;
|
||||
}
|
||||
|
||||
Field1 f1 = 56;
|
||||
}
|
||||
|
||||
message CommunityPostCommentsParamContainer {
|
||||
message Container {
|
||||
string location = 2;
|
||||
string proto_data = 3;
|
||||
}
|
||||
|
||||
Container f0 = 80226972;
|
||||
}
|
||||
|
||||
message CommunityPostCommentsParam {
|
||||
message CommentDataContainer {
|
||||
message CommentData {
|
||||
int32 sortBy = 6;
|
||||
int32 f0 = 15;
|
||||
int32 f1 = 25;
|
||||
string postId = 29;
|
||||
string channelId = 30;
|
||||
}
|
||||
|
||||
CommentData comment_data = 4;
|
||||
int32 f0 = 7;
|
||||
string title = 8;
|
||||
}
|
||||
|
||||
string title = 2;
|
||||
CommentDataContainer comment_data_container = 53;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
syntax = "proto3";
|
||||
package youtube.api.pfiinnertube;
|
||||
|
||||
message AttestationResponseData {
|
||||
optional string challenge = 1;
|
||||
optional string web_response = 2;
|
||||
optional string android_response = 3;
|
||||
optional bytes ios_response = 4;
|
||||
optional int32 error = 5;
|
||||
optional AdblockReporting adblock_reporting = 6;
|
||||
|
||||
message AdblockReporting {
|
||||
optional uint64 reporting_status = 1;
|
||||
optional uint64 broad_spectrum_detection_result = 2;
|
||||
}
|
||||
}
|
||||
15
deno/protos/youtube/api/pfiinnertube/capability_info.proto
Normal file
15
deno/protos/youtube/api/pfiinnertube/capability_info.proto
Normal file
@@ -0,0 +1,15 @@
|
||||
syntax = "proto3";
|
||||
package youtube.api.pfiinnertube;
|
||||
|
||||
message CapabilityInfo {
|
||||
optional string profile = 1;
|
||||
repeated InnerTubeCapability supported_capabilities = 2;
|
||||
repeated InnerTubeCapability disabled_capabilities = 3;
|
||||
optional string snapshot = 5;
|
||||
}
|
||||
|
||||
message InnerTubeCapability {
|
||||
optional uint32 capability = 1;
|
||||
optional uint32 features = 2;
|
||||
optional string experiment_flags = 6;
|
||||
}
|
||||
282
deno/protos/youtube/api/pfiinnertube/client_info.proto
Normal file
282
deno/protos/youtube/api/pfiinnertube/client_info.proto
Normal file
@@ -0,0 +1,282 @@
|
||||
syntax = "proto3";
|
||||
package youtube.api.pfiinnertube;
|
||||
|
||||
message ClientInfo {
|
||||
optional string hl = 1;
|
||||
optional string gl = 2;
|
||||
optional string remote_host = 4;
|
||||
optional string device_id = 6;
|
||||
optional string debug_device_id_override = 8;
|
||||
optional string carrier_geo = 10;
|
||||
optional bool cracked_hl = 11;
|
||||
optional string device_make = 12;
|
||||
optional string device_model = 13;
|
||||
optional string visitor_data = 14;
|
||||
optional string user_agent = 15;
|
||||
optional int32 client_name = 16;
|
||||
optional string client_version = 17;
|
||||
optional string os_name = 18;
|
||||
optional string os_version = 19;
|
||||
optional string project_id = 20;
|
||||
optional string accept_language = 21;
|
||||
optional string accept_region = 22;
|
||||
optional string original_url = 23;
|
||||
optional string raw_device_id = 25;
|
||||
optional string config_data = 27;
|
||||
optional string spacecast_token = 31;
|
||||
optional string internal_geo = 34;
|
||||
optional int32 screen_width_points = 37;
|
||||
optional int32 screen_height_points = 38;
|
||||
optional float screen_width_inches = 39;
|
||||
optional float screen_height_inches = 40;
|
||||
optional int32 screen_pixel_density = 41;
|
||||
optional int32 platform = 42;
|
||||
optional SpacecastClientInfo spacecast_client_info = 45;
|
||||
optional ClientFormFactor client_form_factor = 46;
|
||||
optional string forwarded_for = 48;
|
||||
optional MobileDataPlanInfo mobile_data_plan_info = 49;
|
||||
optional int32 gmscore_version_code = 50; // e.g. 241757026
|
||||
optional bool webp_support = 51;
|
||||
optional CameraType camera_type = 52;
|
||||
optional string experiments_token = 54;
|
||||
optional int32 window_width_points = 55;
|
||||
optional int32 window_height_points = 56;
|
||||
optional ConfigGroupsClientInfo config_info = 62;
|
||||
optional UnpluggedLocationInfo unplugged_location_info = 63;
|
||||
optional int32 android_sdk_version = 64;
|
||||
optional float screen_density_float = 65;
|
||||
optional int32 first_time_sign_in_experiment_ids = 66;
|
||||
optional int32 utc_offset_minutes = 67;
|
||||
optional bool animated_webp_support = 68;
|
||||
optional KidsAppInfo kids_app_info = 69;
|
||||
optional MusicAppInfo music_app_info = 70;
|
||||
optional TvAppInfo tv_app_info = 71;
|
||||
optional string internal_geo_ip = 72;
|
||||
optional UnpluggedAppInfo unplugged_app_info = 73;
|
||||
optional LocationInfo location_info = 74;
|
||||
optional string content_size_category = 76;
|
||||
optional float font_scale = 77;
|
||||
optional UserInterfaceTheme user_interface_theme = 78;
|
||||
optional string time_zone = 80;
|
||||
optional HomeGroupInfo home_group_info = 81;
|
||||
optional bytes eml_template_context = 84;
|
||||
optional bytes cold_app_bundle_config_data = 85;
|
||||
// repeated ExperimentsHeterodyne.ExperimentIds heterodyne_ids = 86;
|
||||
optional string browser_name = 87;
|
||||
optional string browser_version = 88;
|
||||
optional string location_playability_token = 89;
|
||||
optional string chipset = 92; // e.g. "qcom;taro"
|
||||
optional string firmware_version = 93;
|
||||
optional int64 memory_total_kbytes = 95;
|
||||
optional MainAppWebInfo main_app_web_info = 96;
|
||||
optional NotificationPermissionInfo notification_permission_info = 97;
|
||||
optional string device_brand = 98;
|
||||
// optional ClientStoreInfo client_store_info = 99;
|
||||
// optional SRSDataPushVersion srs_datapush_build_ids = 100;
|
||||
// optional PlayerDataPushVersion player_datapush_build_ids = 101;
|
||||
optional GLDeviceInfo gl_device_info = 102;
|
||||
|
||||
enum ClientFormFactor {
|
||||
// @TODO: Check these.
|
||||
UNKNOWN_FORM_FACTOR = 0;
|
||||
FORM_FACTOR_VAL1 = 1;
|
||||
FORM_FACTOR_VAL2 = 2;
|
||||
}
|
||||
|
||||
enum CameraType {
|
||||
UNKNOWN_CAMERA_TYPE = 0;
|
||||
}
|
||||
|
||||
enum UserInterfaceTheme {
|
||||
USER_INTERFACE_THEME_DARK = 0;
|
||||
USER_INTERFACE_THEME_LIGHT = 1;
|
||||
}
|
||||
|
||||
message MainAppWebInfo {
|
||||
optional string graft_url = 1;
|
||||
optional PwaInstallabilityStatus pwa_installability_status = 2;
|
||||
optional WebDisplayMode web_display_mode = 3;
|
||||
optional bool is_web_native_share_available = 4;
|
||||
optional StoreDigitalGoodsApiSupportStatus store_digital_goods_api_support_status = 5;
|
||||
|
||||
enum StoreDigitalGoodsApiSupportStatus { STORE_DIGITAL_GOODS_API_SUPPORT_STATUS_VAL0 = 0; }
|
||||
enum PwaInstallabilityStatus { PWA_INSTALLABILITY_STATUS_UNKNOWN = 0; }
|
||||
enum WebDisplayMode { WEB_DISPLAY_MODE_UNKNOWN = 0; }
|
||||
}
|
||||
|
||||
message NotificationPermissionInfo {
|
||||
optional NotificationsSetting notifications_setting = 1;
|
||||
optional int64 last_device_opt_in_change_time_ago_sec = 2;
|
||||
enum NotificationsSetting { NOTIFICATIONS_SETTING_UNKNOWN = 0; }
|
||||
}
|
||||
|
||||
message GLDeviceInfo {
|
||||
optional string gl_renderer = 1;
|
||||
optional int32 gl_es_version_major = 2;
|
||||
optional int32 gl_es_version_minor = 3;
|
||||
}
|
||||
|
||||
message SpacecastClientInfo {
|
||||
optional SpacecastAppliance appliances = 1;
|
||||
optional SpacecastInteractionLevel interaction_level = 2;
|
||||
|
||||
message SpacecastAppliance {
|
||||
optional bytes content_profile_token = 1;
|
||||
optional OperationalStatus status = 2;
|
||||
optional string hostname = 3;
|
||||
optional bool active = 4;
|
||||
optional string device_id = 5;
|
||||
|
||||
enum OperationalStatus { UNKNOWN = 0; }
|
||||
}
|
||||
|
||||
enum SpacecastInteractionLevel { UNKNOWN = 0; }
|
||||
}
|
||||
|
||||
message MobileDataPlanInfo {
|
||||
optional string cpid = 49;
|
||||
optional string serialized_data_plan_status = 50;
|
||||
optional bool data_saving_quality_picker_enabled = 52;
|
||||
optional string mccmnc = 53;
|
||||
}
|
||||
|
||||
message ConfigGroupsClientInfo {
|
||||
optional string cold_config_data = 1;
|
||||
optional string cold_hash_data = 3;
|
||||
optional string hot_hash_data = 5;
|
||||
optional string app_install_data = 6;
|
||||
}
|
||||
|
||||
message UnpluggedLocationInfo {
|
||||
optional int32 latitude_e7 = 1;
|
||||
optional int32 longitude_e7 = 2;
|
||||
optional int64 local_timestamp_ms = 3;
|
||||
optional string ip_address = 4;
|
||||
optional string timezone = 5;
|
||||
optional bool prefer_24_hour_time = 6;
|
||||
optional int32 location_radius_meters = 7;
|
||||
optional bool is_initial_load = 8;
|
||||
optional bool browser_permission_granted = 9;
|
||||
optional int32 client_permission_state = 10;
|
||||
optional string location_override_token = 11;
|
||||
}
|
||||
|
||||
message KidsAppInfo {
|
||||
optional KidsContentSettings content_settings = 1;
|
||||
optional KidsParentCurationMode parent_curation_mode = 2;
|
||||
optional KidsCategorySettings category_settings = 3;
|
||||
optional KidsUserEducationSettings user_education_settings = 4;
|
||||
|
||||
message KidsContentSettings {
|
||||
optional YTKidsNoSearchMode kids_no_search_mode = 1;
|
||||
optional YTKidsAgeUpMode age_up_mode = 2;
|
||||
optional KidsContentDensity content_density = 3;
|
||||
|
||||
enum YTKidsNoSearchMode {
|
||||
YT_KIDS_NO_SEARCH_MODE_OFF = 0;
|
||||
YT_KIDS_NO_SEARCH_MODE_ON = 1;
|
||||
}
|
||||
|
||||
enum YTKidsAgeUpMode {
|
||||
YT_KIDS_AGE_UP_MODE_OFF = 0;
|
||||
YT_KIDS_AGE_UP_MODE_ON = 1;
|
||||
}
|
||||
|
||||
enum KidsContentDensity {
|
||||
// @TODO: Check these.
|
||||
YT_KIDS_CONTENT_DENSITY_VAL1 = 0;
|
||||
YT_KIDS_CONTENT_DENSITY_VAL2 = 1;
|
||||
YT_KIDS_CONTENT_DENSITY_VAL3 = 2;
|
||||
}
|
||||
}
|
||||
|
||||
enum KidsParentCurationMode { KPCM_UNKNOWN = 0; }
|
||||
|
||||
message KidsCategorySettings {
|
||||
optional string enabled_categories = 1;
|
||||
}
|
||||
|
||||
message KidsUserEducationSettings {
|
||||
optional bool has_seen_home_chip_bar_user_education = 1;
|
||||
optional bool has_seen_home_pivot_bar_user_education = 2;
|
||||
optional bool has_seen_parent_muir_user_education = 3;
|
||||
}
|
||||
}
|
||||
|
||||
message MusicAppInfo {
|
||||
optional MusicPlayBackMode play_back_mode = 1;
|
||||
optional MusicLocationMasterSwitch music_location_master_switch = 2;
|
||||
optional MusicActivityMasterSwitch music_activity_master_switch = 3;
|
||||
optional bool offline_mixtape_enabled = 4;
|
||||
optional bool auto_offline_enabled = 5;
|
||||
optional IosBackgroundRefreshStatus ios_background_refresh_status = 6;
|
||||
optional int32 smart_downloads_song_limit = 7;
|
||||
optional bool transitioned_from_mixtape_to_smart_downloads = 8;
|
||||
optional PwaInstallabilityStatus pwa_installability_status = 9;
|
||||
optional WebDisplayMode web_display_mode = 10;
|
||||
optional MusicTier music_tier = 11;
|
||||
optional StoreDigitalGoodsApiSupportStatus store_digital_goods_api_support_status = 12;
|
||||
optional int64 smart_downloads_time_since_last_opt_out_sec = 13;
|
||||
|
||||
enum MusicPlayBackMode { MPBM_UNKNOWN = 0; }
|
||||
enum MusicLocationMasterSwitch { MLMS_UNKNOWN = 0; }
|
||||
enum MusicActivityMasterSwitch { MAMS_UNKNOWN = 0; }
|
||||
enum IosBackgroundRefreshStatus { UNKNOWN_STATUS = 0; }
|
||||
enum PwaInstallabilityStatus { PIS_UNKNOWN = 0; }
|
||||
enum WebDisplayMode { WDM_UNKNOWN = 0; }
|
||||
enum MusicTier { UNKNOWN_TIER = 0; }
|
||||
enum StoreDigitalGoodsApiSupportStatus { SDGAS_UNKNOWN = 0; }
|
||||
}
|
||||
|
||||
message TvAppInfo {
|
||||
optional string mdx_impacted_sessions_server_events = 3;
|
||||
optional bool enable_privacy_filter = 6;
|
||||
optional bool zylon_left_nav = 7;
|
||||
optional string certification_scope = 9;
|
||||
optional string living_room_po_token_id = 10;
|
||||
optional string js_engine_string = 12;
|
||||
optional VoiceCapability voice_capability = 13;
|
||||
optional string system_integrator = 14;
|
||||
optional string android_build_fingerprint = 18;
|
||||
optional string cobalt_app_version = 19;
|
||||
optional string cobalt_starboard_version = 20;
|
||||
optional bool use_start_playback_preview_command = 22;
|
||||
optional bool should_show_persistent_signin_on_home = 23;
|
||||
optional string android_play_services_version = 24;
|
||||
|
||||
message VoiceCapability {
|
||||
optional bool has_soft_mic_support = 1;
|
||||
optional bool has_hard_mic_support = 2;
|
||||
}
|
||||
}
|
||||
|
||||
message UnpluggedAppInfo {
|
||||
optional bool enable_filter_mode = 2;
|
||||
optional bool ios_notification_permission = 3;
|
||||
optional bool force_enable_epg_3 = 7;
|
||||
}
|
||||
|
||||
message LocationInfo {
|
||||
optional int32 location_info_status = 1;
|
||||
optional UrlStatus ulr_status = 2;
|
||||
optional string latitude_e7 = 3;
|
||||
optional string longitude_e7 = 4;
|
||||
optional string horizontal_accuracy_meters = 5;
|
||||
optional string location_freshness_ms = 6;
|
||||
optional int32 location_permission_authorization_status = 7;
|
||||
optional string location_override_token = 8;
|
||||
optional bool force_location_playability_token_refresh = 9;
|
||||
|
||||
message UrlStatus {
|
||||
optional int32 reporting_enabled_setting = 1;
|
||||
optional int32 history_enabled_setting = 2;
|
||||
optional bool is_allowed = 3;
|
||||
optional bool is_active = 4;
|
||||
}
|
||||
}
|
||||
|
||||
message HomeGroupInfo {
|
||||
optional bool is_part_of_group = 1;
|
||||
optional bool is_group = 3;
|
||||
}
|
||||
}
|
||||
14
deno/protos/youtube/api/pfiinnertube/get_watch_request.proto
Normal file
14
deno/protos/youtube/api/pfiinnertube/get_watch_request.proto
Normal file
@@ -0,0 +1,14 @@
|
||||
syntax = "proto3";
|
||||
package youtube.api.pfiinnertube;
|
||||
|
||||
import "youtube/api/pfiinnertube/innertube_context.proto";
|
||||
import "youtube/api/pfiinnertube/player_request.proto";
|
||||
import "youtube/api/pfiinnertube/watch_next_request.proto";
|
||||
import "youtube/api/pfiinnertube/reel_item_watch_request.proto";
|
||||
|
||||
message GetWatchRequest {
|
||||
optional InnerTubeContext context = 1;
|
||||
optional PlayerRequest player_request = 2;
|
||||
optional WatchNextRequest watch_next_request = 3;
|
||||
optional ReelItemWatchRequest reel_item_watch_request = 4;
|
||||
}
|
||||
45
deno/protos/youtube/api/pfiinnertube/innertube_context.proto
Normal file
45
deno/protos/youtube/api/pfiinnertube/innertube_context.proto
Normal file
@@ -0,0 +1,45 @@
|
||||
syntax = "proto3";
|
||||
package youtube.api.pfiinnertube;
|
||||
|
||||
import "misc/common.proto";
|
||||
import "youtube/api/pfiinnertube/client_info.proto";
|
||||
import "youtube/api/pfiinnertube/user_info.proto";
|
||||
import "youtube/api/pfiinnertube/capability_info.proto";
|
||||
import "youtube/api/pfiinnertube/request_info.proto";
|
||||
import "youtube/api/pfiinnertube/third_party_info.proto";
|
||||
|
||||
message InnerTubeContext {
|
||||
optional ClientInfo client = 1;
|
||||
optional UserInfo user = 3;
|
||||
optional CapabilityInfo capabilities = 4;
|
||||
optional RequestInfo request = 5;
|
||||
optional InnerTubeContext.ClickTrackingInfo click_tracking = 6;
|
||||
optional ThirdPartyInfo third_party = 7;
|
||||
optional ClientInfo remote_client = 8;
|
||||
optional InnerTubeContext.AdSignalsInfo ad_signals_info = 9;
|
||||
optional InnerTubeContext.ExperimentalData experimental_data = 10;
|
||||
optional string client_screen_nonce = 11;
|
||||
repeated InnerTubeContext.ActivePlayerInfo active_players = 12;
|
||||
|
||||
message ExperimentalData {
|
||||
repeated .misc.KeyValuePair params = 1;
|
||||
}
|
||||
|
||||
message ActivePlayerInfo {
|
||||
optional bytes player_context_params = 1;
|
||||
}
|
||||
|
||||
message ClickTrackingInfo {
|
||||
optional bytes click_tracking_params = 2;
|
||||
}
|
||||
|
||||
message AdSignalsInfo {
|
||||
repeated .misc.KeyValuePair params = 1;
|
||||
optional string bid = 2;
|
||||
optional string mutsu_id = 3;
|
||||
optional string consent_bump_state = 4;
|
||||
optional string advertising_id = 7;
|
||||
optional bool limit_ad_tracking = 9;
|
||||
optional string attribution_os_supported_version = 10;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
syntax = "proto3";
|
||||
package youtube.api.pfiinnertube;
|
||||
|
||||
import "youtube/api/pfiinnertube/innertube_context.proto";
|
||||
|
||||
message MetadataUpdateRequest {
|
||||
optional InnerTubeContext context = 1;
|
||||
optional string encrypted_video_id = 2;
|
||||
optional MdeTitleUpdateRequest title = 3;
|
||||
optional MdeDescriptionUpdateRequest description = 4;
|
||||
optional MdePrivacyUpdateRequest privacy = 5;
|
||||
optional MdeTagsUpdateRequest tags = 6;
|
||||
optional MdeCategoryUpdateRequest category = 7;
|
||||
optional MdeLicenseUpdateRequest license = 8;
|
||||
optional MdeAgeRestrictionUpdateRequest age_restriction = 11;
|
||||
optional MdeVideoStillRequestParams video_still = 20;
|
||||
optional MdeMadeForKidsUpdateRequestParams made_for_kids = 68;
|
||||
optional MdeRacyRequestParams racy = 69;
|
||||
|
||||
message MdeTitleUpdateRequest {
|
||||
optional string new_title = 1;
|
||||
}
|
||||
|
||||
message MdeDescriptionUpdateRequest {
|
||||
optional string new_description = 1;
|
||||
}
|
||||
|
||||
message MdePrivacyUpdateRequest {
|
||||
optional int32 new_privacy = 1;
|
||||
optional bool clear_privacy_draft = 2;
|
||||
}
|
||||
|
||||
message MdeTagsUpdateRequest {
|
||||
repeated string new_tags = 1;
|
||||
}
|
||||
|
||||
message MdeCategoryUpdateRequest {
|
||||
optional int32 new_category_id = 1;
|
||||
}
|
||||
|
||||
message MdeLicenseUpdateRequest {
|
||||
optional string new_license_id = 1;
|
||||
}
|
||||
|
||||
message MdeMadeForKidsUpdateRequestParams {
|
||||
optional int32 operation = 1;
|
||||
optional int32 new_mfk = 2;
|
||||
}
|
||||
|
||||
message MdeRacyRequestParams {
|
||||
optional int32 operation = 1;
|
||||
optional int32 new_racy = 2;
|
||||
}
|
||||
|
||||
message MdeAgeRestrictionUpdateRequest {
|
||||
optional bool new_is_age_restricted = 1;
|
||||
}
|
||||
|
||||
message MdeVideoStillRequestParams {
|
||||
optional int32 operation = 1;
|
||||
optional int32 new_still_id = 2;
|
||||
optional CustomThumbnailImage image = 3;
|
||||
optional CustomThumbnailImage test_image = 4;
|
||||
repeated ThumbnailExperimentImageData experiment_image = 6;
|
||||
|
||||
message ThumbnailExperimentImageData {
|
||||
optional CustomThumbnailImage image = 1;
|
||||
}
|
||||
|
||||
message CustomThumbnailImage {
|
||||
optional bytes raw_bytes = 1;
|
||||
optional string data_uri = 2;
|
||||
optional int64 frame_timestamp_us = 4;
|
||||
optional bool is_vertical = 5;
|
||||
}
|
||||
}
|
||||
}
|
||||
60
deno/protos/youtube/api/pfiinnertube/playback_context.proto
Normal file
60
deno/protos/youtube/api/pfiinnertube/playback_context.proto
Normal file
@@ -0,0 +1,60 @@
|
||||
syntax = "proto3";
|
||||
package youtube.api.pfiinnertube;
|
||||
|
||||
message PlaybackContext {
|
||||
optional ContentPlaybackContext content_playback_context = 1;
|
||||
|
||||
// @TODO: Dump these messages
|
||||
// optional AdPlaybackContext ad_playback_context = 2;
|
||||
// optional PrefetchPlaybackContext prefetch_playback_context = 3;
|
||||
// optional ResumePlaybackContext resume_playback_context = 4;
|
||||
// optional OfflinePlaybackContext offline_playback_context = 5;
|
||||
// optional ReloadPlaybackContext reload_playback_context = 7;
|
||||
// optional DevicePlaybackCapabilities device_playback_capabilities = 8;
|
||||
|
||||
message ContentPlaybackContext {
|
||||
optional string device_signals = 1;
|
||||
optional string rev_share_client_id = 3;
|
||||
optional uint32 time_since_last_ad_seconds = 4;
|
||||
optional int64 lact_milliseconds = 5;
|
||||
optional uint32 autoplays_since_last_ad = 6;
|
||||
optional uint32 vis = 8;
|
||||
optional bool fling = 9;
|
||||
optional bool splay = 10;
|
||||
optional bool autoplay = 11;
|
||||
optional uint64 time_of_last_instream_preroll_ad = 13;
|
||||
optional string current_url = 15;
|
||||
optional string referer = 16;
|
||||
optional bool load_annotations_by_demand = 23;
|
||||
optional bool auto_captions_default_on = 24;
|
||||
// optional ForceAdParameters force_ad_parameters = 25;
|
||||
optional bool sliced_bread = 27;
|
||||
optional bool autonav = 29;
|
||||
optional bool trailer = 30;
|
||||
// optional MdxPlaybackContext mdx_context = 31;
|
||||
// optional LivePlaybackContext live_context = 32;
|
||||
optional int32 player_width_pixels = 34;
|
||||
optional int32 player_height_pixels = 35;
|
||||
// optional Html5Preference html5_preference = 36;
|
||||
optional int32 snd = 37;
|
||||
optional int32 vnd = 38;
|
||||
// optional UnpluggedContentPlaybackContext unplugged_context = 40;
|
||||
optional int32 uao = 41;
|
||||
optional bool muted_autoplay = 44;
|
||||
// optional AutonavSettingsState autonav_state = 45;
|
||||
optional bool enable_privacy_filter = 46;
|
||||
optional bool is_living_room_deeplink = 47;
|
||||
optional uint32 signature_timestamp = 48;
|
||||
// optional TrailerPlaybackContext trailer_context = 49;
|
||||
optional bool is_inline_playback_no_ad = 50;
|
||||
optional bool is_inline_unmuted_playback = 51;
|
||||
// optional CustomTabContext custom_tab_context = 52;
|
||||
// optional VideoPlaybackPosition player_playback_position = 54;
|
||||
optional int64 play_package_version = 55;
|
||||
// optional CoWatchPlaybackContext co_watch_context = 56;
|
||||
// optional WatchAmbientModePlaybackContext watch_ambient_mode_context = 57;
|
||||
// optional CompositeVideoPlaybackContext composite_video_context = 58;
|
||||
optional bool is_sequence_entry = 60;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
syntax = "proto3";
|
||||
package youtube.api.pfiinnertube;
|
||||
|
||||
message PlayerAttestationRequestData {
|
||||
optional IosguardChallengeRequestData iosguard_request = 1;
|
||||
optional bool omit_botguard_data = 2;
|
||||
|
||||
message IosguardChallengeRequestData {
|
||||
optional bytes challenge_request = 1;
|
||||
}
|
||||
}
|
||||
36
deno/protos/youtube/api/pfiinnertube/player_request.proto
Normal file
36
deno/protos/youtube/api/pfiinnertube/player_request.proto
Normal file
@@ -0,0 +1,36 @@
|
||||
syntax = "proto3";
|
||||
package youtube.api.pfiinnertube;
|
||||
|
||||
import "youtube/api/pfiinnertube/innertube_context.proto";
|
||||
import "youtube/api/pfiinnertube/service_integrity_dimensions.proto";
|
||||
import "youtube/api/pfiinnertube/player_request_caption_params.proto";
|
||||
import "youtube/api/pfiinnertube/player_attestation_request_data.proto";
|
||||
import "youtube/api/pfiinnertube/playback_context.proto";
|
||||
|
||||
message PlayerRequest {
|
||||
optional InnerTubeContext context = 1;
|
||||
optional string video_id = 2;
|
||||
optional bool content_check_ok = 3;
|
||||
optional PlaybackContext playback_context = 4;
|
||||
optional bool racy_check_ok = 5;
|
||||
optional string id = 6;
|
||||
optional string t = 7;
|
||||
optional bool for_offline = 8;
|
||||
optional string playlist_id = 9;
|
||||
optional int32 playlist_index = 10;
|
||||
optional uint32 start_time_secs = 11;
|
||||
optional string params = 12;
|
||||
optional bytes offline_sharing_wrapped_key = 14;
|
||||
optional PlayerAttestationRequestData attestation_request = 16;
|
||||
optional string referring_app = 17;
|
||||
optional string referrer = 18;
|
||||
optional string serialized_third_party_embed_config = 19;
|
||||
optional bool proxied_by_onesie = 20;
|
||||
optional string host_app_token = 22;
|
||||
optional string cpn = 23;
|
||||
optional bool override_muted_at_start = 25;
|
||||
optional PlayerRequestCaptionParams caption_params = 26;
|
||||
optional ServiceIntegrityDimensions service_integrity_dimensions = 27;
|
||||
// optional VideoQualitySetting video_quality_setting_params = 28;
|
||||
optional bytes deferred_player_token = 29;
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
syntax = "proto3";
|
||||
package youtube.api.pfiinnertube;
|
||||
|
||||
message PlayerRequestCaptionParams {
|
||||
optional bool device_captions_on = 1;
|
||||
optional string device_captions_lang_pref = 2;
|
||||
optional string viewer_selected_caption_langs = 3;
|
||||
optional string cc_lang_pref = 4;
|
||||
optional bool cc_load_policy_on = 5;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
syntax = "proto3";
|
||||
package youtube.api.pfiinnertube;
|
||||
|
||||
import "youtube/api/pfiinnertube/innertube_context.proto";
|
||||
import "youtube/api/pfiinnertube/player_request.proto";
|
||||
|
||||
message ReelItemWatchRequest {
|
||||
optional InnerTubeContext context = 1;
|
||||
optional PlayerRequest player_request = 2;
|
||||
optional string params = 3;
|
||||
optional bool disable_player_response = 4;
|
||||
}
|
||||
51
deno/protos/youtube/api/pfiinnertube/request_info.proto
Normal file
51
deno/protos/youtube/api/pfiinnertube/request_info.proto
Normal file
@@ -0,0 +1,51 @@
|
||||
syntax = "proto3";
|
||||
package youtube.api.pfiinnertube;
|
||||
|
||||
import "misc/common.proto";
|
||||
import "youtube/api/pfiinnertube/attestation_response_data.proto";
|
||||
|
||||
message RequestInfo {
|
||||
optional string third_party_digest = 6;
|
||||
optional bool use_ssl = 7;
|
||||
optional bool return_error_detail = 9;
|
||||
// "If-None-Match" HTTP header?
|
||||
optional string if_none_match = 12;
|
||||
optional bool return_log_entry = 13;
|
||||
optional bool is_prefetch = 14;
|
||||
repeated misc.KeyValuePair internal_experiment_flags = 15;
|
||||
optional bool return_debug_data = 16;
|
||||
optional string innertubez = 18;
|
||||
optional bool trace_proto = 23;
|
||||
optional bool return_log_entry_json = 24;
|
||||
optional string sherlog_username = 25;
|
||||
// repeated ConsistencyTokenJar consistency_token_jars = 26;
|
||||
optional ReauthRequestInfo reauth_request_info = 29;
|
||||
optional SessionInfo session_info = 30;
|
||||
optional bool return_log_entry_proto = 31;
|
||||
// External pre-request context as a string
|
||||
optional string external_prequest_context = 32;
|
||||
// repeated InnerTubeTokenJar innertube_token_jar = 33;
|
||||
// Would a botguard/droidguard response here allow playback?
|
||||
optional AttestationResponseData attestation_response_data = 34;
|
||||
optional bytes eats = 35;
|
||||
optional RequestQoS request_qos = 36;
|
||||
|
||||
// Quality of Service?
|
||||
message RequestQoS {
|
||||
optional Criticality criticality = 1;
|
||||
}
|
||||
|
||||
enum Criticality {
|
||||
CRITICALITY_UNSPECIFIED = 0;
|
||||
CRITICALITY_CRITICAL = 1;
|
||||
CRITICALITY_NON_CRITICAL = 2;
|
||||
}
|
||||
|
||||
message SessionInfo {
|
||||
optional string token = 1;
|
||||
}
|
||||
|
||||
message ReauthRequestInfo {
|
||||
optional string encoded_reauth_proof_token = 1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
syntax = "proto3";
|
||||
package youtube.api.pfiinnertube;
|
||||
|
||||
message ServiceIntegrityDimensions {
|
||||
optional bytes po_token = 1;
|
||||
}
|
||||
17
deno/protos/youtube/api/pfiinnertube/third_party_info.proto
Normal file
17
deno/protos/youtube/api/pfiinnertube/third_party_info.proto
Normal file
@@ -0,0 +1,17 @@
|
||||
syntax = "proto3";
|
||||
package youtube.api.pfiinnertube;
|
||||
|
||||
message ThirdPartyInfo {
|
||||
optional string developer_key = 1;
|
||||
optional string app_name = 2;
|
||||
optional string app_publisher = 3;
|
||||
optional string embed_url = 4;
|
||||
optional string app_version = 6;
|
||||
optional ThirdPartyInfo.EmbeddedPlayerContext embedded_player_context = 7;
|
||||
|
||||
message EmbeddedPlayerContext {
|
||||
optional string ancestor_origins = 1;
|
||||
optional string embedded_player_encrypted_context = 2;
|
||||
optional bool ancestor_origins_supported = 3;
|
||||
}
|
||||
}
|
||||
19
deno/protos/youtube/api/pfiinnertube/user_info.proto
Normal file
19
deno/protos/youtube/api/pfiinnertube/user_info.proto
Normal file
@@ -0,0 +1,19 @@
|
||||
syntax = "proto3";
|
||||
package youtube.api.pfiinnertube;
|
||||
|
||||
message UserInfo {
|
||||
optional string on_behalf_of_user = 3;
|
||||
optional bool enable_safety_mode = 7;
|
||||
repeated UserInfo.CredentialTransferToken credential_transfer_tokens = 12;
|
||||
optional UserInfo.DelegatePurchases delegate_purchases = 13;
|
||||
optional UserInfo.KidsParent kids_parent = 14;
|
||||
optional bool is_incognito = 15;
|
||||
optional bool locked_safety_mode = 16;
|
||||
optional UserInfo.DelegationContext delegation_context = 17;
|
||||
optional string serialized_delegation_context = 18;
|
||||
|
||||
message KidsParent { }
|
||||
message DelegatePurchases { }
|
||||
message DelegationContext { }
|
||||
message CredentialTransferToken { }
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
syntax = "proto3";
|
||||
package youtube.api.pfiinnertube;
|
||||
|
||||
import "youtube/api/pfiinnertube/innertube_context.proto";
|
||||
|
||||
message WatchNextRequest {
|
||||
optional InnerTubeContext context = 1;
|
||||
optional string video_id = 2;
|
||||
optional string playlist_id = 4;
|
||||
optional string params = 6;
|
||||
optional string continuation = 8;
|
||||
optional bool is_ad_playback = 9;
|
||||
optional bool mdx_use_dev_server = 10;
|
||||
optional string referrer = 12;
|
||||
optional string referring_app = 13;
|
||||
optional string ad_params = 16;
|
||||
optional bool request_music_sequence = 18;
|
||||
optional bool enable_mdx_autoplay = 21;
|
||||
optional bool is_mdx_playback = 22;
|
||||
optional bool racy_check_ok = 24;
|
||||
optional bool content_check_ok = 25;
|
||||
optional bool is_audio_only = 26;
|
||||
optional bool autonav_enabled = 27;
|
||||
optional bool enable_persistent_playlist_panel = 30;
|
||||
optional string playlist_set_video_id = 31;
|
||||
optional bool show_ru_invalid_token_message = 35;
|
||||
optional string serialized_third_party_embed_config = 37;
|
||||
optional bool show_content_owner_only = 38;
|
||||
optional bool is_embed_preview = 42;
|
||||
optional string last_scrubbed_inline_playback_video_id = 43;
|
||||
optional string last_audio_turned_on_inline_playback_video_id = 44;
|
||||
optional string last_audio_turned_off_inline_playback_video_id = 45;
|
||||
optional bool captions_requested = 47;
|
||||
optional bytes queue_context_params = 50;
|
||||
optional bool show_shorts_only = 55;
|
||||
}
|
||||
@@ -1,20 +1,9 @@
|
||||
import Session from './core/Session.ts';
|
||||
|
||||
import { Kids, Music, Studio } from './core/clients/index.ts';
|
||||
import { AccountManager, InteractionManager, PlaylistManager } from './core/managers/index.ts';
|
||||
import { Feed, TabbedFeed } from './core/mixins/index.ts';
|
||||
|
||||
import {
|
||||
BrowseEndpoint,
|
||||
GetNotificationMenuEndpoint,
|
||||
GuideEndpoint,
|
||||
NextEndpoint,
|
||||
PlayerEndpoint,
|
||||
ResolveURLEndpoint,
|
||||
SearchEndpoint,
|
||||
Reel,
|
||||
Notification
|
||||
} from './core/endpoints/index.ts';
|
||||
|
||||
import {
|
||||
Channel,
|
||||
Comments,
|
||||
@@ -28,307 +17,403 @@ import {
|
||||
Search,
|
||||
VideoInfo
|
||||
} from './parser/youtube/index.ts';
|
||||
import { ShortFormVideoInfo } from './parser/ytshorts/index.ts';
|
||||
|
||||
import { VideoInfo as ShortsVideoInfo } from './parser/ytshorts/index.ts';
|
||||
|
||||
import { NavigateAction } from './parser/continuations.ts';
|
||||
import NavigationEndpoint from './parser/classes/NavigationEndpoint.ts';
|
||||
|
||||
import * as Proto from './proto/index.ts';
|
||||
import * as Constants from './utils/Constants.ts';
|
||||
import { InnertubeError, generateRandomString, throwIfMissing } from './utils/Utils.ts';
|
||||
|
||||
|
||||
import type { ApiResponse } from './core/Actions.ts';
|
||||
import type { INextRequest } from './types/index.ts';
|
||||
import type { IBrowseResponse, IParsedResponse } from './parser/types/index.ts';
|
||||
import type { DownloadOptions, FormatOptions } from './types/FormatUtils.ts';
|
||||
import type { SessionOptions } from './core/Session.ts';
|
||||
import type Format from './parser/classes/misc/Format.ts';
|
||||
|
||||
export type InnertubeConfig = SessionOptions;
|
||||
import * as Constants from './utils/Constants.ts';
|
||||
import { generateRandomString, InnertubeError, throwIfMissing, u8ToBase64 } from './utils/Utils.ts';
|
||||
|
||||
export type InnerTubeClient = 'WEB' | 'iOS' | 'ANDROID' | 'YTMUSIC_ANDROID' | 'YTMUSIC' | 'YTSTUDIO_ANDROID' | 'TV_EMBEDDED' | 'YTKIDS';
|
||||
import type { ApiResponse } from './core/Actions.ts';
|
||||
import type {
|
||||
DownloadOptions,
|
||||
EngagementType,
|
||||
FormatOptions,
|
||||
GetVideoInfoOptions,
|
||||
InnerTubeClient,
|
||||
InnerTubeConfig,
|
||||
SearchFilters
|
||||
} from './types/index.ts';
|
||||
import type { IBrowseResponse, IParsedResponse } from './parser/index.ts';
|
||||
|
||||
export type SearchFilters = Partial<{
|
||||
upload_date: 'all' | 'hour' | 'today' | 'week' | 'month' | 'year';
|
||||
type: 'all' | 'video' | 'channel' | 'playlist' | 'movie';
|
||||
duration: 'all' | 'short' | 'medium' | 'long';
|
||||
sort_by: 'relevance' | 'rating' | 'upload_date' | 'view_count';
|
||||
features: ('hd' | 'subtitles' | 'creative_commons' | '3d' | 'live' | 'purchased' | '4k' | '360' | 'location' | 'hdr' | 'vr180')[];
|
||||
}>;
|
||||
import {
|
||||
CommunityPostCommentsParam,
|
||||
CommunityPostCommentsParamContainer,
|
||||
CommunityPostParams,
|
||||
GetCommentsSectionParams,
|
||||
Hashtag,
|
||||
ReelSequence,
|
||||
SearchFilter,
|
||||
SearchFilter_Filters_Duration,
|
||||
SearchFilter_Filters_SearchType,
|
||||
SearchFilter_Filters_UploadDate,
|
||||
SearchFilter_Prioritize
|
||||
} from '../protos/generated/misc/params.ts';
|
||||
|
||||
/**
|
||||
* Provides access to various services and modules in the YouTube API.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* import { Innertube, UniversalCache } from 'youtubei.ts';
|
||||
* const innertube = await Innertube.create({ cache: new UniversalCache(true)});
|
||||
* ```
|
||||
*/
|
||||
export default class Innertube {
|
||||
#session: Session;
|
||||
readonly #session: Session;
|
||||
|
||||
constructor(session: Session) {
|
||||
this.#session = session;
|
||||
}
|
||||
|
||||
static async create(config: InnertubeConfig = {}): Promise<Innertube> {
|
||||
static async create(config: InnerTubeConfig = {}): Promise<Innertube> {
|
||||
return new Innertube(await Session.create(config));
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves video info.
|
||||
* @param target - The video id or `NavigationEndpoint`.
|
||||
* @param client - The client to use.
|
||||
*/
|
||||
async getInfo(target: string | NavigationEndpoint, client?: InnerTubeClient): Promise<VideoInfo> {
|
||||
async getInfo(target: string | NavigationEndpoint, options?: GetVideoInfoOptions): Promise<VideoInfo> {
|
||||
throwIfMissing({ target });
|
||||
|
||||
let next_payload: INextRequest;
|
||||
const payload = {
|
||||
videoId: target instanceof NavigationEndpoint ? target.payload?.videoId : target,
|
||||
playlistId: target instanceof NavigationEndpoint ? target.payload?.playlistId : undefined,
|
||||
playlistIndex: target instanceof NavigationEndpoint ? target.payload?.playlistIndex : undefined,
|
||||
params: target instanceof NavigationEndpoint ? target.payload?.params : undefined,
|
||||
racyCheckOk: true,
|
||||
contentCheckOk: true
|
||||
};
|
||||
|
||||
if (target instanceof NavigationEndpoint) {
|
||||
next_payload = NextEndpoint.build({
|
||||
video_id: target.payload?.videoId,
|
||||
playlist_id: target.payload?.playlistId,
|
||||
params: target.payload?.params,
|
||||
playlist_index: target.payload?.index
|
||||
});
|
||||
} else if (typeof target === 'string') {
|
||||
next_payload = NextEndpoint.build({
|
||||
video_id: target
|
||||
});
|
||||
} else {
|
||||
throw new InnertubeError('Invalid target, expected either a video id or a valid NavigationEndpoint', target);
|
||||
const watch_endpoint = new NavigationEndpoint({ watchEndpoint: payload });
|
||||
const watch_next_endpoint = new NavigationEndpoint({ watchNextEndpoint: payload });
|
||||
|
||||
const session = this.#session;
|
||||
|
||||
const extra_payload: Record<string, any> = {
|
||||
playbackContext: {
|
||||
contentPlaybackContext: {
|
||||
vis: 0,
|
||||
splay: false,
|
||||
lactMilliseconds: '-1',
|
||||
signatureTimestamp: session.player?.signature_timestamp
|
||||
}
|
||||
},
|
||||
client: options?.client
|
||||
};
|
||||
|
||||
if (options?.po_token) {
|
||||
extra_payload.serviceIntegrityDimensions = {
|
||||
poToken: options.po_token
|
||||
};
|
||||
} else if (session.po_token) {
|
||||
extra_payload.serviceIntegrityDimensions = {
|
||||
poToken: session.po_token
|
||||
};
|
||||
}
|
||||
|
||||
if (!next_payload.videoId)
|
||||
throw new InnertubeError('Video id cannot be empty', next_payload);
|
||||
const watch_response = watch_endpoint.call(session.actions, extra_payload);
|
||||
const watch_next_response = watch_next_endpoint.call(session.actions);
|
||||
|
||||
const player_payload = PlayerEndpoint.build({
|
||||
video_id: next_payload.videoId,
|
||||
playlist_id: next_payload?.playlistId,
|
||||
client: client,
|
||||
sts: this.#session.player?.sts
|
||||
});
|
||||
|
||||
const player_response = this.actions.execute(PlayerEndpoint.PATH, player_payload);
|
||||
const next_response = this.actions.execute(NextEndpoint.PATH, next_payload);
|
||||
const response = await Promise.all([ player_response, next_response ]);
|
||||
const response = await Promise.all([ watch_response, watch_next_response ]);
|
||||
|
||||
const cpn = generateRandomString(16);
|
||||
|
||||
return new VideoInfo(response, this.actions, cpn);
|
||||
return new VideoInfo(response, session.actions, cpn);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves basic video info.
|
||||
* @param video_id - The video id.
|
||||
* @param client - The client to use.
|
||||
*/
|
||||
async getBasicInfo(video_id: string, client?: InnerTubeClient): Promise<VideoInfo> {
|
||||
async getBasicInfo(video_id: string, options?: GetVideoInfoOptions): Promise<VideoInfo> {
|
||||
throwIfMissing({ video_id });
|
||||
|
||||
const response = await this.actions.execute(
|
||||
PlayerEndpoint.PATH, PlayerEndpoint.build({
|
||||
video_id: video_id,
|
||||
client: client,
|
||||
sts: this.#session.player?.sts
|
||||
})
|
||||
);
|
||||
const watch_endpoint = new NavigationEndpoint({
|
||||
watchEndpoint: {
|
||||
videoId: video_id,
|
||||
racyCheckOk: true,
|
||||
contentCheckOk: true
|
||||
}
|
||||
});
|
||||
|
||||
const session = this.#session;
|
||||
|
||||
const extra_payload: Record<string, any> = {
|
||||
playbackContext: {
|
||||
contentPlaybackContext: {
|
||||
vis: 0,
|
||||
splay: false,
|
||||
lactMilliseconds: '-1',
|
||||
signatureTimestamp: session.player?.signature_timestamp
|
||||
}
|
||||
},
|
||||
client: options?.client
|
||||
};
|
||||
|
||||
if (options?.po_token) {
|
||||
extra_payload.serviceIntegrityDimensions = {
|
||||
poToken: options.po_token
|
||||
};
|
||||
} else if (session.po_token) {
|
||||
extra_payload.serviceIntegrityDimensions = {
|
||||
poToken: session.po_token
|
||||
};
|
||||
}
|
||||
|
||||
const watch_response = await watch_endpoint.call(session.actions, extra_payload);
|
||||
|
||||
const cpn = generateRandomString(16);
|
||||
|
||||
return new VideoInfo([ response ], this.actions, cpn);
|
||||
return new VideoInfo([ watch_response ], session.actions, cpn);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves shorts info.
|
||||
* @param short_id - The short id.
|
||||
* @param client - The client to use.
|
||||
*/
|
||||
async getShortsWatchItem(short_id: string, client?: InnerTubeClient): Promise<ShortsVideoInfo> {
|
||||
throwIfMissing({ short_id });
|
||||
async getShortsVideoInfo(video_id: string, client?: InnerTubeClient): Promise<ShortFormVideoInfo> {
|
||||
throwIfMissing({ video_id });
|
||||
|
||||
const watchResponse = this.actions.execute(
|
||||
Reel.WatchEndpoint.PATH, Reel.WatchEndpoint.build({
|
||||
short_id: short_id,
|
||||
client: client
|
||||
})
|
||||
);
|
||||
const reel_watch_endpoint = new NavigationEndpoint({
|
||||
reelWatchEndpoint: {
|
||||
disablePlayerResponse: false,
|
||||
params: 'CAUwAg%3D%3D',
|
||||
videoId: video_id
|
||||
}
|
||||
});
|
||||
|
||||
const sequenceResponse = this.actions.execute(
|
||||
Reel.WatchSequenceEndpoint.PATH, Reel.WatchSequenceEndpoint.build({
|
||||
sequenceParams: Proto.encodeReelSequence(short_id)
|
||||
})
|
||||
);
|
||||
const actions = this.#session.actions;
|
||||
|
||||
const response = await Promise.all([ watchResponse, sequenceResponse ]);
|
||||
const reel_watch_response = reel_watch_endpoint.call(actions, { client });
|
||||
|
||||
return new ShortsVideoInfo(response, this.actions);
|
||||
const writer = ReelSequence.encode({
|
||||
shortId: video_id,
|
||||
params: {
|
||||
number: 5
|
||||
},
|
||||
feature2: 25,
|
||||
feature3: 0
|
||||
});
|
||||
|
||||
const params = encodeURIComponent(u8ToBase64(writer.finish()));
|
||||
|
||||
const sequence_response = actions.execute('/reel/reel_watch_sequence', { sequenceParams: params });
|
||||
|
||||
const response = await Promise.all([ reel_watch_response, sequence_response ]);
|
||||
|
||||
const cpn = generateRandomString(16);
|
||||
|
||||
return new ShortFormVideoInfo([ response[0] ], actions, cpn, response[1]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches a given query.
|
||||
* @param query - The search query.
|
||||
* @param filters - Search filters.
|
||||
*/
|
||||
async search(query: string, filters: SearchFilters = {}): Promise<Search> {
|
||||
throwIfMissing({ query });
|
||||
|
||||
const response = await this.actions.execute(
|
||||
SearchEndpoint.PATH, SearchEndpoint.build({
|
||||
query, params: filters ? Proto.encodeSearchFilters(filters) : undefined
|
||||
})
|
||||
);
|
||||
const search_filter: SearchFilter = {};
|
||||
|
||||
search_filter.filters = {};
|
||||
|
||||
if (filters.prioritize) {
|
||||
search_filter.prioritize = SearchFilter_Prioritize[filters.prioritize.toUpperCase() as keyof typeof SearchFilter_Prioritize];
|
||||
}
|
||||
|
||||
if (filters.upload_date) {
|
||||
search_filter.filters.uploadDate = SearchFilter_Filters_UploadDate[filters.upload_date.toUpperCase() as keyof typeof SearchFilter_Filters_UploadDate];
|
||||
}
|
||||
|
||||
if (filters.type) {
|
||||
search_filter.filters.type = SearchFilter_Filters_SearchType[filters.type.toUpperCase() as keyof typeof SearchFilter_Filters_SearchType];
|
||||
}
|
||||
|
||||
if (filters.duration) {
|
||||
search_filter.filters.duration = SearchFilter_Filters_Duration[filters.duration.toUpperCase() as keyof typeof SearchFilter_Filters_Duration];
|
||||
}
|
||||
|
||||
if (filters.features) {
|
||||
for (const feature of filters.features) {
|
||||
switch (feature) {
|
||||
case '360':
|
||||
search_filter.filters.features360 = true;
|
||||
break;
|
||||
case '3d':
|
||||
search_filter.filters.features3d = true;
|
||||
break;
|
||||
case '4k':
|
||||
search_filter.filters.features4k = true;
|
||||
break;
|
||||
case 'creative_commons':
|
||||
search_filter.filters.featuresCreativeCommons = true;
|
||||
break;
|
||||
case 'hd':
|
||||
search_filter.filters.featuresHd = true;
|
||||
break;
|
||||
case 'hdr':
|
||||
search_filter.filters.featuresHdr = true;
|
||||
break;
|
||||
case 'live':
|
||||
search_filter.filters.featuresLive = true;
|
||||
break;
|
||||
case 'location':
|
||||
search_filter.filters.featuresLocation = true;
|
||||
break;
|
||||
case 'purchased':
|
||||
search_filter.filters.featuresPurchased = true;
|
||||
break;
|
||||
case 'subtitles':
|
||||
search_filter.filters.featuresSubtitles = true;
|
||||
break;
|
||||
case 'vr180':
|
||||
search_filter.filters.featuresVr180 = true;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const search_endpoint = new NavigationEndpoint({
|
||||
searchEndpoint: {
|
||||
query,
|
||||
params: filters ? encodeURIComponent(u8ToBase64(SearchFilter.encode(search_filter).finish())) : undefined
|
||||
}
|
||||
});
|
||||
const response = await search_endpoint.call(this.#session.actions);
|
||||
|
||||
return new Search(this.actions, response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves search suggestions for a given query.
|
||||
* @param query - The search query.
|
||||
*/
|
||||
async getSearchSuggestions(query: string): Promise<string[]> {
|
||||
throwIfMissing({ query });
|
||||
async getSearchSuggestions(query: string, previous_query?: string): Promise<string[]> {
|
||||
const session = this.#session;
|
||||
|
||||
const url = new URL(`${Constants.URLS.YT_SUGGESTIONS}search`);
|
||||
url.searchParams.set('q', query);
|
||||
url.searchParams.set('hl', this.#session.context.client.hl);
|
||||
url.searchParams.set('gl', this.#session.context.client.gl);
|
||||
url.searchParams.set('ds', 'yt');
|
||||
const url = new URL(`${Constants.URLS.YT_SUGGESTIONS}/complete/search`);
|
||||
url.searchParams.set('client', 'youtube');
|
||||
url.searchParams.set('xssi', 't');
|
||||
url.searchParams.set('oe', 'UTF');
|
||||
url.searchParams.set('gs_ri', 'youtube');
|
||||
url.searchParams.set('gs_id', '0');
|
||||
url.searchParams.set('cp', '0');
|
||||
url.searchParams.set('ds', 'yt');
|
||||
url.searchParams.set('sugexp', Constants.CLIENTS.WEB.SUGG_EXP_ID);
|
||||
url.searchParams.set('hl', session.context.client.hl);
|
||||
url.searchParams.set('gl', session.context.client.gl);
|
||||
url.searchParams.set('q', query);
|
||||
|
||||
const response = await this.#session.http.fetch(url);
|
||||
const response_data = await response.text();
|
||||
if (previous_query)
|
||||
url.searchParams.set('pq', previous_query);
|
||||
|
||||
const data = JSON.parse(response_data.replace(')]}\'', ''));
|
||||
const suggestions = data[1].map((suggestion: any) => suggestion[0]);
|
||||
const response = await session.http.fetch_function(url, {
|
||||
headers: {
|
||||
'Cookie': session.cookie || ''
|
||||
}
|
||||
});
|
||||
|
||||
const text = await response.text();
|
||||
|
||||
return suggestions;
|
||||
const data = JSON.parse(text.replace('window.google.ac.h(', '').slice(0, -1));
|
||||
return data[1].map((suggestion: (string | number)[]) => suggestion[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves comments for a video.
|
||||
* @param video_id - The video id.
|
||||
* @param sort_by - Sorting options.
|
||||
*/
|
||||
async getComments(video_id: string, sort_by?: 'TOP_COMMENTS' | 'NEWEST_FIRST'): Promise<Comments> {
|
||||
async getComments(video_id: string, sort_by?: 'TOP_COMMENTS' | 'NEWEST_FIRST', comment_id?: string): Promise<Comments> {
|
||||
throwIfMissing({ video_id });
|
||||
|
||||
const response = await this.actions.execute(
|
||||
NextEndpoint.PATH, NextEndpoint.build({
|
||||
continuation: Proto.encodeCommentsSectionParams(video_id, {
|
||||
sort_by: sort_by || 'TOP_COMMENTS'
|
||||
})
|
||||
})
|
||||
);
|
||||
const SORT_OPTIONS = {
|
||||
TOP_COMMENTS: 0,
|
||||
NEWEST_FIRST: 1
|
||||
};
|
||||
|
||||
const token = GetCommentsSectionParams.encode({
|
||||
ctx: {
|
||||
videoId: video_id
|
||||
},
|
||||
unkParam: 6,
|
||||
params: {
|
||||
opts: {
|
||||
videoId: video_id,
|
||||
sortBy: SORT_OPTIONS[sort_by || 'TOP_COMMENTS'],
|
||||
type: 2,
|
||||
commentId: comment_id || ''
|
||||
},
|
||||
target: 'comments-section'
|
||||
}
|
||||
});
|
||||
|
||||
const continuation = encodeURIComponent(u8ToBase64(token.finish()));
|
||||
|
||||
const continuation_command = new NavigationEndpoint({
|
||||
continuationCommand: {
|
||||
request: 'CONTINUATION_REQUEST_TYPE_WATCH_NEXT',
|
||||
token: continuation
|
||||
}
|
||||
});
|
||||
|
||||
const response = await continuation_command.call(this.#session.actions);
|
||||
|
||||
return new Comments(this.actions, response.data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves YouTube's home feed (aka recommendations).
|
||||
*/
|
||||
async getHomeFeed(): Promise<HomeFeed> {
|
||||
const response = await this.actions.execute(
|
||||
BrowseEndpoint.PATH, BrowseEndpoint.build({ browse_id: 'FEwhat_to_watch' })
|
||||
);
|
||||
const browse_endpoint = new NavigationEndpoint({ browseEndpoint: { browseId: 'FEwhat_to_watch' } });
|
||||
const response = await browse_endpoint.call(this.#session.actions);
|
||||
return new HomeFeed(this.actions, response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves YouTube's content guide.
|
||||
*/
|
||||
async getGuide(): Promise<Guide> {
|
||||
const response = await this.actions.execute(GuideEndpoint.PATH);
|
||||
const response = await this.actions.execute('/guide');
|
||||
return new Guide(response.data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the account's library.
|
||||
*/
|
||||
async getLibrary(): Promise<Library> {
|
||||
const response = await this.actions.execute(
|
||||
BrowseEndpoint.PATH, BrowseEndpoint.build({ browse_id: 'FElibrary' })
|
||||
);
|
||||
const browse_endpoint = new NavigationEndpoint({ browseEndpoint: { browseId: 'FElibrary' } });
|
||||
const response = await browse_endpoint.call(this.#session.actions);
|
||||
return new Library(this.actions, response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves watch history.
|
||||
* Which can also be achieved with {@link getLibrary}.
|
||||
*/
|
||||
async getHistory(): Promise<History> {
|
||||
const response = await this.actions.execute(
|
||||
BrowseEndpoint.PATH, BrowseEndpoint.build({ browse_id: 'FEhistory' })
|
||||
);
|
||||
const browse_endpoint = new NavigationEndpoint({ browseEndpoint: { browseId: 'FEhistory' } });
|
||||
const response = await browse_endpoint.call(this.#session.actions);
|
||||
return new History(this.actions, response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves Trending content.
|
||||
*/
|
||||
async getTrending(): Promise<TabbedFeed<IBrowseResponse>> {
|
||||
const response = await this.actions.execute(
|
||||
BrowseEndpoint.PATH, { ...BrowseEndpoint.build({ browse_id: 'FEtrending' }), parse: true }
|
||||
);
|
||||
return new TabbedFeed(this.actions, response);
|
||||
async getCourses(): Promise<Feed<IBrowseResponse>> {
|
||||
const browse_endpoint = new NavigationEndpoint({ browseEndpoint: { browseId: 'FEcourses_destination' } });
|
||||
const response = await browse_endpoint.call(this.#session.actions, { parse: true });
|
||||
return new Feed(this.actions, response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves Subscriptions feed.
|
||||
*/
|
||||
async getSubscriptionsFeed(): Promise<Feed<IBrowseResponse>> {
|
||||
const response = await this.actions.execute(
|
||||
BrowseEndpoint.PATH, { ...BrowseEndpoint.build({ browse_id: 'FEsubscriptions' }), parse: true }
|
||||
);
|
||||
const browse_endpoint = new NavigationEndpoint({ browseEndpoint: { browseId: 'FEsubscriptions' } });
|
||||
const response = await browse_endpoint.call(this.#session.actions, { parse: true });
|
||||
return new Feed(this.actions, response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves Channels feed.
|
||||
*/
|
||||
async getChannelsFeed(): Promise<Feed<IBrowseResponse>> {
|
||||
const response = await this.actions.execute(
|
||||
BrowseEndpoint.PATH, { ...BrowseEndpoint.build({ browse_id: 'FEchannels' }), parse: true }
|
||||
);
|
||||
const browse_endpoint = new NavigationEndpoint({ browseEndpoint: { browseId: 'FEchannels' } });
|
||||
const response = await browse_endpoint.call(this.#session.actions, { parse: true });
|
||||
return new Feed(this.actions, response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves contents for a given channel.
|
||||
* @param id - Channel id
|
||||
*/
|
||||
async getChannel(id: string): Promise<Channel> {
|
||||
throwIfMissing({ id });
|
||||
const response = await this.actions.execute(
|
||||
BrowseEndpoint.PATH, BrowseEndpoint.build({ browse_id: id })
|
||||
);
|
||||
return new Channel(this.actions, response);
|
||||
const browse_endpoint = new NavigationEndpoint({ browseEndpoint: { browseId: id } });
|
||||
let response = await browse_endpoint.call<IBrowseResponse>(this.#session.actions, { parse: true });
|
||||
|
||||
if (response.on_response_received_actions?.[0]?.is(NavigateAction)) {
|
||||
response = await response.on_response_received_actions[0].endpoint.call<IBrowseResponse>(this.#session.actions, { parse: true });
|
||||
}
|
||||
|
||||
return new Channel(this.actions, response, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves notifications.
|
||||
*/
|
||||
async getNotifications(): Promise<NotificationsMenu> {
|
||||
const response = await this.actions.execute(
|
||||
GetNotificationMenuEndpoint.PATH, GetNotificationMenuEndpoint.build({
|
||||
notifications_menu_request_type: 'NOTIFICATIONS_MENU_REQUEST_TYPE_INBOX'
|
||||
})
|
||||
);
|
||||
const response = await this.actions.execute('/notification/get_notification_menu', { notificationsMenuRequestType: 'NOTIFICATIONS_MENU_REQUEST_TYPE_INBOX' });
|
||||
return new NotificationsMenu(this.actions, response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves unseen notifications count.
|
||||
*/
|
||||
async getUnseenNotificationsCount(): Promise<number> {
|
||||
const response = await this.actions.execute(Notification.GetUnseenCountEndpoint.PATH);
|
||||
// TODO: properly parse this
|
||||
const response = await this.actions.execute('/notification/get_unseen_count');
|
||||
// FIXME: properly parse this.
|
||||
return response.data?.unseenCount || response.data?.actions?.[0].updateNotificationsUnseenCountAction?.unseenCount || 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves playlist contents.
|
||||
* @param id - Playlist id
|
||||
* Retrieves the user's playlists.
|
||||
*/
|
||||
async getPlaylists(): Promise<Feed<IBrowseResponse>> {
|
||||
const browse_endpoint = new NavigationEndpoint({ browseEndpoint: { browseId: 'FEplaylist_aggregation' } });
|
||||
const response = await browse_endpoint.call(this.#session.actions, { parse: true });
|
||||
return new Feed(this.actions, response);
|
||||
}
|
||||
|
||||
async getPlaylist(id: string): Promise<Playlist> {
|
||||
throwIfMissing({ id });
|
||||
|
||||
@@ -336,26 +421,26 @@ export default class Innertube {
|
||||
id = `VL${id}`;
|
||||
}
|
||||
|
||||
const response = await this.actions.execute(
|
||||
BrowseEndpoint.PATH, BrowseEndpoint.build({ browse_id: id })
|
||||
);
|
||||
const browse_endpoint = new NavigationEndpoint({ browseEndpoint: { browseId: id } });
|
||||
const response = await browse_endpoint.call(this.#session.actions);
|
||||
|
||||
return new Playlist(this.actions, response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a given hashtag's page.
|
||||
* @param hashtag - The hashtag to fetch.
|
||||
*/
|
||||
async getHashtag(hashtag: string): Promise<HashtagFeed> {
|
||||
throwIfMissing({ hashtag });
|
||||
|
||||
const response = await this.actions.execute(
|
||||
BrowseEndpoint.PATH, BrowseEndpoint.build({
|
||||
browse_id: 'FEhashtag',
|
||||
params: Proto.encodeHashtag(hashtag)
|
||||
})
|
||||
);
|
||||
const writer = Hashtag.encode({
|
||||
params: {
|
||||
hashtag,
|
||||
type: 1
|
||||
}
|
||||
});
|
||||
|
||||
const params = encodeURIComponent(u8ToBase64(writer.finish()));
|
||||
|
||||
const browse_endpoint = new NavigationEndpoint({ browseEndpoint: { browseId: 'FEhashtag', params } });
|
||||
const response = await browse_endpoint.call(this.#session.actions);
|
||||
|
||||
return new HashtagFeed(this.actions, response);
|
||||
}
|
||||
@@ -369,36 +454,121 @@ export default class Innertube {
|
||||
* @param options - Format options.
|
||||
*/
|
||||
async getStreamingData(video_id: string, options: FormatOptions = {}): Promise<Format> {
|
||||
const info = await this.getBasicInfo(video_id);
|
||||
return info.chooseFormat(options);
|
||||
const info = await this.getBasicInfo(video_id, options);
|
||||
|
||||
const format = info.chooseFormat(options);
|
||||
format.url = await format.decipher(this.#session.player);
|
||||
|
||||
return format;
|
||||
}
|
||||
|
||||
/**
|
||||
* Downloads a given video. If you only need the direct download link see {@link getStreamingData}.
|
||||
* Downloads a given video. If all you need the direct download link, see {@link getStreamingData}.
|
||||
* If you wish to retrieve the video info too, have a look at {@link getBasicInfo} or {@link getInfo}.
|
||||
* @param video_id - The video id.
|
||||
* @param options - Download options.
|
||||
*/
|
||||
async download(video_id: string, options?: DownloadOptions): Promise<ReadableStream<Uint8Array>> {
|
||||
const info = await this.getBasicInfo(video_id, options?.client);
|
||||
const info = await this.getBasicInfo(video_id, options);
|
||||
return info.download(options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the given URL.
|
||||
* @param url - The URL.
|
||||
*/
|
||||
async resolveURL(url: string): Promise<NavigationEndpoint> {
|
||||
const response = await this.actions.execute(
|
||||
ResolveURLEndpoint.PATH, { ...ResolveURLEndpoint.build({ url }), parse: true }
|
||||
);
|
||||
const response = await this.actions.execute('/navigation/resolve_url', { url, parse: true });
|
||||
|
||||
if (!response.endpoint)
|
||||
throw new InnertubeError('Failed to resolve URL. Expected a NavigationEndpoint but got undefined', response);
|
||||
|
||||
return response.endpoint;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a post page given a post id and the channel id
|
||||
*/
|
||||
async getPost(post_id: string, channel_id: string) : Promise<Feed<IBrowseResponse>> {
|
||||
throwIfMissing({ post_id, channel_id });
|
||||
const writer = CommunityPostParams.encode({
|
||||
f1: {
|
||||
ucid1: channel_id,
|
||||
postId: post_id,
|
||||
ucid2: channel_id
|
||||
}
|
||||
});
|
||||
|
||||
const params = encodeURIComponent(u8ToBase64(writer.finish()).replace(/\+/g, '-').replace(/\//g, '_'));
|
||||
|
||||
const browse_endpoint = new NavigationEndpoint({ browseEndpoint: { browseId: 'FEpost_detail', params: params } });
|
||||
|
||||
const response = await browse_endpoint.call(this.#session.actions, { parse: true });
|
||||
return new Feed(this.actions, response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the comments of a post.
|
||||
*/
|
||||
async getPostComments(post_id: string, channel_id: string, sort_by?: 'TOP_COMMENTS' | 'NEWEST_FIRST'): Promise<Comments> {
|
||||
throwIfMissing({ post_id, channel_id });
|
||||
|
||||
const SORT_OPTIONS = {
|
||||
TOP_COMMENTS: 0,
|
||||
NEWEST_FIRST: 1
|
||||
};
|
||||
|
||||
const writer1 = CommunityPostCommentsParam.encode({
|
||||
title: 'posts',
|
||||
commentDataContainer: {
|
||||
title: 'comments-section',
|
||||
commentData: {
|
||||
sortBy: SORT_OPTIONS[sort_by || 'TOP_COMMENTS'],
|
||||
f0: 2,
|
||||
f1: 0,
|
||||
channelId: channel_id,
|
||||
postId: post_id
|
||||
},
|
||||
f0: 0
|
||||
}
|
||||
});
|
||||
|
||||
const writer2 = CommunityPostCommentsParamContainer.encode({
|
||||
f0: {
|
||||
location: 'FEcomment_post_detail_page_web_top_level',
|
||||
protoData: encodeURIComponent(u8ToBase64(writer1.finish()).replace(/\+/g, '-').replace(/\//g, '_'))
|
||||
}
|
||||
});
|
||||
|
||||
const continuation = encodeURIComponent(u8ToBase64(writer2.finish()));
|
||||
|
||||
const continuation_command = new NavigationEndpoint({
|
||||
continuationCommand: {
|
||||
request: 'CONTINUATION_REQUEST_TYPE_BROWSE',
|
||||
token: continuation
|
||||
}
|
||||
});
|
||||
|
||||
const response = await continuation_command.call(this.#session.actions);
|
||||
|
||||
return new Comments(this.actions, response.data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches an attestation challenge.
|
||||
*/
|
||||
async getAttestationChallenge(engagement_type: EngagementType, ids?: Record<string, any>[]) {
|
||||
const payload: Record<string, any> = {
|
||||
engagementType: engagement_type
|
||||
};
|
||||
|
||||
if (ids)
|
||||
payload.ids = ids;
|
||||
|
||||
return this.actions.execute('/att/get', { parse: true, ...payload });
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility method to call an endpoint without having to use {@link Actions}.
|
||||
* @param endpoint -The endpoint to call.
|
||||
* @param args - Call arguments.
|
||||
*/
|
||||
call<T extends IParsedResponse>(endpoint: NavigationEndpoint, args: { [key: string]: any; parse: true }): Promise<T>;
|
||||
call(endpoint: NavigationEndpoint, args?: { [key: string]: any; parse?: false }): Promise<ApiResponse>;
|
||||
@@ -461,4 +631,4 @@ export default class Innertube {
|
||||
get session() {
|
||||
return this.#session;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,54 +1,53 @@
|
||||
import { Parser, NavigateAction } from '../parser/index.ts';
|
||||
import type {
|
||||
IBrowseResponse,
|
||||
IGetChallengeResponse,
|
||||
IGetNotificationsMenuResponse,
|
||||
INextResponse,
|
||||
IParsedResponse,
|
||||
IPlayerResponse,
|
||||
IRawResponse,
|
||||
IResolveURLResponse,
|
||||
ISearchResponse,
|
||||
IUpdatedMetadataResponse
|
||||
} from '../parser/index.ts';
|
||||
import { NavigateAction, Parser } from '../parser/index.ts';
|
||||
import { InnertubeError } from '../utils/Utils.ts';
|
||||
|
||||
import type { Session } from './index.ts';
|
||||
|
||||
import type {
|
||||
IBrowseResponse, IGetNotificationsMenuResponse,
|
||||
INextResponse, IPlayerResponse, IResolveURLResponse,
|
||||
ISearchResponse, IUpdatedMetadataResponse,
|
||||
IParsedResponse, IRawResponse
|
||||
} from '../parser/types/index.ts';
|
||||
|
||||
export interface ApiResponse {
|
||||
success: boolean;
|
||||
status_code: number;
|
||||
data: IRawResponse;
|
||||
}
|
||||
|
||||
export type InnertubeEndpoint = '/player' | '/search' | '/browse' | '/next' | '/reel' | '/updated_metadata' | '/notification/get_notification_menu' | string;
|
||||
export type InnertubeEndpoint =
|
||||
'/player'
|
||||
| '/search'
|
||||
| '/browse'
|
||||
| '/next'
|
||||
| '/reel'
|
||||
| '/updated_metadata'
|
||||
| '/notification/get_notification_menu'
|
||||
| '/att/get'
|
||||
| string;
|
||||
|
||||
export type ParsedResponse<T> =
|
||||
T extends '/player' ? IPlayerResponse :
|
||||
T extends '/search' ? ISearchResponse :
|
||||
T extends '/browse' ? IBrowseResponse :
|
||||
T extends '/next' ? INextResponse :
|
||||
T extends '/updated_metadata' ? IUpdatedMetadataResponse :
|
||||
T extends '/navigation/resolve_url' ? IResolveURLResponse :
|
||||
T extends '/notification/get_notification_menu' ? IGetNotificationsMenuResponse :
|
||||
IParsedResponse;
|
||||
T extends '/search' ? ISearchResponse :
|
||||
T extends '/browse' ? IBrowseResponse :
|
||||
T extends '/next' ? INextResponse :
|
||||
T extends '/updated_metadata' ? IUpdatedMetadataResponse :
|
||||
T extends '/navigation/resolve_url' ? IResolveURLResponse :
|
||||
T extends '/notification/get_notification_menu' ? IGetNotificationsMenuResponse :
|
||||
T extends '/att/get' ? IGetChallengeResponse :
|
||||
IParsedResponse;
|
||||
|
||||
export default class Actions {
|
||||
#session: Session;
|
||||
public session: Session;
|
||||
|
||||
constructor(session: Session) {
|
||||
this.#session = session;
|
||||
}
|
||||
|
||||
get session(): Session {
|
||||
return this.#session;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mimmics the Axios API using Fetch's Response object.
|
||||
* @param response - The response object.
|
||||
*/
|
||||
async #wrap(response: Response): Promise<ApiResponse> {
|
||||
return {
|
||||
success: response.ok,
|
||||
status_code: response.status,
|
||||
data: JSON.parse(await response.text())
|
||||
};
|
||||
this.session = session;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -57,7 +56,9 @@ export default class Actions {
|
||||
* @param client - The client to use.
|
||||
* @param params - Call parameters.
|
||||
*/
|
||||
async stats(url: string, client: { client_name: string; client_version: string }, params: { [key: string]: any }): Promise<Response> {
|
||||
async stats(url: string, client: { client_name: string; client_version: string }, params: {
|
||||
[key: string]: any
|
||||
}): Promise<Response> {
|
||||
const s_url = new URL(url);
|
||||
|
||||
s_url.searchParams.set('ver', '2');
|
||||
@@ -69,9 +70,7 @@ export default class Actions {
|
||||
s_url.searchParams.set(key, params[key]);
|
||||
}
|
||||
|
||||
const response = await this.#session.http.fetch(s_url);
|
||||
|
||||
return response;
|
||||
return await this.session.http.fetch(s_url);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -79,19 +78,40 @@ export default class Actions {
|
||||
* @param endpoint - The endpoint to call.
|
||||
* @param args - Call arguments
|
||||
*/
|
||||
async execute<T extends InnertubeEndpoint>(endpoint: T, args: { [key: string]: any; parse: true; protobuf?: false; serialized_data?: any }): Promise<ParsedResponse<T>>;
|
||||
async execute<T extends InnertubeEndpoint>(endpoint: T, args?: { [key: string]: any; parse?: false; protobuf?: true; serialized_data?: any }): Promise<ApiResponse>;
|
||||
async execute<T extends InnertubeEndpoint>(endpoint: T, args?: { [key: string]: any; parse?: boolean; protobuf?: boolean; serialized_data?: any }): Promise<ParsedResponse<T> | ApiResponse> {
|
||||
async execute<T extends InnertubeEndpoint>(endpoint: T, args: {
|
||||
[key: string]: any;
|
||||
parse: true;
|
||||
protobuf?: false;
|
||||
serialized_data?: any;
|
||||
skip_auth_check?: boolean
|
||||
}): Promise<ParsedResponse<T>>;
|
||||
async execute<T extends InnertubeEndpoint>(endpoint: T, args?: {
|
||||
[key: string]: any;
|
||||
parse?: false;
|
||||
protobuf?: true;
|
||||
serialized_data?: any;
|
||||
skip_auth_check?: boolean
|
||||
}): Promise<ApiResponse>;
|
||||
async execute<T extends InnertubeEndpoint>(endpoint: T, args?: {
|
||||
[key: string]: any;
|
||||
parse?: boolean;
|
||||
protobuf?: boolean;
|
||||
serialized_data?: any;
|
||||
skip_auth_check?: boolean
|
||||
}): Promise<ParsedResponse<T> | ApiResponse> {
|
||||
let data;
|
||||
|
||||
if (args && !args.protobuf) {
|
||||
data = { ...args };
|
||||
|
||||
if (Reflect.has(data, 'browseId')) {
|
||||
if (this.#needsLogin(data.browseId) && !this.#session.logged_in)
|
||||
if (Reflect.has(data, 'browseId') && !args.skip_auth_check) {
|
||||
if (this.#needsLogin(data.browseId) && !this.session.logged_in)
|
||||
throw new InnertubeError('You must be signed in to perform this operation.');
|
||||
}
|
||||
|
||||
if (Reflect.has(data, 'skip_auth_check'))
|
||||
delete data.skip_auth_check;
|
||||
|
||||
if (Reflect.has(data, 'override_endpoint'))
|
||||
delete data.override_endpoint;
|
||||
|
||||
@@ -131,7 +151,7 @@ export default class Actions {
|
||||
|
||||
const target_endpoint = Reflect.has(args || {}, 'override_endpoint') ? args?.override_endpoint : endpoint;
|
||||
|
||||
const response = await this.#session.http.fetch(target_endpoint, {
|
||||
const response = await this.session.http.fetch(target_endpoint, {
|
||||
method: 'POST',
|
||||
body: args?.protobuf ? data : JSON.stringify((data || {})),
|
||||
headers: {
|
||||
@@ -145,7 +165,7 @@ export default class Actions {
|
||||
let parsed_response = Parser.parseResponse<ParsedResponse<T>>(await response.json());
|
||||
|
||||
// Handle redirects
|
||||
if (this.#isBrowse(parsed_response) && parsed_response.on_response_received_actions?.first()?.type === 'navigateAction') {
|
||||
if (this.#isBrowse(parsed_response) && parsed_response.on_response_received_actions?.[0]?.type === 'navigateAction') {
|
||||
const navigate_action = parsed_response.on_response_received_actions.firstOfType(NavigateAction);
|
||||
if (navigate_action) {
|
||||
parsed_response = await navigate_action.endpoint.call(this, { parse: true });
|
||||
@@ -155,7 +175,12 @@ export default class Actions {
|
||||
return parsed_response;
|
||||
}
|
||||
|
||||
return this.#wrap(response);
|
||||
// Mimics the Axios API using Fetch's Response object.
|
||||
return {
|
||||
success: response.ok,
|
||||
status_code: response.status,
|
||||
data: await response.json()
|
||||
};
|
||||
}
|
||||
|
||||
#isBrowse(response: IParsedResponse): response is IBrowseResponse {
|
||||
@@ -168,6 +193,7 @@ export default class Actions {
|
||||
'FEhistory',
|
||||
'FEsubscriptions',
|
||||
'FEchannels',
|
||||
'FEplaylist_aggregation',
|
||||
'FEmusic_listening_review',
|
||||
'FEmusic_library_landing',
|
||||
'SPaccount_overview',
|
||||
|
||||
@@ -1,303 +0,0 @@
|
||||
import { Log, Constants } from '../utils/index.ts';
|
||||
import { OAuthError, Platform } from '../utils/Utils.ts';
|
||||
import type Session from './Session.ts';
|
||||
|
||||
/**
|
||||
* Represents the credentials used for authentication.
|
||||
*/
|
||||
export interface Credentials {
|
||||
/**
|
||||
* Token used to sign in.
|
||||
*/
|
||||
access_token: string;
|
||||
/**
|
||||
* Token used to get a new access token.
|
||||
*/
|
||||
refresh_token: string;
|
||||
/**
|
||||
* Access token's expiration date, which is usually 24hrs-ish.
|
||||
*/
|
||||
expires: Date;
|
||||
/**
|
||||
* Optional client ID.
|
||||
*/
|
||||
client_id?: string;
|
||||
/**
|
||||
* Optional client secret.
|
||||
*/
|
||||
client_secret?: string;
|
||||
}
|
||||
|
||||
// TODO: actual type info for this.
|
||||
export type OAuthAuthPendingData = any;
|
||||
|
||||
export type OAuthAuthEventHandler = (data: {
|
||||
credentials: Credentials;
|
||||
status: 'SUCCESS';
|
||||
}) => any;
|
||||
|
||||
export type OAuthAuthPendingEventHandler = (data: OAuthAuthPendingData) => any;
|
||||
export type OAuthAuthErrorEventHandler = (err: OAuthError) => any;
|
||||
|
||||
export type OAuthClientIdentity = {
|
||||
client_id: string;
|
||||
client_secret: string;
|
||||
};
|
||||
|
||||
export default class OAuth {
|
||||
static TAG = 'OAuth';
|
||||
|
||||
#identity?: Record<string, string>;
|
||||
#session: Session;
|
||||
#credentials?: Credentials;
|
||||
#polling_interval = 5;
|
||||
|
||||
constructor(session: Session) {
|
||||
this.#session = session;
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the auth flow in case no valid credentials are available.
|
||||
*/
|
||||
async init(credentials?: Credentials): Promise<void> {
|
||||
this.#credentials = credentials;
|
||||
|
||||
if (this.validateCredentials()) {
|
||||
if (!this.has_access_token_expired)
|
||||
this.#session.emit('auth', {
|
||||
credentials: this.#credentials,
|
||||
status: 'SUCCESS'
|
||||
});
|
||||
} else if (!(await this.#loadCachedCredentials())) {
|
||||
await this.#getUserCode();
|
||||
}
|
||||
}
|
||||
|
||||
async cacheCredentials(): Promise<void> {
|
||||
const encoder = new TextEncoder();
|
||||
const data = encoder.encode(JSON.stringify(this.#credentials));
|
||||
await this.#session.cache?.set('youtubei_oauth_credentials', data.buffer);
|
||||
}
|
||||
|
||||
async #loadCachedCredentials(): Promise<boolean> {
|
||||
const data = await this.#session.cache?.get('youtubei_oauth_credentials');
|
||||
if (!data) return false;
|
||||
|
||||
const decoder = new TextDecoder();
|
||||
const credentials = JSON.parse(decoder.decode(data));
|
||||
|
||||
this.#credentials = {
|
||||
access_token: credentials.access_token,
|
||||
refresh_token: credentials.refresh_token,
|
||||
client_id: credentials.client_id,
|
||||
client_secret: credentials.client_secret,
|
||||
expires: new Date(credentials.expires)
|
||||
};
|
||||
|
||||
this.#session.emit('auth', {
|
||||
credentials: this.#credentials,
|
||||
status: 'SUCCESS'
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
async removeCache(): Promise<void> {
|
||||
await this.#session.cache?.remove('youtubei_oauth_credentials');
|
||||
}
|
||||
|
||||
/**
|
||||
* Asks the server for a user code and verification URL.
|
||||
*/
|
||||
async #getUserCode(): Promise<void> {
|
||||
this.#identity = await this.#getClientIdentity();
|
||||
|
||||
const data = {
|
||||
client_id: this.#identity.client_id,
|
||||
scope: Constants.OAUTH.SCOPE,
|
||||
device_id: Platform.shim.uuidv4(),
|
||||
device_model: Constants.OAUTH.MODEL_NAME
|
||||
};
|
||||
|
||||
const response = await this.#session.http.fetch_function(new URL('/o/oauth2/device/code', Constants.URLS.YT_BASE), {
|
||||
body: JSON.stringify(data),
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
const response_data = await response.json();
|
||||
|
||||
this.#session.emit('auth-pending', response_data);
|
||||
this.#polling_interval = response_data.interval;
|
||||
this.#startPolling(response_data.device_code);
|
||||
}
|
||||
|
||||
/**
|
||||
* Polls the authorization server until access is granted by the user.
|
||||
*/
|
||||
#startPolling(device_code: string): void {
|
||||
const poller = setInterval(async () => {
|
||||
const data = {
|
||||
...this.#identity,
|
||||
code: device_code,
|
||||
grant_type: Constants.OAUTH.GRANT_TYPE
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await this.#session.http.fetch_function(new URL('/o/oauth2/token', Constants.URLS.YT_BASE), {
|
||||
body: JSON.stringify(data),
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
const response_data = await response.json();
|
||||
|
||||
if (response_data.error) {
|
||||
switch (response_data.error) {
|
||||
case 'access_denied':
|
||||
this.#session.emit('auth-error', new OAuthError('Access was denied.', { status: 'ACCESS_DENIED' }));
|
||||
break;
|
||||
case 'expired_token':
|
||||
this.#session.emit('auth-error', new OAuthError('The device code has expired, restarting auth flow.', { status: 'DEVICE_CODE_EXPIRED' }));
|
||||
clearInterval(poller);
|
||||
this.#getUserCode();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const expiration_date = new Date(new Date().getTime() + response_data.expires_in * 1000);
|
||||
|
||||
this.#credentials = {
|
||||
access_token: response_data.access_token,
|
||||
refresh_token: response_data.refresh_token,
|
||||
client_id: this.#identity?.client_id,
|
||||
client_secret: this.#identity?.client_secret,
|
||||
expires: expiration_date
|
||||
};
|
||||
|
||||
this.#session.emit('auth', {
|
||||
credentials: this.#credentials,
|
||||
status: 'SUCCESS'
|
||||
});
|
||||
|
||||
clearInterval(poller);
|
||||
} catch (err) {
|
||||
clearInterval(poller);
|
||||
return this.#session.emit('auth-error', new OAuthError('Could not obtain user code.', { status: 'FAILED', error: err }));
|
||||
}
|
||||
}, this.#polling_interval * 1000);
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh access token if the same has expired.
|
||||
*/
|
||||
async refreshIfRequired(): Promise<void> {
|
||||
if (this.has_access_token_expired) {
|
||||
await this.#refreshAccessToken();
|
||||
}
|
||||
}
|
||||
|
||||
async #refreshAccessToken(): Promise<void> {
|
||||
if (!this.#credentials) return;
|
||||
this.#identity = await this.#getClientIdentity();
|
||||
|
||||
const data = {
|
||||
...this.#identity,
|
||||
refresh_token: this.#credentials.refresh_token,
|
||||
grant_type: 'refresh_token'
|
||||
};
|
||||
|
||||
const response = await this.#session.http.fetch_function(new URL('/o/oauth2/token', Constants.URLS.YT_BASE), {
|
||||
body: JSON.stringify(data),
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
const response_data = await response.json();
|
||||
const expiration_date = new Date(new Date().getTime() + response_data.expires_in * 1000);
|
||||
|
||||
this.#credentials = {
|
||||
access_token: response_data.access_token,
|
||||
refresh_token: response_data.refresh_token || this.#credentials.refresh_token,
|
||||
client_id: this.#identity.client_id,
|
||||
client_secret: this.#identity.client_secret,
|
||||
expires: expiration_date
|
||||
};
|
||||
|
||||
this.#session.emit('update-credentials', {
|
||||
credentials: this.#credentials,
|
||||
status: 'SUCCESS'
|
||||
});
|
||||
}
|
||||
|
||||
async revokeCredentials(): Promise<Response | undefined> {
|
||||
if (!this.#credentials) return;
|
||||
await this.removeCache();
|
||||
return this.#session.http.fetch_function(new URL(`/o/oauth2/revoke?token=${encodeURIComponent(this.#credentials.access_token)}`, Constants.URLS.YT_BASE), {
|
||||
method: 'post'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves client identity from YouTube TV.
|
||||
*/
|
||||
async #getClientIdentity(): Promise<OAuthClientIdentity> {
|
||||
if (this.#credentials?.client_id && this.credentials?.client_secret) {
|
||||
Log.info(OAuth.TAG, 'Using custom OAuth2 credentials.\n');
|
||||
return {
|
||||
client_id: this.#credentials.client_id,
|
||||
client_secret: this.credentials.client_secret
|
||||
};
|
||||
}
|
||||
|
||||
const response = await this.#session.http.fetch_function(new URL('/tv', Constants.URLS.YT_BASE), { headers: Constants.OAUTH.HEADERS });
|
||||
|
||||
const response_data = await response.text();
|
||||
const url_body = Constants.OAUTH.REGEX.AUTH_SCRIPT.exec(response_data)?.[1];
|
||||
|
||||
if (!url_body)
|
||||
throw new OAuthError('Could not obtain script url.', { status: 'FAILED' });
|
||||
|
||||
Log.info(OAuth.TAG, `Got YouTubeTV script URL (${url_body})`);
|
||||
|
||||
const script = await this.#session.http.fetch(url_body, { baseURL: Constants.URLS.YT_BASE });
|
||||
|
||||
const client_identity = (await script.text())
|
||||
.replace(/\n/g, '')
|
||||
.match(Constants.OAUTH.REGEX.CLIENT_IDENTITY);
|
||||
|
||||
const groups = client_identity?.groups as OAuthClientIdentity | null;
|
||||
|
||||
if (!groups)
|
||||
throw new OAuthError('Could not obtain client identity.', { status: 'FAILED' });
|
||||
|
||||
Log.info(OAuth.TAG, 'OAuth2 credentials retrieved.\n', groups);
|
||||
|
||||
return groups;
|
||||
}
|
||||
|
||||
get credentials(): Credentials | undefined {
|
||||
return this.#credentials;
|
||||
}
|
||||
|
||||
get has_access_token_expired(): boolean {
|
||||
const timestamp = this.#credentials ? new Date(this.#credentials.expires).getTime() : -Infinity;
|
||||
return new Date().getTime() > timestamp;
|
||||
}
|
||||
|
||||
validateCredentials(): this is this & { credentials: Credentials } {
|
||||
return this.#credentials &&
|
||||
Reflect.has(this.#credentials, 'access_token') &&
|
||||
Reflect.has(this.#credentials, 'refresh_token') &&
|
||||
Reflect.has(this.#credentials, 'expires') || false;
|
||||
}
|
||||
}
|
||||
326
deno/src/core/OAuth2.ts
Normal file
326
deno/src/core/OAuth2.ts
Normal file
@@ -0,0 +1,326 @@
|
||||
import { OAuth2Error, Platform } from '../utils/Utils.ts';
|
||||
import { Log, Constants } from '../utils/index.ts';
|
||||
import type Session from './Session.ts';
|
||||
|
||||
const TAG = 'OAuth2';
|
||||
|
||||
export type OAuth2ClientID = {
|
||||
client_id: string;
|
||||
client_secret: string;
|
||||
};
|
||||
|
||||
export type OAuth2Tokens = {
|
||||
access_token: string;
|
||||
expiry_date: string;
|
||||
expires_in?: number;
|
||||
refresh_token: string;
|
||||
scope?: string;
|
||||
token_type?: string;
|
||||
client?: OAuth2ClientID;
|
||||
};
|
||||
|
||||
export type DeviceAndUserCode = {
|
||||
device_code: string;
|
||||
expires_in: number;
|
||||
interval: number;
|
||||
user_code: string;
|
||||
verification_url: string;
|
||||
error_code?: string;
|
||||
};
|
||||
|
||||
export type OAuth2AuthEventHandler = (data: { credentials: OAuth2Tokens; }) => void;
|
||||
export type OAuth2AuthPendingEventHandler = (data: DeviceAndUserCode) => void;
|
||||
export type OAuth2AuthErrorEventHandler = (err: OAuth2Error) => void;
|
||||
|
||||
export default class OAuth2 {
|
||||
#session: Session;
|
||||
|
||||
public YTTV_URL: URL;
|
||||
public AUTH_SERVER_CODE_URL: URL;
|
||||
public AUTH_SERVER_TOKEN_URL: URL;
|
||||
public AUTH_SERVER_REVOKE_TOKEN_URL: URL;
|
||||
|
||||
public client_id: OAuth2ClientID | undefined;
|
||||
public oauth2_tokens: OAuth2Tokens | undefined;
|
||||
|
||||
constructor(session: Session) {
|
||||
this.#session = session;
|
||||
this.YTTV_URL = new URL('/tv', Constants.URLS.YT_BASE);
|
||||
this.AUTH_SERVER_CODE_URL = new URL('/o/oauth2/device/code', Constants.URLS.YT_BASE);
|
||||
this.AUTH_SERVER_TOKEN_URL = new URL('/o/oauth2/token', Constants.URLS.YT_BASE);
|
||||
this.AUTH_SERVER_REVOKE_TOKEN_URL = new URL('/o/oauth2/revoke', Constants.URLS.YT_BASE);
|
||||
}
|
||||
|
||||
async init(tokens?: OAuth2Tokens): Promise<void> {
|
||||
if (tokens) {
|
||||
this.setTokens(tokens);
|
||||
|
||||
if (this.shouldRefreshToken()) {
|
||||
await this.refreshAccessToken();
|
||||
}
|
||||
|
||||
this.#session.emit('auth', { credentials: this.oauth2_tokens });
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const loaded_from_cache = await this.#loadFromCache();
|
||||
|
||||
if (loaded_from_cache) {
|
||||
Log.info(TAG, 'Loaded OAuth2 tokens from cache.', this.oauth2_tokens);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.client_id)
|
||||
this.client_id = await this.getClientID();
|
||||
|
||||
// Initialize OAuth2 flow
|
||||
const device_and_user_code = await this.getDeviceAndUserCode();
|
||||
|
||||
this.#session.emit('auth-pending', device_and_user_code);
|
||||
|
||||
this.pollForAccessToken(device_and_user_code);
|
||||
}
|
||||
|
||||
setTokens(tokens: OAuth2Tokens): void {
|
||||
const tokensMod = tokens;
|
||||
|
||||
// Convert access token remaining lifetime to ISO string
|
||||
if (tokensMod.expires_in) {
|
||||
tokensMod.expiry_date = new Date(Date.now() + tokensMod.expires_in * 1000).toISOString();
|
||||
delete tokensMod.expires_in; // We don't need this anymore
|
||||
}
|
||||
|
||||
if (!this.validateTokens(tokensMod))
|
||||
throw new OAuth2Error('Invalid tokens provided.');
|
||||
|
||||
this.oauth2_tokens = tokensMod;
|
||||
|
||||
if (tokensMod.client) {
|
||||
Log.info(TAG, 'Using provided client id and secret.');
|
||||
this.client_id = tokensMod.client;
|
||||
}
|
||||
}
|
||||
|
||||
async cacheCredentials(): Promise<void> {
|
||||
const encoder = new TextEncoder();
|
||||
const data = encoder.encode(JSON.stringify(this.oauth2_tokens));
|
||||
await this.#session.cache?.set('youtubei_oauth_credentials', data.buffer);
|
||||
}
|
||||
|
||||
async #loadFromCache(): Promise<boolean> {
|
||||
const data = await this.#session.cache?.get('youtubei_oauth_credentials');
|
||||
if (!data)
|
||||
return false;
|
||||
|
||||
const decoder = new TextDecoder();
|
||||
const credentials = JSON.parse(decoder.decode(data));
|
||||
|
||||
this.setTokens(credentials);
|
||||
|
||||
this.#session.emit('auth', { credentials });
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
async removeCache(): Promise<void> {
|
||||
await this.#session.cache?.remove('youtubei_oauth_credentials');
|
||||
}
|
||||
|
||||
pollForAccessToken(device_and_user_code: DeviceAndUserCode): void {
|
||||
if (!this.client_id)
|
||||
throw new OAuth2Error('Client ID is missing.');
|
||||
|
||||
const { device_code, interval } = device_and_user_code;
|
||||
const { client_id, client_secret } = this.client_id;
|
||||
|
||||
const payload = {
|
||||
client_id,
|
||||
client_secret,
|
||||
code: device_code,
|
||||
grant_type: 'http://oauth.net/grant_type/device/1.0'
|
||||
};
|
||||
|
||||
const connInterval = setInterval(async () => {
|
||||
const response = await this.#http.fetch_function(this.AUTH_SERVER_TOKEN_URL, {
|
||||
body: JSON.stringify(payload),
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
const response_data = await response.json();
|
||||
|
||||
if (response_data.error) {
|
||||
switch (response_data.error) {
|
||||
case 'access_denied':
|
||||
this.#session.emit('auth-error', new OAuth2Error('Access was denied.', response_data));
|
||||
clearInterval(connInterval);
|
||||
break;
|
||||
case 'expired_token':
|
||||
this.#session.emit('auth-error', new OAuth2Error('The device code has expired.', response_data));
|
||||
clearInterval(connInterval);
|
||||
break;
|
||||
case 'authorization_pending':
|
||||
case 'slow_down':
|
||||
Log.info(TAG, 'Polling for access token...');
|
||||
break;
|
||||
default:
|
||||
this.#session.emit('auth-error', new OAuth2Error('Server returned an unexpected error.', response_data));
|
||||
clearInterval(connInterval);
|
||||
break;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
this.setTokens(response_data);
|
||||
|
||||
this.#session.emit('auth', { credentials: this.oauth2_tokens });
|
||||
|
||||
clearInterval(connInterval);
|
||||
}, interval * 1000);
|
||||
}
|
||||
|
||||
async revokeCredentials(): Promise<Response | undefined> {
|
||||
if (!this.oauth2_tokens)
|
||||
throw new OAuth2Error('Access token not found');
|
||||
|
||||
await this.removeCache();
|
||||
|
||||
const url = this.AUTH_SERVER_REVOKE_TOKEN_URL;
|
||||
url.searchParams.set('token', this.oauth2_tokens.access_token);
|
||||
|
||||
return this.#session.http.fetch_function(url, { method: 'POST' });
|
||||
}
|
||||
|
||||
async refreshAccessToken(): Promise<void> {
|
||||
if (!this.client_id)
|
||||
this.client_id = await this.getClientID();
|
||||
|
||||
if (!this.oauth2_tokens)
|
||||
throw new OAuth2Error('No tokens available to refresh.');
|
||||
|
||||
const { client_id, client_secret } = this.client_id;
|
||||
const { refresh_token } = this.oauth2_tokens;
|
||||
|
||||
const payload = {
|
||||
client_id,
|
||||
client_secret,
|
||||
refresh_token,
|
||||
grant_type: 'refresh_token'
|
||||
};
|
||||
|
||||
const response = await this.#http.fetch_function(this.AUTH_SERVER_TOKEN_URL, {
|
||||
body: JSON.stringify(payload),
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
if (!response.ok)
|
||||
throw new OAuth2Error(`Failed to refresh access token: ${response.status}`);
|
||||
|
||||
const response_data = await response.json();
|
||||
|
||||
if (response_data.error_code)
|
||||
throw new OAuth2Error('Authorization server returned an error', response_data);
|
||||
|
||||
this.oauth2_tokens.access_token = response_data.access_token;
|
||||
this.oauth2_tokens.expiry_date = new Date(Date.now() + response_data.expires_in * 1000).toISOString();
|
||||
|
||||
this.#session.emit('update-credentials', { credentials: this.oauth2_tokens });
|
||||
}
|
||||
|
||||
async getDeviceAndUserCode(): Promise<DeviceAndUserCode> {
|
||||
if (!this.client_id)
|
||||
throw new OAuth2Error('Client ID is missing.');
|
||||
|
||||
const { client_id } = this.client_id;
|
||||
|
||||
const payload = {
|
||||
client_id,
|
||||
scope: 'http://gdata.youtube.com https://www.googleapis.com/auth/youtube-paid-content',
|
||||
device_id: Platform.shim.uuidv4(),
|
||||
device_model: 'ytlr::'
|
||||
};
|
||||
|
||||
const response = await this.#http.fetch_function(this.AUTH_SERVER_CODE_URL, {
|
||||
body: JSON.stringify(payload),
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
if (!response.ok)
|
||||
throw new OAuth2Error(`Failed to get device/user code: ${response.status}`);
|
||||
|
||||
const response_data = await response.json();
|
||||
|
||||
if (response_data.error_code)
|
||||
throw new OAuth2Error('Authorization server returned an error', response_data);
|
||||
|
||||
return response_data;
|
||||
}
|
||||
|
||||
async getClientID(): Promise<OAuth2ClientID> {
|
||||
const yttv_response = await this.#http.fetch_function(this.YTTV_URL, {
|
||||
headers: {
|
||||
'User-Agent': 'Mozilla/5.0 (ChromiumStylePlatform) Cobalt/Version',
|
||||
'Referer': 'https://www.youtube.com/tv',
|
||||
'Accept-Language': 'en-US'
|
||||
}
|
||||
});
|
||||
|
||||
if (!yttv_response.ok)
|
||||
throw new OAuth2Error(`Failed to get client ID: ${yttv_response.status}`);
|
||||
|
||||
const yttv_response_data = await yttv_response.text();
|
||||
|
||||
let script_url_body: RegExpExecArray | null;
|
||||
|
||||
if ((script_url_body = Constants.OAUTH.REGEX.TV_SCRIPT.exec(yttv_response_data)) !== null) {
|
||||
Log.info(TAG, `Got YouTubeTV script URL (${script_url_body[1]})`);
|
||||
|
||||
const script_response = await this.#http.fetch(script_url_body[1], { baseURL: Constants.URLS.YT_BASE });
|
||||
|
||||
if (!script_response.ok)
|
||||
throw new OAuth2Error(`TV script request failed with status code ${script_response.status}`);
|
||||
|
||||
const script_response_data = await script_response.text();
|
||||
|
||||
const client_identity = script_response_data
|
||||
.match(Constants.OAUTH.REGEX.CLIENT_IDENTITY);
|
||||
|
||||
if (!client_identity || !client_identity.groups)
|
||||
throw new OAuth2Error('Could not obtain client ID.');
|
||||
|
||||
const { client_id, client_secret } = client_identity.groups;
|
||||
|
||||
Log.info(TAG, `Client identity retrieved (clientId=${client_id}, clientSecret=${client_secret}).`);
|
||||
|
||||
return {
|
||||
client_id,
|
||||
client_secret
|
||||
};
|
||||
}
|
||||
|
||||
throw new OAuth2Error('Could not obtain script URL.');
|
||||
}
|
||||
|
||||
shouldRefreshToken(): boolean {
|
||||
if (!this.oauth2_tokens)
|
||||
return false;
|
||||
return Date.now() > new Date(this.oauth2_tokens.expiry_date).getTime();
|
||||
}
|
||||
|
||||
validateTokens(tokens: OAuth2Tokens): boolean {
|
||||
return !(!tokens.access_token || !tokens.refresh_token || !tokens.expiry_date);
|
||||
}
|
||||
|
||||
get #http() {
|
||||
return this.#session.http;
|
||||
}
|
||||
}
|
||||
@@ -1,52 +1,80 @@
|
||||
import { Log, Constants } from '../utils/index.ts';
|
||||
import { Platform, getRandomUserAgent, getStringBetweenStrings, PlayerError } from '../utils/Utils.ts';
|
||||
import type { ICache, FetchFunction } from '../types/index.ts';
|
||||
import type { FetchFunction, ICache } from '../types/index.ts';
|
||||
import { Constants, BinarySerializer, Log } from '../utils/index.ts';
|
||||
|
||||
import {
|
||||
getRandomUserAgent,
|
||||
getStringBetweenStrings,
|
||||
getNsigProcessorFn,
|
||||
Platform,
|
||||
PlayerError
|
||||
} from '../utils/Utils.ts';
|
||||
|
||||
import { JsExtractor, JsAnalyzer } from '../utils/index.ts';
|
||||
import { nsigMatcher, timestampMatcher } from '../utils/javascript/matchers.ts';
|
||||
|
||||
import type { ExtractionConfig } from '../utils/javascript/JsAnalyzer.ts';
|
||||
import type { BuildScriptResult } from '../utils/javascript/JsExtractor.ts';
|
||||
|
||||
import packageInfo from '../../package.json' with { type: 'json' };
|
||||
|
||||
const TAG = 'Player';
|
||||
|
||||
interface SerializablePlayer {
|
||||
playerId: string;
|
||||
signatureTimestamp: number;
|
||||
libraryVersion: string;
|
||||
data?: BuildScriptResult;
|
||||
}
|
||||
|
||||
interface PlayerInitializationOptions {
|
||||
cache?: ICache;
|
||||
signature_timestamp: number;
|
||||
data: BuildScriptResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents YouTube's player script. This is required to decipher signatures.
|
||||
*/
|
||||
export default class Player {
|
||||
static TAG = 'Player';
|
||||
public po_token?: string;
|
||||
|
||||
#nsig_sc;
|
||||
#sig_sc;
|
||||
#sig_sc_timestamp;
|
||||
#player_id;
|
||||
constructor(public player_id: string, public signature_timestamp: number, public data?: BuildScriptResult) { /** no-op */ }
|
||||
|
||||
constructor(signature_timestamp: number, sig_sc: string, nsig_sc: string, player_id: string) {
|
||||
this.#nsig_sc = nsig_sc;
|
||||
this.#sig_sc = sig_sc;
|
||||
this.#sig_sc_timestamp = signature_timestamp;
|
||||
this.#player_id = player_id;
|
||||
}
|
||||
public static async create(
|
||||
cache: ICache | undefined,
|
||||
fetch: FetchFunction = Platform.shim.fetch,
|
||||
po_token?: string, player_id?: string
|
||||
): Promise<Player> {
|
||||
if (!player_id) {
|
||||
const url = new URL('/iframe_api', Constants.URLS.YT_BASE);
|
||||
const res = await fetch(url);
|
||||
|
||||
static async create(cache: ICache | undefined, fetch: FetchFunction = Platform.shim.fetch): Promise<Player> {
|
||||
const url = new URL('/iframe_api', Constants.URLS.YT_BASE);
|
||||
const res = await fetch(url);
|
||||
if (!res.ok)
|
||||
throw new PlayerError(`Failed to get player id: ${res.status} (${res.statusText})`);
|
||||
|
||||
if (res.status !== 200)
|
||||
throw new PlayerError('Failed to request player id');
|
||||
const js = await res.text();
|
||||
|
||||
const js = await res.text();
|
||||
player_id = getStringBetweenStrings(js, 'player\\/', '\\/');
|
||||
}
|
||||
|
||||
const player_id = getStringBetweenStrings(js, 'player\\/', '\\/');
|
||||
|
||||
Log.info(Player.TAG, `Got player id (${player_id}). Checking for cached players..`);
|
||||
Log.info(TAG, `Using player id (${player_id}). Checking for cached players..`);
|
||||
|
||||
if (!player_id)
|
||||
throw new PlayerError('Failed to get player id');
|
||||
|
||||
// We have the player id, now we can check if we have a cached player.
|
||||
if (cache) {
|
||||
Log.info(Player.TAG, 'Found a cached player.');
|
||||
const cached_player = await Player.fromCache(cache, player_id);
|
||||
if (cached_player)
|
||||
if (cached_player) {
|
||||
Log.info(TAG, 'Found up-to-date player data in cache.');
|
||||
cached_player.po_token = po_token;
|
||||
return cached_player;
|
||||
}
|
||||
}
|
||||
|
||||
const player_url = new URL(`/s/player/${player_id}/player_ias.vflset/en_US/base.js`, Constants.URLS.YT_BASE);
|
||||
const player_url = new URL(`/s/player/${player_id}/player_es6.vflset/en_US/base.js`, Constants.URLS.YT_BASE);
|
||||
|
||||
Log.info(Player.TAG, `Could not find any cached player. Will download a new player from ${player_url}.`);
|
||||
Log.info(TAG, `Could not find any cached player. Will download a new player from ${player_url}.`);
|
||||
|
||||
const player_res = await fetch(player_url, {
|
||||
headers: {
|
||||
@@ -60,16 +88,45 @@ export default class Player {
|
||||
|
||||
const player_js = await player_res.text();
|
||||
|
||||
const sig_timestamp = this.extractSigTimestamp(player_js);
|
||||
const sig_sc = this.extractSigSourceCode(player_js);
|
||||
const nsig_sc = this.extractNSigSourceCode(player_js);
|
||||
const nsigFunctionName = 'nsigFunction';
|
||||
const timestampVarName = 'signatureTimestampVar';
|
||||
|
||||
Log.info(Player.TAG, `Got signature timestamp (${sig_timestamp}) and algorithms needed to decipher signatures.`);
|
||||
const extractions: ExtractionConfig[] = [
|
||||
{ friendlyName: nsigFunctionName, match: nsigMatcher },
|
||||
{ friendlyName: timestampVarName, match: timestampMatcher, collectDependencies: false }
|
||||
];
|
||||
|
||||
return await Player.fromSource(cache, sig_timestamp, sig_sc, nsig_sc, player_id);
|
||||
const jsAnalyzer = new JsAnalyzer(player_js, { extractions });
|
||||
const jsExtractor = new JsExtractor(jsAnalyzer);
|
||||
|
||||
const result = jsExtractor.buildScript({
|
||||
disallowSideEffectInitializers: true,
|
||||
exportRawValues: true,
|
||||
rawValueOnly: [ timestampVarName ]
|
||||
});
|
||||
|
||||
if (result.exportedRawValues && !(timestampVarName in result.exportedRawValues)) {
|
||||
Log.warn(TAG, 'Failed to extract signature timestamp.');
|
||||
}
|
||||
|
||||
if (!result.exported.includes(nsigFunctionName)) {
|
||||
Log.warn(TAG, 'Failed to extract n/sig decipher function.');
|
||||
}
|
||||
|
||||
const signatureTimestamp = result.exportedRawValues?.[timestampVarName];
|
||||
|
||||
const player = await Player.fromSource(player_id, {
|
||||
cache,
|
||||
signature_timestamp: parseInt(signatureTimestamp) || 0,
|
||||
data: result
|
||||
});
|
||||
|
||||
player.po_token = po_token;
|
||||
|
||||
return player;
|
||||
}
|
||||
|
||||
decipher(url?: string, signature_cipher?: string, cipher?: string, this_response_nsig_cache?: Map<string, string>): string {
|
||||
async decipher(url?: string, signature_cipher?: string, cipher?: string, this_response_nsig_cache?: Map<string, string>): Promise<string> {
|
||||
url = url || signature_cipher || cipher;
|
||||
|
||||
if (!url)
|
||||
@@ -78,76 +135,108 @@ export default class Player {
|
||||
const args = new URLSearchParams(url);
|
||||
const url_components = new URL(args.get('url') || url);
|
||||
|
||||
if (signature_cipher || cipher) {
|
||||
const signature = Platform.shim.eval(this.#sig_sc, {
|
||||
sig: args.get('s')
|
||||
});
|
||||
|
||||
Log.info(Player.TAG, `Transformed signature ${args.get('s')} to ${signature}.`);
|
||||
|
||||
if (typeof signature !== 'string')
|
||||
throw new PlayerError('Failed to decipher signature');
|
||||
|
||||
const sp = args.get('sp');
|
||||
|
||||
sp ?
|
||||
url_components.searchParams.set(sp, signature) :
|
||||
url_components.searchParams.set('signature', signature);
|
||||
}
|
||||
|
||||
const n = url_components.searchParams.get('n');
|
||||
const s = args.get('s');
|
||||
const sp = args.get('sp');
|
||||
|
||||
if (n) {
|
||||
let nsig;
|
||||
if (this.data && ((signature_cipher || cipher) || n)) {
|
||||
const eval_args: { sig?: string | null; n?: string | null; sp?: string | null } = {};
|
||||
|
||||
if (this_response_nsig_cache && this_response_nsig_cache.has(n)) {
|
||||
nsig = this_response_nsig_cache.get(n) as string;
|
||||
} else {
|
||||
nsig = Platform.shim.eval(this.#nsig_sc, {
|
||||
nsig: n
|
||||
});
|
||||
if (signature_cipher || cipher) {
|
||||
eval_args.sig = s;
|
||||
eval_args.sp = sp;
|
||||
}
|
||||
|
||||
Log.info(Player.TAG, `Transformed nsig ${n} to ${nsig}.`);
|
||||
|
||||
if (typeof nsig !== 'string')
|
||||
throw new PlayerError('Failed to decipher nsig');
|
||||
|
||||
if (nsig.startsWith('enhanced_except_')) {
|
||||
Log.warn(Player.TAG, 'Could not transform nsig, download may be throttled.\nChanging the InnerTube client to "ANDROID" might help!');
|
||||
} else if (this_response_nsig_cache) {
|
||||
this_response_nsig_cache.set(n, nsig);
|
||||
if (n) {
|
||||
if (this_response_nsig_cache?.has(n)) {
|
||||
const nsig = this_response_nsig_cache.get(n) as string;
|
||||
url_components.searchParams.set('n', nsig);
|
||||
} else {
|
||||
eval_args.n = n;
|
||||
}
|
||||
}
|
||||
|
||||
url_components.searchParams.set('n', nsig);
|
||||
if (Object.keys(eval_args).length > 0) {
|
||||
// Shallow copy to avoid mutating the original data.
|
||||
const data = { ...this.data };
|
||||
|
||||
data.output = `${data.output}\n${getNsigProcessorFn(eval_args.n, eval_args.sp, eval_args.sig)}`;
|
||||
|
||||
const result = await Platform.shim.eval(data, eval_args) as Record<string, unknown>;
|
||||
|
||||
if (typeof result !== 'object' || result === null) {
|
||||
throw new PlayerError('Got invalid result from player script evaluation.');
|
||||
}
|
||||
|
||||
if (typeof eval_args.sig === 'string') {
|
||||
const signatureResult = result.sig;
|
||||
|
||||
Log.info(TAG, `Transformed signature from ${s} to ${signatureResult}.`);
|
||||
|
||||
if (typeof signatureResult !== 'string')
|
||||
throw new PlayerError('Got invalid signature from player script evaluation.');
|
||||
|
||||
if (sp) {
|
||||
url_components.searchParams.set(sp, signatureResult);
|
||||
} else {
|
||||
url_components.searchParams.set('signature', signatureResult);
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof eval_args.n === 'string') {
|
||||
const nResult = result.n;
|
||||
Log.info(TAG, `Transformed n from ${n} to ${nResult}.`);
|
||||
|
||||
if (typeof nResult !== 'string')
|
||||
throw new PlayerError('Failed to decipher nsig');
|
||||
|
||||
if (nResult.startsWith('enhanced_except_')) {
|
||||
Log.warn(TAG, `Decipher script returned an error (n=${n}):`, nResult);
|
||||
} else if (this_response_nsig_cache) {
|
||||
this_response_nsig_cache.set(n as string, nResult);
|
||||
}
|
||||
|
||||
url_components.searchParams.set('n', nResult);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// @NOTE: SABR requests should include the PoToken (not base64d, but as bytes!) in the payload.
|
||||
if (url_components.searchParams.get('sabr') !== '1' && this.po_token)
|
||||
url_components.searchParams.set('pot', this.po_token);
|
||||
|
||||
const client = url_components.searchParams.get('c');
|
||||
|
||||
switch (client) {
|
||||
case 'WEB':
|
||||
url_components.searchParams.set('cver', Constants.CLIENTS.WEB.VERSION);
|
||||
break;
|
||||
case 'MWEB':
|
||||
url_components.searchParams.set('cver', Constants.CLIENTS.MWEB.VERSION);
|
||||
break;
|
||||
case 'WEB_REMIX':
|
||||
url_components.searchParams.set('cver', Constants.CLIENTS.YTMUSIC.VERSION);
|
||||
break;
|
||||
case 'WEB_KIDS':
|
||||
url_components.searchParams.set('cver', Constants.CLIENTS.WEB_KIDS.VERSION);
|
||||
break;
|
||||
case 'ANDROID':
|
||||
url_components.searchParams.set('cver', Constants.CLIENTS.ANDROID.VERSION);
|
||||
case 'TVHTML5':
|
||||
url_components.searchParams.set('cver', Constants.CLIENTS.TV.VERSION);
|
||||
break;
|
||||
case 'ANDROID_MUSIC':
|
||||
url_components.searchParams.set('cver', Constants.CLIENTS.YTMUSIC_ANDROID.VERSION);
|
||||
case 'TVHTML5_SIMPLY':
|
||||
url_components.searchParams.set('cver', Constants.CLIENTS.TV_SIMPLY.VERSION);
|
||||
break;
|
||||
case 'TVHTML5_SIMPLY_EMBEDDED_PLAYER':
|
||||
url_components.searchParams.set('cver', Constants.CLIENTS.TV_EMBEDDED.VERSION);
|
||||
break;
|
||||
case 'WEB_EMBEDDED_PLAYER':
|
||||
url_components.searchParams.set('cver', Constants.CLIENTS.WEB_EMBEDDED.VERSION);
|
||||
break;
|
||||
}
|
||||
|
||||
const result = url_components.toString();
|
||||
|
||||
Log.info(Player.TAG, `Full deciphered URL: ${result}`);
|
||||
Log.info(TAG, `Deciphered URL: ${result}`);
|
||||
|
||||
return url_components.toString();
|
||||
}
|
||||
@@ -158,94 +247,46 @@ export default class Player {
|
||||
if (!buffer)
|
||||
return null;
|
||||
|
||||
const view = new DataView(buffer);
|
||||
const version = view.getUint32(0, true);
|
||||
try {
|
||||
const deserializedCache = BinarySerializer.deserialize<SerializablePlayer>(new Uint8Array(buffer));
|
||||
|
||||
if (version !== Player.LIBRARY_VERSION)
|
||||
if (deserializedCache.libraryVersion !== packageInfo.version) {
|
||||
Log.warn(TAG, `Cached player data is from a different library version (${deserializedCache.libraryVersion}). Ignoring it.`);
|
||||
return null;
|
||||
}
|
||||
|
||||
return new Player(deserializedCache.playerId, deserializedCache.signatureTimestamp, deserializedCache.data);
|
||||
} catch (e) {
|
||||
Log.error(TAG, 'Failed to deserialize player data from cache:', e);
|
||||
return null;
|
||||
|
||||
const sig_timestamp = view.getUint32(4, true);
|
||||
|
||||
const sig_len = view.getUint32(8, true);
|
||||
const sig_buf = buffer.slice(12, 12 + sig_len);
|
||||
const nsig_buf = buffer.slice(12 + sig_len);
|
||||
|
||||
const decoder = new TextDecoder();
|
||||
|
||||
const sig_sc = decoder.decode(sig_buf);
|
||||
const nsig_sc = decoder.decode(nsig_buf);
|
||||
|
||||
return new Player(sig_timestamp, sig_sc, nsig_sc, player_id);
|
||||
}
|
||||
}
|
||||
|
||||
static async fromSource(cache: ICache | undefined, sig_timestamp: number, sig_sc: string, nsig_sc: string, player_id: string): Promise<Player> {
|
||||
const player = new Player(sig_timestamp, sig_sc, nsig_sc, player_id);
|
||||
await player.cache(cache);
|
||||
static async fromSource(player_id: string, options: PlayerInitializationOptions): Promise<Player> {
|
||||
const player = new Player(player_id, options.signature_timestamp, options.data);
|
||||
await player.cache(options.cache);
|
||||
return player;
|
||||
}
|
||||
|
||||
async cache(cache?: ICache): Promise<void> {
|
||||
if (!cache) return;
|
||||
if (!cache || !this.data)
|
||||
return;
|
||||
|
||||
const encoder = new TextEncoder();
|
||||
const buffer = BinarySerializer.serialize({
|
||||
playerId: this.player_id,
|
||||
signatureTimestamp: this.signature_timestamp,
|
||||
libraryVersion: packageInfo.version,
|
||||
data: this.data
|
||||
});
|
||||
|
||||
const sig_buf = encoder.encode(this.#sig_sc);
|
||||
const nsig_buf = encoder.encode(this.#nsig_sc);
|
||||
|
||||
const buffer = new ArrayBuffer(12 + sig_buf.byteLength + nsig_buf.byteLength);
|
||||
const view = new DataView(buffer);
|
||||
|
||||
view.setUint32(0, Player.LIBRARY_VERSION, true);
|
||||
view.setUint32(4, this.#sig_sc_timestamp, true);
|
||||
view.setUint32(8, sig_buf.byteLength, true);
|
||||
|
||||
new Uint8Array(buffer).set(sig_buf, 12);
|
||||
new Uint8Array(buffer).set(nsig_buf, 12 + sig_buf.byteLength);
|
||||
|
||||
await cache.set(this.#player_id, new Uint8Array(buffer));
|
||||
}
|
||||
|
||||
static extractSigTimestamp(data: string): number {
|
||||
return parseInt(getStringBetweenStrings(data, 'signatureTimestamp:', ',') || '0');
|
||||
}
|
||||
|
||||
static extractSigSourceCode(data: string): string {
|
||||
const calls = getStringBetweenStrings(data, 'function(a){a=a.split("")', 'return a.join("")}');
|
||||
const obj_name = calls?.split(/\.|\[/)?.[0]?.replace(';', '')?.trim();
|
||||
const functions = getStringBetweenStrings(data, `var ${obj_name}={`, '};');
|
||||
|
||||
if (!functions || !calls)
|
||||
Log.warn(Player.TAG, 'Failed to extract signature decipher algorithm.');
|
||||
|
||||
return `function descramble_sig(a) { a = a.split(""); let ${obj_name}={${functions}}${calls} return a.join("") } descramble_sig(sig);`;
|
||||
}
|
||||
|
||||
static extractNSigSourceCode(data: string): string {
|
||||
const sc = `function descramble_nsig(a) { let b=a.split("")${getStringBetweenStrings(data, 'b=a.split("")', '}return b.join("")}')}} return b.join(""); } descramble_nsig(nsig)`;
|
||||
|
||||
if (!sc)
|
||||
Log.warn(Player.TAG, 'Failed to extract n-token decipher algorithm');
|
||||
|
||||
return sc;
|
||||
await cache.set(this.player_id, buffer);
|
||||
}
|
||||
|
||||
get url(): string {
|
||||
return new URL(`/s/player/${this.#player_id}/player_ias.vflset/en_US/base.js`, Constants.URLS.YT_BASE).toString();
|
||||
}
|
||||
|
||||
get sts(): number {
|
||||
return this.#sig_sc_timestamp;
|
||||
}
|
||||
|
||||
get nsig_sc(): string {
|
||||
return this.#nsig_sc;
|
||||
}
|
||||
|
||||
get sig_sc(): string {
|
||||
return this.#sig_sc;
|
||||
return new URL(`/s/player/${this.player_id}/player_ias.vflset/en_US/base.js`, Constants.URLS.YT_BASE).toString();
|
||||
}
|
||||
|
||||
static get LIBRARY_VERSION(): number {
|
||||
return 2;
|
||||
return 14;
|
||||
}
|
||||
}
|
||||
@@ -1,60 +1,83 @@
|
||||
import OAuth from './OAuth.ts';
|
||||
import { Log, EventEmitter, HTTPClient } from '../utils/index.ts';
|
||||
import * as Constants from '../utils/Constants.ts';
|
||||
import * as Proto from '../proto/index.ts';
|
||||
import Actions from './Actions.ts';
|
||||
import OAuth2 from './OAuth2.ts';
|
||||
import Player from './Player.ts';
|
||||
|
||||
import * as Constants from '../utils/Constants.ts';
|
||||
import { EventEmitter, HTTPClient, BinarySerializer, Log, ProtoUtils } from '../utils/index.ts';
|
||||
import {
|
||||
generateRandomString, getRandomUserAgent,
|
||||
InnertubeError, Platform, SessionError
|
||||
} from '../utils/Utils.ts';
|
||||
import packageInfo from '../../package.json' with { type: 'json' };
|
||||
|
||||
import type { DeviceCategory } from '../utils/Utils.ts';
|
||||
import type { FetchFunction, ICache } from '../types/index.ts';
|
||||
import type {
|
||||
Credentials, OAuthAuthErrorEventHandler,
|
||||
OAuthAuthEventHandler, OAuthAuthPendingEventHandler
|
||||
} from './OAuth.ts';
|
||||
OAuth2Tokens,
|
||||
OAuth2AuthErrorEventHandler,
|
||||
OAuth2AuthPendingEventHandler,
|
||||
OAuth2AuthEventHandler
|
||||
} from './OAuth2.ts';
|
||||
import type { IRawResponse } from '../parser/index.ts';
|
||||
|
||||
export enum ClientType {
|
||||
WEB = 'WEB',
|
||||
MWEB = 'MWEB',
|
||||
KIDS = 'WEB_KIDS',
|
||||
MUSIC = 'WEB_REMIX',
|
||||
IOS = 'iOS',
|
||||
ANDROID = 'ANDROID',
|
||||
ANDROID_VR = 'ANDROID_VR',
|
||||
ANDROID_MUSIC = 'ANDROID_MUSIC',
|
||||
ANDROID_CREATOR = 'ANDROID_CREATOR',
|
||||
TV_EMBEDDED = 'TVHTML5_SIMPLY_EMBEDDED_PLAYER'
|
||||
TV = 'TVHTML5',
|
||||
TV_SIMPLY = 'TVHTML5_SIMPLY',
|
||||
TV_EMBEDDED = 'TVHTML5_SIMPLY_EMBEDDED_PLAYER',
|
||||
WEB_EMBEDDED = 'WEB_EMBEDDED_PLAYER',
|
||||
WEB_CREATOR = 'WEB_CREATOR'
|
||||
}
|
||||
|
||||
export interface Context {
|
||||
export type Context = {
|
||||
client: {
|
||||
hl: string;
|
||||
gl: string;
|
||||
remoteHost?: string;
|
||||
screenDensityFloat: number;
|
||||
screenHeightPoints: number;
|
||||
screenPixelDensity: number;
|
||||
screenWidthPoints: number;
|
||||
visitorData: string;
|
||||
screenDensityFloat?: number;
|
||||
screenHeightPoints?: number;
|
||||
screenPixelDensity?: number;
|
||||
screenWidthPoints?: number;
|
||||
visitorData?: string;
|
||||
clientName: string;
|
||||
clientVersion: string;
|
||||
clientScreen?: string,
|
||||
androidSdkVersion?: string;
|
||||
androidSdkVersion?: number;
|
||||
osName: string;
|
||||
osVersion: string;
|
||||
platform: string;
|
||||
clientFormFactor: string;
|
||||
userInterfaceTheme: string;
|
||||
userInterfaceTheme?: string;
|
||||
timeZone: string;
|
||||
userAgent?: string;
|
||||
userAgent: string;
|
||||
browserName?: string;
|
||||
browserVersion?: string;
|
||||
originalUrl: string;
|
||||
originalUrl?: string;
|
||||
deviceMake: string;
|
||||
deviceModel: string;
|
||||
deviceExperimentId?: string;
|
||||
rolloutToken?: string;
|
||||
utcOffsetMinutes: number;
|
||||
mainAppWebInfo?: {
|
||||
graftUrl: string;
|
||||
pwaInstallabilityStatus: string;
|
||||
webDisplayMode: string;
|
||||
isWebNativeShareAvailable: boolean;
|
||||
};
|
||||
memoryTotalKbytes?: string;
|
||||
configInfo?: {
|
||||
appInstallData?: string;
|
||||
coldConfigData?: string;
|
||||
coldHashData?: string;
|
||||
hotHashData?: string;
|
||||
},
|
||||
kidsAppInfo?: {
|
||||
categorySettings: {
|
||||
enabledCategories: string[];
|
||||
@@ -73,9 +96,36 @@ export interface Context {
|
||||
thirdParty?: {
|
||||
embedUrl: string;
|
||||
};
|
||||
request?: {
|
||||
useSsl: boolean;
|
||||
internalExperimentFlags: any[];
|
||||
};
|
||||
}
|
||||
|
||||
export interface SessionOptions {
|
||||
type ContextData = {
|
||||
hl: string;
|
||||
gl: string;
|
||||
remote_host?: string;
|
||||
visitor_data: string;
|
||||
client_name: string;
|
||||
client_version: string;
|
||||
user_agent: string;
|
||||
os_name: string;
|
||||
os_version: string;
|
||||
device_category: string;
|
||||
time_zone: string;
|
||||
enable_safety_mode: boolean;
|
||||
browser_name?: string;
|
||||
browser_version?: string;
|
||||
app_install_data?: string;
|
||||
device_make: string;
|
||||
device_model: string;
|
||||
on_behalf_of_user?: string;
|
||||
device_experiment_id?: string;
|
||||
rollout_token?: string;
|
||||
}
|
||||
|
||||
export type SessionOptions = {
|
||||
/**
|
||||
* Language.
|
||||
*/
|
||||
@@ -84,10 +134,14 @@ export interface SessionOptions {
|
||||
* Geolocation.
|
||||
*/
|
||||
location?: string;
|
||||
/**
|
||||
* User agent (InnerTube requests only).
|
||||
*/
|
||||
user_agent?: string;
|
||||
/**
|
||||
* The account index to use. This is useful if you have multiple accounts logged in.
|
||||
* **NOTE:**
|
||||
* Only works if you are signed in with cookies.
|
||||
*
|
||||
* **NOTE:** Only works if you are signed in with cookies.
|
||||
*/
|
||||
account_index?: number;
|
||||
/**
|
||||
@@ -96,6 +150,7 @@ export interface SessionOptions {
|
||||
on_behalf_of_user?: string;
|
||||
/**
|
||||
* Specifies whether to retrieve the JS player. Disabling this will make session creation faster.
|
||||
*
|
||||
* **NOTE:** Deciphering formats is not possible without the JS player.
|
||||
*/
|
||||
retrieve_player?: boolean;
|
||||
@@ -103,11 +158,27 @@ export interface SessionOptions {
|
||||
* Specifies whether to enable safety mode. This will prevent the session from loading any potentially unsafe content.
|
||||
*/
|
||||
enable_safety_mode?: boolean;
|
||||
/**
|
||||
* Specifies whether to retrieve the InnerTube config. Useful for "onesie" requests.
|
||||
*/
|
||||
retrieve_innertube_config?: boolean;
|
||||
/**
|
||||
* Specifies whether to generate the session data locally or retrieve it from YouTube.
|
||||
* This can be useful if you need more performance.
|
||||
*
|
||||
* **NOTE:** If you are using the cache option and a session has already been generated, this will be ignored.
|
||||
* If you want to force a new session to be generated, you must clear the cache or disable session caching.
|
||||
*/
|
||||
generate_session_locally?: boolean;
|
||||
/**
|
||||
* If set to `true`, session creation will fail if it's not possible to retrieve session data from YouTube.
|
||||
* If `false`, a local fallback will be used.
|
||||
*/
|
||||
fail_fast?: boolean;
|
||||
/**
|
||||
* Specifies whether the session data should be cached.
|
||||
*/
|
||||
enable_session_cache?: boolean;
|
||||
/**
|
||||
* Platform to use for the session.
|
||||
*/
|
||||
@@ -121,7 +192,7 @@ export interface SessionOptions {
|
||||
*/
|
||||
timezone?: string;
|
||||
/**
|
||||
* Used to cache the deciphering functions from the JS player.
|
||||
* Used to cache algorithms, session data, and OAuth2 tokens.
|
||||
*/
|
||||
cache?: ICache;
|
||||
/**
|
||||
@@ -137,18 +208,40 @@ export interface SessionOptions {
|
||||
* Fetch function to use.
|
||||
*/
|
||||
fetch?: FetchFunction;
|
||||
/**
|
||||
* Session bound Proof of Origin Token. This is an attestation token generated by BotGuard/DroidGuard. It is used to confirm that the request is coming from a real client.
|
||||
*/
|
||||
po_token?: string;
|
||||
/**
|
||||
* Player ID override.
|
||||
* In most cases, this isn't necessary; but when YouTube introduces breaking changes,
|
||||
* forcing an older Player ID can help work around temporary issues.
|
||||
*/
|
||||
player_id?: string;
|
||||
}
|
||||
|
||||
export interface SessionData {
|
||||
export type SessionData = {
|
||||
context: Context;
|
||||
api_key: string;
|
||||
api_version: string;
|
||||
config_data?: string;
|
||||
}
|
||||
|
||||
interface SerializableSession extends SessionData {
|
||||
library_version: number;
|
||||
}
|
||||
|
||||
export type SWSessionData = {
|
||||
context_data: ContextData;
|
||||
api_key: string;
|
||||
api_version: string;
|
||||
}
|
||||
|
||||
export type SessionArgs = {
|
||||
lang: string;
|
||||
location: string;
|
||||
time_zone: string;
|
||||
user_agent: string;
|
||||
device_category: DeviceCategory;
|
||||
client_name: ClientType;
|
||||
enable_safety_mode: boolean;
|
||||
@@ -156,135 +249,306 @@ export type SessionArgs = {
|
||||
on_behalf_of_user: string | undefined;
|
||||
}
|
||||
|
||||
const TAG = 'Session';
|
||||
|
||||
/**
|
||||
* Represents an InnerTube session. This holds all the data needed to make requests to YouTube.
|
||||
*/
|
||||
export default class Session extends EventEmitter {
|
||||
static TAG = 'Session';
|
||||
public oauth: OAuth2;
|
||||
public http: HTTPClient;
|
||||
public logged_in: boolean;
|
||||
public actions: Actions;
|
||||
public user_agent?: string;
|
||||
|
||||
#api_version: string;
|
||||
#key: string;
|
||||
#context: Context;
|
||||
#account_index: number;
|
||||
#player?: Player;
|
||||
|
||||
oauth: OAuth;
|
||||
http: HTTPClient;
|
||||
logged_in: boolean;
|
||||
actions: Actions;
|
||||
cache?: ICache;
|
||||
|
||||
constructor(context: Context, api_key: string, api_version: string, account_index: number, player?: Player, cookie?: string, fetch?: FetchFunction, cache?: ICache) {
|
||||
constructor(
|
||||
public context: Context,
|
||||
public api_key: string,
|
||||
public api_version: string,
|
||||
public account_index: number,
|
||||
public config_data?: string,
|
||||
public player?: Player,
|
||||
public cookie?: string,
|
||||
fetch?: FetchFunction,
|
||||
public cache?: ICache,
|
||||
public po_token?: string
|
||||
) {
|
||||
super();
|
||||
this.#context = context;
|
||||
this.#account_index = account_index;
|
||||
this.#key = api_key;
|
||||
this.#api_version = api_version;
|
||||
this.#player = player;
|
||||
this.http = new HTTPClient(this, cookie, fetch);
|
||||
this.actions = new Actions(this);
|
||||
this.oauth = new OAuth(this);
|
||||
this.oauth = new OAuth2(this);
|
||||
this.logged_in = !!cookie;
|
||||
this.cache = cache;
|
||||
this.user_agent = context.client.userAgent;
|
||||
}
|
||||
|
||||
on(type: 'auth', listener: OAuthAuthEventHandler): void;
|
||||
on(type: 'auth-pending', listener: OAuthAuthPendingEventHandler): void;
|
||||
on(type: 'auth-error', listener: OAuthAuthErrorEventHandler): void;
|
||||
on(type: 'update-credentials', listener: OAuthAuthEventHandler): void;
|
||||
on(type: 'auth', listener: OAuth2AuthEventHandler): void;
|
||||
on(type: 'auth-pending', listener: OAuth2AuthPendingEventHandler): void;
|
||||
on(type: 'auth-error', listener: OAuth2AuthErrorEventHandler): void;
|
||||
on(type: 'update-credentials', listener: OAuth2AuthEventHandler): void;
|
||||
|
||||
on(type: string, listener: (...args: any[]) => void): void {
|
||||
super.on(type, listener);
|
||||
}
|
||||
|
||||
once(type: 'auth', listener: OAuthAuthEventHandler): void;
|
||||
once(type: 'auth-pending', listener: OAuthAuthPendingEventHandler): void;
|
||||
once(type: 'auth-error', listener: OAuthAuthErrorEventHandler): void;
|
||||
once(type: 'auth', listener: OAuth2AuthEventHandler): void;
|
||||
once(type: 'auth-pending', listener: OAuth2AuthPendingEventHandler): void;
|
||||
once(type: 'auth-error', listener: OAuth2AuthErrorEventHandler): void;
|
||||
|
||||
once(type: string, listener: (...args: any[]) => void): void {
|
||||
super.once(type, listener);
|
||||
}
|
||||
|
||||
static async create(options: SessionOptions = {}) {
|
||||
const { context, api_key, api_version, account_index } = await Session.getSessionData(
|
||||
const { context, api_key, api_version, account_index, config_data } = await Session.getSessionData(
|
||||
options.lang,
|
||||
options.location,
|
||||
options.account_index,
|
||||
options.visitor_data,
|
||||
options.user_agent,
|
||||
options.enable_safety_mode,
|
||||
options.generate_session_locally,
|
||||
options.fail_fast,
|
||||
options.device_category,
|
||||
options.client_type,
|
||||
options.timezone,
|
||||
options.fetch,
|
||||
options.on_behalf_of_user
|
||||
options.on_behalf_of_user,
|
||||
options.cache,
|
||||
options.enable_session_cache,
|
||||
options.po_token,
|
||||
options.retrieve_innertube_config
|
||||
);
|
||||
|
||||
return new Session(
|
||||
context, api_key, api_version, account_index,
|
||||
options.retrieve_player === false ? undefined : await Player.create(options.cache, options.fetch),
|
||||
options.cookie, options.fetch, options.cache
|
||||
context, api_key, api_version, account_index, config_data,
|
||||
options.retrieve_player === false ? undefined : await Player.create(options.cache, options.fetch, options.po_token, options.player_id),
|
||||
options.cookie, options.fetch, options.cache, options.po_token
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves session data from cache.
|
||||
* @param cache - A valid cache implementation.
|
||||
* @param session_args - User provided session arguments.
|
||||
*/
|
||||
static async fromCache(cache: ICache, session_args: SessionArgs): Promise<SessionData | null> {
|
||||
const buffer = await cache.get('innertube_session_data');
|
||||
|
||||
if (!buffer)
|
||||
return null;
|
||||
|
||||
try {
|
||||
const session_data = BinarySerializer.deserialize<SerializableSession>(new Uint8Array(buffer));
|
||||
|
||||
if (session_data.library_version !== parseInt(packageInfo.version.split('.', 1)[0])) {
|
||||
Log.warn(TAG, `Cached session data is from a different library version (${session_data.library_version}). Regenerating session data.`);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (session_args.visitor_data) {
|
||||
session_data.context.client.visitorData = session_args.visitor_data;
|
||||
}
|
||||
|
||||
if (session_args.lang)
|
||||
session_data.context.client.hl = session_args.lang;
|
||||
|
||||
if (session_args.location)
|
||||
session_data.context.client.gl = session_args.location;
|
||||
|
||||
if (session_args.on_behalf_of_user)
|
||||
session_data.context.user.onBehalfOfUser = session_args.on_behalf_of_user;
|
||||
|
||||
if (session_args.user_agent)
|
||||
session_data.context.client.userAgent = session_args.user_agent;
|
||||
|
||||
if (session_args.client_name) {
|
||||
const client = Object.values(Constants.CLIENTS).find((c) => c.NAME === session_args.client_name);
|
||||
if (client) {
|
||||
session_data.context.client.clientName = client.NAME;
|
||||
session_data.context.client.clientVersion = client.VERSION;
|
||||
} else Log.warn(TAG, `Unknown client name: ${session_args.client_name}.`);
|
||||
}
|
||||
|
||||
session_data.context.client.timeZone = session_args.time_zone;
|
||||
session_data.context.client.platform = session_args.device_category.toUpperCase();
|
||||
session_data.context.user.enableSafetyMode = session_args.enable_safety_mode;
|
||||
|
||||
return session_data;
|
||||
} catch (error) {
|
||||
Log.error(TAG, 'Failed to deserialize session data from cache.', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
static async getSessionData(
|
||||
lang = '',
|
||||
location = '',
|
||||
account_index = 0,
|
||||
visitor_data = '',
|
||||
user_agent: string = getRandomUserAgent('desktop'),
|
||||
enable_safety_mode = false,
|
||||
generate_session_locally = false,
|
||||
fail_fast = false,
|
||||
device_category: DeviceCategory = 'desktop',
|
||||
client_name: ClientType = ClientType.WEB,
|
||||
tz: string = Intl.DateTimeFormat().resolvedOptions().timeZone,
|
||||
fetch: FetchFunction = Platform.shim.fetch,
|
||||
on_behalf_of_user?: string
|
||||
on_behalf_of_user?: string,
|
||||
cache?: ICache,
|
||||
enable_session_cache = true,
|
||||
po_token?: string,
|
||||
retrieve_innertube_config = true
|
||||
) {
|
||||
let session_data: SessionData;
|
||||
const session_args = {
|
||||
lang,
|
||||
location,
|
||||
time_zone: tz,
|
||||
user_agent,
|
||||
device_category,
|
||||
client_name,
|
||||
enable_safety_mode,
|
||||
visitor_data,
|
||||
on_behalf_of_user,
|
||||
po_token
|
||||
};
|
||||
|
||||
const session_args = { lang, location, time_zone: tz, device_category, client_name, enable_safety_mode, visitor_data, on_behalf_of_user };
|
||||
let session_data: SessionData | undefined;
|
||||
|
||||
Log.info(Session.TAG, 'Retrieving InnerTube session.');
|
||||
|
||||
if (generate_session_locally) {
|
||||
session_data = this.#generateSessionData(session_args);
|
||||
} else {
|
||||
try {
|
||||
// This can fail if the data changes or the request is blocked for some reason.
|
||||
session_data = await this.#retrieveSessionData(session_args, fetch);
|
||||
} catch (err) {
|
||||
Log.error(Session.TAG, 'Failed to retrieve session data from server. Will try to generate it locally.');
|
||||
session_data = this.#generateSessionData(session_args);
|
||||
if (cache && enable_session_cache) {
|
||||
const cached_session_data = await this.fromCache(cache, session_args);
|
||||
if (cached_session_data) {
|
||||
Log.info(TAG, 'Found session data in cache.');
|
||||
session_data = cached_session_data;
|
||||
}
|
||||
}
|
||||
|
||||
Log.info(Session.TAG, 'Got session data.\n', session_data);
|
||||
if (!session_data) {
|
||||
Log.info(TAG, 'Generating session data.');
|
||||
|
||||
let api_key: string = Constants.CLIENTS.WEB.API_KEY;
|
||||
let api_version: string = Constants.CLIENTS.WEB.API_VERSION;
|
||||
|
||||
let context_data: ContextData = {
|
||||
hl: lang || 'en',
|
||||
gl: location || 'US',
|
||||
remote_host: '',
|
||||
user_agent: user_agent,
|
||||
visitor_data: visitor_data || ProtoUtils.encodeVisitorData(generateRandomString(11), Math.floor(Date.now() / 1000)),
|
||||
client_name: client_name,
|
||||
client_version: Object.values(Constants.CLIENTS).find((v) => v.NAME === client_name)?.VERSION ?? Constants.CLIENTS.WEB.VERSION,
|
||||
device_category: device_category.toUpperCase(),
|
||||
os_name: 'Windows',
|
||||
os_version: '10.0',
|
||||
time_zone: tz,
|
||||
browser_name: 'Chrome',
|
||||
browser_version: '125.0.0.0',
|
||||
device_make: '',
|
||||
device_model: '',
|
||||
enable_safety_mode: enable_safety_mode
|
||||
};
|
||||
|
||||
if (!generate_session_locally) {
|
||||
try {
|
||||
const sw_session_data = await this.#getSessionData(session_args, fetch);
|
||||
api_key = sw_session_data.api_key;
|
||||
api_version = sw_session_data.api_version;
|
||||
context_data = sw_session_data.context_data;
|
||||
} catch (error) {
|
||||
if (fail_fast)
|
||||
throw error;
|
||||
Log.error(TAG, 'Failed to retrieve session data from server. Session data generated locally will be used instead.', error);
|
||||
}
|
||||
}
|
||||
|
||||
if (on_behalf_of_user) {
|
||||
context_data.on_behalf_of_user = on_behalf_of_user;
|
||||
}
|
||||
|
||||
session_data = {
|
||||
api_key,
|
||||
api_version,
|
||||
context: this.#buildContext(context_data)
|
||||
};
|
||||
|
||||
if (retrieve_innertube_config) {
|
||||
try {
|
||||
Log.info(TAG, 'Retrieving InnerTube config data.');
|
||||
|
||||
const config_headers: Record<string, any> = {
|
||||
'Accept-Language': lang,
|
||||
'Accept': '*/*',
|
||||
'Referer': Constants.URLS.YT_BASE,
|
||||
'X-Goog-Visitor-Id': context_data.visitor_data,
|
||||
'X-Origin': Constants.URLS.YT_BASE,
|
||||
'X-Youtube-Client-Version': context_data.client_version
|
||||
};
|
||||
|
||||
if (Platform.shim.server) {
|
||||
config_headers['User-Agent'] = user_agent;
|
||||
config_headers['Origin'] = Constants.URLS.YT_BASE;
|
||||
}
|
||||
|
||||
const config = await fetch(`${Constants.URLS.API.PRODUCTION_1}v1/config?prettyPrint=false`, {
|
||||
headers: config_headers,
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ context: session_data.context })
|
||||
});
|
||||
|
||||
const configJson = await config.json() as IRawResponse;
|
||||
|
||||
const coldConfigData = configJson.responseContext?.globalConfigGroup?.rawColdConfigGroup?.configData;
|
||||
const coldHashData = configJson.responseContext?.globalConfigGroup?.coldHashData;
|
||||
const hotHashData = configJson.responseContext?.globalConfigGroup?.hotHashData;
|
||||
|
||||
session_data.config_data = configJson.configData;
|
||||
session_data.context.client.configInfo = {
|
||||
...session_data.context.client.configInfo,
|
||||
coldConfigData,
|
||||
coldHashData,
|
||||
hotHashData
|
||||
};
|
||||
} catch (error) {
|
||||
Log.error(TAG, 'Failed to retrieve config data.', error);
|
||||
}
|
||||
}
|
||||
|
||||
if (enable_session_cache)
|
||||
await this.#storeSession(session_data, cache);
|
||||
}
|
||||
|
||||
Log.debug(TAG, 'Session data:', session_data);
|
||||
|
||||
return { ...session_data, account_index };
|
||||
}
|
||||
|
||||
static #getVisitorID(visitor_data: string) {
|
||||
const decoded_visitor_data = Proto.decodeVisitorData(visitor_data);
|
||||
Log.info(Session.TAG, 'Custom visitor data decoded successfully.\n', decoded_visitor_data);
|
||||
return decoded_visitor_data.id;
|
||||
static async #storeSession(session_data: SessionData, cache?: ICache) {
|
||||
if (!cache) return;
|
||||
|
||||
Log.info(TAG, 'Compressing and caching session data.');
|
||||
|
||||
const buffer = BinarySerializer.serialize({
|
||||
...session_data,
|
||||
library_version: parseInt(packageInfo.version)
|
||||
});
|
||||
|
||||
await cache.set('innertube_session_data', buffer);
|
||||
}
|
||||
|
||||
static async #retrieveSessionData(options: SessionArgs, fetch: FetchFunction = Platform.shim.fetch): Promise<SessionData> {
|
||||
const url = new URL('/sw.js_data', Constants.URLS.YT_BASE);
|
||||
|
||||
static async #getSessionData(options: SessionArgs, fetch: FetchFunction = Platform.shim.fetch): Promise<SWSessionData> {
|
||||
let visitor_id = generateRandomString(11);
|
||||
|
||||
if (options.visitor_data) {
|
||||
if (options.visitor_data)
|
||||
visitor_id = this.#getVisitorID(options.visitor_data);
|
||||
}
|
||||
|
||||
const url = new URL('/sw.js_data', Constants.URLS.YT_BASE);
|
||||
|
||||
const res = await fetch(url, {
|
||||
headers: {
|
||||
'accept-language': options.lang || 'en-US',
|
||||
'user-agent': getRandomUserAgent('desktop'),
|
||||
'accept': '*/*',
|
||||
'referer': 'https://www.youtube.com/sw.js',
|
||||
'cookie': `PREF=tz=${options.time_zone.replace('/', '.')};VISITOR_INFO1_LIVE=${visitor_id};`
|
||||
'Accept-Language': options.lang || 'en-US',
|
||||
'User-Agent': options.user_agent,
|
||||
'Accept': '*/*',
|
||||
'Referer': `${Constants.URLS.YT_BASE}/sw.js`,
|
||||
'Cookie': `PREF=tz=${options.time_zone.replace('/', '.')};VISITOR_INFO1_LIVE=${visitor_id};`
|
||||
}
|
||||
});
|
||||
|
||||
@@ -292,6 +556,10 @@ export default class Session extends EventEmitter {
|
||||
throw new SessionError(`Failed to retrieve session data: ${res.status}`);
|
||||
|
||||
const text = await res.text();
|
||||
|
||||
if (!text.startsWith(')]}\''))
|
||||
throw new SessionError('Invalid JSPB response');
|
||||
|
||||
const data = JSON.parse(text.replace(/^\)\]\}'/, ''));
|
||||
|
||||
const ytcfg = data[0][2];
|
||||
@@ -300,105 +568,110 @@ export default class Session extends EventEmitter {
|
||||
|
||||
const [ [ device_info ], api_key ] = ytcfg;
|
||||
|
||||
const context: Context = {
|
||||
client: {
|
||||
hl: device_info[0],
|
||||
gl: options.location || device_info[2],
|
||||
remoteHost: device_info[3],
|
||||
screenDensityFloat: 1,
|
||||
screenHeightPoints: 1080,
|
||||
screenPixelDensity: 1,
|
||||
screenWidthPoints: 1920,
|
||||
visitorData: device_info[13],
|
||||
clientName: options.client_name,
|
||||
clientVersion: device_info[16],
|
||||
osName: device_info[17],
|
||||
osVersion: device_info[18],
|
||||
platform: options.device_category.toUpperCase(),
|
||||
clientFormFactor: 'UNKNOWN_FORM_FACTOR',
|
||||
userInterfaceTheme: 'USER_INTERFACE_THEME_LIGHT',
|
||||
timeZone: device_info[79] || options.time_zone,
|
||||
browserName: device_info[86],
|
||||
browserVersion: device_info[87],
|
||||
originalUrl: Constants.URLS.YT_BASE,
|
||||
deviceMake: device_info[11],
|
||||
deviceModel: device_info[12],
|
||||
utcOffsetMinutes: -new Date().getTimezoneOffset()
|
||||
},
|
||||
user: {
|
||||
enableSafetyMode: options.enable_safety_mode,
|
||||
lockedSafetyMode: false,
|
||||
onBehalfOfUser: options.on_behalf_of_user
|
||||
}
|
||||
const config_info = device_info[61];
|
||||
const app_install_data = config_info[config_info.length - 1];
|
||||
|
||||
const context_info = {
|
||||
hl: options.lang || device_info[0],
|
||||
gl: options.location || device_info[1],
|
||||
remote_host: device_info[3],
|
||||
visitor_data: options.visitor_data || device_info[13],
|
||||
user_agent: options.user_agent,
|
||||
client_name: options.client_name,
|
||||
client_version: options.client_name === 'WEB' ? device_info[16] : Object.values(Constants.CLIENTS).find(
|
||||
(c) => c.NAME === options.client_name
|
||||
)?.VERSION || device_info[16],
|
||||
os_name: device_info[17],
|
||||
os_version: device_info[18],
|
||||
time_zone: device_info[79] || options.time_zone,
|
||||
device_category: options.device_category,
|
||||
browser_name: device_info[86],
|
||||
browser_version: device_info[87],
|
||||
device_make: device_info[11],
|
||||
device_model: device_info[12],
|
||||
app_install_data: app_install_data,
|
||||
device_experiment_id: device_info[103],
|
||||
rollout_token: device_info[107],
|
||||
enable_safety_mode: options.enable_safety_mode
|
||||
};
|
||||
|
||||
return { context, api_key, api_version };
|
||||
return { context_data: context_info, api_key, api_version };
|
||||
}
|
||||
|
||||
static #generateSessionData(options: SessionArgs): SessionData {
|
||||
let visitor_id = generateRandomString(11);
|
||||
|
||||
if (options.visitor_data) {
|
||||
visitor_id = this.#getVisitorID(options.visitor_data);
|
||||
}
|
||||
|
||||
static #buildContext(args: ContextData) {
|
||||
const context: Context = {
|
||||
client: {
|
||||
hl: options.lang || 'en',
|
||||
gl: options.location || 'US',
|
||||
hl: args.hl || 'en',
|
||||
gl: args.gl || 'US',
|
||||
remoteHost: args.remote_host,
|
||||
screenDensityFloat: 1,
|
||||
screenHeightPoints: 1080,
|
||||
screenHeightPoints: 1440,
|
||||
screenPixelDensity: 1,
|
||||
screenWidthPoints: 1920,
|
||||
visitorData: Proto.encodeVisitorData(visitor_id, Math.floor(Date.now() / 1000)),
|
||||
clientName: options.client_name,
|
||||
clientVersion: Constants.CLIENTS.WEB.VERSION,
|
||||
osName: 'Windows',
|
||||
osVersion: '10.0',
|
||||
platform: options.device_category.toUpperCase(),
|
||||
screenWidthPoints: 2560,
|
||||
visitorData: args.visitor_data,
|
||||
clientName: args.client_name,
|
||||
clientVersion: args.client_version,
|
||||
osName: args.os_name,
|
||||
osVersion: args.os_version,
|
||||
userAgent: args.user_agent,
|
||||
platform: args.device_category.toUpperCase(),
|
||||
clientFormFactor: 'UNKNOWN_FORM_FACTOR',
|
||||
userInterfaceTheme: 'USER_INTERFACE_THEME_LIGHT',
|
||||
timeZone: options.time_zone,
|
||||
timeZone: args.time_zone,
|
||||
originalUrl: Constants.URLS.YT_BASE,
|
||||
deviceMake: '',
|
||||
deviceModel: '',
|
||||
utcOffsetMinutes: -new Date().getTimezoneOffset()
|
||||
},
|
||||
user: {
|
||||
enableSafetyMode: options.enable_safety_mode,
|
||||
lockedSafetyMode: false,
|
||||
onBehalfOfUser: options.on_behalf_of_user
|
||||
}
|
||||
};
|
||||
|
||||
return { context, api_key: Constants.CLIENTS.WEB.API_KEY, api_version: Constants.CLIENTS.WEB.API_VERSION };
|
||||
}
|
||||
|
||||
async signIn(credentials?: Credentials): Promise<void> {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
const error_handler: OAuthAuthErrorEventHandler = (err) => reject(err);
|
||||
|
||||
this.once('auth', (data) => {
|
||||
this.off('auth-error', error_handler);
|
||||
|
||||
if (data.status === 'SUCCESS') {
|
||||
this.logged_in = true;
|
||||
resolve();
|
||||
deviceMake: args.device_make,
|
||||
deviceModel: args.device_model,
|
||||
browserName: args.browser_name,
|
||||
browserVersion: args.browser_version,
|
||||
utcOffsetMinutes: -Math.floor((new Date()).getTimezoneOffset()),
|
||||
memoryTotalKbytes: '8000000',
|
||||
rolloutToken: args.rollout_token,
|
||||
deviceExperimentId: args.device_experiment_id,
|
||||
mainAppWebInfo: {
|
||||
graftUrl: Constants.URLS.YT_BASE,
|
||||
pwaInstallabilityStatus: 'PWA_INSTALLABILITY_STATUS_UNKNOWN',
|
||||
webDisplayMode: 'WEB_DISPLAY_MODE_BROWSER',
|
||||
isWebNativeShareAvailable: true
|
||||
}
|
||||
},
|
||||
user: {
|
||||
enableSafetyMode: args.enable_safety_mode,
|
||||
lockedSafetyMode: false
|
||||
},
|
||||
request: {
|
||||
useSsl: true,
|
||||
internalExperimentFlags: []
|
||||
}
|
||||
};
|
||||
|
||||
reject(data);
|
||||
});
|
||||
if (args.app_install_data)
|
||||
context.client.configInfo = { appInstallData: args.app_install_data };
|
||||
|
||||
if (args.on_behalf_of_user)
|
||||
context.user.onBehalfOfUser = args.on_behalf_of_user;
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
static #getVisitorID(visitor_data: string) {
|
||||
const decoded_visitor_data = ProtoUtils.decodeVisitorData(visitor_data);
|
||||
return decoded_visitor_data.id;
|
||||
}
|
||||
|
||||
async signIn(credentials?: OAuth2Tokens): Promise<void> {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
const error_handler: OAuth2AuthErrorEventHandler = (err) => reject(err);
|
||||
|
||||
this.once('auth-error', error_handler);
|
||||
|
||||
this.once('auth', () => {
|
||||
this.off('auth-error', error_handler);
|
||||
this.logged_in = true;
|
||||
resolve();
|
||||
});
|
||||
|
||||
try {
|
||||
await this.oauth.init(credentials);
|
||||
|
||||
if (this.oauth.validateCredentials()) {
|
||||
await this.oauth.refreshIfRequired();
|
||||
this.logged_in = true;
|
||||
resolve();
|
||||
}
|
||||
} catch (err) {
|
||||
reject(err);
|
||||
}
|
||||
@@ -418,41 +691,15 @@ export default class Session extends EventEmitter {
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* InnerTube API key.
|
||||
*/
|
||||
get key(): string {
|
||||
return this.#key;
|
||||
}
|
||||
|
||||
/**
|
||||
* InnerTube API version.
|
||||
*/
|
||||
get api_version(): string {
|
||||
return this.#api_version;
|
||||
}
|
||||
|
||||
get client_version(): string {
|
||||
return this.#context.client.clientVersion;
|
||||
return this.context.client.clientVersion;
|
||||
}
|
||||
|
||||
get client_name(): string {
|
||||
return this.#context.client.clientName;
|
||||
}
|
||||
|
||||
get account_index(): number {
|
||||
return this.#account_index;
|
||||
}
|
||||
|
||||
get context(): Context {
|
||||
return this.#context;
|
||||
}
|
||||
|
||||
get player(): Player | undefined {
|
||||
return this.#player;
|
||||
return this.context.client.clientName;
|
||||
}
|
||||
|
||||
get lang(): string {
|
||||
return this.#context.client.hl;
|
||||
return this.context.client.hl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,10 @@
|
||||
import { Parser } from '../../parser/index.ts';
|
||||
import { Channel, HomeFeed, Search, VideoInfo } from '../../parser/ytkids/index.ts';
|
||||
import { InnertubeError, generateRandomString } from '../../utils/Utils.ts';
|
||||
import NavigationEndpoint from '../../parser/classes/NavigationEndpoint.ts';
|
||||
import KidsBlocklistPickerItem from '../../parser/classes/ytkids/KidsBlocklistPickerItem.ts';
|
||||
|
||||
import {
|
||||
BrowseEndpoint, NextEndpoint,
|
||||
PlayerEndpoint, SearchEndpoint
|
||||
} from '../endpoints/index.ts';
|
||||
|
||||
import { BlocklistPickerEndpoint } from '../endpoints/kids/index.ts';
|
||||
|
||||
import { InnertubeError, generateRandomString } from '../../utils/Utils.ts';
|
||||
import type { Session, ApiResponse } from '../index.ts';
|
||||
import type { GetVideoInfoOptions } from '../../types/index.ts';
|
||||
|
||||
export default class Kids {
|
||||
#session: Session;
|
||||
@@ -19,66 +13,60 @@ export default class Kids {
|
||||
this.#session = session;
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches the given query.
|
||||
* @param query - The query.
|
||||
*/
|
||||
async search(query: string): Promise<Search> {
|
||||
const response = await this.#session.actions.execute(
|
||||
SearchEndpoint.PATH, SearchEndpoint.build({ client: 'YTKIDS', query })
|
||||
);
|
||||
const search_endpoint = new NavigationEndpoint({ searchEndpoint: { query } });
|
||||
const response = await search_endpoint.call(this.#session.actions, { client: 'YTKIDS' });
|
||||
return new Search(this.#session.actions, response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves video info.
|
||||
* @param video_id - The video id.
|
||||
*/
|
||||
async getInfo(video_id: string): Promise<VideoInfo> {
|
||||
const player_payload = PlayerEndpoint.build({
|
||||
sts: this.#session.player?.sts,
|
||||
client: 'YTKIDS',
|
||||
video_id
|
||||
});
|
||||
async getInfo(video_id: string, options?: Omit<GetVideoInfoOptions, 'client'>): Promise<VideoInfo> {
|
||||
const payload = { videoId: video_id };
|
||||
const watch_endpoint = new NavigationEndpoint({ watchEndpoint: payload });
|
||||
const watch_next_endpoint = new NavigationEndpoint({ watchNextEndpoint: payload });
|
||||
|
||||
const next_payload = NextEndpoint.build({
|
||||
video_id,
|
||||
const session = this.#session;
|
||||
|
||||
const extra_payload: Record<string, any> = {
|
||||
playbackContext: {
|
||||
contentPlaybackContext: {
|
||||
vis: 0,
|
||||
splay: false,
|
||||
lactMilliseconds: '-1',
|
||||
signatureTimestamp: session.player?.signature_timestamp
|
||||
}
|
||||
},
|
||||
client: 'YTKIDS'
|
||||
});
|
||||
};
|
||||
|
||||
const player_response = this.#session.actions.execute(PlayerEndpoint.PATH, player_payload);
|
||||
const next_response = this.#session.actions.execute(NextEndpoint.PATH, next_payload);
|
||||
const response = await Promise.all([ player_response, next_response ]);
|
||||
if (options?.po_token) {
|
||||
extra_payload.serviceIntegrityDimensions = {
|
||||
poToken: options.po_token
|
||||
};
|
||||
} else if (session.po_token) {
|
||||
extra_payload.serviceIntegrityDimensions = {
|
||||
poToken: session.po_token
|
||||
};
|
||||
}
|
||||
|
||||
const watch_response = watch_endpoint.call(session.actions, extra_payload);
|
||||
|
||||
const watch_next_response = watch_next_endpoint.call(session.actions, { client: 'YTKIDS' });
|
||||
|
||||
const response = await Promise.all([ watch_response, watch_next_response ]);
|
||||
const cpn = generateRandomString(16);
|
||||
|
||||
return new VideoInfo(response, this.#session.actions, cpn);
|
||||
return new VideoInfo(response, session.actions, cpn);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the contents of the given channel.
|
||||
* @param channel_id - The channel id.
|
||||
*/
|
||||
async getChannel(channel_id: string): Promise<Channel> {
|
||||
const response = await this.#session.actions.execute(
|
||||
BrowseEndpoint.PATH, BrowseEndpoint.build({
|
||||
browse_id: channel_id,
|
||||
client: 'YTKIDS'
|
||||
})
|
||||
);
|
||||
const browse_endpoint = new NavigationEndpoint({ browseEndpoint: { browseId: channel_id } });
|
||||
const response = await browse_endpoint.call(this.#session.actions, { client: 'YTKIDS' });
|
||||
return new Channel(this.#session.actions, response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the home feed.
|
||||
*/
|
||||
async getHomeFeed(): Promise<HomeFeed> {
|
||||
const response = await this.#session.actions.execute(
|
||||
BrowseEndpoint.PATH, BrowseEndpoint.build({
|
||||
browse_id: 'FEkids_home',
|
||||
client: 'YTKIDS'
|
||||
})
|
||||
);
|
||||
const browse_endpoint = new NavigationEndpoint({ browseEndpoint: { browseId: 'FEkids_home' } });
|
||||
const response = await browse_endpoint.call(this.#session.actions, { client: 'YTKIDS' });
|
||||
return new HomeFeed(this.#session.actions, response);
|
||||
}
|
||||
|
||||
@@ -89,11 +77,20 @@ export default class Kids {
|
||||
* @returns A list of API responses.
|
||||
*/
|
||||
async blockChannel(channel_id: string): Promise<ApiResponse[]> {
|
||||
if (!this.#session.logged_in)
|
||||
const session = this.#session;
|
||||
|
||||
if (!session.logged_in)
|
||||
throw new InnertubeError('You must be signed in to perform this operation.');
|
||||
|
||||
const blocklist_payload = BlocklistPickerEndpoint.build({ channel_id: channel_id });
|
||||
const response = await this.#session.actions.execute(BlocklistPickerEndpoint.PATH, blocklist_payload );
|
||||
const kids_blocklist_picker_command = new NavigationEndpoint({
|
||||
getKidsBlocklistPickerCommand: {
|
||||
blockedForKidsContent: {
|
||||
external_channel_id: channel_id
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const response = await kids_blocklist_picker_command.call(session.actions, { client: 'YTKIDS' });
|
||||
const popup = response.data.command.confirmDialogEndpoint;
|
||||
const popup_fragment = { contents: popup.content, engagementPanels: [] };
|
||||
const kid_picker = Parser.parseResponse(popup_fragment);
|
||||
@@ -107,7 +104,7 @@ export default class Kids {
|
||||
|
||||
for (const kid of kids) {
|
||||
if (!kid.block_button?.is_toggled) {
|
||||
kid.setActions(this.#session.actions);
|
||||
kid.setActions(session.actions);
|
||||
// Block channel and add to the response list.
|
||||
responses.push(await kid.blockChannel());
|
||||
}
|
||||
|
||||
@@ -1,39 +1,38 @@
|
||||
import * as Proto from '../../proto/index.ts';
|
||||
import { InnertubeError, generateRandomString, throwIfMissing } from '../../utils/Utils.ts';
|
||||
import { generateRandomString, InnertubeError, throwIfMissing, u8ToBase64 } from '../../utils/Utils.ts';
|
||||
|
||||
import {
|
||||
Album, Artist, Explore,
|
||||
HomeFeed, Library, Playlist,
|
||||
Recap, Search, TrackInfo
|
||||
Album,
|
||||
Artist,
|
||||
Explore,
|
||||
HomeFeed,
|
||||
Library,
|
||||
Playlist,
|
||||
Recap,
|
||||
Search,
|
||||
TrackInfo
|
||||
} from '../../parser/ytmusic/index.ts';
|
||||
|
||||
import AutomixPreviewVideo from '../../parser/classes/AutomixPreviewVideo.ts';
|
||||
import Message from '../../parser/classes/Message.ts';
|
||||
import MusicCarouselShelf from '../../parser/classes/MusicCarouselShelf.ts';
|
||||
import MusicDescriptionShelf from '../../parser/classes/MusicDescriptionShelf.ts';
|
||||
import MusicQueue from '../../parser/classes/MusicQueue.ts';
|
||||
import MusicResponsiveListItem from '../../parser/classes/MusicResponsiveListItem.ts';
|
||||
import MusicTwoRowItem from '../../parser/classes/MusicTwoRowItem.ts';
|
||||
import NavigationEndpoint from '../../parser/classes/NavigationEndpoint.ts';
|
||||
import PlaylistPanel from '../../parser/classes/PlaylistPanel.ts';
|
||||
import SearchSuggestionsSection from '../../parser/classes/SearchSuggestionsSection.ts';
|
||||
import SectionList from '../../parser/classes/SectionList.ts';
|
||||
import Tab from '../../parser/classes/Tab.ts';
|
||||
|
||||
import {
|
||||
BrowseEndpoint,
|
||||
NextEndpoint,
|
||||
PlayerEndpoint,
|
||||
SearchEndpoint
|
||||
} from '../endpoints/index.ts';
|
||||
|
||||
import { GetSearchSuggestionsEndpoint } from '../endpoints/music/index.ts';
|
||||
import { SearchFilter } from '../../../protos/generated/misc/params.ts';
|
||||
|
||||
import type { ObservedArray } from '../../parser/helpers.ts';
|
||||
import type { MusicSearchFilters } from '../../types/index.ts';
|
||||
import type { GetVideoInfoOptions, MusicSearchFilters } from '../../types/index.ts';
|
||||
import type { Actions, Session } from '../index.ts';
|
||||
|
||||
export default class Music {
|
||||
#session: Session;
|
||||
#actions: Actions;
|
||||
readonly #actions: Actions;
|
||||
|
||||
constructor(session: Session) {
|
||||
this.#session = session;
|
||||
@@ -43,57 +42,85 @@ export default class Music {
|
||||
/**
|
||||
* Retrieves track info. Passing a list item of type MusicTwoRowItem automatically starts a radio.
|
||||
* @param target - Video id or a list item.
|
||||
* @param options - Options for fetching video info.
|
||||
*/
|
||||
getInfo(target: string | MusicTwoRowItem): Promise<TrackInfo> {
|
||||
getInfo(target: string | MusicTwoRowItem | MusicResponsiveListItem | NavigationEndpoint, options?: Omit<GetVideoInfoOptions, 'client'>): Promise<TrackInfo> {
|
||||
if (target instanceof MusicTwoRowItem) {
|
||||
return this.#fetchInfoFromListItem(target);
|
||||
} else if (typeof target === 'string') {
|
||||
return this.#fetchInfoFromVideoId(target);
|
||||
return this.#fetchInfoFromEndpoint(target.endpoint, options);
|
||||
} else if (target instanceof MusicResponsiveListItem) {
|
||||
return this.#fetchInfoFromEndpoint(target.overlay?.content?.endpoint ?? target.endpoint, options);
|
||||
} else if (target instanceof NavigationEndpoint) {
|
||||
return this.#fetchInfoFromEndpoint(target, options);
|
||||
}
|
||||
|
||||
throw new InnertubeError('Invalid target, expected either a video id or a valid MusicTwoRowItem', target);
|
||||
return this.#fetchInfoFromVideoId(target, options);
|
||||
}
|
||||
|
||||
async #fetchInfoFromVideoId(video_id: string): Promise<TrackInfo> {
|
||||
const player_payload = PlayerEndpoint.build({
|
||||
video_id,
|
||||
sts: this.#session.player?.sts,
|
||||
async #fetchInfoFromVideoId(video_id: string, options?: GetVideoInfoOptions): Promise<TrackInfo> {
|
||||
const payload = { videoId: video_id, racyCheckOk: true, contentCheckOk: true };
|
||||
const watch_endpoint = new NavigationEndpoint({ watchEndpoint: payload });
|
||||
const watch_next_endpoint = new NavigationEndpoint({ watchNextEndpoint: payload });
|
||||
|
||||
const extra_payload: Record<string, any> = {
|
||||
playbackContext: {
|
||||
contentPlaybackContext: {
|
||||
vis: 0,
|
||||
splay: false,
|
||||
lactMilliseconds: '-1',
|
||||
signatureTimestamp: this.#session.player?.signature_timestamp
|
||||
}
|
||||
},
|
||||
client: 'YTMUSIC'
|
||||
});
|
||||
};
|
||||
|
||||
const next_payload = NextEndpoint.build({
|
||||
video_id,
|
||||
client: 'YTMUSIC'
|
||||
});
|
||||
if (options?.po_token) {
|
||||
extra_payload.serviceIntegrityDimensions = {
|
||||
poToken: options.po_token
|
||||
};
|
||||
} else if (this.#session.po_token) {
|
||||
extra_payload.serviceIntegrityDimensions = {
|
||||
poToken: this.#session.po_token
|
||||
};
|
||||
}
|
||||
|
||||
const player_response = this.#actions.execute(PlayerEndpoint.PATH, player_payload);
|
||||
const next_response = this.#actions.execute(NextEndpoint.PATH, next_payload);
|
||||
const response = await Promise.all([ player_response, next_response ]);
|
||||
const watch_response = watch_endpoint.call(this.#actions, extra_payload);
|
||||
|
||||
const watch_next_response = watch_next_endpoint.call(this.#actions, { client: 'YTMUSIC' });
|
||||
|
||||
const response = await Promise.all([ watch_response, watch_next_response ]);
|
||||
const cpn = generateRandomString(16);
|
||||
|
||||
return new TrackInfo(response, this.#actions, cpn);
|
||||
}
|
||||
|
||||
async #fetchInfoFromListItem(list_item: MusicTwoRowItem | undefined): Promise<TrackInfo> {
|
||||
if (!list_item)
|
||||
throw new InnertubeError('List item cannot be undefined');
|
||||
|
||||
if (!list_item.endpoint)
|
||||
async #fetchInfoFromEndpoint(endpoint?: NavigationEndpoint, options?: GetVideoInfoOptions): Promise<TrackInfo> {
|
||||
if (!endpoint)
|
||||
throw new Error('This item does not have an endpoint.');
|
||||
|
||||
const player_response = list_item.endpoint.call(this.#actions, {
|
||||
client: 'YTMUSIC',
|
||||
const extra_payload: Record<string, any> = {
|
||||
playbackContext: {
|
||||
contentPlaybackContext: {
|
||||
...{
|
||||
signatureTimestamp: this.#session.player?.sts
|
||||
}
|
||||
vis: 0,
|
||||
splay: false,
|
||||
lactMilliseconds: '-1',
|
||||
signatureTimestamp: this.#session.player?.signature_timestamp
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
client: 'YTMUSIC'
|
||||
};
|
||||
|
||||
const next_response = list_item.endpoint.call(this.#actions, {
|
||||
if (options?.po_token) {
|
||||
extra_payload.serviceIntegrityDimensions = {
|
||||
poToken: options.po_token
|
||||
};
|
||||
} else if (this.#session.po_token) {
|
||||
extra_payload.serviceIntegrityDimensions = {
|
||||
poToken: this.#session.po_token
|
||||
};
|
||||
}
|
||||
|
||||
const player_response = endpoint.call(this.#actions, extra_payload);
|
||||
|
||||
const next_response = endpoint.call(this.#actions, {
|
||||
client: 'YTMUSIC',
|
||||
enablePersistentPlaylistPanel: true,
|
||||
override_endpoint: '/next'
|
||||
@@ -105,143 +132,85 @@ export default class Music {
|
||||
return new TrackInfo(response, this.#actions, cpn);
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches on YouTube Music.
|
||||
* @param query - Search query.
|
||||
* @param filters - Search filters.
|
||||
*/
|
||||
async search(query: string, filters: MusicSearchFilters = {}): Promise<Search> {
|
||||
throwIfMissing({ query });
|
||||
|
||||
const response = await this.#actions.execute(
|
||||
SearchEndpoint.PATH, SearchEndpoint.build({
|
||||
query, client: 'YTMUSIC',
|
||||
params: filters.type && filters.type !== 'all' ? Proto.encodeMusicSearchFilters(filters) : undefined
|
||||
})
|
||||
);
|
||||
let params: string | undefined;
|
||||
|
||||
if (filters.type && filters.type !== 'all') {
|
||||
const writer = SearchFilter.encode({
|
||||
filters: {
|
||||
musicSearchType: {
|
||||
[filters.type]: true
|
||||
}
|
||||
}
|
||||
});
|
||||
params = encodeURIComponent(u8ToBase64(writer.finish()));
|
||||
}
|
||||
|
||||
const search_endpoint = new NavigationEndpoint({ searchEndpoint: { query, params } });
|
||||
const response = await search_endpoint.call(this.#actions, { client: 'YTMUSIC' });
|
||||
|
||||
return new Search(response, this.#actions, Reflect.has(filters, 'type') && filters.type !== 'all');
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the home feed.
|
||||
*/
|
||||
async getHomeFeed(): Promise<HomeFeed> {
|
||||
const response = await this.#actions.execute(
|
||||
BrowseEndpoint.PATH, BrowseEndpoint.build({
|
||||
browse_id: 'FEmusic_home',
|
||||
client: 'YTMUSIC'
|
||||
})
|
||||
);
|
||||
|
||||
const browse_endpoint = new NavigationEndpoint({ browseEndpoint: { browseId: 'FEmusic_home' } });
|
||||
const response = await browse_endpoint.call(this.#actions, { client: 'YTMUSIC' });
|
||||
return new HomeFeed(response, this.#actions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the Explore feed.
|
||||
*/
|
||||
async getExplore(): Promise<Explore> {
|
||||
const response = await this.#actions.execute(
|
||||
BrowseEndpoint.PATH, BrowseEndpoint.build({
|
||||
client: 'YTMUSIC',
|
||||
browse_id: 'FEmusic_explore'
|
||||
})
|
||||
);
|
||||
|
||||
const browse_endpoint = new NavigationEndpoint({ browseEndpoint: { browseId: 'FEmusic_explore' } });
|
||||
const response = await browse_endpoint.call(this.#actions, { client: 'YTMUSIC' });
|
||||
return new Explore(response);
|
||||
// TODO: return new Explore(response, this.#actions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the library.
|
||||
*/
|
||||
async getLibrary(): Promise<Library> {
|
||||
const response = await this.#actions.execute(
|
||||
BrowseEndpoint.PATH, BrowseEndpoint.build({
|
||||
client: 'YTMUSIC',
|
||||
browse_id: 'FEmusic_library_landing'
|
||||
})
|
||||
);
|
||||
|
||||
const browse_endpoint = new NavigationEndpoint({ browseEndpoint: { browseId: 'FEmusic_library_landing' } });
|
||||
const response = await browse_endpoint.call(this.#actions, { client: 'YTMUSIC' });
|
||||
return new Library(response, this.#actions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves artist's info & content.
|
||||
* @param artist_id - The artist id.
|
||||
*/
|
||||
async getArtist(artist_id: string): Promise<Artist> {
|
||||
throwIfMissing({ artist_id });
|
||||
|
||||
if (!artist_id.startsWith('UC') && !artist_id.startsWith('FEmusic_library_privately_owned_artist'))
|
||||
if (!artist_id || !artist_id.startsWith('UC') && !artist_id.startsWith('FEmusic_library_privately_owned_artist'))
|
||||
throw new InnertubeError('Invalid artist id', artist_id);
|
||||
|
||||
const response = await this.#actions.execute(
|
||||
BrowseEndpoint.PATH, BrowseEndpoint.build({
|
||||
client: 'YTMUSIC',
|
||||
browse_id: artist_id
|
||||
})
|
||||
);
|
||||
const browse_endpoint = new NavigationEndpoint({ browseEndpoint: { browseId: artist_id } });
|
||||
const response = await browse_endpoint.call(this.#actions, { client: 'YTMUSIC' });
|
||||
|
||||
return new Artist(response, this.#actions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves album.
|
||||
* @param album_id - The album id.
|
||||
*/
|
||||
async getAlbum(album_id: string): Promise<Album> {
|
||||
throwIfMissing({ album_id });
|
||||
|
||||
if (!album_id.startsWith('MPR') && !album_id.startsWith('FEmusic_library_privately_owned_release'))
|
||||
if (!album_id || !album_id.startsWith('MPR') && !album_id.startsWith('FEmusic_library_privately_owned_release'))
|
||||
throw new InnertubeError('Invalid album id', album_id);
|
||||
|
||||
const response = await this.#actions.execute(
|
||||
BrowseEndpoint.PATH, BrowseEndpoint.build({
|
||||
client: 'YTMUSIC',
|
||||
browse_id: album_id
|
||||
})
|
||||
);
|
||||
const browse_endpoint = new NavigationEndpoint({ browseEndpoint: { browseId: album_id } });
|
||||
const response = await browse_endpoint.call(this.#actions, { client: 'YTMUSIC' });
|
||||
|
||||
return new Album(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves playlist.
|
||||
* @param playlist_id - The playlist id.
|
||||
*/
|
||||
async getPlaylist(playlist_id: string): Promise<Playlist> {
|
||||
throwIfMissing({ playlist_id });
|
||||
|
||||
if (!playlist_id.startsWith('VL')) {
|
||||
if (!playlist_id.startsWith('VL'))
|
||||
playlist_id = `VL${playlist_id}`;
|
||||
}
|
||||
|
||||
const response = await this.#actions.execute(
|
||||
BrowseEndpoint.PATH, BrowseEndpoint.build({
|
||||
client: 'YTMUSIC',
|
||||
browse_id: playlist_id
|
||||
})
|
||||
);
|
||||
const browse_endpoint = new NavigationEndpoint({ browseEndpoint: { browseId: playlist_id } });
|
||||
const response = await browse_endpoint.call(this.#actions, { client: 'YTMUSIC' });
|
||||
|
||||
return new Playlist(response, this.#actions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves up next.
|
||||
* @param video_id - The video id.
|
||||
* @param automix - Whether to enable automix.
|
||||
*/
|
||||
async getUpNext(video_id: string, automix = true): Promise<PlaylistPanel> {
|
||||
throwIfMissing({ video_id });
|
||||
|
||||
const response = await this.#actions.execute(
|
||||
NextEndpoint.PATH, { ...NextEndpoint.build({ video_id, client: 'YTMUSIC' }), parse: true }
|
||||
);
|
||||
const watch_next_endpoint = new NavigationEndpoint({ watchNextEndpoint: { videoId: video_id } });
|
||||
const response = await watch_next_endpoint.call(this.#actions, { client: 'YTMUSIC', parse: true });
|
||||
|
||||
const tabs = response.contents_memo?.getType(Tab);
|
||||
|
||||
const tab = tabs?.first();
|
||||
const tab = tabs?.[0];
|
||||
|
||||
if (!tab)
|
||||
throw new InnertubeError('Could not find target tab.');
|
||||
@@ -268,26 +237,21 @@ export default class Music {
|
||||
if (!page || !page.contents_memo)
|
||||
throw new InnertubeError('Could not fetch automix');
|
||||
|
||||
return page.contents_memo.getType(PlaylistPanel).first();
|
||||
return page.contents_memo.getType(PlaylistPanel)[0];
|
||||
}
|
||||
|
||||
return playlist_panel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves related content.
|
||||
* @param video_id - The video id.
|
||||
*/
|
||||
async getRelated(video_id: string): Promise<ObservedArray<MusicCarouselShelf | MusicDescriptionShelf>> {
|
||||
async getRelated(video_id: string): Promise<SectionList | Message> {
|
||||
throwIfMissing({ video_id });
|
||||
|
||||
const response = await this.#actions.execute(
|
||||
NextEndpoint.PATH, { ...NextEndpoint.build({ video_id, client: 'YTMUSIC' }), parse: true }
|
||||
);
|
||||
const watch_next_endpoint = new NavigationEndpoint({ watchNextEndpoint: { videoId: video_id } });
|
||||
const response = await watch_next_endpoint.call(this.#actions, { client: 'YTMUSIC', parse: true });
|
||||
|
||||
const tabs = response.contents_memo?.getType(Tab);
|
||||
|
||||
const tab = tabs?.matchCondition((tab) => tab.endpoint.payload.browseEndpointContextSupportedConfigs?.browseEndpointContextMusicConfig?.pageType === 'MUSIC_PAGE_TYPE_TRACK_RELATED');
|
||||
const tab = tabs?.find((tab) => tab.endpoint.payload.browseEndpointContextSupportedConfigs?.browseEndpointContextMusicConfig?.pageType === 'MUSIC_PAGE_TYPE_TRACK_RELATED');
|
||||
|
||||
if (!tab)
|
||||
throw new InnertubeError('Could not find target tab.');
|
||||
@@ -297,25 +261,18 @@ export default class Music {
|
||||
if (!page.contents)
|
||||
throw new InnertubeError('Unexpected response', page);
|
||||
|
||||
const shelves = page.contents.item().as(SectionList).contents.as(MusicCarouselShelf, MusicDescriptionShelf);
|
||||
|
||||
return shelves;
|
||||
return page.contents.item().as(SectionList, Message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves song lyrics.
|
||||
* @param video_id - The video id.
|
||||
*/
|
||||
async getLyrics(video_id: string): Promise<MusicDescriptionShelf | undefined> {
|
||||
throwIfMissing({ video_id });
|
||||
|
||||
const response = await this.#actions.execute(
|
||||
NextEndpoint.PATH, { ...NextEndpoint.build({ video_id, client: 'YTMUSIC' }), parse: true }
|
||||
);
|
||||
const watch_next_endpoint = new NavigationEndpoint({ watchNextEndpoint: { videoId: video_id } });
|
||||
const response = await watch_next_endpoint.call(this.#actions, { client: 'YTMUSIC', parse: true });
|
||||
|
||||
const tabs = response.contents_memo?.getType(Tab);
|
||||
|
||||
const tab = tabs?.matchCondition((tab) => tab.endpoint.payload.browseEndpointContextSupportedConfigs?.browseEndpointContextMusicConfig?.pageType === 'MUSIC_PAGE_TYPE_TRACK_LYRICS');
|
||||
const tab = tabs?.find((tab) => tab.endpoint.payload.browseEndpointContextSupportedConfigs?.browseEndpointContextMusicConfig?.pageType === 'MUSIC_PAGE_TYPE_TRACK_LYRICS');
|
||||
|
||||
if (!tab)
|
||||
throw new InnertubeError('Could not find target tab.');
|
||||
@@ -333,35 +290,22 @@ export default class Music {
|
||||
return section_list.firstOfType(MusicDescriptionShelf);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves recap.
|
||||
*/
|
||||
async getRecap(): Promise<Recap> {
|
||||
const response = await this.#actions.execute(
|
||||
BrowseEndpoint.PATH, BrowseEndpoint.build({
|
||||
client: 'YTMUSIC_ANDROID',
|
||||
browse_id: 'FEmusic_listening_review'
|
||||
})
|
||||
);
|
||||
|
||||
const browse_endpoint = new NavigationEndpoint({ browseEndpoint: { browseId: 'FEmusic_listening_review' } });
|
||||
const response = await browse_endpoint.call(this.#actions, { client: 'YTMUSIC' });
|
||||
return new Recap(response, this.#actions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves search suggestions for the given query.
|
||||
* @param query - The query.
|
||||
*/
|
||||
async getSearchSuggestions(query: string): Promise<ObservedArray<SearchSuggestionsSection>> {
|
||||
const response = await this.#actions.execute(
|
||||
GetSearchSuggestionsEndpoint.PATH,
|
||||
{ ...GetSearchSuggestionsEndpoint.build({ input: query }), parse: true }
|
||||
);
|
||||
async getSearchSuggestions(input: string): Promise<ObservedArray<SearchSuggestionsSection>> {
|
||||
const response = await this.#actions.execute('/music/get_search_suggestions', {
|
||||
input,
|
||||
client: 'YTMUSIC',
|
||||
parse: true
|
||||
});
|
||||
|
||||
if (!response.contents_memo)
|
||||
return [] as unknown as ObservedArray<SearchSuggestionsSection>;
|
||||
|
||||
const search_suggestions_sections = response.contents_memo.getType(SearchSuggestionsSection);
|
||||
|
||||
return search_suggestions_sections;
|
||||
return response.contents_memo.getType(SearchSuggestionsSection);
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,11 @@
|
||||
import * as Proto from '../../proto/index.ts';
|
||||
import { Constants } from '../../utils/index.ts';
|
||||
import { InnertubeError, MissingParamError, Platform } from '../../utils/Utils.ts';
|
||||
import { CreateVideoEndpoint } from '../endpoints/upload/index.ts';
|
||||
import { InnertubeError, Platform } from '../../utils/Utils.ts';
|
||||
|
||||
import type { UpdateVideoMetadataOptions, UploadedVideoMetadataOptions } from '../../types/Clients.ts';
|
||||
import type { UpdateVideoMetadataOptions, UploadedVideoMetadataOptions } from '../../types/Misc.ts';
|
||||
import type { ApiResponse, Session } from '../index.ts';
|
||||
|
||||
import { MetadataUpdateRequest } from '../../../protos/generated/youtube/api/pfiinnertube/metadata_update_request.ts';
|
||||
|
||||
interface UploadResult {
|
||||
status: string;
|
||||
scottyResourceId: string;
|
||||
@@ -27,56 +27,116 @@ export default class Studio {
|
||||
}
|
||||
|
||||
/**
|
||||
* Uploads a custom thumbnail and sets it for a video.
|
||||
* Updates the metadata of a video.
|
||||
* @example
|
||||
* ```ts
|
||||
* const buffer = fs.readFileSync('./my_awesome_thumbnail.jpg');
|
||||
* const response = await yt.studio.setThumbnail(video_id, buffer);
|
||||
* ```
|
||||
*/
|
||||
async setThumbnail(video_id: string, buffer: Uint8Array): Promise<ApiResponse> {
|
||||
if (!this.#session.logged_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.');
|
||||
|
||||
const payload = Proto.encodeCustomThumbnailPayload(video_id, buffer);
|
||||
|
||||
const response = await this.#session.actions.execute('/video_manager/metadata_update', {
|
||||
protobuf: true,
|
||||
serialized_data: payload
|
||||
});
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates a given video's metadata.
|
||||
* @example
|
||||
* ```ts
|
||||
* const response = await yt.studio.updateVideoMetadata('videoid', {
|
||||
* const videoId = 'abcdefg';
|
||||
* const thumbnail = fs.readFileSync('./my_awesome_thumbnail.jpg');
|
||||
*
|
||||
* const response = await yt.studio.updateVideoMetadata(videoId, {
|
||||
* tags: [ 'astronomy', 'NASA', 'APOD' ],
|
||||
* title: 'Artemis Mission',
|
||||
* description: 'A nicely written description...',
|
||||
* category: 27,
|
||||
* license: 'creative_commons'
|
||||
* license: 'creative_commons',
|
||||
* thumbnail,
|
||||
* // ...
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
async updateVideoMetadata(video_id: string, metadata: UpdateVideoMetadataOptions): Promise<ApiResponse> {
|
||||
if (!this.#session.logged_in)
|
||||
const session = this.#session;
|
||||
|
||||
if (!session.logged_in)
|
||||
throw new InnertubeError('You must be signed in to perform this operation.');
|
||||
|
||||
const payload = Proto.encodeVideoMetadataPayload(video_id, metadata);
|
||||
const payload: MetadataUpdateRequest = {
|
||||
context: {
|
||||
client: {
|
||||
osName: 'Android',
|
||||
clientName: parseInt(Constants.CLIENT_NAME_IDS.ANDROID),
|
||||
clientVersion: Constants.CLIENTS.ANDROID.VERSION,
|
||||
androidSdkVersion: Constants.CLIENTS.ANDROID.SDK_VERSION,
|
||||
visitorData: session.context.client.visitorData,
|
||||
osVersion: '13',
|
||||
acceptLanguage: session.context.client.hl,
|
||||
acceptRegion: session.context.client.gl,
|
||||
deviceMake: 'Google',
|
||||
deviceModel: 'sdk_gphone64_x86_64',
|
||||
screenHeightPoints: 840,
|
||||
screenWidthPoints: 432,
|
||||
configInfo: {
|
||||
appInstallData: session.context.client.configInfo?.appInstallData
|
||||
},
|
||||
timeZone: session.context.client.timeZone,
|
||||
chipset: 'qcom;taro'
|
||||
},
|
||||
activePlayers: []
|
||||
},
|
||||
encryptedVideoId: video_id
|
||||
};
|
||||
|
||||
const response = await this.#session.actions.execute('/video_manager/metadata_update', {
|
||||
if (metadata.title)
|
||||
payload.title = { newTitle: metadata.title };
|
||||
|
||||
if (metadata.description)
|
||||
payload.description = { newDescription: metadata.description };
|
||||
|
||||
if (metadata.license)
|
||||
payload.license = { newLicenseId: metadata.license };
|
||||
|
||||
if (metadata.tags)
|
||||
payload.tags = { newTags: metadata.tags };
|
||||
|
||||
if (metadata.thumbnail) {
|
||||
payload.videoStill = {
|
||||
operation: 3,
|
||||
image: {
|
||||
rawBytes: metadata.thumbnail
|
||||
},
|
||||
experimentImage: []
|
||||
};
|
||||
}
|
||||
|
||||
if (Reflect.has(metadata, 'category'))
|
||||
payload.category = { newCategoryId: metadata.category };
|
||||
|
||||
if (Reflect.has(metadata, 'privacy')) {
|
||||
switch (metadata.privacy) {
|
||||
case 'PUBLIC':
|
||||
payload.privacy = { newPrivacy: 1 };
|
||||
break;
|
||||
case 'UNLISTED':
|
||||
payload.privacy = { newPrivacy: 2 };
|
||||
break;
|
||||
case 'PRIVATE':
|
||||
payload.privacy = { newPrivacy: 3 };
|
||||
break;
|
||||
default:
|
||||
throw new Error('Invalid privacy setting');
|
||||
}
|
||||
}
|
||||
|
||||
if (Reflect.has(metadata, 'made_for_kids')) {
|
||||
payload.madeForKids = {
|
||||
operation: 1,
|
||||
newMfk: metadata.made_for_kids ? 1 : 2
|
||||
};
|
||||
}
|
||||
|
||||
if (Reflect.has(metadata, 'age_restricted')) {
|
||||
payload.racy = {
|
||||
operation: 1,
|
||||
newRacy: metadata.age_restricted ? 1 : 2
|
||||
};
|
||||
}
|
||||
|
||||
const writer = MetadataUpdateRequest.encode(payload);
|
||||
|
||||
return await session.actions.execute('/video_manager/metadata_update', {
|
||||
protobuf: true,
|
||||
serialized_data: payload
|
||||
serialized_data: writer.finish()
|
||||
});
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -97,9 +157,7 @@ export default class Studio {
|
||||
if (upload_result.status !== 'STATUS_SUCCESS')
|
||||
throw new InnertubeError('Could not process video.');
|
||||
|
||||
const response = await this.#setVideoMetadata(initial_data, upload_result, metadata);
|
||||
|
||||
return response;
|
||||
return await this.#setVideoMetadata(initial_data, upload_result, metadata);
|
||||
}
|
||||
|
||||
async #getInitialUploadData(): Promise<InitialUploadData> {
|
||||
@@ -152,39 +210,32 @@ export default class Studio {
|
||||
if (!response.ok)
|
||||
throw new InnertubeError('Could not upload video');
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
return data;
|
||||
return await response.json();
|
||||
}
|
||||
|
||||
async #setVideoMetadata(initial_data: InitialUploadData, upload_result: UploadResult, metadata: UploadedVideoMetadataOptions) {
|
||||
const response = await this.#session.actions.execute(
|
||||
CreateVideoEndpoint.PATH, CreateVideoEndpoint.build({
|
||||
resource_id: {
|
||||
scotty_resource_id: {
|
||||
id: upload_result.scottyResourceId
|
||||
}
|
||||
return await this.#session.actions.execute('/upload/createvideo', {
|
||||
resourceId: {
|
||||
scottyResourceId: {
|
||||
id: upload_result.scottyResourceId
|
||||
}
|
||||
},
|
||||
frontendUploadId: initial_data.frontend_upload_id,
|
||||
initialMetadata: {
|
||||
title: {
|
||||
newTitle: metadata.title
|
||||
},
|
||||
frontend_upload_id: initial_data.frontend_upload_id,
|
||||
initial_metadata: {
|
||||
title: {
|
||||
new_title: metadata.title || new Date().toDateString()
|
||||
},
|
||||
description: {
|
||||
new_description: metadata.description || '',
|
||||
should_segment: true
|
||||
},
|
||||
privacy: {
|
||||
new_privacy: metadata.privacy || 'PRIVATE'
|
||||
},
|
||||
draft_state: {
|
||||
is_draft: metadata.is_draft
|
||||
}
|
||||
description: {
|
||||
newDescription: metadata.description,
|
||||
shouldSegment: true
|
||||
},
|
||||
client: 'ANDROID'
|
||||
})
|
||||
);
|
||||
|
||||
return response;
|
||||
privacy: {
|
||||
newPrivacy: metadata.privacy || 'PRIVATE'
|
||||
},
|
||||
draftState: {
|
||||
isDraft: !!metadata.is_draft
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
import type { IBrowseRequest, BrowseEndpointOptions } from '../../types/index.ts';
|
||||
|
||||
export const PATH = '/browse';
|
||||
|
||||
/**
|
||||
* Builds a `/browse` request payload.
|
||||
* @param opts - The options to use.
|
||||
* @returns The payload.
|
||||
*/
|
||||
export function build(opts: BrowseEndpointOptions): IBrowseRequest {
|
||||
return {
|
||||
...{
|
||||
browseId: opts.browse_id,
|
||||
params: opts.params,
|
||||
continuation: opts.continuation,
|
||||
client: opts.client
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
import type { IGetNotificationMenuRequest, GetNotificationMenuEndpointOptions } from '../../types/index.ts';
|
||||
|
||||
export const PATH = '/notification/get_notification_menu';
|
||||
|
||||
/**
|
||||
* Builds a `/get_notification_menu` request payload.
|
||||
* @param opts - The options to use.
|
||||
* @returns The payload.
|
||||
*/
|
||||
export function build(opts: GetNotificationMenuEndpointOptions): IGetNotificationMenuRequest {
|
||||
return {
|
||||
...{
|
||||
notificationsMenuRequestType: opts.notifications_menu_request_type
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
export const PATH = '/guide';
|
||||
@@ -1,21 +0,0 @@
|
||||
import type { INextRequest, NextEndpointOptions } from '../../types/index.ts';
|
||||
|
||||
export const PATH = '/next';
|
||||
|
||||
/**
|
||||
* Builds a `/next` request payload.
|
||||
* @param opts - The options to use.
|
||||
* @returns The payload.
|
||||
*/
|
||||
export function build(opts: NextEndpointOptions): INextRequest {
|
||||
return {
|
||||
...{
|
||||
videoId: opts.video_id,
|
||||
playlistId: opts.playlist_id,
|
||||
params: opts.params,
|
||||
playlistIndex: opts.playlist_index,
|
||||
client: opts.client,
|
||||
continuation: opts.continuation
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
import type { IPlayerRequest, PlayerEndpointOptions } from '../../types/index.ts';
|
||||
|
||||
export const PATH = '/player';
|
||||
|
||||
/**
|
||||
* Builds a `/player` request payload.
|
||||
* @param opts - The options to use.
|
||||
* @returns The payload.
|
||||
*/
|
||||
export function build(opts: PlayerEndpointOptions): IPlayerRequest {
|
||||
const is_android =
|
||||
opts.client === 'ANDROID' ||
|
||||
opts.client === 'YTMUSIC_ANDROID' ||
|
||||
opts.client === 'YTSTUDIO_ANDROID';
|
||||
|
||||
return {
|
||||
playbackContext: {
|
||||
contentPlaybackContext: {
|
||||
vis: 0,
|
||||
splay: false,
|
||||
referer: opts.playlist_id ?
|
||||
`https://www.youtube.com/watch?v=${opts.video_id}&list=${opts.playlist_id}` :
|
||||
`https://www.youtube.com/watch?v=${opts.video_id}`,
|
||||
currentUrl: opts.playlist_id ?
|
||||
`/watch?v=${opts.video_id}&list=${opts.playlist_id}` :
|
||||
`/watch?v=${opts.video_id}`,
|
||||
autonavState: 'STATE_ON',
|
||||
autoCaptionsDefaultOn: false,
|
||||
html5Preference: 'HTML5_PREF_WANTS',
|
||||
lactMilliseconds: '-1',
|
||||
...{
|
||||
signatureTimestamp: opts.sts
|
||||
}
|
||||
}
|
||||
},
|
||||
attestationRequest: {
|
||||
omitBotguardData: true
|
||||
},
|
||||
racyCheckOk: true,
|
||||
contentCheckOk: true,
|
||||
videoId: opts.video_id,
|
||||
...{
|
||||
client: opts.client,
|
||||
playlistId: opts.playlist_id,
|
||||
// Workaround streaming URLs returning 403 or getting throttled when using Android based clients.
|
||||
params: is_android ? '2AMBCgIQBg' : opts.params
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
import type { IResolveURLRequest, ResolveURLEndpointOptions } from '../../types/index.ts';
|
||||
|
||||
export const PATH = '/navigation/resolve_url';
|
||||
|
||||
/**
|
||||
* Builds a `/resolve_url` request payload.
|
||||
* @param opts - The options to use.
|
||||
* @returns The payload.
|
||||
*/
|
||||
export function build(opts: ResolveURLEndpointOptions): IResolveURLRequest {
|
||||
return {
|
||||
...{
|
||||
url: opts.url
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
import type { ISearchRequest, SearchEndpointOptions } from '../../types/index.ts';
|
||||
|
||||
export const PATH = '/search';
|
||||
|
||||
/**
|
||||
* Builds a `/search` request payload.
|
||||
* @param opts - The options to use.
|
||||
* @returns The payload.
|
||||
*/
|
||||
export function build(opts: SearchEndpointOptions): ISearchRequest {
|
||||
return {
|
||||
...{
|
||||
query: opts.query,
|
||||
params: opts.params,
|
||||
continuation: opts.continuation,
|
||||
client: opts.client
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
import type { IAccountListRequest } from '../../../types/index.ts';
|
||||
|
||||
export const PATH = '/account/accounts_list';
|
||||
|
||||
/**
|
||||
* Builds a `/account/accounts_list` request payload.
|
||||
* @returns The payload.
|
||||
*/
|
||||
export function build(): IAccountListRequest {
|
||||
return {
|
||||
client: 'ANDROID'
|
||||
};
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
export * as AccountListEndpoint from './AccountListEndpoint.ts';
|
||||
@@ -1,24 +0,0 @@
|
||||
import type { IEditPlaylistRequest, EditPlaylistEndpointOptions } from '../../../types/index.ts';
|
||||
|
||||
export const PATH = '/browse/edit_playlist';
|
||||
|
||||
/**
|
||||
* Builds a `/browse/edit_playlist` request payload.
|
||||
* @param opts - The options to use.
|
||||
* @returns The payload.
|
||||
*/
|
||||
export function build(opts: EditPlaylistEndpointOptions): IEditPlaylistRequest {
|
||||
return {
|
||||
playlistId: opts.playlist_id,
|
||||
actions: opts.actions.map((action) => ({
|
||||
action: action.action,
|
||||
...{
|
||||
addedVideoId: action.added_video_id,
|
||||
setVideoId: action.set_video_id,
|
||||
movedSetVideoIdPredecessor: action.moved_set_video_id_predecessor,
|
||||
playlistDescription: action.playlist_description,
|
||||
playlistName: action.playlist_name
|
||||
}
|
||||
}))
|
||||
};
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
export * as EditPlaylistEndpoint from './EditPlaylistEndpoint.ts';
|
||||
@@ -1,15 +0,0 @@
|
||||
import type { IChannelEditDescriptionRequest, ChannelEditDescriptionEndpointOptions } from '../../../types/Endpoints.ts';
|
||||
|
||||
export const PATH = '/channel/edit_description';
|
||||
|
||||
/**
|
||||
* Builds a `/channel/edit_description` request payload.
|
||||
* @param options - The options to use.
|
||||
* @returns The payload.
|
||||
*/
|
||||
export function build(options: ChannelEditDescriptionEndpointOptions): IChannelEditDescriptionRequest {
|
||||
return {
|
||||
givenDescription: options.given_description,
|
||||
client: 'ANDROID'
|
||||
};
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
import type { IChannelEditNameRequest, ChannelEditNameEndpointOptions } from '../../../types/index.ts';
|
||||
|
||||
export const PATH = '/channel/edit_name';
|
||||
|
||||
/**
|
||||
* Builds a `/channel/edit_name` request payload.
|
||||
* @param options - The options to use.
|
||||
* @returns The payload.
|
||||
*/
|
||||
export function build(options: ChannelEditNameEndpointOptions): IChannelEditNameRequest {
|
||||
return {
|
||||
givenName: options.given_name,
|
||||
client: 'ANDROID'
|
||||
};
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
export * as EditNameEndpoint from './EditNameEndpoint.ts';
|
||||
export * as EditDescriptionEndpoint from './EditDescriptionEndpoint.ts';
|
||||
@@ -1,18 +0,0 @@
|
||||
import type { ICreateCommentRequest, CreateCommentEndpointOptions } from '../../../types/index.ts';
|
||||
|
||||
export const PATH = '/comment/create_comment';
|
||||
|
||||
/**
|
||||
* Builds a `/comment/create_comment` request payload.
|
||||
* @param options - The options to use.
|
||||
* @returns The payload.
|
||||
*/
|
||||
export function build(options: CreateCommentEndpointOptions): ICreateCommentRequest {
|
||||
return {
|
||||
commentText: options.comment_text,
|
||||
createCommentParams: options.create_comment_params,
|
||||
...{
|
||||
client: options.client
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
import type { IPerformCommentActionRequest, PerformCommentActionEndpointOptions } from '../../../types/index.ts';
|
||||
|
||||
export const PATH = '/comment/perform_comment_action';
|
||||
|
||||
/**
|
||||
* Builds a `/comment/perform_comment_action` request payload.
|
||||
* @param options - The options to use.
|
||||
* @returns The payload.
|
||||
*/
|
||||
export function build(options: PerformCommentActionEndpointOptions): IPerformCommentActionRequest {
|
||||
return {
|
||||
actions: options.actions,
|
||||
...{
|
||||
client: options.client
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
export * as PerformCommentActionEndpoint from './PerformCommentActionEndpoint.ts';
|
||||
export * as CreateCommentEndpoint from './CreateCommentEndpoint.ts';
|
||||
@@ -1,20 +0,0 @@
|
||||
export * as BrowseEndpoint from './BrowseEndpoint.ts';
|
||||
export * as GetNotificationMenuEndpoint from './GetNotificationMenuEndpoint.ts';
|
||||
export * as GuideEndpoint from './GuideEndpoint.ts';
|
||||
export * as NextEndpoint from './NextEndpoint.ts';
|
||||
export * as PlayerEndpoint from './PlayerEndpoint.ts';
|
||||
export * as ResolveURLEndpoint from './ResolveURLEndpoint.ts';
|
||||
export * as SearchEndpoint from './SearchEndpoint.ts';
|
||||
|
||||
export * as Account from './account/index.ts';
|
||||
export * as Browse from './browse/index.ts';
|
||||
export * as Channel from './channel/index.ts';
|
||||
export * as Comment from './comment/index.ts';
|
||||
export * as Like from './like/index.ts';
|
||||
export * as Music from './music/index.ts';
|
||||
export * as Notification from './notification/index.ts';
|
||||
export * as Playlist from './playlist/index.ts';
|
||||
export * as Subscription from './subscription/index.ts';
|
||||
export * as Reel from './reel/index.ts';
|
||||
export * as Upload from './upload/index.ts';
|
||||
export * as Kids from './kids/index.ts';
|
||||
@@ -1,12 +0,0 @@
|
||||
import type { IBlocklistPickerRequest, BlocklistPickerRequestEndpointOptions } from '../../../types/index.ts';
|
||||
|
||||
export const PATH = '/kids/get_kids_blocklist_picker';
|
||||
|
||||
/**
|
||||
* Builds a `/kids/get_kids_blocklist_picker` request payload.
|
||||
* @param options - The options to use.
|
||||
* @returns The payload.
|
||||
*/
|
||||
export function build(options: BlocklistPickerRequestEndpointOptions): IBlocklistPickerRequest {
|
||||
return { blockedForKidsContent: { external_channel_id: options.channel_id } };
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
export * as BlocklistPickerEndpoint from './BlocklistPickerEndpoint.ts';
|
||||
@@ -1,19 +0,0 @@
|
||||
import type { IDislikeRequest, DislikeEndpointOptions } from '../../../types/index.ts';
|
||||
|
||||
export const PATH = '/like/dislike';
|
||||
|
||||
/**
|
||||
* Builds a `/like/dislike` endpoint payload.
|
||||
* @param options - The options to use.
|
||||
* @returns The payload.
|
||||
*/
|
||||
export function build(options: DislikeEndpointOptions): IDislikeRequest {
|
||||
return {
|
||||
target: {
|
||||
videoId: options.target.video_id
|
||||
},
|
||||
...{
|
||||
client: options.client
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
import type { ILikeRequest, LikeEndpointOptions } from '../../../types/index.ts';
|
||||
|
||||
export const PATH = '/like/like';
|
||||
|
||||
/**
|
||||
* Builds a `/like/like` endpoint payload.
|
||||
* @param options - The options to use.
|
||||
* @returns The payload.
|
||||
*/
|
||||
export function build(options: LikeEndpointOptions): ILikeRequest {
|
||||
return {
|
||||
target: {
|
||||
videoId: options.target.video_id
|
||||
},
|
||||
...{
|
||||
client: options.client
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
import type { IRemoveLikeRequest, RemoveLikeEndpointOptions } from '../../../types/index.ts';
|
||||
|
||||
export const PATH = '/like/removelike';
|
||||
|
||||
/**
|
||||
* Builds a `/like/removelike` endpoint payload.
|
||||
* @param options - The options to use.
|
||||
* @returns The payload.
|
||||
*/
|
||||
export function build(options: RemoveLikeEndpointOptions): IRemoveLikeRequest {
|
||||
return {
|
||||
target: {
|
||||
videoId: options.target.video_id
|
||||
},
|
||||
...{
|
||||
client: options.client
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
export * as LikeEndpoint from './LikeEndpoint.ts';
|
||||
export * as DislikeEndpoint from './DislikeEndpoint.ts';
|
||||
export * as RemoveLikeEndpoint from './RemoveLikeEndpoint.ts';
|
||||
@@ -1,16 +0,0 @@
|
||||
import type { IMusicGetSearchSuggestionsRequest, MusicGetSearchSuggestionsEndpointOptions } from '../../../types/index.ts';
|
||||
|
||||
|
||||
export const PATH = '/music/get_search_suggestions';
|
||||
|
||||
/**
|
||||
* Builds a `/music/get_search_suggestions` request payload.
|
||||
* @param opts - The options to use.
|
||||
* @returns The payload.
|
||||
*/
|
||||
export function build(opts: MusicGetSearchSuggestionsEndpointOptions): IMusicGetSearchSuggestionsRequest {
|
||||
return {
|
||||
input: opts.input,
|
||||
client: 'YTMUSIC'
|
||||
};
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
export * as GetSearchSuggestionsEndpoint from './GetSearchSuggestionsEndpoint.ts';
|
||||
@@ -1 +0,0 @@
|
||||
export const PATH = '/notification/get_unseen_count';
|
||||
@@ -1,17 +0,0 @@
|
||||
import type { IModifyChannelPreferenceRequest, ModifyChannelPreferenceEndpointOptions } from '../../../types/index.ts';
|
||||
|
||||
export const PATH = '/notification/modify_channel_preference';
|
||||
|
||||
/**
|
||||
* Builds a `/notification/modify_channel_preference` request payload.
|
||||
* @param options - The options to use.
|
||||
* @returns The payload.
|
||||
*/
|
||||
export function build(options: ModifyChannelPreferenceEndpointOptions): IModifyChannelPreferenceRequest {
|
||||
return {
|
||||
params: options.params,
|
||||
...{
|
||||
client: options.client
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
export * as GetUnseenCountEndpoint from './GetUnseenCountEndpoint.ts';
|
||||
export * as ModifyChannelPreferenceEndpoint from './ModifyChannelPreferenceEndpoint.ts';
|
||||
@@ -1,15 +0,0 @@
|
||||
import type { ICreatePlaylistRequest, CreatePlaylistEndpointOptions } from '../../../types/index.ts';
|
||||
|
||||
export const PATH = '/playlist/create';
|
||||
|
||||
/**
|
||||
* Builds a `/playlist/create` request payload.
|
||||
* @param opts - The options to use.
|
||||
* @returns The payload.
|
||||
*/
|
||||
export function build(opts: CreatePlaylistEndpointOptions): ICreatePlaylistRequest {
|
||||
return {
|
||||
title: opts.title,
|
||||
ids: opts.ids
|
||||
};
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
import type { IDeletePlaylistRequest, DeletePlaylistEndpointOptions } from '../../../types/index.ts';
|
||||
|
||||
export const PATH = '/playlist/delete';
|
||||
|
||||
/**
|
||||
* Builds a `/playlist/delete` request payload.
|
||||
* @param opts - The options to use.
|
||||
* @returns The payload.
|
||||
*/
|
||||
export function build(opts: DeletePlaylistEndpointOptions): IDeletePlaylistRequest {
|
||||
return {
|
||||
playlistId: opts.playlist_id
|
||||
};
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
export * as CreateEndpoint from './CreateEndpoint.ts';
|
||||
export * as DeleteEndpoint from './DeleteEndpoint.ts';
|
||||
@@ -1,18 +0,0 @@
|
||||
import type { IReelWatchRequest, ReelWatchEndpointOptions } from '../../../types/index.ts';
|
||||
|
||||
export const PATH = '/reel/reel_item_watch';
|
||||
|
||||
/**
|
||||
* Builds a `/reel/reel_watch_sequence` request payload.
|
||||
* @param opts - The options to use.
|
||||
* @returns The payload.
|
||||
*/
|
||||
export function build(opts: ReelWatchEndpointOptions): IReelWatchRequest {
|
||||
return {
|
||||
playerRequest: {
|
||||
videoId: opts.short_id,
|
||||
params: opts.params ?? 'CAUwAg%3D%3D'
|
||||
},
|
||||
params: opts.params ?? 'CAUwAg%3D%3D'
|
||||
};
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
import type { IReelSequenceRequest, ReelWatchSequenceEndpointOptions } from '../../../types/index.ts';
|
||||
|
||||
export const PATH = '/reel/reel_watch_sequence';
|
||||
|
||||
/**
|
||||
* Builds a `/reel/reel_watch_sequence` request payload.
|
||||
* @param opts - The options to use.
|
||||
* @returns The payload.
|
||||
*/
|
||||
export function build(opts: ReelWatchSequenceEndpointOptions): IReelSequenceRequest {
|
||||
return {
|
||||
sequenceParams: opts.sequenceParams
|
||||
};
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
export * as WatchEndpoint from './WatchEndpoint.ts';
|
||||
export * as WatchSequenceEndpoint from './WatchSequenceEndpoint.ts';
|
||||
@@ -1,18 +0,0 @@
|
||||
import type { ISubscribeRequest, SubscribeEndpointOptions } from '../../../types/index.ts';
|
||||
|
||||
export const PATH = '/subscription/subscribe';
|
||||
|
||||
/**
|
||||
* Builds a `/subscription/subscribe` endpoint payload.
|
||||
* @param options - The options to use.
|
||||
* @returns The payload.
|
||||
*/
|
||||
export function build(options: SubscribeEndpointOptions): ISubscribeRequest {
|
||||
return {
|
||||
channelIds: options.channel_ids,
|
||||
...{
|
||||
client: options.client,
|
||||
params: options.params
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
import type { IUnsubscribeRequest, UnsubscribeEndpointOptions } from '../../../types/index.ts';
|
||||
|
||||
export const PATH = '/subscription/unsubscribe';
|
||||
|
||||
/**
|
||||
* Builds a `/subscription/unsubscribe` endpoint payload.
|
||||
* @param options - The options to use.
|
||||
* @returns The payload.
|
||||
*/
|
||||
export function build(options: UnsubscribeEndpointOptions): IUnsubscribeRequest {
|
||||
return {
|
||||
channelIds: options.channel_ids,
|
||||
...{
|
||||
client: options.client,
|
||||
params: options.params
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
export * as SubscribeEndpoint from './SubscribeEndpoint.ts';
|
||||
export * as UnsubscribeEndpoint from './UnsubscribeEndpoint.ts';
|
||||
@@ -1,37 +0,0 @@
|
||||
import type { ICreateVideoRequest, CreateVideoEndpointOptions } from '../../../types/index.ts';
|
||||
|
||||
export const PATH = '/upload/createvideo';
|
||||
|
||||
/**
|
||||
* Builds a `/upload/createvideo` request payload.
|
||||
* @param opts - The options to use.
|
||||
* @returns The payload.
|
||||
*/
|
||||
export function build(opts: CreateVideoEndpointOptions): ICreateVideoRequest {
|
||||
return {
|
||||
resourceId: {
|
||||
scottyResourceId: {
|
||||
id: opts.resource_id.scotty_resource_id.id
|
||||
}
|
||||
},
|
||||
frontendUploadId: opts.frontend_upload_id,
|
||||
initialMetadata: {
|
||||
title: {
|
||||
newTitle: opts.initial_metadata.title.new_title
|
||||
},
|
||||
description: {
|
||||
newDescription: opts.initial_metadata.description.new_description,
|
||||
shouldSegment: opts.initial_metadata.description.should_segment
|
||||
},
|
||||
privacy: {
|
||||
newPrivacy: opts.initial_metadata.privacy.new_privacy
|
||||
},
|
||||
draftState: {
|
||||
isDraft: !!opts.initial_metadata.draft_state.is_draft
|
||||
}
|
||||
},
|
||||
...{
|
||||
client: opts.client
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
export * as CreateVideoEndpoint from './CreateVideoEndpoint.ts';
|
||||
@@ -7,10 +7,9 @@ export * from './Actions.ts';
|
||||
export { default as Player } from './Player.ts';
|
||||
export * from './Player.ts';
|
||||
|
||||
export { default as OAuth } from './OAuth.ts';
|
||||
export * from './OAuth.ts';
|
||||
export { default as OAuth2 } from './OAuth2.ts';
|
||||
export * from './OAuth2.ts';
|
||||
|
||||
export * as Clients from './clients/index.ts';
|
||||
export * as Endpoints from './endpoints/index.ts';
|
||||
export * as Managers from './managers/index.ts';
|
||||
export * as Mixins from './mixins/index.ts';
|
||||
@@ -1,119 +1,55 @@
|
||||
import type { Actions, ApiResponse } from '../index.ts';
|
||||
import type { Actions } from '../index.ts';
|
||||
|
||||
import AccountInfo from '../../parser/youtube/AccountInfo.ts';
|
||||
import Analytics from '../../parser/youtube/Analytics.ts';
|
||||
import Settings from '../../parser/youtube/Settings.ts';
|
||||
import TimeWatched from '../../parser/youtube/TimeWatched.ts';
|
||||
import NavigationEndpoint from '../../parser/classes/NavigationEndpoint.ts';
|
||||
|
||||
import * as Proto from '../../proto/index.ts';
|
||||
import { InnertubeError } from '../../utils/Utils.ts';
|
||||
import { Account, BrowseEndpoint, Channel } from '../endpoints/index.ts';
|
||||
import { AccountItem } from '../../parser/nodes.ts';
|
||||
|
||||
export default class AccountManager {
|
||||
#actions: Actions;
|
||||
|
||||
channel: {
|
||||
editName: (new_name: string) => Promise<ApiResponse>;
|
||||
editDescription: (new_description: string) => Promise<ApiResponse>;
|
||||
getBasicAnalytics: () => Promise<Analytics>;
|
||||
};
|
||||
readonly #actions: Actions;
|
||||
|
||||
constructor(actions: Actions) {
|
||||
this.#actions = actions;
|
||||
|
||||
this.channel = {
|
||||
/**
|
||||
* Edits channel name.
|
||||
* @param new_name - The new channel name.
|
||||
*/
|
||||
editName: (new_name: string) => {
|
||||
if (!this.#actions.session.logged_in)
|
||||
throw new InnertubeError('You must be signed in to perform this operation.');
|
||||
|
||||
return this.#actions.execute(
|
||||
Channel.EditNameEndpoint.PATH,
|
||||
Channel.EditNameEndpoint.build({
|
||||
given_name: new_name
|
||||
})
|
||||
);
|
||||
},
|
||||
/**
|
||||
* Edits channel description.
|
||||
* @param new_description - The new description.
|
||||
*/
|
||||
editDescription: (new_description: string) => {
|
||||
if (!this.#actions.session.logged_in)
|
||||
throw new InnertubeError('You must be signed in to perform this operation.');
|
||||
|
||||
return this.#actions.execute(
|
||||
Channel.EditDescriptionEndpoint.PATH,
|
||||
Channel.EditDescriptionEndpoint.build({
|
||||
given_description: new_description
|
||||
})
|
||||
);
|
||||
},
|
||||
/**
|
||||
* Retrieves basic channel analytics.
|
||||
*/
|
||||
getBasicAnalytics: () => this.getAnalytics()
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves channel info.
|
||||
* Retrieves the list of channels belonging to the signed-in account. Only useful when signed in through cookie. If signed in through OAuth, you will get the active channel only.
|
||||
*/
|
||||
async getInfo(): Promise<AccountInfo> {
|
||||
async getInfo(all: true): Promise<AccountItem[]>;
|
||||
/**
|
||||
* Retrieves the active channel info for the signed-in account. Throws error if `on_behalf_of_user` was used to create the Innertube instance; use `getInfo(true)` instead.
|
||||
*/
|
||||
async getInfo(all?: false): Promise<AccountInfo>;
|
||||
async getInfo(all = false): Promise<AccountInfo | AccountItem[]> {
|
||||
if (!this.#actions.session.logged_in)
|
||||
throw new InnertubeError('You must be signed in to perform this operation.');
|
||||
|
||||
const response = await this.#actions.execute(
|
||||
Account.AccountListEndpoint.PATH,
|
||||
Account.AccountListEndpoint.build()
|
||||
);
|
||||
if (!all && !!this.#actions.session.context.user.onBehalfOfUser) {
|
||||
throw new InnertubeError('Boolean argument must be true when "on_behalf_of_user" is specified.');
|
||||
}
|
||||
|
||||
if (all) {
|
||||
const get_accounts_list_endpoint = new NavigationEndpoint({ getAccountsListInnertubeEndpoint: {
|
||||
requestType: 'ACCOUNTS_LIST_REQUEST_TYPE_CHANNEL_SWITCHER',
|
||||
callCircumstance: 'SWITCHING_USERS_FULL'
|
||||
} });
|
||||
const response = await get_accounts_list_endpoint.call(this.#actions, { client: 'WEB', parse: true });
|
||||
return response.actions_memo?.getType(AccountItem) || [];
|
||||
}
|
||||
|
||||
const get_accounts_list_endpoint = new NavigationEndpoint({ getAccountsListInnertubeEndpoint: {} });
|
||||
const response = await get_accounts_list_endpoint.call(this.#actions, { client: 'TV' });
|
||||
return new AccountInfo(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves time watched statistics.
|
||||
*/
|
||||
async getTimeWatched(): Promise<TimeWatched> {
|
||||
const response = await this.#actions.execute(
|
||||
BrowseEndpoint.PATH, BrowseEndpoint.build({
|
||||
browse_id: 'SPtime_watched',
|
||||
client: 'ANDROID'
|
||||
})
|
||||
);
|
||||
|
||||
return new TimeWatched(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens YouTube settings.
|
||||
* Gets YouTube settings.
|
||||
*/
|
||||
async getSettings(): Promise<Settings> {
|
||||
const response = await this.#actions.execute(
|
||||
BrowseEndpoint.PATH, BrowseEndpoint.build({
|
||||
browse_id: 'SPaccount_overview'
|
||||
})
|
||||
);
|
||||
const browse_endpoint = new NavigationEndpoint({ browseEndpoint: { browseId: 'SPaccount_overview' } });
|
||||
const response = await browse_endpoint.call(this.#actions);
|
||||
return new Settings(this.#actions, response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves basic channel analytics.
|
||||
*/
|
||||
async getAnalytics(): Promise<Analytics> {
|
||||
const info = await this.getInfo();
|
||||
|
||||
const response = await this.#actions.execute(
|
||||
BrowseEndpoint.PATH, BrowseEndpoint.build({
|
||||
browse_id: 'FEanalytics_screen',
|
||||
params: Proto.encodeChannelAnalyticsParams(info.footers?.endpoint.payload.browseId),
|
||||
client: 'ANDROID'
|
||||
})
|
||||
);
|
||||
|
||||
return new Analytics(response);
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,11 @@
|
||||
import * as Proto from '../../proto/index.ts';
|
||||
|
||||
import { throwIfMissing } from '../../utils/Utils.ts';
|
||||
import { LikeEndpoint, DislikeEndpoint, RemoveLikeEndpoint } from '../endpoints/like/index.ts';
|
||||
import { SubscribeEndpoint, UnsubscribeEndpoint } from '../endpoints/subscription/index.ts';
|
||||
import { CreateCommentEndpoint, PerformCommentActionEndpoint } from '../endpoints/comment/index.ts';
|
||||
import { ModifyChannelPreferenceEndpoint } from '../endpoints/notification/index.ts';
|
||||
|
||||
import * as ProtoUtils from '../../utils/ProtoUtils.ts';
|
||||
import { throwIfMissing, u8ToBase64 } from '../../utils/Utils.ts';
|
||||
import { CreateCommentParams, NotificationPreferences } from '../../../protos/generated/misc/params.ts';
|
||||
import type { Actions, ApiResponse } from '../index.ts';
|
||||
import NavigationEndpoint from '../../parser/classes/NavigationEndpoint.ts';
|
||||
|
||||
export default class InteractionManager {
|
||||
#actions: Actions;
|
||||
readonly #actions: Actions;
|
||||
|
||||
constructor(actions: Actions) {
|
||||
this.#actions = actions;
|
||||
@@ -25,14 +21,14 @@ export default class InteractionManager {
|
||||
if (!this.#actions.session.logged_in)
|
||||
throw new Error('You must be signed in to perform this operation.');
|
||||
|
||||
const action = await this.#actions.execute(
|
||||
LikeEndpoint.PATH, LikeEndpoint.build({
|
||||
client: 'ANDROID',
|
||||
target: { video_id }
|
||||
})
|
||||
);
|
||||
const like_endpoint = new NavigationEndpoint({
|
||||
likeEndpoint: {
|
||||
status: 'LIKE',
|
||||
target: video_id
|
||||
}
|
||||
});
|
||||
|
||||
return action;
|
||||
return like_endpoint.call(this.#actions, { client: 'TV' });
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -45,14 +41,14 @@ export default class InteractionManager {
|
||||
if (!this.#actions.session.logged_in)
|
||||
throw new Error('You must be signed in to perform this operation.');
|
||||
|
||||
const action = await this.#actions.execute(
|
||||
DislikeEndpoint.PATH, DislikeEndpoint.build({
|
||||
client: 'ANDROID',
|
||||
target: { video_id }
|
||||
})
|
||||
);
|
||||
const dislike_endpoint = new NavigationEndpoint({
|
||||
likeEndpoint: {
|
||||
status: 'DISLIKE',
|
||||
target: video_id
|
||||
}
|
||||
});
|
||||
|
||||
return action;
|
||||
return dislike_endpoint.call(this.#actions, { client: 'TV' });
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -65,18 +61,18 @@ export default class InteractionManager {
|
||||
if (!this.#actions.session.logged_in)
|
||||
throw new Error('You must be signed in to perform this operation.');
|
||||
|
||||
const action = await this.#actions.execute(
|
||||
RemoveLikeEndpoint.PATH, RemoveLikeEndpoint.build({
|
||||
client: 'ANDROID',
|
||||
target: { video_id }
|
||||
})
|
||||
);
|
||||
const remove_like_endpoint = new NavigationEndpoint({
|
||||
likeEndpoint: {
|
||||
status: 'INDIFFERENT',
|
||||
target: video_id
|
||||
}
|
||||
});
|
||||
|
||||
return action;
|
||||
return remove_like_endpoint.call(this.#actions, { client: 'TV' });
|
||||
}
|
||||
|
||||
/**
|
||||
* Subscribes to a given channel.
|
||||
* Subscribes to the given channel.
|
||||
* @param channel_id - The channel ID
|
||||
*/
|
||||
async subscribe(channel_id: string): Promise<ApiResponse> {
|
||||
@@ -85,19 +81,18 @@ export default class InteractionManager {
|
||||
if (!this.#actions.session.logged_in)
|
||||
throw new Error('You must be signed in to perform this operation.');
|
||||
|
||||
const action = await this.#actions.execute(
|
||||
SubscribeEndpoint.PATH, SubscribeEndpoint.build({
|
||||
client: 'ANDROID',
|
||||
channel_ids: [ channel_id ],
|
||||
const subscribe_endpoint = new NavigationEndpoint({
|
||||
subscribeEndpoint: {
|
||||
channelIds: [ channel_id ],
|
||||
params: 'EgIIAhgA'
|
||||
})
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
return action;
|
||||
return subscribe_endpoint.call(this.#actions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unsubscribes from a given channel.
|
||||
* Unsubscribes from the given channel.
|
||||
* @param channel_id - The channel ID
|
||||
*/
|
||||
async unsubscribe(channel_id: string): Promise<ApiResponse> {
|
||||
@@ -106,15 +101,14 @@ export default class InteractionManager {
|
||||
if (!this.#actions.session.logged_in)
|
||||
throw new Error('You must be signed in to perform this operation.');
|
||||
|
||||
const action = await this.#actions.execute(
|
||||
UnsubscribeEndpoint.PATH, UnsubscribeEndpoint.build({
|
||||
client: 'ANDROID',
|
||||
channel_ids: [ channel_id ],
|
||||
const unsubscribe_endpoint = new NavigationEndpoint({
|
||||
unsubscribeEndpoint: {
|
||||
channelIds: [ channel_id ],
|
||||
params: 'CgIIAhgA'
|
||||
})
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
return action;
|
||||
return unsubscribe_endpoint.call(this.#actions);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -128,35 +122,39 @@ export default class InteractionManager {
|
||||
if (!this.#actions.session.logged_in)
|
||||
throw new Error('You must be signed in to perform this operation.');
|
||||
|
||||
const action = await this.#actions.execute(
|
||||
CreateCommentEndpoint.PATH, CreateCommentEndpoint.build({
|
||||
comment_text: text,
|
||||
create_comment_params: Proto.encodeCommentParams(video_id),
|
||||
client: 'ANDROID'
|
||||
})
|
||||
);
|
||||
const writer = CreateCommentParams.encode({
|
||||
videoId: video_id,
|
||||
params: {
|
||||
index: 0
|
||||
},
|
||||
number: 7
|
||||
});
|
||||
|
||||
return action;
|
||||
const params = encodeURIComponent(u8ToBase64(writer.finish()));
|
||||
|
||||
const create_comment_endpoint = new NavigationEndpoint({
|
||||
createCommentEndpoint: {
|
||||
commentText: text,
|
||||
createCommentParams: params
|
||||
}
|
||||
});
|
||||
|
||||
return create_comment_endpoint.call(this.#actions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Translates a given text using YouTube's comment translate feature.
|
||||
*
|
||||
* Translates a given text using YouTube's comment translation feature.
|
||||
* @param text - The text to translate
|
||||
* @param target_language - an ISO language code
|
||||
* @param args - optional arguments
|
||||
*/
|
||||
async translate(text: string, target_language: string, args: { video_id?: string; comment_id?: string; } = {}) {
|
||||
throwIfMissing({ text, target_language });
|
||||
|
||||
const target_action = Proto.encodeCommentActionParams(22, { text, target_language, ...args });
|
||||
|
||||
const response = await this.#actions.execute(
|
||||
PerformCommentActionEndpoint.PATH, PerformCommentActionEndpoint.build({
|
||||
client: 'ANDROID',
|
||||
actions: [ target_action ]
|
||||
})
|
||||
);
|
||||
const action = ProtoUtils.encodeCommentActionParams(22, { text, target_language, ...args });
|
||||
|
||||
const perform_comment_action_endpoint = new NavigationEndpoint({ performCommentActionEndpoint: { action } });
|
||||
const response = await perform_comment_action_endpoint.call(this.#actions);
|
||||
const mutation = response.data.frameworkUpdates.entityBatchUpdate.mutations[0].payload.commentEntityPayload;
|
||||
|
||||
return {
|
||||
@@ -188,13 +186,17 @@ export default class InteractionManager {
|
||||
if (!Object.keys(pref_types).includes(type.toUpperCase()))
|
||||
throw new Error(`Invalid notification preference type: ${type}`);
|
||||
|
||||
const action = await this.#actions.execute(
|
||||
ModifyChannelPreferenceEndpoint.PATH, ModifyChannelPreferenceEndpoint.build({
|
||||
client: 'WEB',
|
||||
params: Proto.encodeNotificationPref(channel_id, pref_types[type.toUpperCase() as keyof typeof pref_types])
|
||||
})
|
||||
);
|
||||
const writer = NotificationPreferences.encode({
|
||||
channelId: channel_id,
|
||||
prefId: {
|
||||
index: pref_types[type.toUpperCase() as keyof typeof pref_types]
|
||||
},
|
||||
number0: 0, number1: 4
|
||||
});
|
||||
|
||||
return action;
|
||||
const params = encodeURIComponent(u8ToBase64(writer.finish()));
|
||||
|
||||
const modify_channel_notification_preference_endpoint = new NavigationEndpoint({ modifyChannelNotificationPreferenceEndpoint: { params } });
|
||||
return modify_channel_notification_preference_endpoint.call(this.#actions);
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,12 @@
|
||||
import { InnertubeError, throwIfMissing } from '../../utils/Utils.ts';
|
||||
import { EditPlaylistEndpoint } from '../endpoints/browse/index.ts';
|
||||
import { BrowseEndpoint } from '../endpoints/index.ts';
|
||||
import { CreateEndpoint, DeleteEndpoint } from '../endpoints/playlist/index.ts';
|
||||
import Playlist from '../../parser/youtube/Playlist.ts';
|
||||
|
||||
import type { Actions } from '../index.ts';
|
||||
import type { Feed } from '../mixins/index.ts';
|
||||
import type { EditPlaylistEndpointOptions } from '../../types/index.ts';
|
||||
import NavigationEndpoint from '../../parser/classes/NavigationEndpoint.ts';
|
||||
|
||||
export default class PlaylistManager {
|
||||
#actions: Actions;
|
||||
readonly #actions: Actions;
|
||||
|
||||
constructor(actions: Actions) {
|
||||
this.#actions = actions;
|
||||
@@ -26,12 +23,14 @@ export default class PlaylistManager {
|
||||
if (!this.#actions.session.logged_in)
|
||||
throw new InnertubeError('You must be signed in to perform this operation.');
|
||||
|
||||
const response = await this.#actions.execute(
|
||||
CreateEndpoint.PATH, CreateEndpoint.build({
|
||||
ids: video_ids,
|
||||
title
|
||||
})
|
||||
);
|
||||
const create_playlist_endpoint = new NavigationEndpoint({
|
||||
createPlaylistServiceEndpoint: {
|
||||
title,
|
||||
videoIds: video_ids
|
||||
}
|
||||
});
|
||||
|
||||
const response = await create_playlist_endpoint.call(this.#actions);
|
||||
|
||||
return {
|
||||
success: response.success,
|
||||
@@ -51,11 +50,13 @@ export default class PlaylistManager {
|
||||
if (!this.#actions.session.logged_in)
|
||||
throw new InnertubeError('You must be signed in to perform this operation.');
|
||||
|
||||
const response = await this.#actions.execute(
|
||||
DeleteEndpoint.PATH, DeleteEndpoint.build({
|
||||
playlist_id
|
||||
})
|
||||
);
|
||||
const delete_playlist_endpoint = new NavigationEndpoint({
|
||||
deletePlaylistServiceEndpoint: {
|
||||
sourcePlaylistId: playlist_id
|
||||
}
|
||||
});
|
||||
|
||||
const response = await delete_playlist_endpoint.call(this.#actions);
|
||||
|
||||
return {
|
||||
playlist_id,
|
||||
@@ -64,6 +65,46 @@ export default class PlaylistManager {
|
||||
data: response.data
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a given playlist to the library of a user.
|
||||
* @param playlist_id - The playlist ID.
|
||||
*/
|
||||
async addToLibrary(playlist_id: string){
|
||||
throwIfMissing({ playlist_id });
|
||||
|
||||
if (!this.#actions.session.logged_in)
|
||||
throw new InnertubeError('You must be signed in to perform this operation.');
|
||||
|
||||
const like_playlist_endpoint = new NavigationEndpoint({
|
||||
likeEndpoint: {
|
||||
status: 'LIKE',
|
||||
target: playlist_id
|
||||
}
|
||||
});
|
||||
|
||||
return await like_playlist_endpoint.call(this.#actions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a given playlist to the library of a user.
|
||||
* @param playlist_id - The playlist ID.
|
||||
*/
|
||||
async removeFromLibrary(playlist_id: string){
|
||||
throwIfMissing({ playlist_id });
|
||||
|
||||
if (!this.#actions.session.logged_in)
|
||||
throw new InnertubeError('You must be signed in to perform this operation.');
|
||||
|
||||
const remove_like_playlist_endpoint = new NavigationEndpoint({
|
||||
likeEndpoint: {
|
||||
status: 'INDIFFERENT',
|
||||
target: playlist_id
|
||||
}
|
||||
});
|
||||
|
||||
return await remove_like_playlist_endpoint.call(this.#actions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds videos to a given playlist.
|
||||
@@ -76,15 +117,17 @@ export default class PlaylistManager {
|
||||
if (!this.#actions.session.logged_in)
|
||||
throw new InnertubeError('You must be signed in to perform this operation.');
|
||||
|
||||
const response = await this.#actions.execute(
|
||||
EditPlaylistEndpoint.PATH, EditPlaylistEndpoint.build({
|
||||
const playlist_edit_endpoint = new NavigationEndpoint({
|
||||
playlistEditEndpoint: {
|
||||
playlistId: playlist_id,
|
||||
actions: video_ids.map((id) => ({
|
||||
action: 'ACTION_ADD_VIDEO',
|
||||
added_video_id: id
|
||||
})),
|
||||
playlist_id
|
||||
})
|
||||
);
|
||||
addedVideoId: id
|
||||
}))
|
||||
}
|
||||
});
|
||||
|
||||
const response = await playlist_edit_endpoint.call(this.#actions);
|
||||
|
||||
return {
|
||||
playlist_id,
|
||||
@@ -96,31 +139,29 @@ export default class PlaylistManager {
|
||||
* Removes videos from a given playlist.
|
||||
* @param playlist_id - The playlist ID.
|
||||
* @param video_ids - An array of video IDs to remove from the playlist.
|
||||
* @param use_set_video_ids - Option to remove videos using set video IDs.
|
||||
*/
|
||||
async removeVideos(playlist_id: string, video_ids: string[]): Promise<{ playlist_id: string; action_result: any }> {
|
||||
async removeVideos(playlist_id: string, video_ids: string[], use_set_video_ids = false): Promise<{ playlist_id: string; action_result: any }> {
|
||||
throwIfMissing({ playlist_id, video_ids });
|
||||
|
||||
if (!this.#actions.session.logged_in)
|
||||
throw new InnertubeError('You must be signed in to perform this operation.');
|
||||
|
||||
const info = await this.#actions.execute(
|
||||
BrowseEndpoint.PATH, { ...BrowseEndpoint.build({ browse_id: `VL${playlist_id}` }), parse: true }
|
||||
);
|
||||
|
||||
const playlist = new Playlist(this.#actions, info, true);
|
||||
const playlist = await this.#getPlaylist(playlist_id);
|
||||
|
||||
if (!playlist.info.is_editable)
|
||||
throw new InnertubeError('This playlist cannot be edited.', playlist_id);
|
||||
|
||||
const payload: EditPlaylistEndpointOptions = { playlist_id, actions: [] };
|
||||
const payload = { playlistId: playlist_id, actions: [] as Record<string, any>[] };
|
||||
|
||||
const getSetVideoIds = async (pl: Feed): Promise<void> => {
|
||||
const videos = pl.videos.filter((video) => video_ids.includes(video.key('id').string()));
|
||||
const key_id = use_set_video_ids ? 'set_video_id' : 'id';
|
||||
const videos = pl.videos.filter((video) => video_ids.includes(video.key(key_id).string()));
|
||||
|
||||
videos.forEach((video) =>
|
||||
payload.actions.push({
|
||||
action: 'ACTION_REMOVE_VIDEO',
|
||||
set_video_id: video.key('set_video_id').string()
|
||||
setVideoId: video.key('set_video_id').string()
|
||||
})
|
||||
);
|
||||
|
||||
@@ -135,9 +176,8 @@ export default class PlaylistManager {
|
||||
if (!payload.actions.length)
|
||||
throw new InnertubeError('Given video ids were not found in this playlist.', video_ids);
|
||||
|
||||
const response = await this.#actions.execute(
|
||||
EditPlaylistEndpoint.PATH, EditPlaylistEndpoint.build(payload)
|
||||
);
|
||||
const playlist_edit_endpoint = new NavigationEndpoint({ playlistEditEndpoint: payload });
|
||||
const response = await playlist_edit_endpoint.call(this.#actions);
|
||||
|
||||
return {
|
||||
playlist_id,
|
||||
@@ -157,16 +197,12 @@ export default class PlaylistManager {
|
||||
if (!this.#actions.session.logged_in)
|
||||
throw new InnertubeError('You must be signed in to perform this operation.');
|
||||
|
||||
const info = await this.#actions.execute(
|
||||
BrowseEndpoint.PATH, { ...BrowseEndpoint.build({ browse_id: `VL${playlist_id}` }), parse: true }
|
||||
);
|
||||
|
||||
const playlist = new Playlist(this.#actions, info, true);
|
||||
const playlist = await this.#getPlaylist(playlist_id);
|
||||
|
||||
if (!playlist.info.is_editable)
|
||||
throw new InnertubeError('This playlist cannot be edited.', playlist_id);
|
||||
|
||||
const payload: EditPlaylistEndpointOptions = { playlist_id, actions: [] };
|
||||
const payload = { playlistId: playlist_id, actions: [] as Record<string, any>[] };
|
||||
|
||||
let set_video_id_0: string | undefined, set_video_id_1: string | undefined;
|
||||
|
||||
@@ -187,22 +223,32 @@ export default class PlaylistManager {
|
||||
|
||||
payload.actions.push({
|
||||
action: 'ACTION_MOVE_VIDEO_AFTER',
|
||||
set_video_id: set_video_id_0,
|
||||
moved_set_video_id_predecessor: set_video_id_1
|
||||
setVideoId: set_video_id_0,
|
||||
movedSetVideoIdPredecessor: set_video_id_1
|
||||
});
|
||||
|
||||
const response = await this.#actions.execute(
|
||||
EditPlaylistEndpoint.PATH, EditPlaylistEndpoint.build(payload)
|
||||
);
|
||||
const playlist_edit_endpoint = new NavigationEndpoint({ playlistEditEndpoint: payload });
|
||||
const response = await playlist_edit_endpoint.call(this.#actions);
|
||||
|
||||
return {
|
||||
playlist_id,
|
||||
action_result: response.data.actions // TODO: implement actions in the parser
|
||||
};
|
||||
}
|
||||
|
||||
async #getPlaylist(playlist_id: string): Promise<Playlist> {
|
||||
if (!playlist_id.startsWith('VL')) {
|
||||
playlist_id = `VL${playlist_id}`;
|
||||
}
|
||||
|
||||
const browse_endpoint = new NavigationEndpoint({ browseEndpoint: { browseId: playlist_id } });
|
||||
const browse_response = await browse_endpoint.call(this.#actions, { parse: true });
|
||||
|
||||
return new Playlist(this.#actions, browse_response, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the name (title) for the given playlist.
|
||||
* Sets the name for the given playlist.
|
||||
* @param playlist_id - The playlist ID.
|
||||
* @param name - The name / title to use for the playlist.
|
||||
*/
|
||||
@@ -212,16 +258,15 @@ export default class PlaylistManager {
|
||||
if (!this.#actions.session.logged_in)
|
||||
throw new InnertubeError('You must be signed in to perform this operation.');
|
||||
|
||||
const payload: EditPlaylistEndpointOptions = { playlist_id, actions: [] };
|
||||
const payload = { playlist_id, actions: [] as Record<string, any>[] };
|
||||
|
||||
payload.actions.push({
|
||||
action: 'ACTION_SET_PLAYLIST_NAME',
|
||||
playlist_name: name
|
||||
playlistName: name
|
||||
});
|
||||
|
||||
const response = await this.#actions.execute(
|
||||
EditPlaylistEndpoint.PATH, EditPlaylistEndpoint.build(payload)
|
||||
);
|
||||
const playlist_edit_endpoint = new NavigationEndpoint({ playlistEditEndpoint: payload });
|
||||
const response = await playlist_edit_endpoint.call(this.#actions);
|
||||
|
||||
return {
|
||||
playlist_id,
|
||||
@@ -240,16 +285,15 @@ export default class PlaylistManager {
|
||||
if (!this.#actions.session.logged_in)
|
||||
throw new InnertubeError('You must be signed in to perform this operation.');
|
||||
|
||||
const payload: EditPlaylistEndpointOptions = { playlist_id, actions: [] };
|
||||
const payload = { playlistId: playlist_id, actions: [] as Record<string, any>[] };
|
||||
|
||||
payload.actions.push({
|
||||
action: 'ACTION_SET_PLAYLIST_DESCRIPTION',
|
||||
playlist_description: description
|
||||
playlistDescription: description
|
||||
});
|
||||
|
||||
const response = await this.#actions.execute(
|
||||
EditPlaylistEndpoint.PATH, EditPlaylistEndpoint.build(payload)
|
||||
);
|
||||
const playlist_edit_endpoint = new NavigationEndpoint({ playlistEditEndpoint: payload });
|
||||
const response = await playlist_edit_endpoint.call(this.#actions);
|
||||
|
||||
return {
|
||||
playlist_id,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { IParsedResponse } from '../../parser/index.ts';
|
||||
import { Parser, ReloadContinuationItemsCommand } from '../../parser/index.ts';
|
||||
import { concatMemos, InnertubeError } from '../../utils/Utils.ts';
|
||||
|
||||
@@ -8,11 +9,13 @@ import CompactVideo from '../../parser/classes/CompactVideo.ts';
|
||||
import GridChannel from '../../parser/classes/GridChannel.ts';
|
||||
import GridPlaylist from '../../parser/classes/GridPlaylist.ts';
|
||||
import GridVideo from '../../parser/classes/GridVideo.ts';
|
||||
import LockupView from '../../parser/classes/LockupView.ts';
|
||||
import Playlist from '../../parser/classes/Playlist.ts';
|
||||
import PlaylistPanelVideo from '../../parser/classes/PlaylistPanelVideo.ts';
|
||||
import PlaylistVideo from '../../parser/classes/PlaylistVideo.ts';
|
||||
import Post from '../../parser/classes/Post.ts';
|
||||
import ReelItem from '../../parser/classes/ReelItem.ts';
|
||||
import ShortsLockupView from '../../parser/classes/ShortsLockupView.ts';
|
||||
import ReelShelf from '../../parser/classes/ReelShelf.ts';
|
||||
import RichShelf from '../../parser/classes/RichShelf.ts';
|
||||
import Shelf from '../../parser/classes/Shelf.ts';
|
||||
@@ -25,22 +28,22 @@ import TwoColumnBrowseResults from '../../parser/classes/TwoColumnBrowseResults.
|
||||
import TwoColumnSearchResults from '../../parser/classes/TwoColumnSearchResults.ts';
|
||||
import WatchCardCompactVideo from '../../parser/classes/WatchCardCompactVideo.ts';
|
||||
|
||||
import type { ApiResponse, Actions } from '../index.ts';
|
||||
import type {
|
||||
Memo, ObservedArray,
|
||||
SuperParsedResult, YTNode
|
||||
} from '../../parser/helpers.ts';
|
||||
import type { Actions, ApiResponse } from '../index.ts';
|
||||
import type { Memo, ObservedArray } from '../../parser/helpers.ts';
|
||||
import type MusicQueue from '../../parser/classes/MusicQueue.ts';
|
||||
import type RichGrid from '../../parser/classes/RichGrid.ts';
|
||||
import type SectionList from '../../parser/classes/SectionList.ts';
|
||||
import type { IParsedResponse } from '../../parser/types/index.ts';
|
||||
import type SecondarySearchContainer from '../../parser/classes/SecondarySearchContainer.ts';
|
||||
import type BrowseFeedActions from '../../parser/classes/BrowseFeedActions.ts';
|
||||
import type ProfileColumn from '../../parser/classes/ProfileColumn.ts';
|
||||
|
||||
export default class Feed<T extends IParsedResponse = IParsedResponse> {
|
||||
#page: T;
|
||||
#continuation?: ObservedArray<ContinuationItem>;
|
||||
#actions: Actions;
|
||||
#memo: Memo;
|
||||
readonly #page: T;
|
||||
readonly #actions: Actions;
|
||||
readonly #memo: Memo;
|
||||
|
||||
#continuation?: ObservedArray<ContinuationItem>;
|
||||
|
||||
constructor(actions: Actions, response: ApiResponse | IParsedResponse, already_parsed = false) {
|
||||
if (this.#isParsed(response) || already_parsed) {
|
||||
this.#page = response as T;
|
||||
@@ -77,6 +80,7 @@ export default class Feed<T extends IParsedResponse = IParsedResponse> {
|
||||
Video,
|
||||
GridVideo,
|
||||
ReelItem,
|
||||
ShortsLockupView,
|
||||
CompactVideo,
|
||||
PlaylistVideo,
|
||||
PlaylistPanelVideo,
|
||||
@@ -88,7 +92,18 @@ export default class Feed<T extends IParsedResponse = IParsedResponse> {
|
||||
* Get all playlists on a given page via memo
|
||||
*/
|
||||
static getPlaylistsFromMemo(memo: Memo) {
|
||||
return memo.getType(Playlist, GridPlaylist);
|
||||
const playlists: ObservedArray<Playlist | GridPlaylist | LockupView> = memo.getType(Playlist, GridPlaylist);
|
||||
|
||||
const lockup_views = memo.getType(LockupView)
|
||||
.filter((lockup) => {
|
||||
return [ 'PLAYLIST', 'ALBUM', 'PODCAST' ].includes(lockup.content_type);
|
||||
});
|
||||
|
||||
if (lockup_views.length > 0) {
|
||||
playlists.push(...lockup_views);
|
||||
}
|
||||
|
||||
return playlists;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -127,9 +142,9 @@ export default class Feed<T extends IParsedResponse = IParsedResponse> {
|
||||
* Returns contents from the page.
|
||||
*/
|
||||
get page_contents(): SectionList | MusicQueue | RichGrid | ReloadContinuationItemsCommand {
|
||||
const tab_content = this.#memo.getType(Tab)?.first().content;
|
||||
const reload_continuation_items = this.#memo.getType(ReloadContinuationItemsCommand).first();
|
||||
const append_continuation_items = this.#memo.getType(AppendContinuationItemsAction).first();
|
||||
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];
|
||||
|
||||
return tab_content || reload_continuation_items || append_continuation_items;
|
||||
}
|
||||
@@ -145,20 +160,20 @@ export default class Feed<T extends IParsedResponse = IParsedResponse> {
|
||||
* Finds shelf by title.
|
||||
*/
|
||||
getShelf(title: string) {
|
||||
return this.shelves.get({ title });
|
||||
return this.shelves.find((shelf) => shelf.title.toString() === title);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns secondary contents from the page.
|
||||
*/
|
||||
get secondary_contents(): SuperParsedResult<YTNode> | undefined {
|
||||
get secondary_contents(): SectionList | SecondarySearchContainer | BrowseFeedActions | ProfileColumn | null {
|
||||
if (!this.#page.contents?.is_node)
|
||||
return undefined;
|
||||
return null;
|
||||
|
||||
const node = this.#page.contents?.item();
|
||||
|
||||
if (!node.is(TwoColumnBrowseResults, TwoColumnSearchResults))
|
||||
return undefined;
|
||||
return null;
|
||||
|
||||
return node.secondary_contents;
|
||||
}
|
||||
@@ -189,9 +204,7 @@ export default class Feed<T extends IParsedResponse = IParsedResponse> {
|
||||
if (this.#continuation.length === 0)
|
||||
throw new InnertubeError('There are no continuations.');
|
||||
|
||||
const response = await this.#continuation[0].endpoint.call<T>(this.#actions, { parse: true });
|
||||
|
||||
return response;
|
||||
return await this.#continuation[0].endpoint.call<T>(this.#actions, { parse: true });
|
||||
}
|
||||
|
||||
this.#continuation = this.#getBodyContinuations();
|
||||
|
||||
@@ -4,7 +4,7 @@ import FeedFilterChipBar from '../../parser/classes/FeedFilterChipBar.ts';
|
||||
import { InnertubeError } from '../../utils/Utils.ts';
|
||||
|
||||
import type { ObservedArray } from '../../parser/helpers.ts';
|
||||
import type { IParsedResponse } from '../../parser/types/index.ts';
|
||||
import type { IParsedResponse } from '../../parser/index.ts';
|
||||
import type { ApiResponse, Actions } from '../index.ts';
|
||||
|
||||
export default class FilterableFeed<T extends IParsedResponse> extends Feed<T> {
|
||||
|
||||
@@ -2,30 +2,55 @@ import { Constants, FormatUtils } from '../../utils/index.ts';
|
||||
import { InnertubeError } from '../../utils/Utils.ts';
|
||||
import { getStreamingInfo } from '../../utils/StreamingInfo.ts';
|
||||
|
||||
import type {
|
||||
INextResponse,
|
||||
IPlayabilityStatus,
|
||||
IPlaybackTracking,
|
||||
IPlayerConfig,
|
||||
IPlayerResponse,
|
||||
IStreamingData
|
||||
} from '../../parser/index.ts';
|
||||
|
||||
import { Parser } from '../../parser/index.ts';
|
||||
import { TranscriptInfo } from '../../parser/youtube/index.ts';
|
||||
import ContinuationItem from '../../parser/classes/ContinuationItem.ts';
|
||||
import PlayerMicroformat from '../../parser/classes/PlayerMicroformat.ts';
|
||||
import MicroformatData from '../../parser/classes/MicroformatData.ts';
|
||||
|
||||
import type { ApiResponse, Actions } from '../index.ts';
|
||||
import type { INextResponse, IPlayerConfig, IPlayerResponse } from '../../parser/index.ts';
|
||||
import type { DownloadOptions, FormatFilter, FormatOptions, URLTransformer } from '../../types/FormatUtils.ts';
|
||||
import type { Actions, ApiResponse } from '../index.ts';
|
||||
import type { DownloadOptions, FormatFilter, FormatOptions, URLTransformer } from '../../types/index.ts';
|
||||
import type Format from '../../parser/classes/misc/Format.ts';
|
||||
import type { DashOptions } from '../../types/DashOptions.ts';
|
||||
import type { ObservedArray } from '../../parser/helpers.ts';
|
||||
|
||||
import type CardCollection from '../../parser/classes/CardCollection.ts';
|
||||
import type Endscreen from '../../parser/classes/Endscreen.ts';
|
||||
import type PlayerAnnotationsExpanded from '../../parser/classes/PlayerAnnotationsExpanded.ts';
|
||||
import type PlayerCaptionsTracklist from '../../parser/classes/PlayerCaptionsTracklist.ts';
|
||||
import type PlayerLiveStoryboardSpec from '../../parser/classes/PlayerLiveStoryboardSpec.ts';
|
||||
import type PlayerStoryboardSpec from '../../parser/classes/PlayerStoryboardSpec.ts';
|
||||
|
||||
export default class MediaInfo {
|
||||
#page: [IPlayerResponse, INextResponse?];
|
||||
#actions: Actions;
|
||||
#cpn: string;
|
||||
#playback_tracking;
|
||||
streaming_data;
|
||||
playability_status;
|
||||
player_config: IPlayerConfig;
|
||||
readonly #page: [ IPlayerResponse, INextResponse? ];
|
||||
readonly #actions: Actions;
|
||||
readonly #cpn: string;
|
||||
readonly #playback_tracking?: IPlaybackTracking;
|
||||
|
||||
constructor(data: [ApiResponse, ApiResponse?], actions: Actions, cpn: string) {
|
||||
public basic_info;
|
||||
public annotations?: ObservedArray<PlayerAnnotationsExpanded>;
|
||||
public storyboards?: PlayerStoryboardSpec | PlayerLiveStoryboardSpec;
|
||||
public endscreen?: Endscreen;
|
||||
public captions?: PlayerCaptionsTracklist;
|
||||
public cards?: CardCollection;
|
||||
public streaming_data?: IStreamingData;
|
||||
public playability_status?: IPlayabilityStatus;
|
||||
public player_config?: IPlayerConfig;
|
||||
|
||||
constructor(data: [ ApiResponse, ApiResponse? ], actions: Actions, cpn: string) {
|
||||
this.#actions = actions;
|
||||
|
||||
const info = Parser.parseResponse<IPlayerResponse>(data[0].data);
|
||||
const next = data?.[1]?.data ? Parser.parseResponse<INextResponse>(data[1].data) : undefined;
|
||||
const info = Parser.parseResponse<IPlayerResponse>(data[0].data.playerResponse ? data[0].data.playerResponse : data[0].data);
|
||||
const next = data[1]?.data ? Parser.parseResponse<INextResponse>(data[1].data) : undefined;
|
||||
|
||||
this.#page = [ info, next ];
|
||||
this.#cpn = cpn;
|
||||
@@ -33,6 +58,38 @@ export default class MediaInfo {
|
||||
if (info.playability_status?.status === 'ERROR')
|
||||
throw new InnertubeError('This video is unavailable', info.playability_status);
|
||||
|
||||
if (info.microformat && !info.microformat?.is(PlayerMicroformat, MicroformatData))
|
||||
throw new InnertubeError('Unsupported microformat', info.microformat);
|
||||
|
||||
this.basic_info = { // This type is inferred so no need for an explicit type
|
||||
...info.video_details,
|
||||
/**
|
||||
* Microformat is a bit redundant, so only
|
||||
* a few things there are interesting to us.
|
||||
*/
|
||||
...{
|
||||
embed: info.microformat?.is(PlayerMicroformat) ? info.microformat?.embed : null,
|
||||
channel: info.microformat?.is(PlayerMicroformat) ? info.microformat?.channel : null,
|
||||
is_unlisted: info.microformat?.is_unlisted,
|
||||
is_family_safe: info.microformat?.is_family_safe,
|
||||
category: info.microformat?.is(PlayerMicroformat) ? info.microformat?.category : null,
|
||||
has_ypc_metadata: info.microformat?.is(PlayerMicroformat) ? info.microformat?.has_ypc_metadata : null,
|
||||
start_timestamp: info.microformat?.is(PlayerMicroformat) ? info.microformat.start_timestamp : null,
|
||||
end_timestamp: info.microformat?.is(PlayerMicroformat) ? info.microformat.end_timestamp : null,
|
||||
view_count: info.microformat?.is(PlayerMicroformat) && isNaN(info.video_details?.view_count as number) ? info.microformat.view_count : info.video_details?.view_count,
|
||||
url_canonical: info.microformat?.is(MicroformatData) ? info.microformat?.url_canonical : null,
|
||||
tags: info.microformat?.is(MicroformatData) ? info.microformat?.tags : null
|
||||
},
|
||||
like_count: undefined as number | undefined,
|
||||
is_liked: undefined as boolean | undefined,
|
||||
is_disliked: undefined as boolean | undefined
|
||||
};
|
||||
|
||||
this.annotations = info.annotations;
|
||||
this.storyboards = info.storyboards;
|
||||
this.endscreen = info.endscreen;
|
||||
this.captions = info.captions;
|
||||
this.cards = info.cards;
|
||||
this.streaming_data = info.streaming_data;
|
||||
this.playability_status = info.playability_status;
|
||||
this.player_config = info.player_config;
|
||||
@@ -41,25 +98,44 @@ export default class MediaInfo {
|
||||
|
||||
/**
|
||||
* Generates a DASH manifest from the streaming data.
|
||||
* @param url_transformer - Function to transform the URLs.
|
||||
* @param format_filter - Function to filter the formats.
|
||||
* @param options - Additional options to customise the manifest generation
|
||||
* @param options
|
||||
* @returns DASH manifest
|
||||
*/
|
||||
async toDash(url_transformer?: URLTransformer, format_filter?: FormatFilter, options: DashOptions = { include_thumbnails: false }): Promise<string> {
|
||||
async toDash(options: {
|
||||
url_transformer?: URLTransformer;
|
||||
format_filter?: FormatFilter;
|
||||
manifest_options?: DashOptions;
|
||||
} = {}): Promise<string> {
|
||||
const player_response = this.#page[0];
|
||||
const manifest_options = options.manifest_options || {};
|
||||
|
||||
if (player_response.video_details && (player_response.video_details.is_live)) {
|
||||
throw new InnertubeError('Generating DASH manifests for live videos is not supported. Please use the DASH and HLS manifests provided by YouTube in `streaming_data.dash_manifest_url` and `streaming_data.hls_manifest_url` instead.');
|
||||
}
|
||||
|
||||
let storyboards;
|
||||
let captions;
|
||||
|
||||
if (options.include_thumbnails && player_response.storyboards) {
|
||||
if (manifest_options.include_thumbnails && player_response.storyboards) {
|
||||
storyboards = player_response.storyboards;
|
||||
}
|
||||
|
||||
return FormatUtils.toDash(this.streaming_data, this.page[0].video_details?.is_post_live_dvr, url_transformer, format_filter, this.#cpn, this.#actions.session.player, this.#actions, storyboards);
|
||||
if (typeof manifest_options.captions_format === 'string' && player_response.captions?.caption_tracks) {
|
||||
captions = player_response.captions.caption_tracks;
|
||||
}
|
||||
|
||||
return FormatUtils.toDash(
|
||||
this.streaming_data,
|
||||
this.page[0].video_details?.is_post_live_dvr,
|
||||
options.url_transformer,
|
||||
options.format_filter,
|
||||
this.#cpn,
|
||||
this.#actions.session.player,
|
||||
this.#actions,
|
||||
storyboards,
|
||||
captions,
|
||||
manifest_options
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -102,7 +178,6 @@ export default class MediaInfo {
|
||||
|
||||
/**
|
||||
* Retrieves the video's transcript.
|
||||
* @param video_id - The video id.
|
||||
*/
|
||||
async getTranscript(): Promise<TranscriptInfo> {
|
||||
const next_response = this.page[1];
|
||||
@@ -113,8 +188,8 @@ export default class MediaInfo {
|
||||
if (!next_response.engagement_panels)
|
||||
throw new InnertubeError('Engagement panels not found. Video likely has no transcript.');
|
||||
|
||||
const transcript_panel = next_response.engagement_panels.get({
|
||||
panel_identifier: 'engagement-panel-searchable-transcript'
|
||||
const transcript_panel = next_response.engagement_panels.find((panel) => {
|
||||
return panel.panel_identifier === 'engagement-panel-searchable-transcript';
|
||||
});
|
||||
|
||||
if (!transcript_panel)
|
||||
@@ -130,10 +205,7 @@ export default class MediaInfo {
|
||||
return new TranscriptInfo(this.actions, response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds video to the watch history.
|
||||
*/
|
||||
async addToWatchHistory(client_name = Constants.CLIENTS.WEB.NAME, client_version = Constants.CLIENTS.WEB.VERSION, replacement = 'https://www.'): Promise<Response> {
|
||||
async addToWatchHistory(client_name?: string, client_version?: string, replacement = 'https://www.'): Promise<Response> {
|
||||
if (!this.#playback_tracking)
|
||||
throw new InnertubeError('Playback tracking not available');
|
||||
|
||||
@@ -146,17 +218,32 @@ export default class MediaInfo {
|
||||
|
||||
const url = this.#playback_tracking.videostats_playback_url.replace('https://s.', replacement);
|
||||
|
||||
const response = await this.#actions.stats(url, {
|
||||
return await this.#actions.stats(url, {
|
||||
client_name: client_name || Constants.CLIENTS.WEB.NAME,
|
||||
client_version: client_version || Constants.CLIENTS.WEB.VERSION
|
||||
}, url_params);
|
||||
}
|
||||
|
||||
async updateWatchTime(startTime: number, client_name: string = Constants.CLIENTS.WEB.NAME, client_version: string = Constants.CLIENTS.WEB.VERSION, replacement = 'https://www.'): Promise<Response> {
|
||||
if (!this.#playback_tracking)
|
||||
throw new InnertubeError('Playback tracking not available');
|
||||
|
||||
const url_params = {
|
||||
cpn: this.#cpn,
|
||||
st: startTime.toFixed(3),
|
||||
et: startTime.toFixed(3),
|
||||
cmt: startTime.toFixed(3),
|
||||
final: '1'
|
||||
};
|
||||
|
||||
const url = this.#playback_tracking.videostats_watchtime_url.replace('https://s.', replacement);
|
||||
|
||||
return await this.#actions.stats(url, {
|
||||
client_name,
|
||||
client_version
|
||||
}, url_params);
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Actions instance.
|
||||
*/
|
||||
get actions(): Actions {
|
||||
return this.#actions;
|
||||
}
|
||||
@@ -169,7 +256,7 @@ export default class MediaInfo {
|
||||
}
|
||||
|
||||
/**
|
||||
* Original parsed InnerTube response.
|
||||
* Parsed InnerTube response.
|
||||
*/
|
||||
get page(): [IPlayerResponse, INextResponse?] {
|
||||
return this.#page;
|
||||
|
||||
@@ -4,11 +4,11 @@ import Tab from '../../parser/classes/Tab.ts';
|
||||
|
||||
import type { Actions, ApiResponse } from '../index.ts';
|
||||
import type { ObservedArray } from '../../parser/helpers.ts';
|
||||
import type { IParsedResponse } from '../../parser/types/ParsedResponse.ts';
|
||||
import type { IParsedResponse } from '../../parser/index.ts';
|
||||
|
||||
export default class TabbedFeed<T extends IParsedResponse> extends Feed<T> {
|
||||
readonly #actions: Actions;
|
||||
#tabs?: ObservedArray<Tab>;
|
||||
#actions: Actions;
|
||||
|
||||
constructor(actions: Actions, data: ApiResponse | IParsedResponse, already_parsed = false) {
|
||||
super(actions, data, already_parsed);
|
||||
|
||||
33
deno/src/parser/classes/AccountItem.ts
Normal file
33
deno/src/parser/classes/AccountItem.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { YTNode } from '../helpers.ts';
|
||||
import { type RawNode } from '../index.ts';
|
||||
import NavigationEndpoint from './NavigationEndpoint.ts';
|
||||
import Text from './misc/Text.ts';
|
||||
import Thumbnail from './misc/Thumbnail.ts';
|
||||
|
||||
/**
|
||||
* Not a real renderer but we treat it as one to keep things organized.
|
||||
*/
|
||||
export default class AccountItem extends YTNode {
|
||||
static type = 'AccountItem';
|
||||
|
||||
account_name: Text;
|
||||
account_photo: Thumbnail[];
|
||||
is_selected: boolean;
|
||||
is_disabled: boolean;
|
||||
has_channel: boolean;
|
||||
endpoint: NavigationEndpoint;
|
||||
account_byline: Text;
|
||||
channel_handle: Text;
|
||||
|
||||
constructor(data: RawNode) {
|
||||
super();
|
||||
this.account_name = new Text(data.accountName);
|
||||
this.account_photo = Thumbnail.fromResponse(data.accountPhoto);
|
||||
this.is_selected = !!data.isSelected;
|
||||
this.is_disabled = !!data.isDisabled;
|
||||
this.has_channel = !!data.hasChannel;
|
||||
this.endpoint = new NavigationEndpoint(data.serviceEndpoint);
|
||||
this.account_byline = new Text(data.accountByline);
|
||||
this.channel_handle = new Text(data.channelHandle);
|
||||
}
|
||||
}
|
||||
@@ -1,47 +1,19 @@
|
||||
import { Parser } from '../index.ts';
|
||||
import AccountItem from './AccountItem.ts';
|
||||
import AccountItemSectionHeader from './AccountItemSectionHeader.ts';
|
||||
import NavigationEndpoint from './NavigationEndpoint.ts';
|
||||
import Text from './misc/Text.ts';
|
||||
import Thumbnail from './misc/Thumbnail.ts';
|
||||
|
||||
import { YTNode, observe, type ObservedArray } from '../helpers.ts';
|
||||
import { YTNode, type ObservedArray } from '../helpers.ts';
|
||||
import type { RawNode } from '../index.ts';
|
||||
|
||||
/**
|
||||
* Not a real renderer but we treat it as one to keep things organized.
|
||||
*/
|
||||
export class AccountItem extends YTNode {
|
||||
static type = 'AccountItem';
|
||||
|
||||
account_name: Text;
|
||||
account_photo: Thumbnail[];
|
||||
is_selected: boolean;
|
||||
is_disabled: boolean;
|
||||
has_channel: boolean;
|
||||
endpoint: NavigationEndpoint;
|
||||
account_byline: Text;
|
||||
|
||||
constructor(data: RawNode) {
|
||||
super();
|
||||
this.account_name = new Text(data.accountName);
|
||||
this.account_photo = Thumbnail.fromResponse(data.accountPhoto);
|
||||
this.is_selected = !!data.isSelected;
|
||||
this.is_disabled = !!data.isDisabled;
|
||||
this.has_channel = !!data.hasChannel;
|
||||
this.endpoint = new NavigationEndpoint(data.serviceEndpoint);
|
||||
this.account_byline = new Text(data.accountByline);
|
||||
}
|
||||
}
|
||||
import CompactLink from './CompactLink.ts';
|
||||
|
||||
export default class AccountItemSection extends YTNode {
|
||||
static type = 'AccountItemSection';
|
||||
|
||||
contents: ObservedArray<AccountItem>;
|
||||
header: AccountItemSectionHeader | null;
|
||||
public contents: ObservedArray<AccountItem | CompactLink>;
|
||||
public header: AccountItemSectionHeader | null;
|
||||
|
||||
constructor(data: RawNode) {
|
||||
super();
|
||||
this.contents = observe<AccountItem>(data.contents.map((ac: RawNode) => new AccountItem(ac.accountItem)));
|
||||
this.contents = Parser.parseArray(data.contents, [ AccountItem, CompactLink ]);
|
||||
this.header = Parser.parseItem(data.header, AccountItemSectionHeader);
|
||||
}
|
||||
}
|
||||
@@ -2,18 +2,19 @@ import { Parser } from '../index.ts';
|
||||
import AccountChannel from './AccountChannel.ts';
|
||||
import AccountItemSection from './AccountItemSection.ts';
|
||||
|
||||
import { YTNode } from '../helpers.ts';
|
||||
import type { RawNode } from '../index.ts';
|
||||
import type { ObservedArray } from '../helpers.ts';
|
||||
import { YTNode } from '../helpers.ts';
|
||||
|
||||
export default class AccountSectionList extends YTNode {
|
||||
static type = 'AccountSectionList';
|
||||
|
||||
contents;
|
||||
footers;
|
||||
public contents: ObservedArray<AccountItemSection>;
|
||||
public footers: ObservedArray<AccountChannel>;
|
||||
|
||||
constructor(data: RawNode) {
|
||||
super();
|
||||
this.contents = Parser.parseItem(data.contents[0], AccountItemSection);
|
||||
this.footers = Parser.parseItem(data.footers[0], AccountChannel);
|
||||
this.contents = Parser.parseArray(data.contents, AccountItemSection);
|
||||
this.footers = Parser.parseArray(data.footers, AccountChannel);
|
||||
}
|
||||
}
|
||||
24
deno/src/parser/classes/ActiveAccountHeader.ts
Normal file
24
deno/src/parser/classes/ActiveAccountHeader.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { YTNode } from '../helpers.ts';
|
||||
import { type RawNode } from '../index.ts';
|
||||
import Text from './misc/Text.ts';
|
||||
import Thumbnail from './misc/Thumbnail.ts';
|
||||
import NavigationEndpoint from './NavigationEndpoint.ts';
|
||||
|
||||
export default class ActiveAccountHeader extends YTNode {
|
||||
static type = 'ActiveAccountHeader';
|
||||
|
||||
public account_name: Text;
|
||||
public account_photo: Thumbnail[];
|
||||
public endpoint: NavigationEndpoint;
|
||||
public manage_account_title: Text;
|
||||
public channel_handle: Text;
|
||||
|
||||
constructor(data: RawNode) {
|
||||
super();
|
||||
this.account_name = new Text(data.accountName);
|
||||
this.account_photo = Thumbnail.fromResponse(data.accountPhoto);
|
||||
this.endpoint = new NavigationEndpoint(data.serviceEndpoint);
|
||||
this.manage_account_title = new Text(data.manageAccountTitle);
|
||||
this.channel_handle = new Text(data.channelHandle);
|
||||
}
|
||||
}
|
||||
18
deno/src/parser/classes/AddToPlaylist.ts
Normal file
18
deno/src/parser/classes/AddToPlaylist.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { type ObservedArray, YTNode } from '../helpers.ts';
|
||||
import { Parser, type RawNode } from '../index.ts';
|
||||
import Button from './Button.ts';
|
||||
import MenuTitle from './MenuTitle.ts';
|
||||
import PlaylistAddToOption from './PlaylistAddToOption.ts';
|
||||
|
||||
export default class AddToPlaylist extends YTNode {
|
||||
static type = 'AddToPlaylist';
|
||||
|
||||
public actions: ObservedArray<MenuTitle | Button>;
|
||||
public playlists: ObservedArray<PlaylistAddToOption>;
|
||||
|
||||
constructor(data: RawNode) {
|
||||
super();
|
||||
this.actions = Parser.parseArray(data.actions, [ MenuTitle, Button ]);
|
||||
this.playlists = Parser.parseArray(data.playlists, PlaylistAddToOption);
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user