fix(examples): Use BgUtils to generate pot [skip ci]

+ Improve readme.
This commit is contained in:
Luan
2024-08-26 18:42:14 -03:00
parent 367a6f7ec5
commit d89909a19a
5 changed files with 386 additions and 103 deletions

View File

@@ -1,61 +1,19 @@
# Browser Usage Example
YouTube.js works in the browser!
## Usage
# 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 in `examples/browser/proxy/deno.ts`.
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.
## Example
**NOTE**: Build the library before running the examples.
```ts
import { Innertube } from "youtubei.js/web.bundle.min";
Web application:
const yt = await Innertube.create({
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(),
});
```shell
cd examples/browser/web
npm install
npm run dev
```
After that, you can use the library as normal.
## Example
We've got a full example in `examples/browser/web` using vite.
Proxy:
```shell
deno run --allow-net --allow-read examples/browser/proxy/deno.ts
```

283
examples/browser/web/package-lock.json generated Normal file
View File

@@ -0,0 +1,283 @@
{
"name": "web",
"version": "0.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "web",
"version": "0.0.0",
"dependencies": {
"bgutils-js": "^1.1.0",
"shaka-player": "^4.3.8"
},
"devDependencies": {
"typescript": "^4.6.4",
"vite": "^3.0.0"
}
},
"node_modules/bgutils-js": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/bgutils-js/-/bgutils-js-1.1.0.tgz",
"integrity": "sha512-+v+MWO02VAfSKuuh9gpjxBTllFGkIiqzZT7ELwScOtm2UWk6MOm7lqkVtzctcjCrG0sgRZccfEbgaEWHozXLSQ==",
"funding": [
"https://github.com/sponsors/LuanRT"
]
},
"node_modules/eme-encryption-scheme-polyfill": {
"version": "2.1.1",
"license": "Apache-2.0"
},
"node_modules/esbuild": {
"version": "0.15.18",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"bin": {
"esbuild": "bin/esbuild"
},
"engines": {
"node": ">=12"
},
"optionalDependencies": {
"@esbuild/android-arm": "0.15.18",
"@esbuild/linux-loong64": "0.15.18",
"esbuild-android-64": "0.15.18",
"esbuild-android-arm64": "0.15.18",
"esbuild-darwin-64": "0.15.18",
"esbuild-darwin-arm64": "0.15.18",
"esbuild-freebsd-64": "0.15.18",
"esbuild-freebsd-arm64": "0.15.18",
"esbuild-linux-32": "0.15.18",
"esbuild-linux-64": "0.15.18",
"esbuild-linux-arm": "0.15.18",
"esbuild-linux-arm64": "0.15.18",
"esbuild-linux-mips64le": "0.15.18",
"esbuild-linux-ppc64le": "0.15.18",
"esbuild-linux-riscv64": "0.15.18",
"esbuild-linux-s390x": "0.15.18",
"esbuild-netbsd-64": "0.15.18",
"esbuild-openbsd-64": "0.15.18",
"esbuild-sunos-64": "0.15.18",
"esbuild-windows-32": "0.15.18",
"esbuild-windows-64": "0.15.18",
"esbuild-windows-arm64": "0.15.18"
}
},
"node_modules/esbuild-windows-64": {
"version": "0.15.18",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=12"
}
},
"node_modules/function-bind": {
"version": "1.1.2",
"dev": true,
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/hasown": {
"version": "2.0.0",
"dev": true,
"license": "MIT",
"dependencies": {
"function-bind": "^1.1.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/is-core-module": {
"version": "2.13.1",
"dev": true,
"license": "MIT",
"dependencies": {
"hasown": "^2.0.0"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/nanoid": {
"version": "3.3.7",
"dev": true,
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"license": "MIT",
"bin": {
"nanoid": "bin/nanoid.cjs"
},
"engines": {
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
}
},
"node_modules/path-parse": {
"version": "1.0.7",
"dev": true,
"license": "MIT"
},
"node_modules/picocolors": {
"version": "1.0.0",
"dev": true,
"license": "ISC"
},
"node_modules/postcss": {
"version": "8.4.33",
"dev": true,
"funding": [
{
"type": "opencollective",
"url": "https://opencollective.com/postcss/"
},
{
"type": "tidelift",
"url": "https://tidelift.com/funding/github/npm/postcss"
},
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"license": "MIT",
"dependencies": {
"nanoid": "^3.3.7",
"picocolors": "^1.0.0",
"source-map-js": "^1.0.2"
},
"engines": {
"node": "^10 || ^12 || >=14"
}
},
"node_modules/resolve": {
"version": "1.22.8",
"dev": true,
"license": "MIT",
"dependencies": {
"is-core-module": "^2.13.0",
"path-parse": "^1.0.7",
"supports-preserve-symlinks-flag": "^1.0.0"
},
"bin": {
"resolve": "bin/resolve"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/rollup": {
"version": "2.79.1",
"dev": true,
"license": "MIT",
"bin": {
"rollup": "dist/bin/rollup"
},
"engines": {
"node": ">=10.0.0"
},
"optionalDependencies": {
"fsevents": "~2.3.2"
}
},
"node_modules/shaka-player": {
"version": "4.7.7",
"license": "Apache-2.0",
"dependencies": {
"eme-encryption-scheme-polyfill": "^2.1.1"
}
},
"node_modules/source-map-js": {
"version": "1.0.2",
"dev": true,
"license": "BSD-3-Clause",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/supports-preserve-symlinks-flag": {
"version": "1.0.0",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/typescript": {
"version": "4.9.5",
"dev": true,
"license": "Apache-2.0",
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"engines": {
"node": ">=4.2.0"
}
},
"node_modules/vite": {
"version": "3.2.8",
"dev": true,
"license": "MIT",
"dependencies": {
"esbuild": "^0.15.9",
"postcss": "^8.4.18",
"resolve": "^1.22.1",
"rollup": "^2.79.1"
},
"bin": {
"vite": "bin/vite.js"
},
"engines": {
"node": "^14.18.0 || >=16.0.0"
},
"optionalDependencies": {
"fsevents": "~2.3.2"
},
"peerDependencies": {
"@types/node": ">= 14",
"less": "*",
"sass": "*",
"stylus": "*",
"sugarss": "*",
"terser": "^5.4.0"
},
"peerDependenciesMeta": {
"@types/node": {
"optional": true
},
"less": {
"optional": true
},
"sass": {
"optional": true
},
"stylus": {
"optional": true
},
"sugarss": {
"optional": true
},
"terser": {
"optional": true
}
}
}
}
}

View File

@@ -13,6 +13,7 @@
"vite": "^3.0.0"
},
"dependencies": {
"bgutils-js": "^1.1.0",
"shaka-player": "^4.3.8"
}
}
}

View File

@@ -1,8 +1,8 @@
import { Innertube, UniversalCache } from '../../../../bundle/browser';
import { Innertube, Proto, UniversalCache, Utils } from '../../../../bundle/browser';
import BG from 'bgutils-js';
// @ts-ignore - Shaka's TS support is not the best.
import shaka from 'shaka-player/dist/shaka-player.ui.js';
import "shaka-player/dist/controls.css";
const title = document.getElementById('title') as HTMLHeadingElement;
@@ -11,51 +11,17 @@ const metadata = document.getElementById('metadata') as HTMLDivElement;
const loader = document.getElementById('loader') as HTMLDivElement;
const form = document.querySelector('form') as HTMLFormElement;
async function main() {
const visitorData = Proto.encodeVisitorData(Utils.generateRandomString(11), Math.floor(Date.now() / 1000));
const poToken = await getPo(visitorData);
const yt = await Innertube.create({
po_token: poToken,
visitor_data: visitorData,
generate_session_locally: true,
fetch: async (input: RequestInfo | URL, init?: RequestInit) => {
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]));
if (input instanceof Request) {
// @ts-ignore
input.duplex = 'half';
}
// Copy over the request.
const request = new Request(
url,
input instanceof Request ? input : undefined,
);
headers.delete('user-agent');
return fetch(request, init ? {
...init,
headers
} : {
headers
});
},
cache: new UniversalCache(false),
fetch: fetchFn,
cache: new UniversalCache(true),
});
form.animate({ opacity: [0, 1] }, { duration: 300, easing: 'ease-in-out' });
@@ -258,6 +224,80 @@ async function main() {
});
}
async function getPo(identity: string): Promise<string | undefined> {
const requestKey = 'O43z0dpjhgX20SCx4KAo';
const bgConfig = {
fetch: fetchFn,
globalObj: window,
requestKey,
identity
};
const challenge = await BG.Challenge.create(bgConfig);
if (!challenge)
throw new Error('Could not get challenge');
if (challenge.script) {
const script = challenge.script.find((sc) => sc !== null);
if (script)
new Function(script)();
} else {
console.warn('Unable to load VM.');
}
const poToken = await BG.PoToken.generate({
program: challenge.challenge,
globalName: challenge.globalName,
bgConfig
});
return poToken;
}
function fetchFn(input: RequestInfo | URL, init?: RequestInit) {
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]));
if (input instanceof Request) {
// @ts-expect-error - x
input.duplex = 'half';
}
// Copy over the request.
const request = new Request(
url,
input instanceof Request ? input : undefined
);
headers.delete('user-agent');
return fetch(request, init ? {
...init,
headers
} : {
headers
});
}
function showUI(args: { hidePlayer?: boolean } = {
hidePlayer: true,
}) {

View File

@@ -14,13 +14,14 @@ import { existsSync, mkdirSync, createWriteStream } from 'fs';
if (!album.contents)
throw new Error('Album appears to be empty');
console.info(`Album "${album.header?.title.toString()}" by ${album.header?.author?.name}`, '\n');
console.info(`Album "${album.header?.title.toString()}"`, '\n');
for (const song of album.contents) {
const stream = await yt.download(song.id as string, {
type: 'audio', // audio, video or video+audio
quality: 'best', // best, bestefficiency, 144p, 240p, 480p, 720p and so on.
format: 'mp4' // media container format
format: 'mp4', // media container format,
client: 'YTMUSIC'
});
console.info(`Downloading ${song.title} (${song.id})`);
@@ -40,5 +41,5 @@ import { existsSync, mkdirSync, createWriteStream } from 'fs';
console.info(`${song.id} - Done!`, '\n');
}
console.info(`Downloaded ${album.header?.song_count}!`);
console.info(`Done!`);
})();