Compare commits

..

6 Commits

Author SHA1 Message Date
LuanRT
aefecd061e chore(release): v2.2.3 2022-09-23 03:19:54 -03:00
LuanRT
7485726f1e refactor: fix a few parsing inconsistencies 2022-09-23 03:06:21 -03:00
LuanRT
9e703abe3a chore(deps): bump jintr to 0.3.1 2022-09-22 18:44:16 -03:00
LuanRT
affbe84284 fix: include thirdParty prop for requests using TV_EMBEDDED (#198)
* dev: update `Context` interface

* dev: include `thirdParty` prop in requests using `TV_EMBEDDED`
2022-09-18 16:58:51 -03:00
Daniel Wykerd
fcbdae3e34 fix: browser example (#197) 2022-09-18 12:46:19 -03:00
LuanRT
059c858021 chore(docs): add a note about streaming data [skip ci] 2022-09-17 21:29:33 -03:00
11 changed files with 86 additions and 34 deletions

View File

@@ -42,7 +42,9 @@
[![Codefactor](https://www.codefactor.io/repository/github/luanrt/youtube.js/badge)][codefactor]
[![Downloads](https://img.shields.io/npm/dt/youtubei.js)][npm]
[![Say thanks](https://img.shields.io/badge/Say%20Thanks-!-1EAEDB.svg)][say-thanks]
<br>
[![Donate](https://img.shields.io/badge/donate-30363D?style=for-the-badge&logo=GitHub-Sponsors&logoColor=#white)][github-sponsors]
</div>
<!-- SPONSORS -->
@@ -527,6 +529,14 @@ Retrieves playlist contents.
### getStreamingData(video_id, options)
Returns deciphered streaming data.
**Note:**
It is recommended to retrieve streaming data from a `VideoInfo`/`TrackInfo` object instead if you want to select formats manually, example:
```ts
const info = await yt.getBasicInfo('somevideoid');
const url = info.streaming_data?.formats[0].decipher(yt.session.player);
console.info('Playback url:', url);
```
**Returns**: `Promise.<object>`
| Param | Type | Description |

View File

@@ -6,17 +6,52 @@ YouTube.js works in the browser!
To use YouTube.js in the browser you must proxy requests through your own server. You can see our simple reference implementation in Deno in `examples/browser/proxy/deno.ts`.
Once the proxy is set up you need to tell Innertube about it when instantiating it.
We'll use our own fetch implementation to proxy requests through our server. This is a simple example, but you can use any fetch implementation you want.
```ts
import { Innertube } from "youtubei.js/build/browser";
const yt = await Innertube.create({
browser_proxy: {
host: "localhost",
schema: 'http',
}
})
fetch: async (input, init) => {
// url
const url = typeof input === 'string'
? new URL(input)
: input instanceof URL
? input
: new URL(input.url);
// transform the url for use with our proxy
url.searchParams.set('__host', url.host);
url.host = 'localhost:8080';
url.protocol = 'http';
const headers = init?.headers
? new Headers(init.headers)
: input instanceof Request
? input.headers
: new Headers();
// now serialize the headers
url.searchParams.set('__headers', JSON.stringify([...headers]));
// copy over the request
const request = new Request(
url,
input instanceof Request ? input : undefined,
);
headers.delete('user-agent');
// fetch the url
return fetch(request, init ? {
...init,
headers
} : {
headers
});
},
cache: new UniversalCache(),
});
```
after that you can use the library as normal.

View File

@@ -18,7 +18,7 @@ const handler = async (request: Request): Promise<Response> => {
'Access-Control-Allow-Origin': request.headers.get('origin') || '*',
'Access-Control-Allow-Methods': '*',
'Access-Control-Allow-Headers':
'Origin, X-Requested-With, Content-Type, Accept, Authorization, x-goog-visitor-id, x-origin, x-youtube-client-version, Accept-Language, Range',
'Origin, X-Requested-With, Content-Type, Accept, Authorization, x-goog-visitor-id, x-origin, x-youtube-client-version, Accept-Language, Range, Referer',
'Access-Control-Max-Age': '86400',
'Access-Control-Allow-Credentials': 'true',
}),
@@ -45,7 +45,7 @@ const handler = async (request: Request): Promise<Response> => {
JSON.parse(url.searchParams.get('__headers') || '{}'),
);
copyHeader('range', request_headers, request.headers);
copyHeader('user-agent', request_headers, request.headers);
!request_headers.has('user-agent') && copyHeader('user-agent', request_headers, request.headers);
url.searchParams.delete('__headers');
// Make the request to YouTube
@@ -62,6 +62,8 @@ const handler = async (request: Request): Promise<Response> => {
copyHeader('content-length', headers, fetchRes.headers);
copyHeader('content-type', headers, fetchRes.headers);
copyHeader('content-disposition', headers, fetchRes.headers);
copyHeader('accept-ranges', headers, fetchRes.headers);
copyHeader('content-range', headers, fetchRes.headers);
// add cors headers
headers.set(

18
package-lock.json generated
View File

@@ -1,19 +1,19 @@
{
"name": "youtubei.js",
"version": "2.2.1",
"version": "2.2.3"
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "youtubei.js",
"version": "2.2.1",
"version": "2.2.3",
"funding": [
"https://github.com/sponsors/LuanRT"
],
"license": "MIT",
"dependencies": {
"@protobuf-ts/runtime": "^2.7.0",
"jintr": "^0.2.0",
"jintr": "^0.3.1",
"linkedom": "^0.14.12",
"undici": "^5.7.0"
},
@@ -3960,9 +3960,9 @@
}
},
"node_modules/jintr": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/jintr/-/jintr-0.2.0.tgz",
"integrity": "sha512-b1TmzzCnVivT6ftmRFtvT/pPu/t/pCDH/GA8yzXKfOGOVG6X/pmrRU9vdWgV1I/EJTKt/i/cvx6MfmGMjlhWMA==",
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/jintr/-/jintr-0.3.1.tgz",
"integrity": "sha512-AUcq8fKL4BE9jDx8TizZmJ9UOvk1CHKFW0nQcWaOaqk9tkLS9S10fNmusTWGEYTncn7U43nXrCbhYko/ylqrSg==",
"funding": [
"https://github.com/sponsors/LuanRT"
],
@@ -8180,9 +8180,9 @@
}
},
"jintr": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/jintr/-/jintr-0.2.0.tgz",
"integrity": "sha512-b1TmzzCnVivT6ftmRFtvT/pPu/t/pCDH/GA8yzXKfOGOVG6X/pmrRU9vdWgV1I/EJTKt/i/cvx6MfmGMjlhWMA==",
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/jintr/-/jintr-0.3.1.tgz",
"integrity": "sha512-AUcq8fKL4BE9jDx8TizZmJ9UOvk1CHKFW0nQcWaOaqk9tkLS9S10fNmusTWGEYTncn7U43nXrCbhYko/ylqrSg==",
"requires": {
"acorn": "^8.8.0"
}

View File

@@ -1,6 +1,6 @@
{
"name": "youtubei.js",
"version": "2.2.1",
"version": "2.2.3",
"description": "Full-featured wrapper around YouTube's private API.",
"main": "./dist/index.js",
"browser": "./bundle/browser.js",
@@ -39,7 +39,7 @@
"license": "MIT",
"dependencies": {
"@protobuf-ts/runtime": "^2.7.0",
"jintr": "^0.2.0",
"jintr": "^0.3.1",
"linkedom": "^0.14.12",
"undici": "^5.7.0"
},

View File

@@ -43,6 +43,9 @@ export interface Context {
user: {
lockedSafetyMode: false;
};
thirdParty?: {
embedUrl: string;
};
request: {
useSsl: true;
};

View File

@@ -6,8 +6,8 @@ class Card extends YTNode {
teaser;
content;
card_id: string;
feature: string;
card_id: string | null;
feature: string | null;
cue_ranges: {
start_card_active_ms: string;
@@ -18,10 +18,10 @@ class Card extends YTNode {
constructor(data: any) {
super();
this.teaser = Parser.parse(data.teaser);
this.content = Parser.parse(data.content);
this.card_id = data.cardId;
this.feature = data.feature;
this.teaser = Parser.parseItem(data.teaser);
this.content = Parser.parseItem(data.content);
this.card_id = data.cardId || null;
this.feature = data.feature || null;
this.cue_ranges = data.cueRanges.map((cr: any) => ({
start_card_active_ms: cr.startCardActiveMs,
@@ -32,4 +32,4 @@ class Card extends YTNode {
}
}
export default Card;
export default Card;

View File

@@ -8,7 +8,7 @@ class ProfileColumnStats extends YTNode {
constructor(data: any) {
super();
this.items = Parser.parse(data.items);
this.items = Parser.parseArray(data.items);
}
// XXX: alias for consistency
@@ -17,4 +17,4 @@ class ProfileColumnStats extends YTNode {
}
}
export default ProfileColumnStats;
export default ProfileColumnStats;

View File

@@ -1,6 +1,7 @@
import Parser from '../index';
import Text from './misc/Text';
import Author from './misc/Author';
import Menu from './menus/Menu';
import Thumbnail from './misc/Thumbnail';
import NavigationEndpoint from './NavigationEndpoint';
import { timeToSeconds } from '../../utils/Utils';
@@ -53,8 +54,8 @@ class Video extends YTNode {
})) || [];
this.thumbnails = Thumbnail.fromResponse(data.thumbnail);
this.thumbnail_overlays = Parser.parse(data.thumbnailOverlays);
this.rich_thumbnail = data.richThumbnail && Parser.parse(data.richThumbnail);
this.thumbnail_overlays = Parser.parseArray(data.thumbnailOverlays);
this.rich_thumbnail = data.richThumbnail ? Parser.parse(data.richThumbnail) : null;
this.author = new Author(data.ownerText, data.ownerBadges, data.channelThumbnailSupportedRenderers?.channelThumbnailWithLinkRenderer?.thumbnail);
this.endpoint = new NavigationEndpoint(data.navigationEndpoint);
this.published = new Text(data.publishedTimeText);
@@ -73,7 +74,7 @@ class Video extends YTNode {
this.show_action_menu = data.showActionMenu;
this.is_watched = data.isWatched || false;
this.menu = Parser.parse(data.menu);
this.menu = Parser.parseItem<Menu>(data.menu, Menu);
}
get description(): string {

View File

@@ -24,7 +24,7 @@ class Search extends Feed {
super(actions, data, already_parsed);
const contents =
this.page.contents?.item().as(TwoColumnSearchResults).primary_contents.item().as(SectionList).contents.array() ||
this.page.contents?.item().as(TwoColumnSearchResults).primary_contents?.item().as(SectionList).contents.array() ||
this.page.on_response_received_commands?.[0].contents;
const secondary_contents_maybe = this.page.contents?.item().key('secondary_contents');
@@ -59,7 +59,7 @@ class Search extends Feed {
if (typeof card === 'string') {
target_card = this.refinement_cards.cards.get({ query: card });
if (!target_card)
throw new InnertubeError('Refinement card not found!', { available_cards: this.refinement_card_queries });
throw new InnertubeError(`Refinement card "${card}" not found`, { available_cards: this.refinement_card_queries });
} else if (card.type === 'SearchRefinementCard') {
target_card = card;
} else {

View File

@@ -147,6 +147,7 @@ export default class HTTPClient {
ctx.client.clientVersion = Constants.CLIENTS.TV_EMBEDDED.VERSION;
ctx.client.clientName = Constants.CLIENTS.TV_EMBEDDED.NAME;
ctx.client.clientScreen = 'EMBED';
ctx.thirdParty = { embedUrl: Constants.URLS.YT_BASE };
break;
default:
break;