mirror of
https://github.com/LuanRT/YouTube.js.git
synced 2026-06-16 19:12:24 +00:00
Compare commits
16 Commits
v12.0.0-de
...
deno
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f21db117e8 | ||
|
|
cc09231db2 | ||
|
|
d46768dedb | ||
|
|
047157ff25 | ||
|
|
620067b773 | ||
|
|
a3aba1fd06 | ||
|
|
fc0ba30d1e | ||
|
|
ddc35db72e | ||
|
|
e94b715e7a | ||
|
|
06ec3075ea | ||
|
|
06bf822ada | ||
|
|
ad1a8818c0 | ||
|
|
adfb76d629 | ||
|
|
c52ed6ffde | ||
|
|
429b42695b | ||
|
|
12dbf35834 |
@@ -37,9 +37,12 @@ yarn add youtubei.js@latest
|
||||
|
||||
# Git (edge version)
|
||||
npm install github:LuanRT/YouTube.js
|
||||
|
||||
# Deno
|
||||
deno add npm:youtubei.js@latest
|
||||
```
|
||||
|
||||
Deno:
|
||||
Deno (deprecated):
|
||||
```ts
|
||||
import { Innertube } from 'https://deno.land/x/youtubei/deno.ts';
|
||||
```
|
||||
@@ -47,7 +50,6 @@ import { Innertube } from 'https://deno.land/x/youtubei/deno.ts';
|
||||
## Basic Usage
|
||||
|
||||
```ts
|
||||
// const { Innertube } = require('youtubei.js');
|
||||
import { Innertube } from 'youtubei.js';
|
||||
const innertube = await Innertube.create(/* options */);
|
||||
```
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "youtubei.js",
|
||||
"version": "12.0.0",
|
||||
"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",
|
||||
@@ -28,16 +28,17 @@
|
||||
},
|
||||
"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"
|
||||
@@ -54,10 +55,6 @@
|
||||
"types": "./dist/src/platform/lib.d.ts",
|
||||
"default": "./bundle/browser.js"
|
||||
},
|
||||
"./web.bundle.min": {
|
||||
"types": "./dist/src/platform/lib.d.ts",
|
||||
"default": "./bundle/browser.min.js"
|
||||
},
|
||||
"./cf-worker": {
|
||||
"types": "./dist/src/platform/lib.d.ts",
|
||||
"default": "./dist/src/platform/cf-worker.js"
|
||||
@@ -75,20 +72,19 @@
|
||||
"Absidue (https://github.com/absidue)"
|
||||
],
|
||||
"scripts": {
|
||||
"test": "jest --verbose",
|
||||
"test": "vitest run --reporter verbose",
|
||||
"lint": "eslint ./src",
|
||||
"lint:fix": "eslint --fix ./src",
|
||||
"clean": "rimraf ./dist ./bundle/browser.js ./bundle/browser.min.js ./bundle/node.cjs ./bundle/cf-worker.js ./bundle/react-native.js ./deno",
|
||||
"build": "npm run clean && npm run build:parser-map && npm run build:esm && npm run bundle:node && npm run bundle:browser && npm run bundle:browser:prod && npm run bundle:cf-worker && npm run bundle:react-native",
|
||||
"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:node": "esbuild ./dist/src/platform/node.js --bundle --target=node16 --keep-names --format=cjs --platform=node --outfile=./bundle/node.cjs --external:undici --external:linkedom --external:tslib --banner:js=\"/* eslint-disable */\"",
|
||||
"bundle:browser": "esbuild ./dist/src/platform/web.js --banner:js=\"/* eslint-disable */\" --bundle --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 --target=es2020 --keep-names --format=esm --platform=neutral --define:global=globalThis --conditions=module --outfile=./bundle/react-native.js",
|
||||
"bundle:browser:prod": "npm run bundle:browser -- --outfile=./bundle/browser.min.js --minify",
|
||||
"bundle:cf-worker": "esbuild ./dist/src/platform/cf-worker.js --banner:js=\"/* eslint-disable */\" --bundle --target=es2020 --keep-names --format=esm --define:global=globalThis --conditions=module --outfile=./bundle/cf-worker.js --platform=node",
|
||||
"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": "tspc --watch"
|
||||
@@ -107,35 +103,27 @@
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@bufbuild/protobuf": "^2.0.0",
|
||||
"jintr": "^3.1.0",
|
||||
"tslib": "^2.5.0",
|
||||
"undici": "^5.19.1"
|
||||
},
|
||||
"overrides": {
|
||||
"typescript": "^5.0.0"
|
||||
"meriyah": "^6.1.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.9.0",
|
||||
"@eslint/js": "^9.37.0",
|
||||
"@types/estree": "^1.0.6",
|
||||
"@types/glob": "^8.1.0",
|
||||
"@types/jest": "^28.1.7",
|
||||
"@types/node": "^17.0.45",
|
||||
"cpy-cli": "^4.2.0",
|
||||
"esbuild": "^0.14.49",
|
||||
"eslint": "^9.9.0",
|
||||
"glob": "^8.0.3",
|
||||
"globals": "^15.9.0",
|
||||
"jest": "^29.7.0",
|
||||
"@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",
|
||||
"rimraf": "^6.0.1",
|
||||
"ts-jest": "^29.1.4",
|
||||
"ts-patch": "^3.0.2",
|
||||
"ts-proto": "^2.2.0",
|
||||
"ts-transformer-inline-file": "^0.2.0",
|
||||
"typedoc": "^0.26.7",
|
||||
"typedoc-plugin-markdown": "^4.2.7",
|
||||
"typescript": "^5.0.0",
|
||||
"typescript-eslint": "^8.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"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-ts_proto. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-ts_proto v2.2.0
|
||||
// protoc v5.28.0
|
||||
// protoc-gen-ts_proto v2.7.7
|
||||
// protoc v6.33.5
|
||||
// source: misc/common.proto
|
||||
|
||||
/* eslint-disable */
|
||||
@@ -35,6 +35,10 @@ export interface KeyValuePair {
|
||||
value?: string | undefined;
|
||||
}
|
||||
|
||||
export interface FormatXTags {
|
||||
xtags: KeyValuePair[];
|
||||
}
|
||||
|
||||
function createBaseHttpHeader(): HttpHeader {
|
||||
return { name: undefined, value: undefined };
|
||||
}
|
||||
@@ -52,25 +56,27 @@ export const HttpHeader: MessageFns<HttpHeader> = {
|
||||
|
||||
decode(input: BinaryReader | Uint8Array, length?: number): HttpHeader {
|
||||
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
|
||||
let end = length === undefined ? reader.len : reader.pos + length;
|
||||
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:
|
||||
case 1: {
|
||||
if (tag !== 10) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.name = reader.string();
|
||||
continue;
|
||||
case 2:
|
||||
}
|
||||
case 2: {
|
||||
if (tag !== 18) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.value = reader.string();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if ((tag & 7) === 4 || tag === 0) {
|
||||
break;
|
||||
@@ -101,32 +107,35 @@ export const FormatId: MessageFns<FormatId> = {
|
||||
|
||||
decode(input: BinaryReader | Uint8Array, length?: number): FormatId {
|
||||
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
|
||||
let end = length === undefined ? reader.len : reader.pos + length;
|
||||
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:
|
||||
case 1: {
|
||||
if (tag !== 8) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.itag = reader.int32();
|
||||
continue;
|
||||
case 2:
|
||||
}
|
||||
case 2: {
|
||||
if (tag !== 16) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.lastModified = longToNumber(reader.uint64());
|
||||
continue;
|
||||
case 3:
|
||||
}
|
||||
case 3: {
|
||||
if (tag !== 26) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.xtags = reader.string();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if ((tag & 7) === 4 || tag === 0) {
|
||||
break;
|
||||
@@ -154,25 +163,27 @@ export const InitRange: MessageFns<InitRange> = {
|
||||
|
||||
decode(input: BinaryReader | Uint8Array, length?: number): InitRange {
|
||||
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
|
||||
let end = length === undefined ? reader.len : reader.pos + length;
|
||||
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:
|
||||
case 1: {
|
||||
if (tag !== 8) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.start = reader.int32();
|
||||
continue;
|
||||
case 2:
|
||||
}
|
||||
case 2: {
|
||||
if (tag !== 16) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.end = reader.int32();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if ((tag & 7) === 4 || tag === 0) {
|
||||
break;
|
||||
@@ -200,25 +211,27 @@ export const IndexRange: MessageFns<IndexRange> = {
|
||||
|
||||
decode(input: BinaryReader | Uint8Array, length?: number): IndexRange {
|
||||
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
|
||||
let end = length === undefined ? reader.len : reader.pos + length;
|
||||
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:
|
||||
case 1: {
|
||||
if (tag !== 8) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.start = reader.int32();
|
||||
continue;
|
||||
case 2:
|
||||
}
|
||||
case 2: {
|
||||
if (tag !== 16) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.end = reader.int32();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if ((tag & 7) === 4 || tag === 0) {
|
||||
break;
|
||||
@@ -246,25 +259,64 @@ export const KeyValuePair: MessageFns<KeyValuePair> = {
|
||||
|
||||
decode(input: BinaryReader | Uint8Array, length?: number): KeyValuePair {
|
||||
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
|
||||
let end = length === undefined ? reader.len : reader.pos + length;
|
||||
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:
|
||||
case 1: {
|
||||
if (tag !== 10) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.key = reader.string();
|
||||
continue;
|
||||
case 2:
|
||||
}
|
||||
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;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-ts_proto. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-ts_proto v2.2.0
|
||||
// protoc v5.28.0
|
||||
// protoc-gen-ts_proto v2.7.7
|
||||
// protoc v6.33.5
|
||||
// source: youtube/api/pfiinnertube/attestation_response_data.proto
|
||||
|
||||
/* eslint-disable */
|
||||
@@ -59,53 +59,59 @@ export const AttestationResponseData: MessageFns<AttestationResponseData> = {
|
||||
|
||||
decode(input: BinaryReader | Uint8Array, length?: number): AttestationResponseData {
|
||||
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
|
||||
let end = length === undefined ? reader.len : reader.pos + length;
|
||||
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:
|
||||
case 1: {
|
||||
if (tag !== 10) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.challenge = reader.string();
|
||||
continue;
|
||||
case 2:
|
||||
}
|
||||
case 2: {
|
||||
if (tag !== 18) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.webResponse = reader.string();
|
||||
continue;
|
||||
case 3:
|
||||
}
|
||||
case 3: {
|
||||
if (tag !== 26) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.androidResponse = reader.string();
|
||||
continue;
|
||||
case 4:
|
||||
}
|
||||
case 4: {
|
||||
if (tag !== 34) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.iosResponse = reader.bytes();
|
||||
continue;
|
||||
case 5:
|
||||
}
|
||||
case 5: {
|
||||
if (tag !== 40) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.error = reader.int32();
|
||||
continue;
|
||||
case 6:
|
||||
}
|
||||
case 6: {
|
||||
if (tag !== 50) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.adblockReporting = AttestationResponseData_AdblockReporting.decode(reader, reader.uint32());
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if ((tag & 7) === 4 || tag === 0) {
|
||||
break;
|
||||
@@ -133,25 +139,27 @@ export const AttestationResponseData_AdblockReporting: MessageFns<AttestationRes
|
||||
|
||||
decode(input: BinaryReader | Uint8Array, length?: number): AttestationResponseData_AdblockReporting {
|
||||
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
|
||||
let end = length === undefined ? reader.len : reader.pos + length;
|
||||
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:
|
||||
case 1: {
|
||||
if (tag !== 8) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.reportingStatus = longToNumber(reader.uint64());
|
||||
continue;
|
||||
case 2:
|
||||
}
|
||||
case 2: {
|
||||
if (tag !== 16) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.broadSpectrumDetectionResult = longToNumber(reader.uint64());
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if ((tag & 7) === 4 || tag === 0) {
|
||||
break;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-ts_proto. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-ts_proto v2.2.0
|
||||
// protoc v5.28.0
|
||||
// protoc-gen-ts_proto v2.7.7
|
||||
// protoc v6.33.5
|
||||
// source: youtube/api/pfiinnertube/capability_info.proto
|
||||
|
||||
/* eslint-disable */
|
||||
@@ -45,39 +45,43 @@ export const CapabilityInfo: MessageFns<CapabilityInfo> = {
|
||||
|
||||
decode(input: BinaryReader | Uint8Array, length?: number): CapabilityInfo {
|
||||
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
|
||||
let end = length === undefined ? reader.len : reader.pos + length;
|
||||
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:
|
||||
case 1: {
|
||||
if (tag !== 10) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.profile = reader.string();
|
||||
continue;
|
||||
case 2:
|
||||
}
|
||||
case 2: {
|
||||
if (tag !== 18) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.supportedCapabilities.push(InnerTubeCapability.decode(reader, reader.uint32()));
|
||||
continue;
|
||||
case 3:
|
||||
}
|
||||
case 3: {
|
||||
if (tag !== 26) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.disabledCapabilities.push(InnerTubeCapability.decode(reader, reader.uint32()));
|
||||
continue;
|
||||
case 5:
|
||||
}
|
||||
case 5: {
|
||||
if (tag !== 42) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.snapshot = reader.string();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if ((tag & 7) === 4 || tag === 0) {
|
||||
break;
|
||||
@@ -108,32 +112,35 @@ export const InnerTubeCapability: MessageFns<InnerTubeCapability> = {
|
||||
|
||||
decode(input: BinaryReader | Uint8Array, length?: number): InnerTubeCapability {
|
||||
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
|
||||
let end = length === undefined ? reader.len : reader.pos + length;
|
||||
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:
|
||||
case 1: {
|
||||
if (tag !== 8) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.capability = reader.uint32();
|
||||
continue;
|
||||
case 2:
|
||||
}
|
||||
case 2: {
|
||||
if (tag !== 16) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.features = reader.uint32();
|
||||
continue;
|
||||
case 6:
|
||||
}
|
||||
case 6: {
|
||||
if (tag !== 50) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.experimentFlags = reader.string();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if ((tag & 7) === 4 || tag === 0) {
|
||||
break;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-ts_proto. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-ts_proto v2.2.0
|
||||
// protoc v5.28.0
|
||||
// protoc-gen-ts_proto v2.7.7
|
||||
// protoc v6.33.5
|
||||
// source: youtube/api/pfiinnertube/get_watch_request.proto
|
||||
|
||||
/* eslint-disable */
|
||||
@@ -43,39 +43,43 @@ export const GetWatchRequest: MessageFns<GetWatchRequest> = {
|
||||
|
||||
decode(input: BinaryReader | Uint8Array, length?: number): GetWatchRequest {
|
||||
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
|
||||
let end = length === undefined ? reader.len : reader.pos + length;
|
||||
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:
|
||||
case 1: {
|
||||
if (tag !== 10) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.context = InnerTubeContext.decode(reader, reader.uint32());
|
||||
continue;
|
||||
case 2:
|
||||
}
|
||||
case 2: {
|
||||
if (tag !== 18) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.playerRequest = PlayerRequest.decode(reader, reader.uint32());
|
||||
continue;
|
||||
case 3:
|
||||
}
|
||||
case 3: {
|
||||
if (tag !== 26) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.watchNextRequest = WatchNextRequest.decode(reader, reader.uint32());
|
||||
continue;
|
||||
case 4:
|
||||
}
|
||||
case 4: {
|
||||
if (tag !== 34) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.reelItemWatchRequest = ReelItemWatchRequest.decode(reader, reader.uint32());
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if ((tag & 7) === 4 || tag === 0) {
|
||||
break;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-ts_proto. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-ts_proto v2.2.0
|
||||
// protoc v5.28.0
|
||||
// protoc-gen-ts_proto v2.7.7
|
||||
// protoc v6.33.5
|
||||
// source: youtube/api/pfiinnertube/innertube_context.proto
|
||||
|
||||
/* eslint-disable */
|
||||
@@ -107,88 +107,99 @@ export const InnerTubeContext: MessageFns<InnerTubeContext> = {
|
||||
|
||||
decode(input: BinaryReader | Uint8Array, length?: number): InnerTubeContext {
|
||||
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
|
||||
let end = length === undefined ? reader.len : reader.pos + length;
|
||||
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:
|
||||
case 1: {
|
||||
if (tag !== 10) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.client = ClientInfo.decode(reader, reader.uint32());
|
||||
continue;
|
||||
case 3:
|
||||
}
|
||||
case 3: {
|
||||
if (tag !== 26) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.user = UserInfo.decode(reader, reader.uint32());
|
||||
continue;
|
||||
case 4:
|
||||
}
|
||||
case 4: {
|
||||
if (tag !== 34) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.capabilities = CapabilityInfo.decode(reader, reader.uint32());
|
||||
continue;
|
||||
case 5:
|
||||
}
|
||||
case 5: {
|
||||
if (tag !== 42) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.request = RequestInfo.decode(reader, reader.uint32());
|
||||
continue;
|
||||
case 6:
|
||||
}
|
||||
case 6: {
|
||||
if (tag !== 50) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.clickTracking = InnerTubeContext_ClickTrackingInfo.decode(reader, reader.uint32());
|
||||
continue;
|
||||
case 7:
|
||||
}
|
||||
case 7: {
|
||||
if (tag !== 58) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.thirdParty = ThirdPartyInfo.decode(reader, reader.uint32());
|
||||
continue;
|
||||
case 8:
|
||||
}
|
||||
case 8: {
|
||||
if (tag !== 66) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.remoteClient = ClientInfo.decode(reader, reader.uint32());
|
||||
continue;
|
||||
case 9:
|
||||
}
|
||||
case 9: {
|
||||
if (tag !== 74) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.adSignalsInfo = InnerTubeContext_AdSignalsInfo.decode(reader, reader.uint32());
|
||||
continue;
|
||||
case 10:
|
||||
}
|
||||
case 10: {
|
||||
if (tag !== 82) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.experimentalData = InnerTubeContext_ExperimentalData.decode(reader, reader.uint32());
|
||||
continue;
|
||||
case 11:
|
||||
}
|
||||
case 11: {
|
||||
if (tag !== 90) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.clientScreenNonce = reader.string();
|
||||
continue;
|
||||
case 12:
|
||||
}
|
||||
case 12: {
|
||||
if (tag !== 98) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.activePlayers.push(InnerTubeContext_ActivePlayerInfo.decode(reader, reader.uint32()));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if ((tag & 7) === 4 || tag === 0) {
|
||||
break;
|
||||
@@ -213,18 +224,19 @@ export const InnerTubeContext_ExperimentalData: MessageFns<InnerTubeContext_Expe
|
||||
|
||||
decode(input: BinaryReader | Uint8Array, length?: number): InnerTubeContext_ExperimentalData {
|
||||
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
|
||||
let end = length === undefined ? reader.len : reader.pos + length;
|
||||
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:
|
||||
case 1: {
|
||||
if (tag !== 10) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.params.push(KeyValuePair.decode(reader, reader.uint32()));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if ((tag & 7) === 4 || tag === 0) {
|
||||
break;
|
||||
@@ -249,18 +261,19 @@ export const InnerTubeContext_ActivePlayerInfo: MessageFns<InnerTubeContext_Acti
|
||||
|
||||
decode(input: BinaryReader | Uint8Array, length?: number): InnerTubeContext_ActivePlayerInfo {
|
||||
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
|
||||
let end = length === undefined ? reader.len : reader.pos + length;
|
||||
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:
|
||||
case 1: {
|
||||
if (tag !== 10) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.playerContextParams = reader.bytes();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if ((tag & 7) === 4 || tag === 0) {
|
||||
break;
|
||||
@@ -285,18 +298,19 @@ export const InnerTubeContext_ClickTrackingInfo: MessageFns<InnerTubeContext_Cli
|
||||
|
||||
decode(input: BinaryReader | Uint8Array, length?: number): InnerTubeContext_ClickTrackingInfo {
|
||||
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
|
||||
let end = length === undefined ? reader.len : reader.pos + length;
|
||||
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:
|
||||
case 2: {
|
||||
if (tag !== 18) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.clickTrackingParams = reader.bytes();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if ((tag & 7) === 4 || tag === 0) {
|
||||
break;
|
||||
@@ -347,60 +361,67 @@ export const InnerTubeContext_AdSignalsInfo: MessageFns<InnerTubeContext_AdSigna
|
||||
|
||||
decode(input: BinaryReader | Uint8Array, length?: number): InnerTubeContext_AdSignalsInfo {
|
||||
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
|
||||
let end = length === undefined ? reader.len : reader.pos + length;
|
||||
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:
|
||||
case 1: {
|
||||
if (tag !== 10) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.params.push(KeyValuePair.decode(reader, reader.uint32()));
|
||||
continue;
|
||||
case 2:
|
||||
}
|
||||
case 2: {
|
||||
if (tag !== 18) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.bid = reader.string();
|
||||
continue;
|
||||
case 3:
|
||||
}
|
||||
case 3: {
|
||||
if (tag !== 26) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.mutsuId = reader.string();
|
||||
continue;
|
||||
case 4:
|
||||
}
|
||||
case 4: {
|
||||
if (tag !== 34) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.consentBumpState = reader.string();
|
||||
continue;
|
||||
case 7:
|
||||
}
|
||||
case 7: {
|
||||
if (tag !== 58) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.advertisingId = reader.string();
|
||||
continue;
|
||||
case 9:
|
||||
}
|
||||
case 9: {
|
||||
if (tag !== 72) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.limitAdTracking = reader.bool();
|
||||
continue;
|
||||
case 10:
|
||||
}
|
||||
case 10: {
|
||||
if (tag !== 82) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.attributionOsSupportedVersion = reader.string();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if ((tag & 7) === 4 || tag === 0) {
|
||||
break;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-ts_proto. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-ts_proto v2.2.0
|
||||
// protoc v5.28.0
|
||||
// protoc-gen-ts_proto v2.7.7
|
||||
// protoc v6.33.5
|
||||
// source: youtube/api/pfiinnertube/metadata_update_request.proto
|
||||
|
||||
/* eslint-disable */
|
||||
@@ -145,95 +145,107 @@ export const MetadataUpdateRequest: MessageFns<MetadataUpdateRequest> = {
|
||||
|
||||
decode(input: BinaryReader | Uint8Array, length?: number): MetadataUpdateRequest {
|
||||
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
|
||||
let end = length === undefined ? reader.len : reader.pos + length;
|
||||
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:
|
||||
case 1: {
|
||||
if (tag !== 10) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.context = InnerTubeContext.decode(reader, reader.uint32());
|
||||
continue;
|
||||
case 2:
|
||||
}
|
||||
case 2: {
|
||||
if (tag !== 18) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.encryptedVideoId = reader.string();
|
||||
continue;
|
||||
case 3:
|
||||
}
|
||||
case 3: {
|
||||
if (tag !== 26) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.title = MetadataUpdateRequest_MdeTitleUpdateRequest.decode(reader, reader.uint32());
|
||||
continue;
|
||||
case 4:
|
||||
}
|
||||
case 4: {
|
||||
if (tag !== 34) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.description = MetadataUpdateRequest_MdeDescriptionUpdateRequest.decode(reader, reader.uint32());
|
||||
continue;
|
||||
case 5:
|
||||
}
|
||||
case 5: {
|
||||
if (tag !== 42) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.privacy = MetadataUpdateRequest_MdePrivacyUpdateRequest.decode(reader, reader.uint32());
|
||||
continue;
|
||||
case 6:
|
||||
}
|
||||
case 6: {
|
||||
if (tag !== 50) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.tags = MetadataUpdateRequest_MdeTagsUpdateRequest.decode(reader, reader.uint32());
|
||||
continue;
|
||||
case 7:
|
||||
}
|
||||
case 7: {
|
||||
if (tag !== 58) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.category = MetadataUpdateRequest_MdeCategoryUpdateRequest.decode(reader, reader.uint32());
|
||||
continue;
|
||||
case 8:
|
||||
}
|
||||
case 8: {
|
||||
if (tag !== 66) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.license = MetadataUpdateRequest_MdeLicenseUpdateRequest.decode(reader, reader.uint32());
|
||||
continue;
|
||||
case 11:
|
||||
}
|
||||
case 11: {
|
||||
if (tag !== 90) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.ageRestriction = MetadataUpdateRequest_MdeAgeRestrictionUpdateRequest.decode(reader, reader.uint32());
|
||||
continue;
|
||||
case 20:
|
||||
}
|
||||
case 20: {
|
||||
if (tag !== 162) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.videoStill = MetadataUpdateRequest_MdeVideoStillRequestParams.decode(reader, reader.uint32());
|
||||
continue;
|
||||
case 68:
|
||||
}
|
||||
case 68: {
|
||||
if (tag !== 546) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.madeForKids = MetadataUpdateRequest_MdeMadeForKidsUpdateRequestParams.decode(reader, reader.uint32());
|
||||
continue;
|
||||
case 69:
|
||||
}
|
||||
case 69: {
|
||||
if (tag !== 554) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.racy = MetadataUpdateRequest_MdeRacyRequestParams.decode(reader, reader.uint32());
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if ((tag & 7) === 4 || tag === 0) {
|
||||
break;
|
||||
@@ -261,18 +273,19 @@ export const MetadataUpdateRequest_MdeTitleUpdateRequest: MessageFns<MetadataUpd
|
||||
|
||||
decode(input: BinaryReader | Uint8Array, length?: number): MetadataUpdateRequest_MdeTitleUpdateRequest {
|
||||
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
|
||||
let end = length === undefined ? reader.len : reader.pos + length;
|
||||
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:
|
||||
case 1: {
|
||||
if (tag !== 10) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.newTitle = reader.string();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if ((tag & 7) === 4 || tag === 0) {
|
||||
break;
|
||||
@@ -302,18 +315,19 @@ export const MetadataUpdateRequest_MdeDescriptionUpdateRequest: MessageFns<
|
||||
|
||||
decode(input: BinaryReader | Uint8Array, length?: number): MetadataUpdateRequest_MdeDescriptionUpdateRequest {
|
||||
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
|
||||
let end = length === undefined ? reader.len : reader.pos + length;
|
||||
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:
|
||||
case 1: {
|
||||
if (tag !== 10) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.newDescription = reader.string();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if ((tag & 7) === 4 || tag === 0) {
|
||||
break;
|
||||
@@ -345,25 +359,27 @@ export const MetadataUpdateRequest_MdePrivacyUpdateRequest: MessageFns<MetadataU
|
||||
|
||||
decode(input: BinaryReader | Uint8Array, length?: number): MetadataUpdateRequest_MdePrivacyUpdateRequest {
|
||||
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
|
||||
let end = length === undefined ? reader.len : reader.pos + length;
|
||||
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:
|
||||
case 1: {
|
||||
if (tag !== 8) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.newPrivacy = reader.int32();
|
||||
continue;
|
||||
case 2:
|
||||
}
|
||||
case 2: {
|
||||
if (tag !== 16) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.clearPrivacyDraft = reader.bool();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if ((tag & 7) === 4 || tag === 0) {
|
||||
break;
|
||||
@@ -388,18 +404,19 @@ export const MetadataUpdateRequest_MdeTagsUpdateRequest: MessageFns<MetadataUpda
|
||||
|
||||
decode(input: BinaryReader | Uint8Array, length?: number): MetadataUpdateRequest_MdeTagsUpdateRequest {
|
||||
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
|
||||
let end = length === undefined ? reader.len : reader.pos + length;
|
||||
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:
|
||||
case 1: {
|
||||
if (tag !== 10) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.newTags.push(reader.string());
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if ((tag & 7) === 4 || tag === 0) {
|
||||
break;
|
||||
@@ -429,18 +446,19 @@ export const MetadataUpdateRequest_MdeCategoryUpdateRequest: MessageFns<
|
||||
|
||||
decode(input: BinaryReader | Uint8Array, length?: number): MetadataUpdateRequest_MdeCategoryUpdateRequest {
|
||||
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
|
||||
let end = length === undefined ? reader.len : reader.pos + length;
|
||||
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:
|
||||
case 1: {
|
||||
if (tag !== 8) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.newCategoryId = reader.int32();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if ((tag & 7) === 4 || tag === 0) {
|
||||
break;
|
||||
@@ -469,18 +487,19 @@ export const MetadataUpdateRequest_MdeLicenseUpdateRequest: MessageFns<MetadataU
|
||||
|
||||
decode(input: BinaryReader | Uint8Array, length?: number): MetadataUpdateRequest_MdeLicenseUpdateRequest {
|
||||
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
|
||||
let end = length === undefined ? reader.len : reader.pos + length;
|
||||
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:
|
||||
case 1: {
|
||||
if (tag !== 10) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.newLicenseId = reader.string();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if ((tag & 7) === 4 || tag === 0) {
|
||||
break;
|
||||
@@ -513,25 +532,27 @@ export const MetadataUpdateRequest_MdeMadeForKidsUpdateRequestParams: MessageFns
|
||||
|
||||
decode(input: BinaryReader | Uint8Array, length?: number): MetadataUpdateRequest_MdeMadeForKidsUpdateRequestParams {
|
||||
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
|
||||
let end = length === undefined ? reader.len : reader.pos + length;
|
||||
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:
|
||||
case 1: {
|
||||
if (tag !== 8) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.operation = reader.int32();
|
||||
continue;
|
||||
case 2:
|
||||
}
|
||||
case 2: {
|
||||
if (tag !== 16) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.newMfk = reader.int32();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if ((tag & 7) === 4 || tag === 0) {
|
||||
break;
|
||||
@@ -559,25 +580,27 @@ export const MetadataUpdateRequest_MdeRacyRequestParams: MessageFns<MetadataUpda
|
||||
|
||||
decode(input: BinaryReader | Uint8Array, length?: number): MetadataUpdateRequest_MdeRacyRequestParams {
|
||||
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
|
||||
let end = length === undefined ? reader.len : reader.pos + length;
|
||||
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:
|
||||
case 1: {
|
||||
if (tag !== 8) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.operation = reader.int32();
|
||||
continue;
|
||||
case 2:
|
||||
}
|
||||
case 2: {
|
||||
if (tag !== 16) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.newRacy = reader.int32();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if ((tag & 7) === 4 || tag === 0) {
|
||||
break;
|
||||
@@ -607,18 +630,19 @@ export const MetadataUpdateRequest_MdeAgeRestrictionUpdateRequest: MessageFns<
|
||||
|
||||
decode(input: BinaryReader | Uint8Array, length?: number): MetadataUpdateRequest_MdeAgeRestrictionUpdateRequest {
|
||||
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
|
||||
let end = length === undefined ? reader.len : reader.pos + length;
|
||||
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:
|
||||
case 1: {
|
||||
if (tag !== 8) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.newIsAgeRestricted = reader.bool();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if ((tag & 7) === 4 || tag === 0) {
|
||||
break;
|
||||
@@ -667,26 +691,28 @@ export const MetadataUpdateRequest_MdeVideoStillRequestParams: MessageFns<
|
||||
|
||||
decode(input: BinaryReader | Uint8Array, length?: number): MetadataUpdateRequest_MdeVideoStillRequestParams {
|
||||
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
|
||||
let end = length === undefined ? reader.len : reader.pos + length;
|
||||
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:
|
||||
case 1: {
|
||||
if (tag !== 8) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.operation = reader.int32();
|
||||
continue;
|
||||
case 2:
|
||||
}
|
||||
case 2: {
|
||||
if (tag !== 16) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.newStillId = reader.int32();
|
||||
continue;
|
||||
case 3:
|
||||
}
|
||||
case 3: {
|
||||
if (tag !== 26) {
|
||||
break;
|
||||
}
|
||||
@@ -696,7 +722,8 @@ export const MetadataUpdateRequest_MdeVideoStillRequestParams: MessageFns<
|
||||
reader.uint32(),
|
||||
);
|
||||
continue;
|
||||
case 4:
|
||||
}
|
||||
case 4: {
|
||||
if (tag !== 34) {
|
||||
break;
|
||||
}
|
||||
@@ -706,7 +733,8 @@ export const MetadataUpdateRequest_MdeVideoStillRequestParams: MessageFns<
|
||||
reader.uint32(),
|
||||
);
|
||||
continue;
|
||||
case 6:
|
||||
}
|
||||
case 6: {
|
||||
if (tag !== 50) {
|
||||
break;
|
||||
}
|
||||
@@ -718,6 +746,7 @@ export const MetadataUpdateRequest_MdeVideoStillRequestParams: MessageFns<
|
||||
),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if ((tag & 7) === 4 || tag === 0) {
|
||||
break;
|
||||
@@ -753,12 +782,12 @@ export const MetadataUpdateRequest_MdeVideoStillRequestParams_ThumbnailExperimen
|
||||
length?: number,
|
||||
): MetadataUpdateRequest_MdeVideoStillRequestParams_ThumbnailExperimentImageData {
|
||||
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
|
||||
let end = length === undefined ? reader.len : reader.pos + length;
|
||||
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:
|
||||
case 1: {
|
||||
if (tag !== 10) {
|
||||
break;
|
||||
}
|
||||
@@ -768,6 +797,7 @@ export const MetadataUpdateRequest_MdeVideoStillRequestParams_ThumbnailExperimen
|
||||
reader.uint32(),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if ((tag & 7) === 4 || tag === 0) {
|
||||
break;
|
||||
@@ -809,39 +839,43 @@ export const MetadataUpdateRequest_MdeVideoStillRequestParams_CustomThumbnailIma
|
||||
length?: number,
|
||||
): MetadataUpdateRequest_MdeVideoStillRequestParams_CustomThumbnailImage {
|
||||
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
|
||||
let end = length === undefined ? reader.len : reader.pos + length;
|
||||
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:
|
||||
case 1: {
|
||||
if (tag !== 10) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.rawBytes = reader.bytes();
|
||||
continue;
|
||||
case 2:
|
||||
}
|
||||
case 2: {
|
||||
if (tag !== 18) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.dataUri = reader.string();
|
||||
continue;
|
||||
case 4:
|
||||
}
|
||||
case 4: {
|
||||
if (tag !== 32) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.frameTimestampUs = longToNumber(reader.int64());
|
||||
continue;
|
||||
case 5:
|
||||
}
|
||||
case 5: {
|
||||
if (tag !== 40) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.isVertical = reader.bool();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if ((tag & 7) === 4 || tag === 0) {
|
||||
break;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-ts_proto. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-ts_proto v2.2.0
|
||||
// protoc v5.28.0
|
||||
// protoc-gen-ts_proto v2.7.7
|
||||
// protoc v6.33.5
|
||||
// source: youtube/api/pfiinnertube/playback_context.proto
|
||||
|
||||
/* eslint-disable */
|
||||
@@ -94,18 +94,19 @@ export const PlaybackContext: MessageFns<PlaybackContext> = {
|
||||
|
||||
decode(input: BinaryReader | Uint8Array, length?: number): PlaybackContext {
|
||||
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
|
||||
let end = length === undefined ? reader.len : reader.pos + length;
|
||||
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:
|
||||
case 1: {
|
||||
if (tag !== 10) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.contentPlaybackContext = PlaybackContext_ContentPlaybackContext.decode(reader, reader.uint32());
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if ((tag & 7) === 4 || tag === 0) {
|
||||
break;
|
||||
@@ -248,221 +249,251 @@ export const PlaybackContext_ContentPlaybackContext: MessageFns<PlaybackContext_
|
||||
|
||||
decode(input: BinaryReader | Uint8Array, length?: number): PlaybackContext_ContentPlaybackContext {
|
||||
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
|
||||
let end = length === undefined ? reader.len : reader.pos + length;
|
||||
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:
|
||||
case 1: {
|
||||
if (tag !== 10) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.deviceSignals = reader.string();
|
||||
continue;
|
||||
case 3:
|
||||
}
|
||||
case 3: {
|
||||
if (tag !== 26) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.revShareClientId = reader.string();
|
||||
continue;
|
||||
case 4:
|
||||
}
|
||||
case 4: {
|
||||
if (tag !== 32) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.timeSinceLastAdSeconds = reader.uint32();
|
||||
continue;
|
||||
case 5:
|
||||
}
|
||||
case 5: {
|
||||
if (tag !== 40) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.lactMilliseconds = longToNumber(reader.int64());
|
||||
continue;
|
||||
case 6:
|
||||
}
|
||||
case 6: {
|
||||
if (tag !== 48) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.autoplaysSinceLastAd = reader.uint32();
|
||||
continue;
|
||||
case 8:
|
||||
}
|
||||
case 8: {
|
||||
if (tag !== 64) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.vis = reader.uint32();
|
||||
continue;
|
||||
case 9:
|
||||
}
|
||||
case 9: {
|
||||
if (tag !== 72) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.fling = reader.bool();
|
||||
continue;
|
||||
case 10:
|
||||
}
|
||||
case 10: {
|
||||
if (tag !== 80) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.splay = reader.bool();
|
||||
continue;
|
||||
case 11:
|
||||
}
|
||||
case 11: {
|
||||
if (tag !== 88) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.autoplay = reader.bool();
|
||||
continue;
|
||||
case 13:
|
||||
}
|
||||
case 13: {
|
||||
if (tag !== 104) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.timeOfLastInstreamPrerollAd = longToNumber(reader.uint64());
|
||||
continue;
|
||||
case 15:
|
||||
}
|
||||
case 15: {
|
||||
if (tag !== 122) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.currentUrl = reader.string();
|
||||
continue;
|
||||
case 16:
|
||||
}
|
||||
case 16: {
|
||||
if (tag !== 130) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.referer = reader.string();
|
||||
continue;
|
||||
case 23:
|
||||
}
|
||||
case 23: {
|
||||
if (tag !== 184) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.loadAnnotationsByDemand = reader.bool();
|
||||
continue;
|
||||
case 24:
|
||||
}
|
||||
case 24: {
|
||||
if (tag !== 192) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.autoCaptionsDefaultOn = reader.bool();
|
||||
continue;
|
||||
case 27:
|
||||
}
|
||||
case 27: {
|
||||
if (tag !== 216) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.slicedBread = reader.bool();
|
||||
continue;
|
||||
case 29:
|
||||
}
|
||||
case 29: {
|
||||
if (tag !== 232) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.autonav = reader.bool();
|
||||
continue;
|
||||
case 30:
|
||||
}
|
||||
case 30: {
|
||||
if (tag !== 240) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.trailer = reader.bool();
|
||||
continue;
|
||||
case 34:
|
||||
}
|
||||
case 34: {
|
||||
if (tag !== 272) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.playerWidthPixels = reader.int32();
|
||||
continue;
|
||||
case 35:
|
||||
}
|
||||
case 35: {
|
||||
if (tag !== 280) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.playerHeightPixels = reader.int32();
|
||||
continue;
|
||||
case 37:
|
||||
}
|
||||
case 37: {
|
||||
if (tag !== 296) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.snd = reader.int32();
|
||||
continue;
|
||||
case 38:
|
||||
}
|
||||
case 38: {
|
||||
if (tag !== 304) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.vnd = reader.int32();
|
||||
continue;
|
||||
case 41:
|
||||
}
|
||||
case 41: {
|
||||
if (tag !== 328) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.uao = reader.int32();
|
||||
continue;
|
||||
case 44:
|
||||
}
|
||||
case 44: {
|
||||
if (tag !== 352) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.mutedAutoplay = reader.bool();
|
||||
continue;
|
||||
case 46:
|
||||
}
|
||||
case 46: {
|
||||
if (tag !== 368) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.enablePrivacyFilter = reader.bool();
|
||||
continue;
|
||||
case 47:
|
||||
}
|
||||
case 47: {
|
||||
if (tag !== 376) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.isLivingRoomDeeplink = reader.bool();
|
||||
continue;
|
||||
case 48:
|
||||
}
|
||||
case 48: {
|
||||
if (tag !== 384) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.signatureTimestamp = reader.uint32();
|
||||
continue;
|
||||
case 50:
|
||||
}
|
||||
case 50: {
|
||||
if (tag !== 400) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.isInlinePlaybackNoAd = reader.bool();
|
||||
continue;
|
||||
case 51:
|
||||
}
|
||||
case 51: {
|
||||
if (tag !== 408) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.isInlineUnmutedPlayback = reader.bool();
|
||||
continue;
|
||||
case 55:
|
||||
}
|
||||
case 55: {
|
||||
if (tag !== 440) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.playPackageVersion = longToNumber(reader.int64());
|
||||
continue;
|
||||
case 60:
|
||||
}
|
||||
case 60: {
|
||||
if (tag !== 480) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.isSequenceEntry = reader.bool();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if ((tag & 7) === 4 || tag === 0) {
|
||||
break;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-ts_proto. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-ts_proto v2.2.0
|
||||
// protoc v5.28.0
|
||||
// protoc-gen-ts_proto v2.7.7
|
||||
// protoc v6.33.5
|
||||
// source: youtube/api/pfiinnertube/player_attestation_request_data.proto
|
||||
|
||||
/* eslint-disable */
|
||||
@@ -38,12 +38,12 @@ export const PlayerAttestationRequestData: MessageFns<PlayerAttestationRequestDa
|
||||
|
||||
decode(input: BinaryReader | Uint8Array, length?: number): PlayerAttestationRequestData {
|
||||
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
|
||||
let end = length === undefined ? reader.len : reader.pos + length;
|
||||
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:
|
||||
case 1: {
|
||||
if (tag !== 10) {
|
||||
break;
|
||||
}
|
||||
@@ -53,13 +53,15 @@ export const PlayerAttestationRequestData: MessageFns<PlayerAttestationRequestDa
|
||||
reader.uint32(),
|
||||
);
|
||||
continue;
|
||||
case 2:
|
||||
}
|
||||
case 2: {
|
||||
if (tag !== 16) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.omitBotguardData = reader.bool();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if ((tag & 7) === 4 || tag === 0) {
|
||||
break;
|
||||
@@ -89,18 +91,19 @@ export const PlayerAttestationRequestData_IosguardChallengeRequestData: MessageF
|
||||
|
||||
decode(input: BinaryReader | Uint8Array, length?: number): PlayerAttestationRequestData_IosguardChallengeRequestData {
|
||||
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
|
||||
let end = length === undefined ? reader.len : reader.pos + length;
|
||||
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:
|
||||
case 1: {
|
||||
if (tag !== 10) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.challengeRequest = reader.bytes();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if ((tag & 7) === 4 || tag === 0) {
|
||||
break;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-ts_proto. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-ts_proto v2.2.0
|
||||
// protoc v5.28.0
|
||||
// protoc-gen-ts_proto v2.7.7
|
||||
// protoc v6.33.5
|
||||
// source: youtube/api/pfiinnertube/player_request.proto
|
||||
|
||||
/* eslint-disable */
|
||||
@@ -152,179 +152,203 @@ export const PlayerRequest: MessageFns<PlayerRequest> = {
|
||||
|
||||
decode(input: BinaryReader | Uint8Array, length?: number): PlayerRequest {
|
||||
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
|
||||
let end = length === undefined ? reader.len : reader.pos + length;
|
||||
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:
|
||||
case 1: {
|
||||
if (tag !== 10) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.context = InnerTubeContext.decode(reader, reader.uint32());
|
||||
continue;
|
||||
case 2:
|
||||
}
|
||||
case 2: {
|
||||
if (tag !== 18) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.videoId = reader.string();
|
||||
continue;
|
||||
case 3:
|
||||
}
|
||||
case 3: {
|
||||
if (tag !== 24) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.contentCheckOk = reader.bool();
|
||||
continue;
|
||||
case 4:
|
||||
}
|
||||
case 4: {
|
||||
if (tag !== 34) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.playbackContext = PlaybackContext.decode(reader, reader.uint32());
|
||||
continue;
|
||||
case 5:
|
||||
}
|
||||
case 5: {
|
||||
if (tag !== 40) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.racyCheckOk = reader.bool();
|
||||
continue;
|
||||
case 6:
|
||||
}
|
||||
case 6: {
|
||||
if (tag !== 50) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.id = reader.string();
|
||||
continue;
|
||||
case 7:
|
||||
}
|
||||
case 7: {
|
||||
if (tag !== 58) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.t = reader.string();
|
||||
continue;
|
||||
case 8:
|
||||
}
|
||||
case 8: {
|
||||
if (tag !== 64) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.forOffline = reader.bool();
|
||||
continue;
|
||||
case 9:
|
||||
}
|
||||
case 9: {
|
||||
if (tag !== 74) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.playlistId = reader.string();
|
||||
continue;
|
||||
case 10:
|
||||
}
|
||||
case 10: {
|
||||
if (tag !== 80) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.playlistIndex = reader.int32();
|
||||
continue;
|
||||
case 11:
|
||||
}
|
||||
case 11: {
|
||||
if (tag !== 88) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.startTimeSecs = reader.uint32();
|
||||
continue;
|
||||
case 12:
|
||||
}
|
||||
case 12: {
|
||||
if (tag !== 98) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.params = reader.string();
|
||||
continue;
|
||||
case 14:
|
||||
}
|
||||
case 14: {
|
||||
if (tag !== 114) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.offlineSharingWrappedKey = reader.bytes();
|
||||
continue;
|
||||
case 16:
|
||||
}
|
||||
case 16: {
|
||||
if (tag !== 130) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.attestationRequest = PlayerAttestationRequestData.decode(reader, reader.uint32());
|
||||
continue;
|
||||
case 17:
|
||||
}
|
||||
case 17: {
|
||||
if (tag !== 138) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.referringApp = reader.string();
|
||||
continue;
|
||||
case 18:
|
||||
}
|
||||
case 18: {
|
||||
if (tag !== 146) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.referrer = reader.string();
|
||||
continue;
|
||||
case 19:
|
||||
}
|
||||
case 19: {
|
||||
if (tag !== 154) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.serializedThirdPartyEmbedConfig = reader.string();
|
||||
continue;
|
||||
case 20:
|
||||
}
|
||||
case 20: {
|
||||
if (tag !== 160) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.proxiedByOnesie = reader.bool();
|
||||
continue;
|
||||
case 22:
|
||||
}
|
||||
case 22: {
|
||||
if (tag !== 178) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.hostAppToken = reader.string();
|
||||
continue;
|
||||
case 23:
|
||||
}
|
||||
case 23: {
|
||||
if (tag !== 186) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.cpn = reader.string();
|
||||
continue;
|
||||
case 25:
|
||||
}
|
||||
case 25: {
|
||||
if (tag !== 200) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.overrideMutedAtStart = reader.bool();
|
||||
continue;
|
||||
case 26:
|
||||
}
|
||||
case 26: {
|
||||
if (tag !== 210) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.captionParams = PlayerRequestCaptionParams.decode(reader, reader.uint32());
|
||||
continue;
|
||||
case 27:
|
||||
}
|
||||
case 27: {
|
||||
if (tag !== 218) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.serviceIntegrityDimensions = ServiceIntegrityDimensions.decode(reader, reader.uint32());
|
||||
continue;
|
||||
case 29:
|
||||
}
|
||||
case 29: {
|
||||
if (tag !== 234) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.deferredPlayerToken = reader.bytes();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if ((tag & 7) === 4 || tag === 0) {
|
||||
break;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-ts_proto. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-ts_proto v2.2.0
|
||||
// protoc v5.28.0
|
||||
// protoc-gen-ts_proto v2.7.7
|
||||
// protoc v6.33.5
|
||||
// source: youtube/api/pfiinnertube/player_request_caption_params.proto
|
||||
|
||||
/* eslint-disable */
|
||||
@@ -49,46 +49,51 @@ export const PlayerRequestCaptionParams: MessageFns<PlayerRequestCaptionParams>
|
||||
|
||||
decode(input: BinaryReader | Uint8Array, length?: number): PlayerRequestCaptionParams {
|
||||
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
|
||||
let end = length === undefined ? reader.len : reader.pos + length;
|
||||
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:
|
||||
case 1: {
|
||||
if (tag !== 8) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.deviceCaptionsOn = reader.bool();
|
||||
continue;
|
||||
case 2:
|
||||
}
|
||||
case 2: {
|
||||
if (tag !== 18) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.deviceCaptionsLangPref = reader.string();
|
||||
continue;
|
||||
case 3:
|
||||
}
|
||||
case 3: {
|
||||
if (tag !== 26) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.viewerSelectedCaptionLangs = reader.string();
|
||||
continue;
|
||||
case 4:
|
||||
}
|
||||
case 4: {
|
||||
if (tag !== 34) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.ccLangPref = reader.string();
|
||||
continue;
|
||||
case 5:
|
||||
}
|
||||
case 5: {
|
||||
if (tag !== 40) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.ccLoadPolicyOn = reader.bool();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if ((tag & 7) === 4 || tag === 0) {
|
||||
break;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-ts_proto. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-ts_proto v2.2.0
|
||||
// protoc v5.28.0
|
||||
// protoc-gen-ts_proto v2.7.7
|
||||
// protoc v6.33.5
|
||||
// source: youtube/api/pfiinnertube/reel_item_watch_request.proto
|
||||
|
||||
/* eslint-disable */
|
||||
@@ -41,39 +41,43 @@ export const ReelItemWatchRequest: MessageFns<ReelItemWatchRequest> = {
|
||||
|
||||
decode(input: BinaryReader | Uint8Array, length?: number): ReelItemWatchRequest {
|
||||
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
|
||||
let end = length === undefined ? reader.len : reader.pos + length;
|
||||
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:
|
||||
case 1: {
|
||||
if (tag !== 10) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.context = InnerTubeContext.decode(reader, reader.uint32());
|
||||
continue;
|
||||
case 2:
|
||||
}
|
||||
case 2: {
|
||||
if (tag !== 18) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.playerRequest = PlayerRequest.decode(reader, reader.uint32());
|
||||
continue;
|
||||
case 3:
|
||||
}
|
||||
case 3: {
|
||||
if (tag !== 26) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.params = reader.string();
|
||||
continue;
|
||||
case 4:
|
||||
}
|
||||
case 4: {
|
||||
if (tag !== 32) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.disablePlayerResponse = reader.bool();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if ((tag & 7) === 4 || tag === 0) {
|
||||
break;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-ts_proto. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-ts_proto v2.2.0
|
||||
// protoc v5.28.0
|
||||
// protoc-gen-ts_proto v2.7.7
|
||||
// protoc v6.33.5
|
||||
// source: youtube/api/pfiinnertube/request_info.proto
|
||||
|
||||
/* eslint-disable */
|
||||
@@ -156,144 +156,163 @@ export const RequestInfo: MessageFns<RequestInfo> = {
|
||||
|
||||
decode(input: BinaryReader | Uint8Array, length?: number): RequestInfo {
|
||||
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
|
||||
let end = length === undefined ? reader.len : reader.pos + length;
|
||||
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:
|
||||
case 6: {
|
||||
if (tag !== 50) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.thirdPartyDigest = reader.string();
|
||||
continue;
|
||||
case 7:
|
||||
}
|
||||
case 7: {
|
||||
if (tag !== 56) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.useSsl = reader.bool();
|
||||
continue;
|
||||
case 9:
|
||||
}
|
||||
case 9: {
|
||||
if (tag !== 72) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.returnErrorDetail = reader.bool();
|
||||
continue;
|
||||
case 12:
|
||||
}
|
||||
case 12: {
|
||||
if (tag !== 98) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.ifNoneMatch = reader.string();
|
||||
continue;
|
||||
case 13:
|
||||
}
|
||||
case 13: {
|
||||
if (tag !== 104) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.returnLogEntry = reader.bool();
|
||||
continue;
|
||||
case 14:
|
||||
}
|
||||
case 14: {
|
||||
if (tag !== 112) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.isPrefetch = reader.bool();
|
||||
continue;
|
||||
case 15:
|
||||
}
|
||||
case 15: {
|
||||
if (tag !== 122) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.internalExperimentFlags.push(KeyValuePair.decode(reader, reader.uint32()));
|
||||
continue;
|
||||
case 16:
|
||||
}
|
||||
case 16: {
|
||||
if (tag !== 128) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.returnDebugData = reader.bool();
|
||||
continue;
|
||||
case 18:
|
||||
}
|
||||
case 18: {
|
||||
if (tag !== 146) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.innertubez = reader.string();
|
||||
continue;
|
||||
case 23:
|
||||
}
|
||||
case 23: {
|
||||
if (tag !== 184) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.traceProto = reader.bool();
|
||||
continue;
|
||||
case 24:
|
||||
}
|
||||
case 24: {
|
||||
if (tag !== 192) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.returnLogEntryJson = reader.bool();
|
||||
continue;
|
||||
case 25:
|
||||
}
|
||||
case 25: {
|
||||
if (tag !== 202) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.sherlogUsername = reader.string();
|
||||
continue;
|
||||
case 29:
|
||||
}
|
||||
case 29: {
|
||||
if (tag !== 234) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.reauthRequestInfo = RequestInfo_ReauthRequestInfo.decode(reader, reader.uint32());
|
||||
continue;
|
||||
case 30:
|
||||
}
|
||||
case 30: {
|
||||
if (tag !== 242) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.sessionInfo = RequestInfo_SessionInfo.decode(reader, reader.uint32());
|
||||
continue;
|
||||
case 31:
|
||||
}
|
||||
case 31: {
|
||||
if (tag !== 248) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.returnLogEntryProto = reader.bool();
|
||||
continue;
|
||||
case 32:
|
||||
}
|
||||
case 32: {
|
||||
if (tag !== 258) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.externalPrequestContext = reader.string();
|
||||
continue;
|
||||
case 34:
|
||||
}
|
||||
case 34: {
|
||||
if (tag !== 274) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.attestationResponseData = AttestationResponseData.decode(reader, reader.uint32());
|
||||
continue;
|
||||
case 35:
|
||||
}
|
||||
case 35: {
|
||||
if (tag !== 282) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.eats = reader.bytes();
|
||||
continue;
|
||||
case 36:
|
||||
}
|
||||
case 36: {
|
||||
if (tag !== 290) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.requestQos = RequestInfo_RequestQoS.decode(reader, reader.uint32());
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if ((tag & 7) === 4 || tag === 0) {
|
||||
break;
|
||||
@@ -318,18 +337,19 @@ export const RequestInfo_RequestQoS: MessageFns<RequestInfo_RequestQoS> = {
|
||||
|
||||
decode(input: BinaryReader | Uint8Array, length?: number): RequestInfo_RequestQoS {
|
||||
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
|
||||
let end = length === undefined ? reader.len : reader.pos + length;
|
||||
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:
|
||||
case 1: {
|
||||
if (tag !== 8) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.criticality = reader.int32() as any;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if ((tag & 7) === 4 || tag === 0) {
|
||||
break;
|
||||
@@ -354,18 +374,19 @@ export const RequestInfo_SessionInfo: MessageFns<RequestInfo_SessionInfo> = {
|
||||
|
||||
decode(input: BinaryReader | Uint8Array, length?: number): RequestInfo_SessionInfo {
|
||||
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
|
||||
let end = length === undefined ? reader.len : reader.pos + length;
|
||||
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:
|
||||
case 1: {
|
||||
if (tag !== 10) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.token = reader.string();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if ((tag & 7) === 4 || tag === 0) {
|
||||
break;
|
||||
@@ -390,18 +411,19 @@ export const RequestInfo_ReauthRequestInfo: MessageFns<RequestInfo_ReauthRequest
|
||||
|
||||
decode(input: BinaryReader | Uint8Array, length?: number): RequestInfo_ReauthRequestInfo {
|
||||
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
|
||||
let end = length === undefined ? reader.len : reader.pos + length;
|
||||
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:
|
||||
case 1: {
|
||||
if (tag !== 10) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.encodedReauthProofToken = reader.string();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if ((tag & 7) === 4 || tag === 0) {
|
||||
break;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-ts_proto. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-ts_proto v2.2.0
|
||||
// protoc v5.28.0
|
||||
// protoc-gen-ts_proto v2.7.7
|
||||
// protoc v6.33.5
|
||||
// source: youtube/api/pfiinnertube/service_integrity_dimensions.proto
|
||||
|
||||
/* eslint-disable */
|
||||
@@ -27,18 +27,19 @@ export const ServiceIntegrityDimensions: MessageFns<ServiceIntegrityDimensions>
|
||||
|
||||
decode(input: BinaryReader | Uint8Array, length?: number): ServiceIntegrityDimensions {
|
||||
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
|
||||
let end = length === undefined ? reader.len : reader.pos + length;
|
||||
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:
|
||||
case 1: {
|
||||
if (tag !== 10) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.poToken = reader.bytes();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if ((tag & 7) === 4 || tag === 0) {
|
||||
break;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-ts_proto. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-ts_proto v2.2.0
|
||||
// protoc v5.28.0
|
||||
// protoc-gen-ts_proto v2.7.7
|
||||
// protoc v6.33.5
|
||||
// source: youtube/api/pfiinnertube/third_party_info.proto
|
||||
|
||||
/* eslint-disable */
|
||||
@@ -60,53 +60,59 @@ export const ThirdPartyInfo: MessageFns<ThirdPartyInfo> = {
|
||||
|
||||
decode(input: BinaryReader | Uint8Array, length?: number): ThirdPartyInfo {
|
||||
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
|
||||
let end = length === undefined ? reader.len : reader.pos + length;
|
||||
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:
|
||||
case 1: {
|
||||
if (tag !== 10) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.developerKey = reader.string();
|
||||
continue;
|
||||
case 2:
|
||||
}
|
||||
case 2: {
|
||||
if (tag !== 18) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.appName = reader.string();
|
||||
continue;
|
||||
case 3:
|
||||
}
|
||||
case 3: {
|
||||
if (tag !== 26) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.appPublisher = reader.string();
|
||||
continue;
|
||||
case 4:
|
||||
}
|
||||
case 4: {
|
||||
if (tag !== 34) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.embedUrl = reader.string();
|
||||
continue;
|
||||
case 6:
|
||||
}
|
||||
case 6: {
|
||||
if (tag !== 50) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.appVersion = reader.string();
|
||||
continue;
|
||||
case 7:
|
||||
}
|
||||
case 7: {
|
||||
if (tag !== 58) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.embeddedPlayerContext = ThirdPartyInfo_EmbeddedPlayerContext.decode(reader, reader.uint32());
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if ((tag & 7) === 4 || tag === 0) {
|
||||
break;
|
||||
@@ -137,32 +143,35 @@ export const ThirdPartyInfo_EmbeddedPlayerContext: MessageFns<ThirdPartyInfo_Emb
|
||||
|
||||
decode(input: BinaryReader | Uint8Array, length?: number): ThirdPartyInfo_EmbeddedPlayerContext {
|
||||
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
|
||||
let end = length === undefined ? reader.len : reader.pos + length;
|
||||
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:
|
||||
case 1: {
|
||||
if (tag !== 10) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.ancestorOrigins = reader.string();
|
||||
continue;
|
||||
case 2:
|
||||
}
|
||||
case 2: {
|
||||
if (tag !== 18) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.embeddedPlayerEncryptedContext = reader.string();
|
||||
continue;
|
||||
case 3:
|
||||
}
|
||||
case 3: {
|
||||
if (tag !== 24) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.ancestorOriginsSupported = reader.bool();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if ((tag & 7) === 4 || tag === 0) {
|
||||
break;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-ts_proto. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-ts_proto v2.2.0
|
||||
// protoc v5.28.0
|
||||
// protoc-gen-ts_proto v2.7.7
|
||||
// protoc v6.33.5
|
||||
// source: youtube/api/pfiinnertube/user_info.proto
|
||||
|
||||
/* eslint-disable */
|
||||
@@ -81,74 +81,83 @@ export const UserInfo: MessageFns<UserInfo> = {
|
||||
|
||||
decode(input: BinaryReader | Uint8Array, length?: number): UserInfo {
|
||||
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
|
||||
let end = length === undefined ? reader.len : reader.pos + length;
|
||||
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:
|
||||
case 3: {
|
||||
if (tag !== 26) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.onBehalfOfUser = reader.string();
|
||||
continue;
|
||||
case 7:
|
||||
}
|
||||
case 7: {
|
||||
if (tag !== 56) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.enableSafetyMode = reader.bool();
|
||||
continue;
|
||||
case 12:
|
||||
}
|
||||
case 12: {
|
||||
if (tag !== 98) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.credentialTransferTokens.push(UserInfo_CredentialTransferToken.decode(reader, reader.uint32()));
|
||||
continue;
|
||||
case 13:
|
||||
}
|
||||
case 13: {
|
||||
if (tag !== 106) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.delegatePurchases = UserInfo_DelegatePurchases.decode(reader, reader.uint32());
|
||||
continue;
|
||||
case 14:
|
||||
}
|
||||
case 14: {
|
||||
if (tag !== 114) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.kidsParent = UserInfo_KidsParent.decode(reader, reader.uint32());
|
||||
continue;
|
||||
case 15:
|
||||
}
|
||||
case 15: {
|
||||
if (tag !== 120) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.isIncognito = reader.bool();
|
||||
continue;
|
||||
case 16:
|
||||
}
|
||||
case 16: {
|
||||
if (tag !== 128) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.lockedSafetyMode = reader.bool();
|
||||
continue;
|
||||
case 17:
|
||||
}
|
||||
case 17: {
|
||||
if (tag !== 138) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.delegationContext = UserInfo_DelegationContext.decode(reader, reader.uint32());
|
||||
continue;
|
||||
case 18:
|
||||
}
|
||||
case 18: {
|
||||
if (tag !== 146) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.serializedDelegationContext = reader.string();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if ((tag & 7) === 4 || tag === 0) {
|
||||
break;
|
||||
@@ -170,7 +179,7 @@ export const UserInfo_KidsParent: MessageFns<UserInfo_KidsParent> = {
|
||||
|
||||
decode(input: BinaryReader | Uint8Array, length?: number): UserInfo_KidsParent {
|
||||
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
|
||||
let end = length === undefined ? reader.len : reader.pos + length;
|
||||
const end = length === undefined ? reader.len : reader.pos + length;
|
||||
const message = createBaseUserInfo_KidsParent();
|
||||
while (reader.pos < end) {
|
||||
const tag = reader.uint32();
|
||||
@@ -196,7 +205,7 @@ export const UserInfo_DelegatePurchases: MessageFns<UserInfo_DelegatePurchases>
|
||||
|
||||
decode(input: BinaryReader | Uint8Array, length?: number): UserInfo_DelegatePurchases {
|
||||
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
|
||||
let end = length === undefined ? reader.len : reader.pos + length;
|
||||
const end = length === undefined ? reader.len : reader.pos + length;
|
||||
const message = createBaseUserInfo_DelegatePurchases();
|
||||
while (reader.pos < end) {
|
||||
const tag = reader.uint32();
|
||||
@@ -222,7 +231,7 @@ export const UserInfo_DelegationContext: MessageFns<UserInfo_DelegationContext>
|
||||
|
||||
decode(input: BinaryReader | Uint8Array, length?: number): UserInfo_DelegationContext {
|
||||
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
|
||||
let end = length === undefined ? reader.len : reader.pos + length;
|
||||
const end = length === undefined ? reader.len : reader.pos + length;
|
||||
const message = createBaseUserInfo_DelegationContext();
|
||||
while (reader.pos < end) {
|
||||
const tag = reader.uint32();
|
||||
@@ -248,7 +257,7 @@ export const UserInfo_CredentialTransferToken: MessageFns<UserInfo_CredentialTra
|
||||
|
||||
decode(input: BinaryReader | Uint8Array, length?: number): UserInfo_CredentialTransferToken {
|
||||
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
|
||||
let end = length === undefined ? reader.len : reader.pos + length;
|
||||
const end = length === undefined ? reader.len : reader.pos + length;
|
||||
const message = createBaseUserInfo_CredentialTransferToken();
|
||||
while (reader.pos < end) {
|
||||
const tag = reader.uint32();
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-ts_proto. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-ts_proto v2.2.0
|
||||
// protoc v5.28.0
|
||||
// protoc-gen-ts_proto v2.7.7
|
||||
// protoc v6.33.5
|
||||
// source: youtube/api/pfiinnertube/watch_next_request.proto
|
||||
|
||||
/* eslint-disable */
|
||||
@@ -170,214 +170,243 @@ export const WatchNextRequest: MessageFns<WatchNextRequest> = {
|
||||
|
||||
decode(input: BinaryReader | Uint8Array, length?: number): WatchNextRequest {
|
||||
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
|
||||
let end = length === undefined ? reader.len : reader.pos + length;
|
||||
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:
|
||||
case 1: {
|
||||
if (tag !== 10) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.context = InnerTubeContext.decode(reader, reader.uint32());
|
||||
continue;
|
||||
case 2:
|
||||
}
|
||||
case 2: {
|
||||
if (tag !== 18) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.videoId = reader.string();
|
||||
continue;
|
||||
case 4:
|
||||
}
|
||||
case 4: {
|
||||
if (tag !== 34) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.playlistId = reader.string();
|
||||
continue;
|
||||
case 6:
|
||||
}
|
||||
case 6: {
|
||||
if (tag !== 50) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.params = reader.string();
|
||||
continue;
|
||||
case 8:
|
||||
}
|
||||
case 8: {
|
||||
if (tag !== 66) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.continuation = reader.string();
|
||||
continue;
|
||||
case 9:
|
||||
}
|
||||
case 9: {
|
||||
if (tag !== 72) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.isAdPlayback = reader.bool();
|
||||
continue;
|
||||
case 10:
|
||||
}
|
||||
case 10: {
|
||||
if (tag !== 80) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.mdxUseDevServer = reader.bool();
|
||||
continue;
|
||||
case 12:
|
||||
}
|
||||
case 12: {
|
||||
if (tag !== 98) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.referrer = reader.string();
|
||||
continue;
|
||||
case 13:
|
||||
}
|
||||
case 13: {
|
||||
if (tag !== 106) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.referringApp = reader.string();
|
||||
continue;
|
||||
case 16:
|
||||
}
|
||||
case 16: {
|
||||
if (tag !== 130) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.adParams = reader.string();
|
||||
continue;
|
||||
case 18:
|
||||
}
|
||||
case 18: {
|
||||
if (tag !== 144) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.requestMusicSequence = reader.bool();
|
||||
continue;
|
||||
case 21:
|
||||
}
|
||||
case 21: {
|
||||
if (tag !== 168) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.enableMdxAutoplay = reader.bool();
|
||||
continue;
|
||||
case 22:
|
||||
}
|
||||
case 22: {
|
||||
if (tag !== 176) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.isMdxPlayback = reader.bool();
|
||||
continue;
|
||||
case 24:
|
||||
}
|
||||
case 24: {
|
||||
if (tag !== 192) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.racyCheckOk = reader.bool();
|
||||
continue;
|
||||
case 25:
|
||||
}
|
||||
case 25: {
|
||||
if (tag !== 200) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.contentCheckOk = reader.bool();
|
||||
continue;
|
||||
case 26:
|
||||
}
|
||||
case 26: {
|
||||
if (tag !== 208) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.isAudioOnly = reader.bool();
|
||||
continue;
|
||||
case 27:
|
||||
}
|
||||
case 27: {
|
||||
if (tag !== 216) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.autonavEnabled = reader.bool();
|
||||
continue;
|
||||
case 30:
|
||||
}
|
||||
case 30: {
|
||||
if (tag !== 240) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.enablePersistentPlaylistPanel = reader.bool();
|
||||
continue;
|
||||
case 31:
|
||||
}
|
||||
case 31: {
|
||||
if (tag !== 250) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.playlistSetVideoId = reader.string();
|
||||
continue;
|
||||
case 35:
|
||||
}
|
||||
case 35: {
|
||||
if (tag !== 280) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.showRuInvalidTokenMessage = reader.bool();
|
||||
continue;
|
||||
case 37:
|
||||
}
|
||||
case 37: {
|
||||
if (tag !== 298) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.serializedThirdPartyEmbedConfig = reader.string();
|
||||
continue;
|
||||
case 38:
|
||||
}
|
||||
case 38: {
|
||||
if (tag !== 304) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.showContentOwnerOnly = reader.bool();
|
||||
continue;
|
||||
case 42:
|
||||
}
|
||||
case 42: {
|
||||
if (tag !== 336) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.isEmbedPreview = reader.bool();
|
||||
continue;
|
||||
case 43:
|
||||
}
|
||||
case 43: {
|
||||
if (tag !== 346) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.lastScrubbedInlinePlaybackVideoId = reader.string();
|
||||
continue;
|
||||
case 44:
|
||||
}
|
||||
case 44: {
|
||||
if (tag !== 354) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.lastAudioTurnedOnInlinePlaybackVideoId = reader.string();
|
||||
continue;
|
||||
case 45:
|
||||
}
|
||||
case 45: {
|
||||
if (tag !== 362) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.lastAudioTurnedOffInlinePlaybackVideoId = reader.string();
|
||||
continue;
|
||||
case 47:
|
||||
}
|
||||
case 47: {
|
||||
if (tag !== 376) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.captionsRequested = reader.bool();
|
||||
continue;
|
||||
case 50:
|
||||
}
|
||||
case 50: {
|
||||
if (tag !== 402) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.queueContextParams = reader.bytes();
|
||||
continue;
|
||||
case 55:
|
||||
}
|
||||
case 55: {
|
||||
if (tag !== 440) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.showShortsOnly = reader.bool();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if ((tag & 7) === 4 || tag === 0) {
|
||||
break;
|
||||
|
||||
@@ -25,4 +25,8 @@ message IndexRange {
|
||||
message KeyValuePair {
|
||||
optional string key = 1;
|
||||
optional string value = 2;
|
||||
}
|
||||
|
||||
message FormatXTags {
|
||||
repeated KeyValuePair xtags = 1;
|
||||
}
|
||||
@@ -7,13 +7,11 @@ message VisitorData {
|
||||
}
|
||||
|
||||
message SearchFilter {
|
||||
optional SortBy sort_by = 1;
|
||||
optional Prioritize prioritize = 1;
|
||||
|
||||
enum SortBy {
|
||||
enum Prioritize {
|
||||
RELEVANCE = 0;
|
||||
RATING = 1;
|
||||
UPLOAD_DATE = 2;
|
||||
VIEW_COUNT = 3;
|
||||
POPULARITY = 3;
|
||||
}
|
||||
|
||||
message Filters {
|
||||
@@ -36,7 +34,6 @@ message SearchFilter {
|
||||
|
||||
enum UploadDate {
|
||||
ANY_DATE = 0;
|
||||
HOUR = 1;
|
||||
TODAY = 2;
|
||||
WEEK = 3;
|
||||
MONTH = 4;
|
||||
@@ -49,13 +46,14 @@ message SearchFilter {
|
||||
CHANNEL = 2;
|
||||
PLAYLIST = 3;
|
||||
MOVIE = 4;
|
||||
SHORTS = 9;
|
||||
}
|
||||
|
||||
enum Duration {
|
||||
ANY_DURATION = 0;
|
||||
SHORT = 1;
|
||||
LONG = 2;
|
||||
MEDIUM = 3;
|
||||
OVER_TWENTY_MINS = 2;
|
||||
UNDER_THREE_MINS = 4;
|
||||
THREE_TO_TWENTY_MINS = 5;
|
||||
}
|
||||
|
||||
message MusicSearchType {
|
||||
@@ -228,4 +226,43 @@ message ShortsParam {
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
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';
|
||||
@@ -16,20 +17,31 @@ import {
|
||||
Search,
|
||||
VideoInfo
|
||||
} from './parser/youtube/index.ts';
|
||||
|
||||
import { ShortFormVideoInfo } from './parser/ytshorts/index.ts';
|
||||
|
||||
import { NavigateAction } from './parser/continuations.ts';
|
||||
import NavigationEndpoint from './parser/classes/NavigationEndpoint.ts';
|
||||
import type Format from './parser/classes/misc/Format.ts';
|
||||
|
||||
import * as Constants from './utils/Constants.ts';
|
||||
import { generateRandomString, InnertubeError, throwIfMissing, u8ToBase64 } from './utils/Utils.ts';
|
||||
|
||||
import type { ApiResponse } from './core/Actions.ts';
|
||||
import type { DownloadOptions, FormatOptions, InnerTubeClient, InnerTubeConfig, SearchFilters } from './types/index.ts';
|
||||
import type {
|
||||
DownloadOptions,
|
||||
EngagementType,
|
||||
FormatOptions,
|
||||
GetVideoInfoOptions,
|
||||
InnerTubeClient,
|
||||
InnerTubeConfig,
|
||||
SearchFilters
|
||||
} from './types/index.ts';
|
||||
import type { IBrowseResponse, IParsedResponse } from './parser/index.ts';
|
||||
import type Format from './parser/classes/misc/Format.ts';
|
||||
|
||||
import {
|
||||
CommunityPostCommentsParam,
|
||||
CommunityPostCommentsParamContainer,
|
||||
CommunityPostParams,
|
||||
GetCommentsSectionParams,
|
||||
Hashtag,
|
||||
ReelSequence,
|
||||
@@ -37,12 +49,12 @@ import {
|
||||
SearchFilter_Filters_Duration,
|
||||
SearchFilter_Filters_SearchType,
|
||||
SearchFilter_Filters_UploadDate,
|
||||
SearchFilter_SortBy
|
||||
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';
|
||||
@@ -60,7 +72,7 @@ export default class Innertube {
|
||||
return new Innertube(await Session.create(config));
|
||||
}
|
||||
|
||||
async getInfo(target: string | NavigationEndpoint, client?: InnerTubeClient): Promise<VideoInfo> {
|
||||
async getInfo(target: string | NavigationEndpoint, options?: GetVideoInfoOptions): Promise<VideoInfo> {
|
||||
throwIfMissing({ target });
|
||||
|
||||
const payload = {
|
||||
@@ -75,53 +87,80 @@ export default class Innertube {
|
||||
const watch_endpoint = new NavigationEndpoint({ watchEndpoint: payload });
|
||||
const watch_next_endpoint = new NavigationEndpoint({ watchNextEndpoint: payload });
|
||||
|
||||
const watch_response = watch_endpoint.call(this.#session.actions, {
|
||||
const session = this.#session;
|
||||
|
||||
const extra_payload: Record<string, any> = {
|
||||
playbackContext: {
|
||||
contentPlaybackContext: {
|
||||
vis: 0,
|
||||
splay: false,
|
||||
lactMilliseconds: '-1',
|
||||
signatureTimestamp: this.#session.player?.sts
|
||||
signatureTimestamp: session.player?.signature_timestamp
|
||||
}
|
||||
},
|
||||
serviceIntegrityDimensions: {
|
||||
poToken: this.#session.po_token
|
||||
},
|
||||
client
|
||||
});
|
||||
client: options?.client
|
||||
};
|
||||
|
||||
const watch_next_response = watch_next_endpoint.call(this.#session.actions);
|
||||
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);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
async getBasicInfo(video_id: string, client?: InnerTubeClient): Promise<VideoInfo> {
|
||||
async getBasicInfo(video_id: string, options?: GetVideoInfoOptions): Promise<VideoInfo> {
|
||||
throwIfMissing({ video_id });
|
||||
|
||||
const watch_endpoint = new NavigationEndpoint({ watchEndpoint: { videoId: video_id } });
|
||||
const watch_endpoint = new NavigationEndpoint({
|
||||
watchEndpoint: {
|
||||
videoId: video_id,
|
||||
racyCheckOk: true,
|
||||
contentCheckOk: true
|
||||
}
|
||||
});
|
||||
|
||||
const watch_response = await watch_endpoint.call(this.#session.actions, {
|
||||
const session = this.#session;
|
||||
|
||||
const extra_payload: Record<string, any> = {
|
||||
playbackContext: {
|
||||
contentPlaybackContext: {
|
||||
vis: 0,
|
||||
splay: false,
|
||||
lactMilliseconds: '-1',
|
||||
signatureTimestamp: this.#session.player?.sts
|
||||
signatureTimestamp: session.player?.signature_timestamp
|
||||
}
|
||||
},
|
||||
serviceIntegrityDimensions: {
|
||||
poToken: this.#session.po_token
|
||||
},
|
||||
client
|
||||
});
|
||||
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([ watch_response ], this.actions, cpn);
|
||||
return new VideoInfo([ watch_response ], session.actions, cpn);
|
||||
}
|
||||
|
||||
async getShortsVideoInfo(video_id: string, client?: InnerTubeClient): Promise<ShortFormVideoInfo> {
|
||||
@@ -135,8 +174,10 @@ export default class Innertube {
|
||||
}
|
||||
});
|
||||
|
||||
const reel_watch_response = reel_watch_endpoint.call(this.#session.actions, { client });
|
||||
|
||||
const actions = this.#session.actions;
|
||||
|
||||
const reel_watch_response = reel_watch_endpoint.call(actions, { client });
|
||||
|
||||
const writer = ReelSequence.encode({
|
||||
shortId: video_id,
|
||||
params: {
|
||||
@@ -148,13 +189,13 @@ export default class Innertube {
|
||||
|
||||
const params = encodeURIComponent(u8ToBase64(writer.finish()));
|
||||
|
||||
const sequence_response = this.actions.execute('/reel/reel_watch_sequence', { sequenceParams: params });
|
||||
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] ], this.actions, cpn, response[1]);
|
||||
return new ShortFormVideoInfo([ response[0] ], actions, cpn, response[1]);
|
||||
}
|
||||
|
||||
async search(query: string, filters: SearchFilters = {}): Promise<Search> {
|
||||
@@ -164,8 +205,8 @@ export default class Innertube {
|
||||
|
||||
search_filter.filters = {};
|
||||
|
||||
if (filters.sort_by) {
|
||||
search_filter.sortBy = SearchFilter_SortBy[filters.sort_by.toUpperCase() as keyof typeof SearchFilter_SortBy];
|
||||
if (filters.prioritize) {
|
||||
search_filter.prioritize = SearchFilter_Prioritize[filters.prioritize.toUpperCase() as keyof typeof SearchFilter_Prioritize];
|
||||
}
|
||||
|
||||
if (filters.upload_date) {
|
||||
@@ -222,29 +263,44 @@ export default class Innertube {
|
||||
}
|
||||
}
|
||||
|
||||
const search_endpoint = new NavigationEndpoint({ searchEndpoint: { query, params: filters ? encodeURIComponent(u8ToBase64(SearchFilter.encode(search_filter).finish())) : undefined } });
|
||||
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);
|
||||
}
|
||||
|
||||
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(')]}\'', ''));
|
||||
return data[1].map((suggestion: any) => suggestion[0]);
|
||||
const response = await session.http.fetch_function(url, {
|
||||
headers: {
|
||||
'Cookie': session.cookie || ''
|
||||
}
|
||||
});
|
||||
|
||||
const text = await response.text();
|
||||
|
||||
const data = JSON.parse(text.replace('window.google.ac.h(', '').slice(0, -1));
|
||||
return data[1].map((suggestion: (string | number)[]) => suggestion[0]);
|
||||
}
|
||||
|
||||
async getComments(video_id: string, sort_by?: 'TOP_COMMENTS' | 'NEWEST_FIRST', comment_id?: string): Promise<Comments> {
|
||||
@@ -284,13 +340,13 @@ export default class Innertube {
|
||||
|
||||
return new Comments(this.actions, response.data);
|
||||
}
|
||||
|
||||
|
||||
async getHomeFeed(): Promise<HomeFeed> {
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
async getGuide(): Promise<Guide> {
|
||||
const response = await this.actions.execute('/guide');
|
||||
return new Guide(response.data);
|
||||
@@ -308,12 +364,6 @@ export default class Innertube {
|
||||
return new History(this.actions, response);
|
||||
}
|
||||
|
||||
async getTrending(): Promise<TabbedFeed<IBrowseResponse>> {
|
||||
const browse_endpoint = new NavigationEndpoint({ browseEndpoint: { browseId: 'FEtrending' } });
|
||||
const response = await browse_endpoint.call(this.#session.actions);
|
||||
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 });
|
||||
@@ -335,8 +385,13 @@ export default class Innertube {
|
||||
async getChannel(id: string): Promise<Channel> {
|
||||
throwIfMissing({ id });
|
||||
const browse_endpoint = new NavigationEndpoint({ browseEndpoint: { browseId: id } });
|
||||
const response = await browse_endpoint.call(this.#session.actions);
|
||||
return new Channel(this.actions, response);
|
||||
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);
|
||||
}
|
||||
|
||||
async getNotifications(): Promise<NotificationsMenu> {
|
||||
@@ -399,10 +454,10 @@ export default class Innertube {
|
||||
* @param options - Format options.
|
||||
*/
|
||||
async getStreamingData(video_id: string, options: FormatOptions = {}): Promise<Format> {
|
||||
const info = await this.getBasicInfo(video_id);
|
||||
const info = await this.getBasicInfo(video_id, options);
|
||||
|
||||
const format = info.chooseFormat(options);
|
||||
format.url = format.decipher(this.#session.player);
|
||||
format.url = await format.decipher(this.#session.player);
|
||||
|
||||
return format;
|
||||
}
|
||||
@@ -414,7 +469,7 @@ export default class Innertube {
|
||||
* @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);
|
||||
}
|
||||
|
||||
@@ -430,6 +485,88 @@ export default class Innertube {
|
||||
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}.
|
||||
*/
|
||||
@@ -494,4 +631,4 @@ export default class Innertube {
|
||||
get session() {
|
||||
return this.#session;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,15 @@
|
||||
import type {
|
||||
IBrowseResponse, IGetNotificationsMenuResponse, INextResponse,
|
||||
IParsedResponse, IPlayerResponse, IRawResponse,
|
||||
IResolveURLResponse, ISearchResponse, IUpdatedMetadataResponse
|
||||
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';
|
||||
|
||||
@@ -15,17 +21,27 @@ export interface ApiResponse {
|
||||
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 {
|
||||
public session: Session;
|
||||
@@ -34,25 +50,15 @@ export default class Actions {
|
||||
this.session = session;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mimics 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())
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes calls to the playback tracking API.
|
||||
* @param url - The URL to call.
|
||||
* @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');
|
||||
@@ -72,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 (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;
|
||||
|
||||
@@ -138,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 });
|
||||
@@ -148,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 {
|
||||
|
||||
@@ -1,38 +1,63 @@
|
||||
import { Log, LZW, Constants } from '../utils/index.ts';
|
||||
import { Platform, getRandomUserAgent, getStringBetweenStrings, findFunction, 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 {
|
||||
public player_id: string;
|
||||
public sts: number;
|
||||
public nsig_sc?: string;
|
||||
public sig_sc?: string;
|
||||
public po_token?: string;
|
||||
|
||||
constructor(player_id: string, signature_timestamp: number, sig_sc?: string, nsig_sc?: string) {
|
||||
this.player_id = player_id;
|
||||
this.sts = signature_timestamp;
|
||||
this.nsig_sc = nsig_sc;
|
||||
this.sig_sc = sig_sc;
|
||||
}
|
||||
constructor(public player_id: string, public signature_timestamp: number, public data?: BuildScriptResult) { /** no-op */ }
|
||||
|
||||
static async create(cache: ICache | undefined, fetch: FetchFunction = Platform.shim.fetch, po_token?: string): Promise<Player> {
|
||||
const url = new URL('/iframe_api', Constants.URLS.YT_BASE);
|
||||
const res = await fetch(url);
|
||||
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);
|
||||
|
||||
if (!res.ok)
|
||||
throw new PlayerError(`Failed to get player id: ${res.status} (${res.statusText})`);
|
||||
if (!res.ok)
|
||||
throw new PlayerError(`Failed to get player id: ${res.status} (${res.statusText})`);
|
||||
|
||||
const js = await res.text();
|
||||
const js = await res.text();
|
||||
|
||||
const player_id = getStringBetweenStrings(js, 'player\\/', '\\/');
|
||||
player_id = getStringBetweenStrings(js, 'player\\/', '\\/');
|
||||
}
|
||||
|
||||
Log.info(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');
|
||||
@@ -47,7 +72,7 @@ export default class 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(TAG, `Could not find any cached player. Will download a new player from ${player_url}.`);
|
||||
|
||||
@@ -63,19 +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(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 }
|
||||
];
|
||||
|
||||
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
|
||||
});
|
||||
|
||||
const player = await Player.fromSource(player_id, sig_timestamp, cache, sig_sc, nsig_sc);
|
||||
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)
|
||||
@@ -84,50 +135,70 @@ export default class Player {
|
||||
const args = new URLSearchParams(url);
|
||||
const url_components = new URL(args.get('url') || url);
|
||||
|
||||
if (this.sig_sc && (signature_cipher || cipher)) {
|
||||
const signature = Platform.shim.eval(this.sig_sc, {
|
||||
sig: args.get('s')
|
||||
});
|
||||
|
||||
Log.info(TAG, `Transformed signature from ${args.get('s')} to ${signature}.`);
|
||||
|
||||
if (typeof signature !== 'string')
|
||||
throw new PlayerError('Failed to decipher signature');
|
||||
|
||||
const sp = args.get('sp');
|
||||
|
||||
if (sp) {
|
||||
url_components.searchParams.set(sp, signature);
|
||||
} else {
|
||||
url_components.searchParams.set('signature', signature);
|
||||
}
|
||||
}
|
||||
|
||||
const n = url_components.searchParams.get('n');
|
||||
const s = args.get('s');
|
||||
const sp = args.get('sp');
|
||||
|
||||
if (this.nsig_sc && 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(TAG, `Transformed n signature from ${n} to ${nsig}.`);
|
||||
|
||||
if (typeof nsig !== 'string')
|
||||
throw new PlayerError('Failed to decipher nsig');
|
||||
|
||||
if (nsig.startsWith('enhanced_except_')) {
|
||||
Log.warn(TAG, 'Something went wrong while deciphering nsig.');
|
||||
} 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.
|
||||
@@ -140,6 +211,9 @@ export default class Player {
|
||||
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;
|
||||
@@ -149,6 +223,9 @@ export default class Player {
|
||||
case 'TVHTML5':
|
||||
url_components.searchParams.set('cver', Constants.CLIENTS.TV.VERSION);
|
||||
break;
|
||||
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;
|
||||
@@ -170,81 +247,39 @@ 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 sig_sc = LZW.decompress(new TextDecoder().decode(sig_buf));
|
||||
const nsig_sc = LZW.decompress(new TextDecoder().decode(nsig_buf));
|
||||
|
||||
return new Player(player_id, sig_timestamp, sig_sc, nsig_sc);
|
||||
}
|
||||
}
|
||||
|
||||
static async fromSource(player_id: string, sig_timestamp: number, cache?: ICache, sig_sc?: string, nsig_sc?: string): Promise<Player> {
|
||||
const player = new Player(player_id, sig_timestamp, sig_sc, nsig_sc);
|
||||
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 || !this.sig_sc || !this.nsig_sc)
|
||||
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(LZW.compress(this.sig_sc));
|
||||
const nsig_buf = encoder.encode(LZW.compress(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.sts, 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(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 | undefined {
|
||||
// This used to be the prefix of the error tag (leaving it here for reference).
|
||||
let nsig_function = findFunction(data, { includes: 'enhanced_except' });
|
||||
|
||||
// This is the suffix of the error tag.
|
||||
if (!nsig_function)
|
||||
nsig_function = findFunction(data, { includes: '-_w8_' });
|
||||
|
||||
// Usually, only this function uses these dates in the entire script.
|
||||
if (!nsig_function)
|
||||
nsig_function = findFunction(data, { includes: '1969' });
|
||||
|
||||
if (nsig_function)
|
||||
return `${nsig_function.result} ${nsig_function.name}(nsig);`;
|
||||
await cache.set(this.player_id, buffer);
|
||||
}
|
||||
|
||||
get url(): string {
|
||||
@@ -252,6 +287,6 @@ export default class Player {
|
||||
}
|
||||
|
||||
static get LIBRARY_VERSION(): number {
|
||||
return 11;
|
||||
return 14;
|
||||
}
|
||||
}
|
||||
@@ -1,27 +1,36 @@
|
||||
import OAuth2 from './OAuth2.ts';
|
||||
import { Log, EventEmitter, HTTPClient, LZW, ProtoUtils } from '../utils/index.ts';
|
||||
import * as Constants from '../utils/Constants.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 { OAuth2Tokens, OAuth2AuthErrorEventHandler, OAuth2AuthPendingEventHandler, OAuth2AuthEventHandler } from './OAuth2.ts';
|
||||
import type {
|
||||
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 = 'TVHTML5',
|
||||
TV_SIMPLY = 'TVHTML5_SIMPLY',
|
||||
TV_EMBEDDED = 'TVHTML5_SIMPLY_EMBEDDED_PLAYER',
|
||||
WEB_EMBEDDED = 'WEB_EMBEDDED_PLAYER',
|
||||
WEB_CREATOR = 'WEB_CREATOR'
|
||||
@@ -47,12 +56,14 @@ export type Context = {
|
||||
clientFormFactor: string;
|
||||
userInterfaceTheme?: string;
|
||||
timeZone: string;
|
||||
userAgent?: string;
|
||||
userAgent: string;
|
||||
browserName?: string;
|
||||
browserVersion?: string;
|
||||
originalUrl?: string;
|
||||
deviceMake: string;
|
||||
deviceModel: string;
|
||||
deviceExperimentId?: string;
|
||||
rolloutToken?: string;
|
||||
utcOffsetMinutes: number;
|
||||
mainAppWebInfo?: {
|
||||
graftUrl: string;
|
||||
@@ -62,7 +73,10 @@ export type Context = {
|
||||
};
|
||||
memoryTotalKbytes?: string;
|
||||
configInfo?: {
|
||||
appInstallData: string;
|
||||
appInstallData?: string;
|
||||
coldConfigData?: string;
|
||||
coldHashData?: string;
|
||||
hotHashData?: string;
|
||||
},
|
||||
kidsAppInfo?: {
|
||||
categorySettings: {
|
||||
@@ -95,6 +109,7 @@ type ContextData = {
|
||||
visitor_data: string;
|
||||
client_name: string;
|
||||
client_version: string;
|
||||
user_agent: string;
|
||||
os_name: string;
|
||||
os_version: string;
|
||||
device_category: string;
|
||||
@@ -106,6 +121,8 @@ type ContextData = {
|
||||
device_make: string;
|
||||
device_model: string;
|
||||
on_behalf_of_user?: string;
|
||||
device_experiment_id?: string;
|
||||
rollout_token?: string;
|
||||
}
|
||||
|
||||
export type SessionOptions = {
|
||||
@@ -117,6 +134,10 @@ export type 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.
|
||||
*
|
||||
@@ -137,6 +158,10 @@ export type 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.
|
||||
@@ -145,6 +170,11 @@ export type SessionOptions = {
|
||||
* 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.
|
||||
*/
|
||||
@@ -179,15 +209,26 @@ export type SessionOptions = {
|
||||
*/
|
||||
fetch?: FetchFunction;
|
||||
/**
|
||||
* 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 genuine client.
|
||||
* 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 type SessionData = {
|
||||
context: Context;
|
||||
api_key: string;
|
||||
api_version: string;
|
||||
config_data?: string;
|
||||
}
|
||||
|
||||
interface SerializableSession extends SessionData {
|
||||
library_version: number;
|
||||
}
|
||||
|
||||
export type SWSessionData = {
|
||||
@@ -200,6 +241,7 @@ export type SessionArgs = {
|
||||
lang: string;
|
||||
location: string;
|
||||
time_zone: string;
|
||||
user_agent: string;
|
||||
device_category: DeviceCategory;
|
||||
client_name: ClientType;
|
||||
enable_safety_mode: boolean;
|
||||
@@ -213,31 +255,30 @@ const TAG = 'Session';
|
||||
* Represents an InnerTube session. This holds all the data needed to make requests to YouTube.
|
||||
*/
|
||||
export default class Session extends EventEmitter {
|
||||
public context: Context;
|
||||
public player?: Player;
|
||||
public oauth: OAuth2;
|
||||
public http: HTTPClient;
|
||||
public logged_in: boolean;
|
||||
public actions: Actions;
|
||||
public cache?: ICache;
|
||||
public key: string;
|
||||
public api_version: string;
|
||||
public account_index: number;
|
||||
public po_token?: string;
|
||||
public user_agent?: string;
|
||||
|
||||
constructor(context: Context, api_key: string, api_version: string, account_index: number, player?: Player, cookie?: string, fetch?: FetchFunction, cache?: ICache, po_token?: string) {
|
||||
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.http = new HTTPClient(this, cookie, fetch);
|
||||
this.actions = new Actions(this);
|
||||
this.oauth = new OAuth2(this);
|
||||
this.logged_in = !!cookie;
|
||||
this.cache = cache;
|
||||
this.account_index = account_index;
|
||||
this.key = api_key;
|
||||
this.api_version = api_version;
|
||||
this.context = context;
|
||||
this.player = player;
|
||||
this.po_token = po_token;
|
||||
this.user_agent = context.client.userAgent;
|
||||
}
|
||||
|
||||
on(type: 'auth', listener: OAuth2AuthEventHandler): void;
|
||||
@@ -258,13 +299,15 @@ export default class Session extends EventEmitter {
|
||||
}
|
||||
|
||||
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,
|
||||
@@ -272,12 +315,13 @@ export default class Session extends EventEmitter {
|
||||
options.on_behalf_of_user,
|
||||
options.cache,
|
||||
options.enable_session_cache,
|
||||
options.po_token
|
||||
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.po_token),
|
||||
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
|
||||
);
|
||||
}
|
||||
@@ -293,32 +337,45 @@ export default class Session extends EventEmitter {
|
||||
if (!buffer)
|
||||
return null;
|
||||
|
||||
const data = new TextDecoder().decode(buffer.slice(4));
|
||||
|
||||
try {
|
||||
const result = JSON.parse(LZW.decompress(data)) as SessionData;
|
||||
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) {
|
||||
result.context.client.visitorData = session_args.visitor_data;
|
||||
session_data.context.client.visitorData = session_args.visitor_data;
|
||||
}
|
||||
|
||||
if (session_args.lang)
|
||||
result.context.client.hl = session_args.lang;
|
||||
session_data.context.client.hl = session_args.lang;
|
||||
|
||||
if (session_args.location)
|
||||
result.context.client.gl = session_args.location;
|
||||
session_data.context.client.gl = session_args.location;
|
||||
|
||||
if (session_args.on_behalf_of_user)
|
||||
result.context.user.onBehalfOfUser = session_args.on_behalf_of_user;
|
||||
session_data.context.user.onBehalfOfUser = session_args.on_behalf_of_user;
|
||||
|
||||
result.context.client.timeZone = session_args.time_zone;
|
||||
result.context.client.platform = session_args.device_category.toUpperCase();
|
||||
result.context.client.clientName = session_args.client_name;
|
||||
result.context.user.enableSafetyMode = session_args.enable_safety_mode;
|
||||
if (session_args.user_agent)
|
||||
session_data.context.client.userAgent = session_args.user_agent;
|
||||
|
||||
return result;
|
||||
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 parse session data from cache.', error);
|
||||
Log.error(TAG, 'Failed to deserialize session data from cache.', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -328,8 +385,10 @@ export default class Session extends EventEmitter {
|
||||
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,
|
||||
@@ -337,9 +396,21 @@ export default class Session extends EventEmitter {
|
||||
on_behalf_of_user?: string,
|
||||
cache?: ICache,
|
||||
enable_session_cache = true,
|
||||
po_token?: string
|
||||
po_token?: string,
|
||||
retrieve_innertube_config = true
|
||||
) {
|
||||
const session_args = { lang, location, time_zone: tz, device_category, client_name, enable_safety_mode, visitor_data, on_behalf_of_user, po_token };
|
||||
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
|
||||
};
|
||||
|
||||
let session_data: SessionData | undefined;
|
||||
|
||||
@@ -354,16 +425,17 @@ export default class Session extends EventEmitter {
|
||||
if (!session_data) {
|
||||
Log.info(TAG, 'Generating session data.');
|
||||
|
||||
let api_key = Constants.CLIENTS.WEB.API_KEY;
|
||||
let api_version = Constants.CLIENTS.WEB.API_VERSION;
|
||||
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: Constants.CLIENTS.WEB.VERSION,
|
||||
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',
|
||||
@@ -382,6 +454,8 @@ export default class Session extends EventEmitter {
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -396,6 +470,48 @@ export default class Session extends EventEmitter {
|
||||
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);
|
||||
}
|
||||
@@ -410,13 +526,12 @@ export default class Session extends EventEmitter {
|
||||
|
||||
Log.info(TAG, 'Compressing and caching session data.');
|
||||
|
||||
const compressed_session_data = new TextEncoder().encode(LZW.compress(JSON.stringify(session_data)));
|
||||
const buffer = BinarySerializer.serialize({
|
||||
...session_data,
|
||||
library_version: parseInt(packageInfo.version)
|
||||
});
|
||||
|
||||
const buffer = new ArrayBuffer(4 + compressed_session_data.byteLength);
|
||||
new DataView(buffer).setUint32(0, compressed_session_data.byteLength, true); // (Luan) XX: Leave this here for debugging purposes
|
||||
new Uint8Array(buffer).set(compressed_session_data, 4);
|
||||
|
||||
await cache.set('innertube_session_data', new Uint8Array(buffer));
|
||||
await cache.set('innertube_session_data', buffer);
|
||||
}
|
||||
|
||||
static async #getSessionData(options: SessionArgs, fetch: FetchFunction = Platform.shim.fetch): Promise<SWSessionData> {
|
||||
@@ -430,7 +545,7 @@ export default class Session extends EventEmitter {
|
||||
const res = await fetch(url, {
|
||||
headers: {
|
||||
'Accept-Language': options.lang || 'en-US',
|
||||
'User-Agent': getRandomUserAgent('desktop'),
|
||||
'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};`
|
||||
@@ -458,11 +573,14 @@ export default class Session extends EventEmitter {
|
||||
|
||||
const context_info = {
|
||||
hl: options.lang || device_info[0],
|
||||
gl: options.location || device_info[2],
|
||||
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: device_info[16],
|
||||
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,
|
||||
@@ -472,6 +590,8 @@ export default class Session extends EventEmitter {
|
||||
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
|
||||
};
|
||||
|
||||
@@ -493,6 +613,7 @@ export default class Session extends EventEmitter {
|
||||
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',
|
||||
@@ -504,6 +625,8 @@ export default class Session extends EventEmitter {
|
||||
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',
|
||||
|
||||
@@ -1,10 +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 type { Session, ApiResponse } from '../index.ts';
|
||||
|
||||
import NavigationEndpoint from '../../parser/classes/NavigationEndpoint.ts';
|
||||
import KidsBlocklistPickerItem from '../../parser/classes/ytkids/KidsBlocklistPickerItem.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,29 +19,43 @@ export default class Kids {
|
||||
return new Search(this.#session.actions, response);
|
||||
}
|
||||
|
||||
async getInfo(video_id: string): Promise<VideoInfo> {
|
||||
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 watch_response = watch_endpoint.call(this.#session.actions, {
|
||||
const session = this.#session;
|
||||
|
||||
const extra_payload: Record<string, any> = {
|
||||
playbackContext: {
|
||||
contentPlaybackContext: {
|
||||
vis: 0,
|
||||
splay: false,
|
||||
lactMilliseconds: '-1',
|
||||
signatureTimestamp: this.#session.player?.sts
|
||||
signatureTimestamp: session.player?.signature_timestamp
|
||||
}
|
||||
},
|
||||
client: 'YTKIDS'
|
||||
});
|
||||
};
|
||||
|
||||
const watch_next_response = watch_next_endpoint.call(this.#session.actions, { client: 'YTKIDS' });
|
||||
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);
|
||||
}
|
||||
|
||||
async getChannel(channel_id: string): Promise<Channel> {
|
||||
@@ -63,7 +77,9 @@ 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 kids_blocklist_picker_command = new NavigationEndpoint({
|
||||
@@ -74,7 +90,7 @@ export default class Kids {
|
||||
}
|
||||
});
|
||||
|
||||
const response = await kids_blocklist_picker_command.call(this.#session.actions, { client: 'YTKIDS' });
|
||||
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);
|
||||
@@ -88,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());
|
||||
}
|
||||
|
||||
@@ -16,8 +16,8 @@ import AutomixPreviewVideo from '../../parser/classes/AutomixPreviewVideo.ts';
|
||||
import Message from '../../parser/classes/Message.ts';
|
||||
import MusicDescriptionShelf from '../../parser/classes/MusicDescriptionShelf.ts';
|
||||
import MusicQueue from '../../parser/classes/MusicQueue.ts';
|
||||
import MusicTwoRowItem from '../../parser/classes/MusicTwoRowItem.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';
|
||||
@@ -27,7 +27,7 @@ import Tab from '../../parser/classes/Tab.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 {
|
||||
@@ -42,34 +42,47 @@ 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 | MusicResponsiveListItem | NavigationEndpoint): Promise<TrackInfo> {
|
||||
getInfo(target: string | MusicTwoRowItem | MusicResponsiveListItem | NavigationEndpoint, options?: Omit<GetVideoInfoOptions, 'client'>): Promise<TrackInfo> {
|
||||
if (target instanceof MusicTwoRowItem) {
|
||||
return this.#fetchInfoFromEndpoint(target.endpoint);
|
||||
return this.#fetchInfoFromEndpoint(target.endpoint, options);
|
||||
} else if (target instanceof MusicResponsiveListItem) {
|
||||
return this.#fetchInfoFromEndpoint(target.overlay?.content?.endpoint ?? target.endpoint);
|
||||
return this.#fetchInfoFromEndpoint(target.overlay?.content?.endpoint ?? target.endpoint, options);
|
||||
} else if (target instanceof NavigationEndpoint) {
|
||||
return this.#fetchInfoFromEndpoint(target);
|
||||
}
|
||||
return this.#fetchInfoFromVideoId(target);
|
||||
return this.#fetchInfoFromEndpoint(target, options);
|
||||
}
|
||||
return this.#fetchInfoFromVideoId(target, options);
|
||||
}
|
||||
|
||||
async #fetchInfoFromVideoId(video_id: string): Promise<TrackInfo> {
|
||||
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 watch_response = watch_endpoint.call(this.#actions, {
|
||||
const extra_payload: Record<string, any> = {
|
||||
playbackContext: {
|
||||
contentPlaybackContext: {
|
||||
vis: 0,
|
||||
splay: false,
|
||||
lactMilliseconds: '-1',
|
||||
signatureTimestamp: this.#session.player?.sts
|
||||
signatureTimestamp: this.#session.player?.signature_timestamp
|
||||
}
|
||||
},
|
||||
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 watch_response = watch_endpoint.call(this.#actions, extra_payload);
|
||||
|
||||
const watch_next_response = watch_next_endpoint.call(this.#actions, { client: 'YTMUSIC' });
|
||||
|
||||
@@ -79,20 +92,33 @@ export default class Music {
|
||||
return new TrackInfo(response, this.#actions, cpn);
|
||||
}
|
||||
|
||||
async #fetchInfoFromEndpoint(endpoint?: NavigationEndpoint): Promise<TrackInfo> {
|
||||
async #fetchInfoFromEndpoint(endpoint?: NavigationEndpoint, options?: GetVideoInfoOptions): Promise<TrackInfo> {
|
||||
if (!endpoint)
|
||||
throw new Error('This item does not have an endpoint.');
|
||||
|
||||
const player_response = 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'
|
||||
};
|
||||
|
||||
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',
|
||||
@@ -184,7 +210,7 @@ export default class Music {
|
||||
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.');
|
||||
@@ -211,7 +237,7 @@ 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;
|
||||
@@ -225,7 +251,7 @@ export default class Music {
|
||||
|
||||
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.');
|
||||
@@ -246,7 +272,7 @@ export default class Music {
|
||||
|
||||
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.');
|
||||
@@ -271,7 +297,11 @@ export default class Music {
|
||||
}
|
||||
|
||||
async getSearchSuggestions(input: string): Promise<ObservedArray<SearchSuggestionsSection>> {
|
||||
const response = await this.#actions.execute('/music/get_search_suggestions', { input, client: 'YTMUSIC', parse: true });
|
||||
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>;
|
||||
|
||||
@@ -45,28 +45,30 @@ export default class Studio {
|
||||
* ```
|
||||
*/
|
||||
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: MetadataUpdateRequest = {
|
||||
context: {
|
||||
client: {
|
||||
osName: 'Android',
|
||||
clientName: parseInt(Constants.CLIENTS.ANDROID.NAME_ID),
|
||||
clientName: parseInt(Constants.CLIENT_NAME_IDS.ANDROID),
|
||||
clientVersion: Constants.CLIENTS.ANDROID.VERSION,
|
||||
androidSdkVersion: Constants.CLIENTS.ANDROID.SDK_VERSION,
|
||||
visitorData: this.#session.context.client.visitorData,
|
||||
visitorData: session.context.client.visitorData,
|
||||
osVersion: '13',
|
||||
acceptLanguage: this.#session.context.client.hl,
|
||||
acceptRegion: this.#session.context.client.gl,
|
||||
acceptLanguage: session.context.client.hl,
|
||||
acceptRegion: session.context.client.gl,
|
||||
deviceMake: 'Google',
|
||||
deviceModel: 'sdk_gphone64_x86_64',
|
||||
screenHeightPoints: 840,
|
||||
screenWidthPoints: 432,
|
||||
configInfo: {
|
||||
appInstallData: this.#session.context.client.configInfo?.appInstallData
|
||||
appInstallData: session.context.client.configInfo?.appInstallData
|
||||
},
|
||||
timeZone: this.#session.context.client.timeZone,
|
||||
timeZone: session.context.client.timeZone,
|
||||
chipset: 'qcom;taro'
|
||||
},
|
||||
activePlayers: []
|
||||
@@ -131,7 +133,7 @@ export default class Studio {
|
||||
|
||||
const writer = MetadataUpdateRequest.encode(payload);
|
||||
|
||||
return await this.#session.actions.execute('/video_manager/metadata_update', {
|
||||
return await session.actions.execute('/video_manager/metadata_update', {
|
||||
protobuf: true,
|
||||
serialized_data: writer.finish()
|
||||
});
|
||||
|
||||
@@ -65,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.
|
||||
@@ -197,12 +237,11 @@ export default class PlaylistManager {
|
||||
}
|
||||
|
||||
async #getPlaylist(playlist_id: string): Promise<Playlist> {
|
||||
let id = playlist_id;
|
||||
if (!playlist_id.startsWith('VL')) {
|
||||
playlist_id = `VL${playlist_id}`;
|
||||
}
|
||||
|
||||
if (!id.startsWith('VL'))
|
||||
id = `VL${id}`;
|
||||
|
||||
const browse_endpoint = new NavigationEndpoint({ browseEndpoint: { browseId: `VL${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);
|
||||
|
||||
@@ -29,10 +29,13 @@ import TwoColumnSearchResults from '../../parser/classes/TwoColumnSearchResults.
|
||||
import WatchCardCompactVideo from '../../parser/classes/WatchCardCompactVideo.ts';
|
||||
|
||||
import type { Actions, ApiResponse } from '../index.ts';
|
||||
import type { Memo, ObservedArray, SuperParsedResult, YTNode } from '../../parser/helpers.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 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> {
|
||||
readonly #page: T;
|
||||
@@ -139,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;
|
||||
}
|
||||
@@ -157,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;
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ import type PlayerLiveStoryboardSpec from '../../parser/classes/PlayerLiveStoryb
|
||||
import type PlayerStoryboardSpec from '../../parser/classes/PlayerStoryboardSpec.ts';
|
||||
|
||||
export default class MediaInfo {
|
||||
readonly #page: [IPlayerResponse, INextResponse?];
|
||||
readonly #page: [ IPlayerResponse, INextResponse? ];
|
||||
readonly #actions: Actions;
|
||||
readonly #cpn: string;
|
||||
readonly #playback_tracking?: IPlaybackTracking;
|
||||
@@ -46,7 +46,7 @@ export default class MediaInfo {
|
||||
public playability_status?: IPlayabilityStatus;
|
||||
public player_config?: IPlayerConfig;
|
||||
|
||||
constructor(data: [ApiResponse, ApiResponse?], actions: Actions, cpn: string) {
|
||||
constructor(data: [ ApiResponse, ApiResponse? ], actions: Actions, cpn: string) {
|
||||
this.#actions = actions;
|
||||
|
||||
const info = Parser.parseResponse<IPlayerResponse>(data[0].data.playerResponse ? data[0].data.playerResponse : data[0].data);
|
||||
@@ -98,13 +98,16 @@ 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.');
|
||||
@@ -113,25 +116,25 @@ export default class MediaInfo {
|
||||
let storyboards;
|
||||
let captions;
|
||||
|
||||
if (options.include_thumbnails && player_response.storyboards) {
|
||||
if (manifest_options.include_thumbnails && player_response.storyboards) {
|
||||
storyboards = player_response.storyboards;
|
||||
}
|
||||
|
||||
if (typeof options.captions_format === 'string' && player_response.captions?.caption_tracks) {
|
||||
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,
|
||||
url_transformer,
|
||||
format_filter,
|
||||
options.url_transformer,
|
||||
options.format_filter,
|
||||
this.#cpn,
|
||||
this.#actions.session.player,
|
||||
this.#actions,
|
||||
storyboards,
|
||||
captions,
|
||||
options
|
||||
manifest_options
|
||||
);
|
||||
}
|
||||
|
||||
@@ -185,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)
|
||||
@@ -202,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');
|
||||
|
||||
@@ -218,6 +218,26 @@ export default class MediaInfo {
|
||||
|
||||
const url = this.#playback_tracking.videostats_playback_url.replace('https://s.', replacement);
|
||||
|
||||
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
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -2,11 +2,13 @@ import Text from './misc/Text.ts';
|
||||
import { YTNode } from '../helpers.ts';
|
||||
import type { RawNode } from '../index.ts';
|
||||
|
||||
export type AlertType = 'UNKNOWN' | 'WARNING' | 'ERROR' | 'SUCCESS' | 'INFO';
|
||||
|
||||
export default class Alert extends YTNode {
|
||||
static type = 'Alert';
|
||||
|
||||
text: Text;
|
||||
alert_type: string;
|
||||
alert_type: AlertType;
|
||||
|
||||
constructor(data: RawNode) {
|
||||
super();
|
||||
|
||||
14
deno/src/parser/classes/AnimatedThumbnailOverlayView.ts
Normal file
14
deno/src/parser/classes/AnimatedThumbnailOverlayView.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { YTNode } from '../helpers.ts';
|
||||
import type { RawNode } from '../types/index.ts';
|
||||
import Thumbnail from './misc/Thumbnail.ts';
|
||||
|
||||
export default class AnimatedThumbnailOverlayView extends YTNode {
|
||||
static type = 'AnimatedThumbnailOverlayView';
|
||||
|
||||
public thumbnail: Thumbnail[];
|
||||
|
||||
constructor(data: RawNode) {
|
||||
super();
|
||||
this.thumbnail = Thumbnail.fromResponse(data.thumbnail);
|
||||
}
|
||||
}
|
||||
25
deno/src/parser/classes/AvatarStackView.ts
Normal file
25
deno/src/parser/classes/AvatarStackView.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { type ObservedArray, YTNode } from '../helpers.ts';
|
||||
import type { RawNode } from '../types/index.ts';
|
||||
import { Parser } from '../index.ts';
|
||||
|
||||
import Text from './misc/Text.ts';
|
||||
import AvatarView from './AvatarView.ts';
|
||||
import RendererContext from './misc/RendererContext.ts';
|
||||
|
||||
export default class AvatarStackView extends YTNode {
|
||||
static type = 'AvatarStackView';
|
||||
|
||||
public avatars: ObservedArray<AvatarView>;
|
||||
public text?: Text;
|
||||
public renderer_context: RendererContext;
|
||||
|
||||
constructor(data: RawNode) {
|
||||
super();
|
||||
this.avatars = Parser.parseArray(data.avatars, AvatarView);
|
||||
|
||||
if (Reflect.has(data, 'text'))
|
||||
this.text = Text.fromAttributed(data.text);
|
||||
|
||||
this.renderer_context = new RendererContext(data.rendererContext);
|
||||
}
|
||||
}
|
||||
25
deno/src/parser/classes/BackgroundPromo.ts
Normal file
25
deno/src/parser/classes/BackgroundPromo.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { YTNode } from '../helpers.ts';
|
||||
import { Parser, type RawNode } from '../index.ts';
|
||||
import Text from './misc/Text.ts';
|
||||
import Button from './Button.ts';
|
||||
import ButtonView from './ButtonView.ts';
|
||||
|
||||
export default class BackgroundPromo extends YTNode {
|
||||
static type = 'BackgroundPromo';
|
||||
|
||||
public body_text?: Text;
|
||||
public cta_button?: Button | ButtonView | null;
|
||||
public icon_type?: string;
|
||||
public title?: Text;
|
||||
|
||||
constructor(data: RawNode) {
|
||||
super();
|
||||
this.body_text = new Text(data.bodyText);
|
||||
this.cta_button = Parser.parseItem(data.ctaButton, [ Button, ButtonView ]);
|
||||
|
||||
if (Reflect.has(data, 'icon'))
|
||||
this.icon_type = data.icon.iconType;
|
||||
|
||||
this.title = new Text(data.title);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { YTNode } from '../helpers.ts';
|
||||
import type { RawNode } from '../types/RawResponse.ts';
|
||||
import type { RawNode } from '../types/index.ts';
|
||||
|
||||
export default class BadgeView extends YTNode {
|
||||
text: string;
|
||||
|
||||
@@ -1,13 +1,19 @@
|
||||
import { Parser, type RawNode } from '../index.ts';
|
||||
import { type ObservedArray, YTNode } from '../helpers.ts';
|
||||
import SubFeedSelector from './SubFeedSelector.ts';
|
||||
import EomSettingsDisclaimer from './EomSettingsDisclaimer.ts';
|
||||
import ToggleButton from './ToggleButton.ts';
|
||||
import CompactLink from './CompactLink.ts';
|
||||
import SearchBox from './SearchBox.ts';
|
||||
import Button from './Button.ts';
|
||||
|
||||
export default class BrowseFeedActions extends YTNode {
|
||||
static type = 'BrowseFeedActions';
|
||||
|
||||
contents: ObservedArray<YTNode>;
|
||||
public contents: ObservedArray<SubFeedSelector | EomSettingsDisclaimer | ToggleButton | CompactLink | SearchBox | Button>;
|
||||
|
||||
constructor(data: RawNode) {
|
||||
super();
|
||||
this.contents = Parser.parseArray(data.contents);
|
||||
this.contents = Parser.parseArray(data.contents, [ SubFeedSelector, EomSettingsDisclaimer, ToggleButton, CompactLink, SearchBox, Button ]);
|
||||
}
|
||||
}
|
||||
@@ -2,34 +2,56 @@ import Text from './misc/Text.ts';
|
||||
import NavigationEndpoint from './NavigationEndpoint.ts';
|
||||
import { YTNode } from '../helpers.ts';
|
||||
import type { RawNode } from '../index.ts';
|
||||
import AccessibilityData, { type AccessibilitySupportedDatas } from './misc/AccessibilityData.ts';
|
||||
|
||||
export default class Button extends YTNode {
|
||||
static type = 'Button';
|
||||
|
||||
text?: string;
|
||||
label?: string;
|
||||
tooltip?: string;
|
||||
icon_type?: string;
|
||||
is_disabled?: boolean;
|
||||
endpoint: NavigationEndpoint;
|
||||
|
||||
public text?: string;
|
||||
public label?: string;
|
||||
public tooltip?: string;
|
||||
public style?: string;
|
||||
public size?: string;
|
||||
public icon_type?: string;
|
||||
public is_disabled?: boolean;
|
||||
public target_id?: string;
|
||||
public endpoint: NavigationEndpoint;
|
||||
public accessibility?: AccessibilitySupportedDatas;
|
||||
|
||||
constructor(data: RawNode) {
|
||||
super();
|
||||
if (Reflect.has(data, 'text'))
|
||||
this.text = new Text(data.text).toString();
|
||||
|
||||
if (Reflect.has(data, 'accessibility') && Reflect.has(data.accessibility, 'label'))
|
||||
if (Reflect.has(data, 'accessibility') && Reflect.has(data.accessibility, 'label')) {
|
||||
this.label = data.accessibility.label;
|
||||
}
|
||||
|
||||
if ('accessibilityData' in data
|
||||
&& 'accessibilityData' in data.accessibilityData) {
|
||||
this.accessibility = {
|
||||
accessibility_data: new AccessibilityData(data.accessibilityData.accessibilityData)
|
||||
};
|
||||
}
|
||||
|
||||
if (Reflect.has(data, 'tooltip'))
|
||||
this.tooltip = data.tooltip;
|
||||
|
||||
if (Reflect.has(data, 'style'))
|
||||
this.style = data.style;
|
||||
|
||||
if (Reflect.has(data, 'size'))
|
||||
this.size = data.size;
|
||||
|
||||
if (Reflect.has(data, 'icon') && Reflect.has(data.icon, 'iconType'))
|
||||
this.icon_type = data.icon.iconType;
|
||||
|
||||
if (Reflect.has(data, 'isDisabled'))
|
||||
this.is_disabled = data.isDisabled;
|
||||
|
||||
if (Reflect.has(data, 'targetId'))
|
||||
this.target_id = data.targetId;
|
||||
|
||||
this.endpoint = new NavigationEndpoint(data.navigationEndpoint || data.serviceEndpoint || data.command);
|
||||
}
|
||||
}
|
||||
18
deno/src/parser/classes/ButtonCardView.ts
Normal file
18
deno/src/parser/classes/ButtonCardView.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { YTNode } from '../helpers.ts';
|
||||
import type { RawNode } from '../index.ts';
|
||||
import RendererContext from './misc/RendererContext.ts';
|
||||
|
||||
export default class ButtonCardView extends YTNode {
|
||||
static type = 'ButtonCardView';
|
||||
|
||||
public title: string;
|
||||
public icon_name: string;
|
||||
public renderer_context: RendererContext;
|
||||
|
||||
constructor(data: RawNode) {
|
||||
super();
|
||||
this.title = data.title;
|
||||
this.icon_name = data.image.sources[0].clientResource.imageName;
|
||||
this.renderer_context = new RendererContext(data.rendererContext);
|
||||
}
|
||||
}
|
||||
@@ -1,28 +1,124 @@
|
||||
import { YTNode } from '../helpers.ts';
|
||||
import type { RawNode } from '../index.ts';
|
||||
import NavigationEndpoint from './NavigationEndpoint.ts';
|
||||
import Thumbnail from './misc/Thumbnail.ts';
|
||||
|
||||
export default class ButtonView extends YTNode {
|
||||
static type = 'ButtonView';
|
||||
|
||||
icon_name: string;
|
||||
title: string;
|
||||
accessibility_text: string;
|
||||
style: string;
|
||||
is_full_width: boolean;
|
||||
button_type: string;
|
||||
button_size: string;
|
||||
on_tap: NavigationEndpoint;
|
||||
public secondary_icon_image?: Thumbnail[];
|
||||
public icon_name?: string;
|
||||
public enable_icon_button?: boolean;
|
||||
public tooltip?: string;
|
||||
public icon_image_flip_for_rtl?: boolean;
|
||||
public button_size?: 'BUTTON_VIEW_MODEL_SIZE_UNKNOWN' | 'BUTTON_VIEW_MODEL_SIZE_DEFAULT' | 'BUTTON_VIEW_MODEL_SIZE_COMPACT' | 'BUTTON_VIEW_MODEL_SIZE_XSMALL' | 'BUTTON_VIEW_MODEL_SIZE_LARGE' | 'BUTTON_VIEW_MODEL_SIZE_XLARGE' | 'BUTTON_VIEW_MODEL_SIZE_XXLARGE';
|
||||
public icon_position?: 'BUTTON_VIEW_MODEL_ICON_POSITION_UNKNOWN' | 'BUTTON_VIEW_MODEL_ICON_POSITION_TRAILING' | 'BUTTON_VIEW_MODEL_ICON_POSITION_LEADING' | 'BUTTON_VIEW_MODEL_ICON_POSITION_ABOVE' | 'BUTTON_VIEW_MODEL_ICON_POSITION_LEADING_TRAILING';
|
||||
public is_full_width?: boolean;
|
||||
public state?: 'BUTTON_VIEW_MODEL_STATE_UNKNOWN' | 'BUTTON_VIEW_MODEL_STATE_ACTIVE' | 'BUTTON_VIEW_MODEL_STATE_INACTIVE' | 'BUTTON_VIEW_MODEL_STATE_DISABLED';
|
||||
public on_disabled_tap?: NavigationEndpoint;
|
||||
public custom_border_color?: number;
|
||||
public on_tap?: NavigationEndpoint;
|
||||
public style?: 'BUTTON_VIEW_MODEL_STYLE_UNKNOWN' | 'BUTTON_VIEW_MODEL_STYLE_CTA' | 'BUTTON_VIEW_MODEL_STYLE_BRAND' | 'BUTTON_VIEW_MODEL_STYLE_ADS_CTA' | 'BUTTON_VIEW_MODEL_STYLE_OVERLAY' | 'BUTTON_VIEW_MODEL_STYLE_CTA_THEMED' | 'BUTTON_VIEW_MODEL_STYLE_BLACK_CTA' | 'BUTTON_VIEW_MODEL_STYLE_CUSTOM' | 'BUTTON_VIEW_MODEL_STYLE_MONO' | 'BUTTON_VIEW_MODEL_STYLE_OVERLAY_DARK' | 'BUTTON_VIEW_MODEL_STYLE_CTA_OVERLAY' | 'BUTTON_VIEW_MODEL_STYLE_BRAND_AI' | 'BUTTON_VIEW_MODEL_STYLE_YT_GRADIENT' | 'BUTTON_VIEW_MODEL_STYLE_BRAND_GRADIENT';
|
||||
public icon_image?: object;
|
||||
public custom_dark_theme_border_color?: number;
|
||||
public title?: string;
|
||||
public target_id?: string;
|
||||
public enable_full_width_margins?: boolean;
|
||||
public custom_font_color?: number;
|
||||
public button_type?: 'BUTTON_VIEW_MODEL_TYPE_UNKNOWN' | 'BUTTON_VIEW_MODEL_TYPE_FILLED' | 'BUTTON_VIEW_MODEL_TYPE_OUTLINE' | 'BUTTON_VIEW_MODEL_TYPE_TEXT' | 'BUTTON_VIEW_MODEL_TYPE_TONAL';
|
||||
public enabled?: boolean;
|
||||
public accessibility_id?: string;
|
||||
public custom_background_color?: number;
|
||||
public on_long_press?: NavigationEndpoint;
|
||||
public title_formatted?: object;
|
||||
public on_visible?: object;
|
||||
public icon_trailing?: boolean;
|
||||
public accessibility_text?: string;
|
||||
|
||||
constructor(data: RawNode) {
|
||||
super();
|
||||
this.icon_name = data.iconName;
|
||||
this.title = data.title;
|
||||
this.accessibility_text = data.accessibilityText;
|
||||
this.style = data.style;
|
||||
this.is_full_width = data.isFullWidth;
|
||||
this.button_type = data.type;
|
||||
this.button_size = data.buttonSize;
|
||||
this.on_tap = new NavigationEndpoint(data.onTap);
|
||||
if ('secondaryIconImage' in data)
|
||||
this.secondary_icon_image = Thumbnail.fromResponse(data.secondaryIconImage);
|
||||
|
||||
if ('iconName' in data)
|
||||
this.icon_name = data.iconName;
|
||||
|
||||
if ('enableIconButton' in data)
|
||||
this.enable_icon_button = data.enableIconButton;
|
||||
|
||||
if ('tooltip' in data)
|
||||
this.tooltip = data.tooltip;
|
||||
|
||||
if ('iconImageFlipForRtl' in data)
|
||||
this.icon_image_flip_for_rtl = data.iconImageFlipForRtl;
|
||||
|
||||
if ('buttonSize' in data)
|
||||
this.button_size = data.buttonSize;
|
||||
|
||||
if ('iconPosition' in data)
|
||||
this.icon_position = data.iconPosition;
|
||||
|
||||
if ('isFullWidth' in data)
|
||||
this.is_full_width = data.isFullWidth;
|
||||
|
||||
if ('state' in data)
|
||||
this.state = data.state;
|
||||
|
||||
if ('onDisabledTap' in data)
|
||||
this.on_disabled_tap = new NavigationEndpoint(data.onDisabledTap);
|
||||
|
||||
if ('customBorderColor' in data)
|
||||
this.custom_border_color = data.customBorderColor;
|
||||
|
||||
if ('onTap' in data)
|
||||
this.on_tap = new NavigationEndpoint(data.onTap);
|
||||
|
||||
if ('style' in data)
|
||||
this.style = data.style;
|
||||
|
||||
if ('iconImage' in data)
|
||||
this.icon_image = data.iconImage;
|
||||
|
||||
if ('customDarkThemeBorderColor' in data)
|
||||
this.custom_dark_theme_border_color = data.customDarkThemeBorderColor;
|
||||
|
||||
if ('title' in data)
|
||||
this.title = data.title;
|
||||
|
||||
if ('targetId' in data)
|
||||
this.target_id = data.targetId;
|
||||
|
||||
if ('enableFullWidthMargins' in data)
|
||||
this.enable_full_width_margins = data.enableFullWidthMargins;
|
||||
|
||||
if ('customFontColor' in data)
|
||||
this.custom_font_color = data.customFontColor;
|
||||
|
||||
if ('type' in data)
|
||||
this.button_type = data.type;
|
||||
|
||||
if ('enabled' in data)
|
||||
this.enabled = data.enabled;
|
||||
|
||||
if ('accessibilityId' in data)
|
||||
this.accessibility_id = data.accessibilityId;
|
||||
|
||||
if ('customBackgroundColor' in data)
|
||||
this.custom_background_color = data.customBackgroundColor;
|
||||
|
||||
if ('onLongPress' in data)
|
||||
this.on_long_press = new NavigationEndpoint(data.onLongPress);
|
||||
|
||||
if ('titleFormatted' in data)
|
||||
this.title_formatted = data.titleFormatted;
|
||||
|
||||
if ('onVisible' in data)
|
||||
this.on_visible = data.onVisible;
|
||||
|
||||
if ('iconTrailing' in data)
|
||||
this.icon_trailing = data.iconTrailing;
|
||||
|
||||
if ('accessibilityText' in data)
|
||||
this.accessibility_text = data.accessibilityText;
|
||||
}
|
||||
}
|
||||
16
deno/src/parser/classes/CarouselItemView.ts
Normal file
16
deno/src/parser/classes/CarouselItemView.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { YTNode } from '../helpers.ts';
|
||||
import { Parser, type RawNode } from '../index.ts';
|
||||
import TextCarouselItemView from './TextCarouselItemView.ts';
|
||||
|
||||
export default class CarouselItemView extends YTNode {
|
||||
static type = 'CarouselItemView';
|
||||
|
||||
item_type: string;
|
||||
carousel_item: TextCarouselItemView | null;
|
||||
|
||||
constructor(data: RawNode) {
|
||||
super();
|
||||
this.item_type = data.itemType;
|
||||
this.carousel_item = Parser.parseItem(data.carouselItem, TextCarouselItemView);
|
||||
}
|
||||
}
|
||||
18
deno/src/parser/classes/CarouselTitleView.ts
Normal file
18
deno/src/parser/classes/CarouselTitleView.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { YTNode } from '../helpers.ts';
|
||||
import { Parser, type RawNode } from '../index.ts';
|
||||
import ButtonView from './ButtonView.ts';
|
||||
|
||||
export default class CarouselTitleView extends YTNode {
|
||||
static type = 'CarouselTitleView';
|
||||
|
||||
title: string;
|
||||
previous_button: ButtonView | null;
|
||||
next_button: ButtonView | null;
|
||||
|
||||
constructor(data: RawNode) {
|
||||
super();
|
||||
this.title = data.title;
|
||||
this.previous_button = Parser.parseItem(data.previousButton, ButtonView);
|
||||
this.next_button = Parser.parseItem(data.nextButton, ButtonView);
|
||||
}
|
||||
}
|
||||
@@ -5,21 +5,21 @@ import NavigationEndpoint from './NavigationEndpoint.ts';
|
||||
export default class ChannelSubMenu extends YTNode {
|
||||
static type = 'ChannelSubMenu';
|
||||
|
||||
content_type_sub_menu_items: {
|
||||
public content_type_sub_menu_items: {
|
||||
endpoint: NavigationEndpoint;
|
||||
selected: boolean;
|
||||
title: string;
|
||||
}[];
|
||||
|
||||
sort_setting;
|
||||
public sort_setting: YTNode;
|
||||
|
||||
constructor(data: RawNode) {
|
||||
super();
|
||||
this.content_type_sub_menu_items = data.contentTypeSubMenuItems.map((item: RawNode) => ({
|
||||
this.content_type_sub_menu_items = data.sortSetting?.sortFilterSubMenuRenderer?.subMenuItems?.map((item: RawNode) => ({
|
||||
endpoint: new NavigationEndpoint(item.navigationEndpoint || item.endpoint),
|
||||
selected: item.selected,
|
||||
title: item.title
|
||||
}));
|
||||
})) || [];
|
||||
this.sort_setting = Parser.parseItem(data.sortSetting);
|
||||
}
|
||||
}
|
||||
@@ -2,18 +2,29 @@ import Thumbnail from './misc/Thumbnail.ts';
|
||||
import NavigationEndpoint from './NavigationEndpoint.ts';
|
||||
import { YTNode } from '../helpers.ts';
|
||||
import type { RawNode } from '../index.ts';
|
||||
import AccessibilityData, { type AccessibilitySupportedDatas } from './misc/AccessibilityData.ts';
|
||||
|
||||
export default class ChannelThumbnailWithLink extends YTNode {
|
||||
static type = 'ChannelThumbnailWithLink';
|
||||
|
||||
thumbnails: Thumbnail[];
|
||||
endpoint: NavigationEndpoint;
|
||||
label?: string;
|
||||
public thumbnails: Thumbnail[];
|
||||
public endpoint: NavigationEndpoint;
|
||||
public accessibility?: AccessibilitySupportedDatas;
|
||||
|
||||
constructor(data: RawNode) {
|
||||
super();
|
||||
this.thumbnails = Thumbnail.fromResponse(data.thumbnail);
|
||||
this.endpoint = new NavigationEndpoint(data.navigationEndpoint);
|
||||
this.label = data.accessibility?.accessibilityData?.label;
|
||||
|
||||
if ('accessibility' in data
|
||||
&& 'accessibilityData' in data.accessibility) {
|
||||
this.accessibility = {
|
||||
accessibility_data: new AccessibilityData(data.accessibility.accessibilityData)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
get label(): string | undefined {
|
||||
return this.accessibility?.accessibility_data?.label;
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@ import ChipView from './ChipView.ts';
|
||||
export default class ChipBarView extends YTNode {
|
||||
static type = 'ChipBarView';
|
||||
|
||||
chips: ObservedArray<ChipView> | null;
|
||||
chips: ObservedArray<ChipView>;
|
||||
|
||||
constructor(data: RawNode) {
|
||||
super();
|
||||
|
||||
46
deno/src/parser/classes/ClientSideToggleMenuItem.ts
Normal file
46
deno/src/parser/classes/ClientSideToggleMenuItem.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import { YTNode } from '../helpers.ts';
|
||||
import type { RawNode } from '../index.ts';
|
||||
import NavigationEndpoint from './NavigationEndpoint.ts';
|
||||
import Text from './misc/Text.ts';
|
||||
|
||||
export default class ClientSideToggleMenuItem extends YTNode {
|
||||
static type = 'ClientSideToggleMenuItem';
|
||||
|
||||
text: Text;
|
||||
icon_type: string;
|
||||
toggled_text: Text;
|
||||
toggled_icon_type: string;
|
||||
is_toggled?: boolean;
|
||||
menu_item_identifier: string;
|
||||
endpoint: NavigationEndpoint;
|
||||
logging_directives?: {
|
||||
visibility: {
|
||||
types: string;
|
||||
},
|
||||
enable_displaylogger_experiment: boolean;
|
||||
};
|
||||
|
||||
constructor(data: RawNode) {
|
||||
super();
|
||||
this.text = new Text(data.defaultText);
|
||||
this.icon_type = data.defaultIcon.iconType;
|
||||
this.toggled_text = new Text(data.toggledText);
|
||||
this.toggled_icon_type = data.toggledIcon.iconType;
|
||||
|
||||
if (Reflect.has(data, 'isToggled')) {
|
||||
this.is_toggled = data.isToggled;
|
||||
}
|
||||
|
||||
this.menu_item_identifier = data.menuItemIdentifier;
|
||||
this.endpoint = new NavigationEndpoint(data.command);
|
||||
|
||||
if (Reflect.has(data, 'loggingDirectives')) {
|
||||
this.logging_directives = {
|
||||
visibility: {
|
||||
types: data.loggingDirectives.visibility.types
|
||||
},
|
||||
enable_displaylogger_experiment: data.loggingDirectives.enableDisplayloggerExperiment
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,23 +2,26 @@ import { YTNode } from '../helpers.ts';
|
||||
import { Parser, type RawNode } from '../index.ts';
|
||||
import ThumbnailView from './ThumbnailView.ts';
|
||||
|
||||
interface StackColor {
|
||||
light_theme?: number;
|
||||
dark_theme?: number;
|
||||
}
|
||||
|
||||
export default class CollectionThumbnailView extends YTNode {
|
||||
static type = 'CollectionThumbnailView';
|
||||
|
||||
primary_thumbnail: ThumbnailView | null;
|
||||
stack_color?: {
|
||||
light_theme: number;
|
||||
dark_theme: number;
|
||||
};
|
||||
public primary_thumbnail: ThumbnailView | null;
|
||||
public stack_color?: StackColor;
|
||||
|
||||
constructor(data: RawNode) {
|
||||
super();
|
||||
|
||||
this.primary_thumbnail = Parser.parseItem(data.primaryThumbnail, ThumbnailView);
|
||||
if (data.stackColor) {
|
||||
|
||||
if ('stackColor' in data) {
|
||||
this.stack_color = {
|
||||
light_theme: data.stackColor.lightTheme,
|
||||
dark_theme: data.stackColor.darkTheme
|
||||
light_theme: data.stackColor?.lightTheme,
|
||||
dark_theme: data.stackColor?.darkTheme
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,14 +6,27 @@ import type { RawNode } from '../index.ts';
|
||||
export default class CompactLink extends YTNode {
|
||||
static type = 'CompactLink';
|
||||
|
||||
title: string;
|
||||
endpoint: NavigationEndpoint;
|
||||
style: string;
|
||||
public title: string;
|
||||
public subtitle?: Text;
|
||||
public endpoint: NavigationEndpoint;
|
||||
public style: string;
|
||||
public icon_type?: string;
|
||||
public secondary_icon_type?: string;
|
||||
|
||||
constructor(data: RawNode) {
|
||||
super();
|
||||
this.title = new Text(data.title).toString();
|
||||
this.endpoint = new NavigationEndpoint(data.navigationEndpoint);
|
||||
|
||||
if ('subtitle' in data)
|
||||
this.subtitle = new Text(data.subtitle);
|
||||
|
||||
if ('icon' in data && 'iconType' in data.icon)
|
||||
this.icon_type = data.icon.iconType;
|
||||
|
||||
if ('secondaryIcon' in data && 'iconType' in data.secondaryIcon)
|
||||
this.secondary_icon_type = data.secondaryIcon.iconType;
|
||||
|
||||
this.endpoint = new NavigationEndpoint(data.navigationEndpoint || data.serviceEndpoint);
|
||||
this.style = data.style;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { timeToSeconds } from '../../utils/Utils.ts';
|
||||
import { YTNode, type ObservedArray } from '../helpers.ts';
|
||||
import { type ObservedArray, YTNode } from '../helpers.ts';
|
||||
import { Parser, type RawNode } from '../index.ts';
|
||||
import Menu from './menus/Menu.ts';
|
||||
import MetadataBadge from './MetadataBadge.ts';
|
||||
@@ -7,53 +7,90 @@ import Author from './misc/Author.ts';
|
||||
import Text from './misc/Text.ts';
|
||||
import Thumbnail from './misc/Thumbnail.ts';
|
||||
import NavigationEndpoint from './NavigationEndpoint.ts';
|
||||
import ThumbnailOverlayTimeStatus from './ThumbnailOverlayTimeStatus.ts';
|
||||
|
||||
export default class CompactVideo extends YTNode {
|
||||
static type = 'CompactVideo';
|
||||
|
||||
id: string;
|
||||
thumbnails: Thumbnail[];
|
||||
rich_thumbnail?: YTNode;
|
||||
title: Text;
|
||||
author: Author;
|
||||
view_count: Text;
|
||||
short_view_count: Text;
|
||||
published: Text;
|
||||
badges: MetadataBadge[];
|
||||
|
||||
duration: {
|
||||
text: string;
|
||||
seconds: number;
|
||||
};
|
||||
|
||||
thumbnail_overlays: ObservedArray<YTNode>;
|
||||
endpoint: NavigationEndpoint;
|
||||
menu: Menu | null;
|
||||
public video_id: string;
|
||||
public thumbnails: Thumbnail[];
|
||||
public rich_thumbnail?: YTNode;
|
||||
public title: Text;
|
||||
public author: Author;
|
||||
public view_count?: Text;
|
||||
public short_view_count?: Text;
|
||||
public short_byline_text?: Text;
|
||||
public long_byline_text?: Text;
|
||||
public published?: Text;
|
||||
public badges: ObservedArray<MetadataBadge>;
|
||||
public thumbnail_overlays: ObservedArray<YTNode>;
|
||||
public endpoint?: NavigationEndpoint;
|
||||
public menu: Menu | null;
|
||||
public length_text?: Text;
|
||||
public is_watched: boolean;
|
||||
public service_endpoints?: NavigationEndpoint[];
|
||||
public service_endpoint?: NavigationEndpoint;
|
||||
public style?: 'COMPACT_VIDEO_STYLE_TYPE_UNKNOWN' | 'COMPACT_VIDEO_STYLE_TYPE_NORMAL' | 'COMPACT_VIDEO_STYLE_TYPE_PROMINENT_THUMBNAIL' | 'COMPACT_VIDEO_STYLE_TYPE_HERO';
|
||||
|
||||
constructor(data: RawNode) {
|
||||
super();
|
||||
this.id = data.videoId;
|
||||
this.thumbnails = Thumbnail.fromResponse(data.thumbnail) || null;
|
||||
|
||||
if (Reflect.has(data, 'richThumbnail')) {
|
||||
this.rich_thumbnail = Parser.parseItem(data.richThumbnail);
|
||||
}
|
||||
|
||||
this.video_id = data.videoId;
|
||||
this.thumbnails = Thumbnail.fromResponse(data.thumbnail);
|
||||
this.title = new Text(data.title);
|
||||
this.author = new Author(data.longBylineText, data.ownerBadges, data.channelThumbnail);
|
||||
this.view_count = new Text(data.viewCountText);
|
||||
this.short_view_count = new Text(data.shortViewCountText);
|
||||
this.published = new Text(data.publishedTimeText);
|
||||
this.is_watched = !!data.isWatched;
|
||||
this.thumbnail_overlays = Parser.parseArray(data.thumbnailOverlays);
|
||||
this.menu = Parser.parseItem(data.menu, Menu);
|
||||
this.badges = Parser.parseArray(data.badges, MetadataBadge);
|
||||
|
||||
this.duration = {
|
||||
text: new Text(data.lengthText).toString(),
|
||||
seconds: timeToSeconds(new Text(data.lengthText).toString())
|
||||
};
|
||||
if ('publishedTimeText' in data)
|
||||
this.published = new Text(data.publishedTimeText);
|
||||
|
||||
if ('shortBylineText' in data)
|
||||
this.view_count = new Text(data.viewCountText);
|
||||
|
||||
this.thumbnail_overlays = Parser.parseArray(data.thumbnailOverlays);
|
||||
this.endpoint = new NavigationEndpoint(data.navigationEndpoint);
|
||||
this.menu = Parser.parseItem(data.menu, Menu);
|
||||
if ('shortViewCountText' in data)
|
||||
this.short_view_count = new Text(data.shortViewCountText);
|
||||
|
||||
if ('richThumbnail' in data)
|
||||
this.rich_thumbnail = Parser.parseItem(data.richThumbnail);
|
||||
|
||||
if ('shortBylineText' in data)
|
||||
this.short_byline_text = new Text(data.shortBylineText);
|
||||
|
||||
if ('longBylineText' in data)
|
||||
this.long_byline_text = new Text(data.longBylineText);
|
||||
|
||||
if ('lengthText' in data)
|
||||
this.length_text = new Text(data.lengthText);
|
||||
|
||||
if ('serviceEndpoints' in data)
|
||||
this.service_endpoints = data.serviceEndpoints.map((endpoint: RawNode) => new NavigationEndpoint(endpoint));
|
||||
|
||||
if ('serviceEndpoint' in data)
|
||||
this.service_endpoint = new NavigationEndpoint(data.serviceEndpoint);
|
||||
|
||||
if ('navigationEndpoint' in data)
|
||||
this.endpoint = new NavigationEndpoint(data.navigationEndpoint);
|
||||
|
||||
if ('style' in data)
|
||||
this.style = data.style;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@linkcode video_id} instead.
|
||||
*/
|
||||
get id(): string {
|
||||
return this.video_id;
|
||||
}
|
||||
|
||||
get duration() {
|
||||
const overlay_time_status = this.thumbnail_overlays.firstOfType(ThumbnailOverlayTimeStatus);
|
||||
const length_text = this.length_text?.toString() || overlay_time_status?.text.toString();
|
||||
return {
|
||||
text: length_text,
|
||||
seconds: length_text ? timeToSeconds(length_text) : 0
|
||||
};
|
||||
}
|
||||
|
||||
get best_thumbnail() {
|
||||
|
||||
10
deno/src/parser/classes/CompositeVideoPrimaryInfo.ts
Normal file
10
deno/src/parser/classes/CompositeVideoPrimaryInfo.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { YTNode } from '../helpers.ts';
|
||||
import type { RawNode } from '../types/index.ts';
|
||||
|
||||
export default class CompositeVideoPrimaryInfo extends YTNode {
|
||||
static type = 'CompositeVideoPrimaryInfo';
|
||||
|
||||
constructor(_data: RawNode) {
|
||||
super();
|
||||
}
|
||||
}
|
||||
@@ -1,26 +1,38 @@
|
||||
import type { ObservedArray } from '../helpers.ts';
|
||||
import { YTNode } from '../helpers.ts';
|
||||
import type { RawNode } from '../index.ts';
|
||||
import { Text } from '../misc.ts';
|
||||
import { Parser } from '../index.ts';
|
||||
import AvatarStackView from './AvatarStackView.ts';
|
||||
import BadgeView from './BadgeView.ts';
|
||||
|
||||
export type MetadataRow = {
|
||||
metadata_parts?: {
|
||||
text: Text;
|
||||
text: Text | null;
|
||||
avatar_stack: AvatarStackView | null;
|
||||
enable_truncation?: boolean;
|
||||
}[];
|
||||
badges: ObservedArray<BadgeView>
|
||||
};
|
||||
|
||||
export default class ContentMetadataView extends YTNode {
|
||||
static type = 'ContentMetadataView';
|
||||
|
||||
metadata_rows: MetadataRow[];
|
||||
delimiter: string;
|
||||
public metadata_rows: MetadataRow[];
|
||||
public delimiter: string;
|
||||
|
||||
constructor(data: RawNode) {
|
||||
super();
|
||||
this.metadata_rows = data.metadataRows.map((row: RawNode) => ({
|
||||
|
||||
this.metadata_rows = data.metadataRows?.map((row: RawNode) => ({
|
||||
metadata_parts: row.metadataParts?.map((part: RawNode) => ({
|
||||
text: Text.fromAttributed(part.text || {})
|
||||
}))
|
||||
}));
|
||||
text: part.text ? Text.fromAttributed(part.text) : null,
|
||||
avatar_stack: Parser.parseItem(part.avatarStack, AvatarStackView),
|
||||
enable_truncation: data.enableTruncation
|
||||
})),
|
||||
badges: Parser.parseArray(row.badges, BadgeView)
|
||||
})) || [];
|
||||
|
||||
this.delimiter = data.delimiter;
|
||||
}
|
||||
}
|
||||
@@ -1,21 +1,19 @@
|
||||
import { YTNode } from '../helpers.ts';
|
||||
import { Parser, type RawNode } from '../index.ts';
|
||||
import NavigationEndpoint from './NavigationEndpoint.ts';
|
||||
import AvatarView from './AvatarView.ts';
|
||||
import RendererContext from './misc/RendererContext.ts';
|
||||
|
||||
export default class DecoratedAvatarView extends YTNode {
|
||||
static type = 'DecoratedAvatarView';
|
||||
|
||||
avatar: AvatarView | null;
|
||||
a11y_label: string;
|
||||
on_tap_endpoint?: NavigationEndpoint;
|
||||
public avatar: AvatarView | null;
|
||||
public a11y_label: string;
|
||||
public renderer_context: RendererContext;
|
||||
|
||||
constructor(data: RawNode) {
|
||||
super();
|
||||
this.avatar = Parser.parseItem(data.avatar, AvatarView);
|
||||
this.a11y_label = data.a11yLabel;
|
||||
if (data.rendererContext?.commandContext?.onTap) {
|
||||
this.on_tap_endpoint = new NavigationEndpoint(data.rendererContext.commandContext.onTap);
|
||||
}
|
||||
this.renderer_context = new RendererContext(data.rendererContext);
|
||||
}
|
||||
}
|
||||
@@ -2,15 +2,16 @@ import { YTNode } from '../helpers.ts';
|
||||
import { Parser, type RawNode } from '../index.ts';
|
||||
import EngagementPanelSectionList from './EngagementPanelSectionList.ts';
|
||||
import Text from './misc/Text.ts';
|
||||
import RendererContext from './misc/RendererContext.ts';
|
||||
|
||||
export default class DescriptionPreviewView extends YTNode {
|
||||
static type = 'DescriptionPreviewView';
|
||||
|
||||
description: Text;
|
||||
max_lines: number;
|
||||
truncation_text: Text;
|
||||
always_show_truncation_text: boolean;
|
||||
more_endpoint?: {
|
||||
public description?: Text;
|
||||
public max_lines?: number;
|
||||
public truncation_text?: Text;
|
||||
public always_show_truncation_text: boolean;
|
||||
public more_endpoint?: {
|
||||
show_engagement_panel_endpoint: {
|
||||
engagement_panel: EngagementPanelSectionList | null,
|
||||
engagement_panel_popup_type: string;
|
||||
@@ -20,15 +21,23 @@ export default class DescriptionPreviewView extends YTNode {
|
||||
}
|
||||
}
|
||||
};
|
||||
public renderer_context: RendererContext;
|
||||
|
||||
constructor(data: RawNode) {
|
||||
super();
|
||||
|
||||
this.description = Text.fromAttributed(data.description);
|
||||
this.max_lines = parseInt(data.maxLines);
|
||||
this.truncation_text = Text.fromAttributed(data.truncationText);
|
||||
if ('description' in data)
|
||||
this.description = Text.fromAttributed(data.description);
|
||||
|
||||
if ('maxLines' in data)
|
||||
this.max_lines = parseInt(data.maxLines);
|
||||
|
||||
if ('truncationText' in data)
|
||||
this.truncation_text = Text.fromAttributed(data.truncationText);
|
||||
|
||||
this.always_show_truncation_text = !!data.alwaysShowTruncationText;
|
||||
|
||||
// @TODO: Do something about this.
|
||||
if (data.rendererContext.commandContext?.onTap?.innertubeCommand?.showEngagementPanelEndpoint) {
|
||||
const endpoint = data.rendererContext.commandContext?.onTap?.innertubeCommand?.showEngagementPanelEndpoint;
|
||||
|
||||
@@ -43,5 +52,7 @@ export default class DescriptionPreviewView extends YTNode {
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
this.renderer_context = new RendererContext(data.rendererContext);
|
||||
}
|
||||
}
|
||||
@@ -3,18 +3,20 @@ import { Parser, type RawNode } from '../index.ts';
|
||||
import DialogHeaderView from './DialogHeaderView.ts';
|
||||
import FormFooterView from './FormFooterView.ts';
|
||||
import CreatePlaylistDialogFormView from './CreatePlaylistDialogFormView.ts';
|
||||
import ListView from './ListView.ts';
|
||||
import PanelFooterView from './PanelFooterView.ts';
|
||||
|
||||
export default class DialogView extends YTNode {
|
||||
static type = 'DialogView';
|
||||
|
||||
public header: DialogHeaderView | null;
|
||||
public footer: FormFooterView | null;
|
||||
public custom_content: CreatePlaylistDialogFormView | null;
|
||||
public footer: FormFooterView | PanelFooterView | null;
|
||||
public custom_content: CreatePlaylistDialogFormView | ListView | null;
|
||||
|
||||
constructor (data: RawNode) {
|
||||
super();
|
||||
this.header = Parser.parseItem(data.header, DialogHeaderView);
|
||||
this.footer = Parser.parseItem(data.footer, FormFooterView);
|
||||
this.custom_content = Parser.parseItem(data.customContent, CreatePlaylistDialogFormView);
|
||||
this.footer = Parser.parseItem(data.footer, [ FormFooterView, PanelFooterView ]);
|
||||
this.custom_content = Parser.parseItem(data.customContent, [ CreatePlaylistDialogFormView, ListView ]);
|
||||
}
|
||||
}
|
||||
20
deno/src/parser/classes/DismissableDialog.ts
Normal file
20
deno/src/parser/classes/DismissableDialog.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { type ObservedArray, YTNode } from '../helpers.ts';
|
||||
import { Parser, type RawNode } from '../index.ts';
|
||||
import DismissableDialogContentSection from './DismissableDialogContentSection.ts';
|
||||
|
||||
export default class DismissableDialog extends YTNode {
|
||||
static type = 'DismissableDialog';
|
||||
|
||||
public title: string;
|
||||
public sections: ObservedArray<DismissableDialogContentSection>;
|
||||
public metadata: YTNode | null;
|
||||
public display_style: string;
|
||||
|
||||
constructor(data: RawNode) {
|
||||
super();
|
||||
this.title = data.title;
|
||||
this.sections = Parser.parseArray(data.sections, DismissableDialogContentSection);
|
||||
this.metadata = Parser.parseItem(data.metadata);
|
||||
this.display_style = data.displayStyle;
|
||||
}
|
||||
}
|
||||
16
deno/src/parser/classes/DismissableDialogContentSection.ts
Normal file
16
deno/src/parser/classes/DismissableDialogContentSection.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { YTNode } from '../helpers.ts';
|
||||
import type { RawNode } from '../index.ts';
|
||||
import Text from './misc/Text.ts';
|
||||
|
||||
export default class DismissableDialogContentSection extends YTNode {
|
||||
static type = 'DismissableDialogContentSection';
|
||||
|
||||
public title: Text;
|
||||
public subtitle: Text;
|
||||
|
||||
constructor(data: RawNode) {
|
||||
super();
|
||||
this.title = new Text(data.title);
|
||||
this.subtitle = new Text(data.subtitle);
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import HorizontalCardList from './HorizontalCardList.ts';
|
||||
import HorizontalList from './HorizontalList.ts';
|
||||
import Text from './misc/Text.ts';
|
||||
import Thumbnail from './misc/Thumbnail.ts';
|
||||
import VideoSummaryContentView from './VideoSummaryContentView.ts';
|
||||
|
||||
export default class ExpandableMetadata extends YTNode {
|
||||
static type = 'ExpandableMetadata';
|
||||
@@ -16,7 +17,7 @@ export default class ExpandableMetadata extends YTNode {
|
||||
expanded_title: Text;
|
||||
};
|
||||
|
||||
expanded_content: HorizontalCardList | HorizontalList | null;
|
||||
expanded_content: VideoSummaryContentView | HorizontalCardList | HorizontalList | null;
|
||||
expand_button: Button | null;
|
||||
collapse_button: Button | null;
|
||||
|
||||
@@ -32,7 +33,7 @@ export default class ExpandableMetadata extends YTNode {
|
||||
};
|
||||
}
|
||||
|
||||
this.expanded_content = Parser.parseItem(data.expandedContent, [ HorizontalCardList, HorizontalList ]);
|
||||
this.expanded_content = Parser.parseItem(data.expandedContent, [ VideoSummaryContentView, HorizontalCardList, HorizontalList ]);
|
||||
this.expand_button = Parser.parseItem(data.expandButton, Button);
|
||||
this.collapse_button = Parser.parseItem(data.collapseButton, Button);
|
||||
}
|
||||
|
||||
@@ -2,9 +2,10 @@ import { type ObservedArray, YTNode } from '../helpers.ts';
|
||||
import { Parser, type RawNode } from '../index.ts';
|
||||
import ButtonView from './ButtonView.ts';
|
||||
import ToggleButtonView from './ToggleButtonView.ts';
|
||||
import SubscribeButtonView from './SubscribeButtonView.ts';
|
||||
|
||||
export type ActionRow = {
|
||||
actions: ObservedArray<ButtonView | ToggleButtonView>;
|
||||
actions: ObservedArray<ButtonView | ToggleButtonView | SubscribeButtonView>;
|
||||
};
|
||||
|
||||
export default class FlexibleActionsView extends YTNode {
|
||||
@@ -16,8 +17,8 @@ export default class FlexibleActionsView extends YTNode {
|
||||
constructor(data: RawNode) {
|
||||
super();
|
||||
this.actions_rows = data.actionsRows.map((row: RawNode) => ({
|
||||
actions: Parser.parseArray(row.actions, [ ButtonView, ToggleButtonView ])
|
||||
actions: Parser.parseArray(row.actions, [ ButtonView, ToggleButtonView, SubscribeButtonView ])
|
||||
}));
|
||||
this.style = data.style;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
15
deno/src/parser/classes/Form.ts
Normal file
15
deno/src/parser/classes/Form.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { YTNode } from '../helpers.ts';
|
||||
import { Parser, type RawNode } from '../index.ts';
|
||||
import { type ObservedArray } from '../helpers.ts';
|
||||
import ToggleFormField from './ToggleFormField.ts';
|
||||
|
||||
export default class Form extends YTNode {
|
||||
static type = 'Form';
|
||||
|
||||
fields: ObservedArray<ToggleFormField>;
|
||||
|
||||
constructor(data: RawNode) {
|
||||
super();
|
||||
this.fields = Parser.parseArray(data.fields, ToggleFormField);
|
||||
}
|
||||
}
|
||||
21
deno/src/parser/classes/FormPopup.ts
Normal file
21
deno/src/parser/classes/FormPopup.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { YTNode } from '../helpers.ts';
|
||||
import { Parser, type RawNode } from '../index.ts';
|
||||
import { type ObservedArray } from '../helpers.ts';
|
||||
import Text from './misc/Text.ts';
|
||||
import Form from './Form.ts';
|
||||
import Button from './Button.ts';
|
||||
|
||||
export default class FormPopup extends YTNode {
|
||||
static type = 'FormPopup';
|
||||
|
||||
title: Text;
|
||||
form: Form | null;
|
||||
buttons: ObservedArray<Button>;
|
||||
|
||||
constructor(data: RawNode) {
|
||||
super();
|
||||
this.title = new Text(data.title);
|
||||
this.form = Parser.parseItem(data.form, Form);
|
||||
this.buttons = Parser.parseArray(data.buttons, Button);
|
||||
}
|
||||
}
|
||||
27
deno/src/parser/classes/GridShelfView.ts
Normal file
27
deno/src/parser/classes/GridShelfView.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import type { ObservedArray } from '../helpers.ts';
|
||||
import { YTNode } from '../helpers.ts';
|
||||
import { Parser, type RawNode } from '../index.ts';
|
||||
import ButtonView from './ButtonView.ts';
|
||||
|
||||
export default class GridShelfView extends YTNode {
|
||||
static type = 'GridShelfView';
|
||||
|
||||
public contents: ObservedArray<YTNode>;
|
||||
public header: YTNode | null;
|
||||
public content_aspect_ratio: string;
|
||||
public enable_vertical_expansion: boolean;
|
||||
public show_more_button: ButtonView | null;
|
||||
public show_less_button: ButtonView | null;
|
||||
public min_collapsed_item_count: number;
|
||||
|
||||
constructor(data: RawNode) {
|
||||
super();
|
||||
this.contents = Parser.parseArray(data.contents);
|
||||
this.header = Parser.parseItem(data.header);
|
||||
this.content_aspect_ratio = data.contentAspectRatio;
|
||||
this.enable_vertical_expansion = data.enableVerticalExpansion;
|
||||
this.show_more_button = Parser.parseItem(data.showMoreButton, ButtonView);
|
||||
this.show_less_button = Parser.parseItem(data.showLessButton, ButtonView);
|
||||
this.min_collapsed_item_count = data.minCollapsedItemCount;
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,7 @@ export default class GridShow extends YTNode {
|
||||
thumbnail_renderer: ShowCustomThumbnail | null;
|
||||
endpoint: NavigationEndpoint;
|
||||
long_byline_text: Text;
|
||||
thumbnail_overlays: ObservedArray<ThumbnailOverlayBottomPanel> | null;
|
||||
thumbnail_overlays: ObservedArray<ThumbnailOverlayBottomPanel>;
|
||||
author: Author;
|
||||
|
||||
constructor(data: RawNode) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { YTNode, type ObservedArray } from '../helpers.ts';
|
||||
import { type ObservedArray, YTNode } from '../helpers.ts';
|
||||
import { Parser, type RawNode } from '../index.ts';
|
||||
import NavigationEndpoint from './NavigationEndpoint.ts';
|
||||
import Menu from './menus/Menu.ts';
|
||||
@@ -9,28 +9,28 @@ import Thumbnail from './misc/Thumbnail.ts';
|
||||
export default class GridVideo extends YTNode {
|
||||
static type = 'GridVideo';
|
||||
|
||||
id: string;
|
||||
title: Text;
|
||||
thumbnails: Thumbnail[];
|
||||
thumbnail_overlays: ObservedArray<YTNode>;
|
||||
rich_thumbnail: YTNode;
|
||||
published: Text;
|
||||
duration: Text | null;
|
||||
author: Author;
|
||||
views: Text;
|
||||
short_view_count: Text;
|
||||
endpoint: NavigationEndpoint;
|
||||
menu: Menu | null;
|
||||
buttons?: ObservedArray<YTNode>;
|
||||
upcoming?: Date;
|
||||
upcoming_text?: Text;
|
||||
is_reminder_set?: boolean;
|
||||
public video_id: string;
|
||||
public title: Text;
|
||||
public thumbnails: Thumbnail[];
|
||||
public thumbnail_overlays: ObservedArray<YTNode>;
|
||||
public rich_thumbnail: YTNode;
|
||||
public published: Text;
|
||||
public duration: Text | null;
|
||||
public author: Author;
|
||||
public views: Text;
|
||||
public short_view_count: Text;
|
||||
public endpoint: NavigationEndpoint;
|
||||
public menu: Menu | null;
|
||||
public buttons?: ObservedArray<YTNode>;
|
||||
public upcoming?: Date;
|
||||
public upcoming_text?: Text;
|
||||
public is_reminder_set?: boolean;
|
||||
|
||||
constructor(data: RawNode) {
|
||||
super();
|
||||
const length_alt = data.thumbnailOverlays.find((overlay: RawNode) => overlay.hasOwnProperty('thumbnailOverlayTimeStatusRenderer'))?.thumbnailOverlayTimeStatusRenderer;
|
||||
|
||||
this.id = data.videoId;
|
||||
this.video_id = data.videoId;
|
||||
this.title = new Text(data.title);
|
||||
this.thumbnails = Thumbnail.fromResponse(data.thumbnail);
|
||||
this.thumbnail_overlays = Parser.parseArray(data.thumbnailOverlays);
|
||||
@@ -54,6 +54,13 @@ export default class GridVideo extends YTNode {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@linkcode video_id} instead.
|
||||
*/
|
||||
get id(): string {
|
||||
return this.video_id;
|
||||
}
|
||||
|
||||
get is_upcoming(): boolean {
|
||||
return Boolean(this.upcoming && this.upcoming > new Date());
|
||||
}
|
||||
|
||||
@@ -10,8 +10,8 @@ export default class HeatMarker extends YTNode {
|
||||
|
||||
constructor(data: RawNode) {
|
||||
super();
|
||||
this.time_range_start_millis = data.timeRangeStartMillis;
|
||||
this.marker_duration_millis = data.markerDurationMillis;
|
||||
this.heat_marker_intensity_score_normalized = data.heatMarkerIntensityScoreNormalized;
|
||||
this.time_range_start_millis = Number.parseInt(data.startMillis, 10);
|
||||
this.marker_duration_millis = Number.parseInt(data.durationMillis, 10);
|
||||
this.heat_marker_intensity_score_normalized = data.intensityScoreNormalized;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Parser, type RawNode } from '../index.ts';
|
||||
import HeatMarker from './HeatMarker.ts';
|
||||
import { type ObservedArray, YTNode } from '../helpers.ts';
|
||||
import TimedMarkerDecoration from './TimedMarkerDecoration.ts';
|
||||
|
||||
export default class Heatmap extends YTNode {
|
||||
static type = 'Heatmap';
|
||||
@@ -17,6 +18,6 @@ export default class Heatmap extends YTNode {
|
||||
this.min_height_dp = data.minHeightDp;
|
||||
this.show_hide_animation_duration_millis = data.showHideAnimationDurationMillis;
|
||||
this.heat_markers = Parser.parseArray(data.heatMarkers, HeatMarker);
|
||||
this.heat_markers_decorations = Parser.parseArray(data.heatMarkersDecorations);
|
||||
this.heat_markers_decorations = Parser.parseArray(data.heatMarkersDecorations, TimedMarkerDecoration);
|
||||
}
|
||||
}
|
||||
21
deno/src/parser/classes/HowThisWasMadeSectionView.ts
Normal file
21
deno/src/parser/classes/HowThisWasMadeSectionView.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { type RawNode } from '../index.ts';
|
||||
import { YTNode } from '../helpers.ts';
|
||||
import Text from './misc/Text.ts';
|
||||
|
||||
export default class HowThisWasMadeSectionView extends YTNode {
|
||||
static type = 'HowThisWasMadeSectionView';
|
||||
|
||||
public section_title?: Text;
|
||||
public body_text?: Text;
|
||||
public body_header?: Text;
|
||||
|
||||
constructor(data: RawNode) {
|
||||
super();
|
||||
if (Reflect.has(data, 'sectionText'))
|
||||
this.section_title = Text.fromAttributed(data.sectionText);
|
||||
if (Reflect.has(data, 'bodyText'))
|
||||
this.body_text = Text.fromAttributed(data.bodyText);
|
||||
if (Reflect.has(data, 'bodyHeader'))
|
||||
this.body_header = Text.fromAttributed(data.bodyHeader);
|
||||
}
|
||||
}
|
||||
14
deno/src/parser/classes/HypePointsFactoid.ts
Normal file
14
deno/src/parser/classes/HypePointsFactoid.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { Parser, type RawNode } from '../index.ts';
|
||||
import { YTNode } from '../helpers.ts';
|
||||
import Factoid from './Factoid.ts';
|
||||
|
||||
export default class HypePointsFactoid extends YTNode {
|
||||
static type = 'HypePointsFactoid';
|
||||
|
||||
public factoid: Factoid | null;
|
||||
|
||||
constructor(data: RawNode) {
|
||||
super();
|
||||
this.factoid = Parser.parseItem(data.factoid, Factoid);
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,7 @@ export default class InteractiveTabbedHeader extends YTNode {
|
||||
title: Text;
|
||||
description: Text;
|
||||
metadata: Text;
|
||||
badges: MetadataBadge[];
|
||||
badges: ObservedArray<MetadataBadge>;
|
||||
box_art: Thumbnail[];
|
||||
banner: Thumbnail[];
|
||||
buttons: ObservedArray<SubscribeButton | Button>;
|
||||
|
||||
38
deno/src/parser/classes/ListItemView.ts
Normal file
38
deno/src/parser/classes/ListItemView.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import type { ObservedArray } from '../helpers.ts';
|
||||
import type { RawNode } from '../types/RawResponse.ts';
|
||||
import { YTNode } from '../helpers.ts';
|
||||
import { Parser } from '../index.ts';
|
||||
|
||||
import AvatarView from './AvatarView.ts';
|
||||
import RendererContext from './misc/RendererContext.ts';
|
||||
import SubscribeButtonView from './SubscribeButtonView.ts';
|
||||
import Text from './misc/Text.ts';
|
||||
|
||||
export default class ListItemView extends YTNode {
|
||||
static type = 'ListItemView';
|
||||
|
||||
public title?: Text;
|
||||
public subtitle?: Text;
|
||||
public leading_accessory: AvatarView | null;
|
||||
public renderer_context?: RendererContext;
|
||||
public trailing_buttons: ObservedArray<SubscribeButtonView>;
|
||||
|
||||
constructor(data: RawNode) {
|
||||
super();
|
||||
if ('title' in data) {
|
||||
this.title = Text.fromAttributed(data.title);
|
||||
}
|
||||
|
||||
if ('subtitle' in data) {
|
||||
this.subtitle = Text.fromAttributed(data.subtitle);
|
||||
}
|
||||
|
||||
this.leading_accessory = Parser.parseItem(data.leadingAccessory, AvatarView);
|
||||
|
||||
if ('rendererContext' in data) {
|
||||
this.renderer_context = new RendererContext(data.rendererContext);
|
||||
}
|
||||
|
||||
this.trailing_buttons = Parser.parseArray(data.trailingButtons?.buttons, SubscribeButtonView);
|
||||
}
|
||||
}
|
||||
18
deno/src/parser/classes/ListView.ts
Normal file
18
deno/src/parser/classes/ListView.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import type { ObservedArray } from '../helpers.ts';
|
||||
import type { RawNode } from '../types/RawResponse.ts';
|
||||
import { YTNode } from '../helpers.ts';
|
||||
import { Parser } from '../index.ts';
|
||||
|
||||
import ListItemView from './ListItemView.ts';
|
||||
|
||||
export default class ListView extends YTNode {
|
||||
static type = 'ListView';
|
||||
|
||||
public items: ObservedArray<ListItemView>;
|
||||
|
||||
constructor(data: RawNode) {
|
||||
super();
|
||||
|
||||
this.items = Parser.parseArray(data.listItems, ListItemView);
|
||||
}
|
||||
}
|
||||
@@ -1,21 +1,24 @@
|
||||
import { YTNode } from '../helpers.ts';
|
||||
import { Parser, type RawNode } from '../index.ts';
|
||||
import ContentMetadataView from './ContentMetadataView.ts';
|
||||
import AvatarStackView from './AvatarStackView.ts';
|
||||
import DecoratedAvatarView from './DecoratedAvatarView.ts';
|
||||
import Text from './misc/Text.ts';
|
||||
import ButtonView from './ButtonView.ts';
|
||||
|
||||
export default class LockupMetadataView extends YTNode {
|
||||
static type = 'LockupMetadataView';
|
||||
|
||||
title: Text;
|
||||
metadata: ContentMetadataView | null;
|
||||
image: DecoratedAvatarView | null;
|
||||
public title: Text;
|
||||
public metadata: ContentMetadataView | null;
|
||||
public image: DecoratedAvatarView | AvatarStackView | null;
|
||||
public menu_button: ButtonView | null;
|
||||
|
||||
constructor(data: RawNode) {
|
||||
super();
|
||||
|
||||
this.title = Text.fromAttributed(data.title);
|
||||
this.metadata = Parser.parseItem(data.metadata, ContentMetadataView);
|
||||
this.image = Parser.parseItem(data.image, DecoratedAvatarView);
|
||||
this.image = Parser.parseItem(data.image, [ DecoratedAvatarView, AvatarStackView ]);
|
||||
this.menu_button = Parser.parseItem(data.menuButton, ButtonView);
|
||||
}
|
||||
}
|
||||
@@ -1,25 +1,25 @@
|
||||
import { YTNode } from '../helpers.ts';
|
||||
import { Parser, type RawNode } from '../index.ts';
|
||||
import ThumbnailView from './ThumbnailView.ts';
|
||||
import CollectionThumbnailView from './CollectionThumbnailView.ts';
|
||||
import LockupMetadataView from './LockupMetadataView.ts';
|
||||
import NavigationEndpoint from './NavigationEndpoint.ts';
|
||||
import RendererContext from './misc/RendererContext.ts';
|
||||
|
||||
export default class LockupView extends YTNode {
|
||||
static type = 'LockupView';
|
||||
|
||||
content_image: CollectionThumbnailView | null;
|
||||
metadata: LockupMetadataView | null;
|
||||
content_id: string;
|
||||
content_type: 'VIDEO' | 'MOVIE' | 'CHANNEL' | 'CLIP' | 'SOURCE' | 'PLAYLIST' | 'ALBUM' | 'PODCAST' | 'SHOPPING_COLLECTION' | 'SHORT' | 'GAME' | 'PRODUCT';
|
||||
on_tap_endpoint: NavigationEndpoint;
|
||||
public content_image: CollectionThumbnailView | ThumbnailView | null;
|
||||
public metadata: LockupMetadataView | null;
|
||||
public content_id: string;
|
||||
public content_type: 'UNSPECIFIED' | 'VIDEO' | 'PLAYLIST' | 'SHORT' | 'CHANNEL' | 'ALBUM' | 'PRODUCT' | 'GAME' | 'CLIP' | 'PODCAST' | 'SOURCE' | 'SHOPPING_COLLECTION' | 'MOVIE';
|
||||
public renderer_context: RendererContext;
|
||||
|
||||
constructor(data: RawNode) {
|
||||
super();
|
||||
|
||||
this.content_image = Parser.parseItem(data.contentImage, CollectionThumbnailView);
|
||||
this.content_image = Parser.parseItem(data.contentImage, [ CollectionThumbnailView, ThumbnailView ]);
|
||||
this.metadata = Parser.parseItem(data.metadata, LockupMetadataView);
|
||||
this.content_id = data.contentId;
|
||||
this.content_type = data.contentType.replace('LOCKUP_CONTENT_TYPE_', '');
|
||||
this.on_tap_endpoint = new NavigationEndpoint(data.rendererContext.commandContext.onTap);
|
||||
this.renderer_context = new RendererContext(data.rendererContext);
|
||||
}
|
||||
}
|
||||
92
deno/src/parser/classes/MacroMarkersListEntity.ts
Normal file
92
deno/src/parser/classes/MacroMarkersListEntity.ts
Normal file
@@ -0,0 +1,92 @@
|
||||
import { YTNode, type ObservedArray, observe } from '../helpers.ts';
|
||||
import type { RawNode } from '../index.ts';
|
||||
import HeatMarker from './HeatMarker.ts';
|
||||
import TimedMarkerDecoration from './TimedMarkerDecoration.ts';
|
||||
import Heatmap from './Heatmap.ts';
|
||||
import * as Parser from '../parser.ts';
|
||||
|
||||
/**
|
||||
* Represents a list of markers for a video. Can contain different types of markers:
|
||||
* - MARKER_TYPE_HEATMAP: Heat map markers showing audience engagement data
|
||||
* - Other marker types may exist but are not currently handled
|
||||
*/
|
||||
export default class MacroMarkersListEntity extends YTNode {
|
||||
static type = 'MacroMarkersListEntity';
|
||||
|
||||
marker_entity_key: string;
|
||||
external_video_id: string;
|
||||
/** The type of markers in this entity (e.g., 'MARKER_TYPE_HEATMAP') */
|
||||
marker_type: string;
|
||||
markers: ObservedArray<HeatMarker>;
|
||||
max_height_dp: number;
|
||||
min_height_dp: number;
|
||||
show_hide_animation_duration_millis: number;
|
||||
timed_marker_decorations: ObservedArray<TimedMarkerDecoration>;
|
||||
|
||||
// Store raw API data for use in toHeatmap
|
||||
private raw_api_markers: RawNode[];
|
||||
private raw_api_decorations: RawNode[];
|
||||
|
||||
constructor(data: RawNode) {
|
||||
super();
|
||||
this.marker_entity_key = data.key;
|
||||
this.external_video_id = data.externalVideoId;
|
||||
this.marker_type = data.markersList?.markerType || '';
|
||||
|
||||
// Store raw API data
|
||||
this.raw_api_markers = data.markersList?.markers || [];
|
||||
this.raw_api_decorations = data.markersList?.markersDecoration?.timedMarkerDecorations || [];
|
||||
|
||||
// Parse markers array using the updated HeatMarker constructor
|
||||
this.markers = observe(
|
||||
this.raw_api_markers.map((marker: RawNode) => new HeatMarker(marker))
|
||||
);
|
||||
|
||||
// Extract metadata
|
||||
const heatmapMetadata = data.markersList?.markersMetadata?.heatmapMetadata;
|
||||
this.max_height_dp = heatmapMetadata?.maxHeightDp || 40;
|
||||
this.min_height_dp = heatmapMetadata?.minHeightDp || 4;
|
||||
this.show_hide_animation_duration_millis =
|
||||
heatmapMetadata?.showHideAnimationDurationMillis || 200;
|
||||
|
||||
// Parse timed marker decorations
|
||||
// Assuming TimedMarkerDecoration constructor handles raw API decoration objects correctly
|
||||
this.timed_marker_decorations = observe(
|
||||
this.raw_api_decorations.map(
|
||||
(decoration: RawNode) => new TimedMarkerDecoration(decoration)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this MacroMarkersListEntity represents heatmap data.
|
||||
* Only heatmap markers can be converted to Heatmap objects.
|
||||
*/
|
||||
isHeatmap(): boolean {
|
||||
return this.marker_type === 'MARKER_TYPE_HEATMAP';
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts this MacroMarkersListEntity to a Heatmap object
|
||||
* for compatibility with existing code. Only works for heatmap markers.
|
||||
* @returns Heatmap object if this entity contains heatmap data, null otherwise
|
||||
*/
|
||||
toHeatmap(): Heatmap | null {
|
||||
if (!this.isHeatmap()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const wrappedHeatMarkers = this.raw_api_markers.map((marker) => ({ HeatMarker: marker }));
|
||||
const wrappedDecorations = this.raw_api_decorations.map((decoration) => ({ TimedMarkerDecoration: decoration }));
|
||||
|
||||
const heatmapRawPayload = {
|
||||
maxHeightDp: this.max_height_dp,
|
||||
minHeightDp: this.min_height_dp,
|
||||
showHideAnimationDurationMillis: this.show_hide_animation_duration_millis,
|
||||
heatMarkers: wrappedHeatMarkers,
|
||||
heatMarkersDecorations: wrappedDecorations
|
||||
};
|
||||
|
||||
return Parser.parseItem({ Heatmap: heatmapRawPayload }, Heatmap);
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,7 @@ export class Marker extends YTNode {
|
||||
marker_key: string;
|
||||
value: {
|
||||
heatmap?: Heatmap | null;
|
||||
chapters?: Chapter[];
|
||||
chapters?: ObservedArray<Chapter>;
|
||||
};
|
||||
|
||||
constructor(data: RawNode) {
|
||||
|
||||
@@ -1,25 +1,41 @@
|
||||
import { YTNode } from '../helpers.ts';
|
||||
import { Parser, type RawNode } from '../index.ts';
|
||||
|
||||
import Button from './Button.ts';
|
||||
import MusicThumbnail from './MusicThumbnail.ts';
|
||||
import NavigationEndpoint from './NavigationEndpoint.ts';
|
||||
import SubscribeButton from './SubscribeButton.ts';
|
||||
import ToggleButton from './ToggleButton.ts';
|
||||
|
||||
import Menu from './menus/Menu.ts';
|
||||
import Text from './misc/Text.ts';
|
||||
|
||||
export default class MusicImmersiveHeader extends YTNode {
|
||||
static type = 'MusicImmersiveHeader';
|
||||
|
||||
title: Text;
|
||||
description: Text;
|
||||
thumbnail: MusicThumbnail | null;
|
||||
public title: Text;
|
||||
public menu: Menu | null;
|
||||
public more_button: ToggleButton | null;
|
||||
public play_button: Button | null;
|
||||
public share_endpoint?: NavigationEndpoint;
|
||||
public start_radio_button: Button | null;
|
||||
public subscription_button: SubscribeButton | null;
|
||||
public description: Text;
|
||||
public thumbnail: MusicThumbnail | null;
|
||||
|
||||
constructor(data: RawNode) {
|
||||
super();
|
||||
this.title = new Text(data.title);
|
||||
this.menu = Parser.parseItem(data.menu, Menu);
|
||||
this.more_button = Parser.parseItem(data.moreButton, ToggleButton);
|
||||
this.play_button = Parser.parseItem(data.playButton, Button);
|
||||
|
||||
if ('shareEndpoint' in data)
|
||||
this.share_endpoint = new NavigationEndpoint(data.shareEndpoint);
|
||||
|
||||
this.start_radio_button = Parser.parseItem(data.startRadioButton, Button);
|
||||
this.subscription_button = Parser.parseItem(data.subscriptionButton, SubscribeButton);
|
||||
this.description = new Text(data.description);
|
||||
this.thumbnail = Parser.parseItem(data.thumbnail, MusicThumbnail);
|
||||
/**
|
||||
Not useful for now.
|
||||
this.menu = Parser.parse(data.menu);
|
||||
this.play_button = Parser.parse(data.playButton);
|
||||
this.start_radio_button = Parser.parse(data.startRadioButton);
|
||||
*/
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,26 @@
|
||||
import { YTNode } from '../helpers.ts';
|
||||
import type { RawNode } from '../index.ts';
|
||||
import AccessibilityData, { type AccessibilitySupportedDatas } from './misc/AccessibilityData.ts';
|
||||
|
||||
export default class MusicInlineBadge extends YTNode {
|
||||
static type = 'MusicInlineBadge';
|
||||
|
||||
icon_type: string;
|
||||
label: string;
|
||||
public icon_type: string;
|
||||
public accessibility?: AccessibilitySupportedDatas;
|
||||
|
||||
constructor(data: RawNode) {
|
||||
super();
|
||||
this.icon_type = data.icon.iconType;
|
||||
this.label = data.accessibilityData.accessibilityData.label;
|
||||
|
||||
if ('accessibilityData' in data
|
||||
&& 'accessibilityData' in data.accessibilityData) {
|
||||
this.accessibility = {
|
||||
accessibility_data: new AccessibilityData(data.accessibilityData.accessibilityData)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
get label(): string | undefined {
|
||||
return this.accessibility?.accessibility_data?.label;
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,17 @@
|
||||
import NavigationEndpoint from './NavigationEndpoint.ts';
|
||||
import { YTNode } from '../helpers.ts';
|
||||
import type { RawNode } from '../index.ts';
|
||||
import AccessibilityData, { type AccessibilitySupportedDatas } from './misc/AccessibilityData.ts';
|
||||
|
||||
export default class MusicPlayButton extends YTNode {
|
||||
static type = 'MusicPlayButton';
|
||||
|
||||
endpoint: NavigationEndpoint;
|
||||
play_icon_type: string;
|
||||
pause_icon_type: string;
|
||||
play_label?: string;
|
||||
pause_label?: string;
|
||||
icon_color: string;
|
||||
public endpoint: NavigationEndpoint;
|
||||
public play_icon_type: string;
|
||||
public pause_icon_type: string;
|
||||
public icon_color: string;
|
||||
public accessibility_play_data?: AccessibilitySupportedDatas;
|
||||
public accessibility_pause_data?: AccessibilitySupportedDatas;
|
||||
|
||||
constructor(data: RawNode) {
|
||||
super();
|
||||
@@ -18,14 +19,28 @@ export default class MusicPlayButton extends YTNode {
|
||||
this.play_icon_type = data.playIcon.iconType;
|
||||
this.pause_icon_type = data.pauseIcon.iconType;
|
||||
|
||||
if (Reflect.has(data, 'accessibilityPlayData')) {
|
||||
this.play_label = data.accessibilityPlayData.accessibilityData?.label;
|
||||
if ('accessibilityPlayData' in data
|
||||
&& 'accessibilityData' in data.accessibilityPlayData) {
|
||||
this.accessibility_play_data = {
|
||||
accessibility_data: new AccessibilityData(data.accessibilityPlayData.accessibilityData)
|
||||
};
|
||||
}
|
||||
|
||||
if (Reflect.has(data, 'accessibilityPauseData')) {
|
||||
this.pause_label = data.accessibilityPauseData.accessibilityData?.label;
|
||||
if ('accessibilityPauseData' in data
|
||||
&& 'accessibilityData' in data.accessibilityPauseData) {
|
||||
this.accessibility_pause_data = {
|
||||
accessibility_data: new AccessibilityData(data.accessibilityPauseData.accessibilityData)
|
||||
};
|
||||
}
|
||||
|
||||
this.icon_color = data.iconColor;
|
||||
}
|
||||
|
||||
get play_label(): string | undefined {
|
||||
return this.accessibility_play_data?.accessibility_data?.label;
|
||||
}
|
||||
|
||||
get pause_label(): string | undefined {
|
||||
return this.accessibility_pause_data?.accessibility_data?.label;
|
||||
}
|
||||
}
|
||||
@@ -1,19 +1,20 @@
|
||||
import { YTNode, type ObservedArray } from '../helpers.ts';
|
||||
import { type ObservedArray, YTNode } from '../helpers.ts';
|
||||
import { Parser, type RawNode } from '../index.ts';
|
||||
import MusicResponsiveListItem from './MusicResponsiveListItem.ts';
|
||||
import ContinuationItem from './ContinuationItem.ts';
|
||||
|
||||
export default class MusicPlaylistShelf extends YTNode {
|
||||
static type = 'MusicPlaylistShelf';
|
||||
|
||||
playlist_id: string;
|
||||
contents: ObservedArray<MusicResponsiveListItem>;
|
||||
collapsed_item_count: number;
|
||||
continuation: string | null;
|
||||
public playlist_id: string;
|
||||
public contents: ObservedArray<MusicResponsiveListItem | ContinuationItem>;
|
||||
public collapsed_item_count: number;
|
||||
public continuation: string | null;
|
||||
|
||||
constructor(data: RawNode) {
|
||||
super();
|
||||
this.playlist_id = data.playlistId;
|
||||
this.contents = Parser.parseArray(data.contents, MusicResponsiveListItem);
|
||||
this.contents = Parser.parseArray(data.contents, [ MusicResponsiveListItem, ContinuationItem ]);
|
||||
this.collapsed_item_count = data.collapsedItemCount;
|
||||
this.continuation = data.continuations?.[0]?.nextContinuationData?.continuation || null;
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ export default class MusicResponsiveHeader extends YTNode {
|
||||
strapline_text_one: Text;
|
||||
strapline_thumbnail: MusicThumbnail | null;
|
||||
second_subtitle: Text;
|
||||
subtitle_badge?: ObservedArray<MusicInlineBadge> | null;
|
||||
subtitle_badge?: ObservedArray<MusicInlineBadge>;
|
||||
description?: MusicDescriptionShelf | null;
|
||||
|
||||
constructor(data: RawNode) {
|
||||
|
||||
@@ -15,21 +15,22 @@ import NavigationEndpoint from './NavigationEndpoint.ts';
|
||||
import Menu from './menus/Menu.ts';
|
||||
import Text from './misc/Text.ts';
|
||||
|
||||
interface PlaylistItemData {
|
||||
video_id: string;
|
||||
playlist_set_video_id: string;
|
||||
}
|
||||
|
||||
export default class MusicResponsiveListItem extends YTNode {
|
||||
static type = 'MusicResponsiveListItem';
|
||||
|
||||
flex_columns: ObservedArray<MusicResponsiveListItemFlexColumn>;
|
||||
fixed_columns: ObservedArray<MusicResponsiveListItemFixedColumn>;
|
||||
#playlist_item_data: {
|
||||
video_id: string;
|
||||
playlist_set_video_id: string;
|
||||
};
|
||||
|
||||
endpoint?: NavigationEndpoint;
|
||||
item_type: 'album' | 'playlist' | 'artist' | 'library_artist' | 'non_music_track' | 'video' | 'song' | 'endpoint' | 'unknown' | 'podcast_show' | undefined;
|
||||
index?: Text;
|
||||
thumbnail?: MusicThumbnail | null;
|
||||
badges;
|
||||
badges?: ObservedArray<YTNode>;
|
||||
menu?: Menu | null;
|
||||
overlay?: MusicItemThumbnailOverlay | null;
|
||||
|
||||
@@ -78,7 +79,7 @@ export default class MusicResponsiveListItem extends YTNode {
|
||||
this.flex_columns = Parser.parseArray(data.flexColumns, MusicResponsiveListItemFlexColumn);
|
||||
this.fixed_columns = Parser.parseArray(data.fixedColumns, MusicResponsiveListItemFixedColumn);
|
||||
|
||||
this.#playlist_item_data = {
|
||||
const playlist_item_data: PlaylistItemData = {
|
||||
video_id: data?.playlistItemData?.videoId || null,
|
||||
playlist_set_video_id: data?.playlistItemData?.playlistSetVideoId || null
|
||||
};
|
||||
@@ -119,7 +120,7 @@ export default class MusicResponsiveListItem extends YTNode {
|
||||
break;
|
||||
case 'MUSIC_PAGE_TYPE_NON_MUSIC_AUDIO_TRACK_PAGE':
|
||||
this.item_type = 'non_music_track';
|
||||
this.#parseNonMusicTrack();
|
||||
this.#parseNonMusicTrack(playlist_item_data);
|
||||
break;
|
||||
case 'MUSIC_PAGE_TYPE_PODCAST_SHOW_DETAIL_PAGE':
|
||||
this.item_type = 'podcast_show';
|
||||
@@ -127,7 +128,7 @@ export default class MusicResponsiveListItem extends YTNode {
|
||||
break;
|
||||
default:
|
||||
if (this.flex_columns[1]) {
|
||||
this.#parseVideoOrSong();
|
||||
this.#parseVideoOrSong(playlist_item_data);
|
||||
} else {
|
||||
this.#parseOther();
|
||||
}
|
||||
@@ -155,7 +156,7 @@ export default class MusicResponsiveListItem extends YTNode {
|
||||
}
|
||||
|
||||
#parseOther() {
|
||||
this.title = this.flex_columns.first().title.toString();
|
||||
this.title = this.flex_columns[0].title.toString();
|
||||
|
||||
if (this.endpoint) {
|
||||
this.item_type = 'endpoint';
|
||||
@@ -164,29 +165,29 @@ export default class MusicResponsiveListItem extends YTNode {
|
||||
}
|
||||
}
|
||||
|
||||
#parseVideoOrSong() {
|
||||
#parseVideoOrSong(playlist_item_data: PlaylistItemData) {
|
||||
const music_video_type = (this.flex_columns.at(0)?.title.runs?.at(0) as TextRun)?.endpoint?.payload?.watchEndpointMusicSupportedConfigs?.watchEndpointMusicConfig?.musicVideoType;
|
||||
switch (music_video_type) {
|
||||
case 'MUSIC_VIDEO_TYPE_UGC':
|
||||
case 'MUSIC_VIDEO_TYPE_OMV':
|
||||
this.item_type = 'video';
|
||||
this.#parseVideo();
|
||||
this.#parseVideo(playlist_item_data);
|
||||
break;
|
||||
case 'MUSIC_VIDEO_TYPE_ATV':
|
||||
this.item_type = 'song';
|
||||
this.#parseSong();
|
||||
this.#parseSong(playlist_item_data);
|
||||
break;
|
||||
default:
|
||||
this.#parseOther();
|
||||
}
|
||||
}
|
||||
|
||||
#parseSong() {
|
||||
this.id = this.#playlist_item_data.video_id || this.endpoint?.payload?.videoId;
|
||||
this.title = this.flex_columns.first().title.toString();
|
||||
#parseSong(playlist_item_data: PlaylistItemData) {
|
||||
this.id = playlist_item_data.video_id || this.endpoint?.payload?.videoId;
|
||||
this.title = this.flex_columns[0].title.toString();
|
||||
|
||||
const duration_text = this.flex_columns.at(1)?.title.runs?.find(
|
||||
(run) => (/^\d+$/).test(run.text.replace(/:/g, '')))?.text || this.fixed_columns.first()?.title?.toString();
|
||||
(run) => (/^\d+$/).test(run.text.replace(/:/g, '')))?.text || this.fixed_columns[0]?.title?.toString();
|
||||
|
||||
if (duration_text) {
|
||||
this.duration = {
|
||||
@@ -228,9 +229,9 @@ export default class MusicResponsiveListItem extends YTNode {
|
||||
}
|
||||
}
|
||||
|
||||
#parseVideo() {
|
||||
this.id = this.#playlist_item_data.video_id;
|
||||
this.title = this.flex_columns.first().title.toString();
|
||||
#parseVideo(playlist_item_data: PlaylistItemData) {
|
||||
this.id = playlist_item_data.video_id;
|
||||
this.title = this.flex_columns[0].title.toString();
|
||||
this.views = this.flex_columns.at(1)?.title.runs?.find((run) => run.text.match(/(.*?) views/))?.toString();
|
||||
|
||||
const author_runs = this.flex_columns.at(1)?.title.runs?.filter(
|
||||
@@ -250,7 +251,7 @@ export default class MusicResponsiveListItem extends YTNode {
|
||||
}
|
||||
|
||||
const duration_text = this.flex_columns[1].title.runs?.find(
|
||||
(run) => (/^\d+$/).test(run.text.replace(/:/g, '')))?.text || this.fixed_columns.first()?.title.runs?.find((run) => (/^\d+$/).test(run.text.replace(/:/g, '')))?.text;
|
||||
(run) => (/^\d+$/).test(run.text.replace(/:/g, '')))?.text || this.fixed_columns[0]?.title.runs?.find((run) => (/^\d+$/).test(run.text.replace(/:/g, '')))?.text;
|
||||
|
||||
if (duration_text) {
|
||||
this.duration = {
|
||||
@@ -262,30 +263,30 @@ export default class MusicResponsiveListItem extends YTNode {
|
||||
|
||||
#parseArtist() {
|
||||
this.id = this.endpoint?.payload?.browseId;
|
||||
this.name = this.flex_columns.first().title.toString();
|
||||
this.name = this.flex_columns[0].title.toString();
|
||||
this.subtitle = this.flex_columns.at(1)?.title;
|
||||
this.subscribers = this.subtitle?.runs?.find((run) => (/^(\d*\.)?\d+[M|K]? subscribers?$/i).test(run.text))?.text || '';
|
||||
}
|
||||
|
||||
#parseLibraryArtist() {
|
||||
this.name = this.flex_columns.first().title.toString();
|
||||
this.name = this.flex_columns[0].title.toString();
|
||||
this.subtitle = this.flex_columns.at(1)?.title;
|
||||
this.song_count = this.subtitle?.runs?.find((run) => (/^\d+(,\d+)? songs?$/i).test(run.text))?.text || '';
|
||||
}
|
||||
|
||||
#parseNonMusicTrack() {
|
||||
this.id = this.#playlist_item_data.video_id || this.endpoint?.payload?.videoId;
|
||||
this.title = this.flex_columns.first().title.toString();
|
||||
#parseNonMusicTrack(playlist_item_data: PlaylistItemData) {
|
||||
this.id = playlist_item_data.video_id || this.endpoint?.payload?.videoId;
|
||||
this.title = this.flex_columns[0].title.toString();
|
||||
}
|
||||
|
||||
#parsePodcastShow() {
|
||||
this.id = this.endpoint?.payload?.browseId;
|
||||
this.title = this.flex_columns.first().title.toString();
|
||||
this.title = this.flex_columns[0].title.toString();
|
||||
}
|
||||
|
||||
#parseAlbum() {
|
||||
this.id = this.endpoint?.payload?.browseId;
|
||||
this.title = this.flex_columns.first().title.toString();
|
||||
this.title = this.flex_columns[0].title.toString();
|
||||
|
||||
const author_run = this.flex_columns.at(1)?.title.runs?.find(
|
||||
(run) =>
|
||||
@@ -308,7 +309,7 @@ export default class MusicResponsiveListItem extends YTNode {
|
||||
|
||||
#parsePlaylist() {
|
||||
this.id = this.endpoint?.payload?.browseId;
|
||||
this.title = this.flex_columns.first().title.toString();
|
||||
this.title = this.flex_columns[0].title.toString();
|
||||
|
||||
const item_count_run = this.flex_columns.at(1)?.title
|
||||
.runs?.find((run) => run.text.match(/\d+ (song|songs)/));
|
||||
|
||||
@@ -2,6 +2,7 @@ import { YTNode } from '../helpers.ts';
|
||||
import { Parser, type IEndpoint, type RawNode } from '../index.ts';
|
||||
import OpenPopupAction from './actions/OpenPopupAction.ts';
|
||||
import CreatePlaylistDialog from './CreatePlaylistDialog.ts';
|
||||
import CommandExecutorCommand from './commands/CommandExecutorCommand.ts';
|
||||
|
||||
import type Actions from '../../core/Actions.ts';
|
||||
import type ModalWithTitleAndButton from './ModalWithTitleAndButton.ts';
|
||||
@@ -124,7 +125,12 @@ export default class NavigationEndpoint extends YTNode {
|
||||
throw new Error('An API caller must be provided');
|
||||
|
||||
if (this.command) {
|
||||
const command = this.command as (YTNode & IEndpoint);
|
||||
let command = this.command as (YTNode & IEndpoint);
|
||||
|
||||
if (command.is(CommandExecutorCommand)) {
|
||||
command = command.commands.at(-1) as (YTNode & IEndpoint);
|
||||
}
|
||||
|
||||
return actions.execute(command.getApiPath(), { ...command.buildRequest(), ...args });
|
||||
}
|
||||
|
||||
|
||||
17
deno/src/parser/classes/OpenOnePickAddVideoModalCommand.ts
Normal file
17
deno/src/parser/classes/OpenOnePickAddVideoModalCommand.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { type RawNode } from '../index.ts';
|
||||
import { YTNode } from '../helpers.ts';
|
||||
|
||||
export default class OpenOnePickAddVideoModalCommand extends YTNode {
|
||||
static type = 'OpenOnePickAddVideoModalCommand';
|
||||
|
||||
list_id: string;
|
||||
modal_title: string;
|
||||
select_button_label: string;
|
||||
|
||||
constructor(data: RawNode) {
|
||||
super();
|
||||
this.list_id = data.listId;
|
||||
this.modal_title = data.modalTitle;
|
||||
this.select_button_label = data.selectButtonLabel;
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,7 @@ export default class PageHeaderView extends YTNode {
|
||||
title: DynamicTextView | null;
|
||||
image: ContentPreviewImageView | DecoratedAvatarView | null;
|
||||
animated_image: ContentPreviewImageView | null;
|
||||
hero_image: ContentPreviewImageView | null;
|
||||
metadata: ContentMetadataView | null;
|
||||
actions: FlexibleActionsView | null;
|
||||
description: DescriptionPreviewView | null;
|
||||
@@ -26,6 +27,7 @@ export default class PageHeaderView extends YTNode {
|
||||
this.title = Parser.parseItem(data.title, DynamicTextView);
|
||||
this.image = Parser.parseItem(data.image, [ ContentPreviewImageView, DecoratedAvatarView ]);
|
||||
this.animated_image = Parser.parseItem(data.animatedImage, ContentPreviewImageView);
|
||||
this.hero_image = Parser.parseItem(data.heroImage, ContentPreviewImageView);
|
||||
this.metadata = Parser.parseItem(data.metadata, ContentMetadataView);
|
||||
this.actions = Parser.parseItem(data.actions, FlexibleActionsView);
|
||||
this.description = Parser.parseItem(data.description, DescriptionPreviewView);
|
||||
|
||||
37
deno/src/parser/classes/PlayerCaptchaView.ts
Normal file
37
deno/src/parser/classes/PlayerCaptchaView.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import { YTNode } from '../helpers.ts';
|
||||
import { Text } from '../misc.ts';
|
||||
import { type RawNode } from '../index.ts';
|
||||
|
||||
export default class PlayerCaptchaView extends YTNode {
|
||||
static type = 'PlayerCaptchaView';
|
||||
|
||||
public captcha_loading_message?: Text;
|
||||
public challenge_reason?: Text;
|
||||
public captcha_successful_message?: Text;
|
||||
public captcha_cookie_set_failure_message?: Text;
|
||||
public captcha_failed_message?: Text;
|
||||
|
||||
constructor(data: RawNode) {
|
||||
super();
|
||||
|
||||
if ('captchaLoadingMessage' in data) {
|
||||
this.captcha_loading_message = Text.fromAttributed(data.captchaLoadingMessage);
|
||||
}
|
||||
|
||||
if ('challengeReason' in data) {
|
||||
this.challenge_reason = Text.fromAttributed(data.challengeReason);
|
||||
}
|
||||
|
||||
if ('captchaSuccessfulMessage' in data) {
|
||||
this.captcha_successful_message = Text.fromAttributed(data.captchaSuccessfulMessage);
|
||||
}
|
||||
|
||||
if ('captchaCookieSetFailureMessage' in data) {
|
||||
this.captcha_cookie_set_failure_message = Text.fromAttributed(data.captchaCookieSetFailureMessage);
|
||||
}
|
||||
|
||||
if ('captchaFailedMessage' in data) {
|
||||
this.captcha_failed_message = Text.fromAttributed(data.captchaFailedMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,7 @@ export default class Playlist extends YTNode {
|
||||
menu: YTNode;
|
||||
badges: ObservedArray<YTNode>;
|
||||
endpoint: NavigationEndpoint;
|
||||
thumbnail_overlays;
|
||||
thumbnail_overlays: ObservedArray<YTNode>;
|
||||
view_playlist?: Text;
|
||||
|
||||
constructor(data: RawNode) {
|
||||
|
||||
17
deno/src/parser/classes/PlaylistThumbnailOverlay.ts
Normal file
17
deno/src/parser/classes/PlaylistThumbnailOverlay.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { YTNode } from '../helpers.ts';
|
||||
import { type RawNode } from '../index.ts';
|
||||
import Text from './misc/Text.ts';
|
||||
|
||||
export default class PlaylistThumbnailOverlay extends YTNode {
|
||||
static type = 'PlaylistThumbnailOverlay';
|
||||
|
||||
public icon_type?: string;
|
||||
public text: Text;
|
||||
|
||||
constructor(data: RawNode) {
|
||||
super();
|
||||
if (Reflect.has(data, 'icon'))
|
||||
this.icon_type = data.icon.iconType;
|
||||
this.text = new Text(data.text);
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,12 @@
|
||||
import { Parser, type RawNode } from '../index.ts';
|
||||
import BackstageImage from './BackstageImage.ts';
|
||||
import type { ObservedArray } from '../helpers.ts';
|
||||
import { YTNode } from '../helpers.ts';
|
||||
|
||||
export default class PostMultiImage extends YTNode {
|
||||
static type = 'PostMultiImage';
|
||||
|
||||
images : BackstageImage[];
|
||||
images: ObservedArray<BackstageImage>;
|
||||
|
||||
constructor(data: RawNode) {
|
||||
super();
|
||||
|
||||
@@ -4,7 +4,7 @@ import { Parser, type RawNode } from '../index.ts';
|
||||
export default class ProfileColumn extends YTNode {
|
||||
static type = 'ProfileColumn';
|
||||
|
||||
items: ObservedArray<YTNode>;
|
||||
public items: ObservedArray<YTNode>;
|
||||
|
||||
constructor(data: RawNode) {
|
||||
super();
|
||||
|
||||
@@ -3,16 +3,17 @@ import type { RawNode } from '../index.ts';
|
||||
import NavigationEndpoint from './NavigationEndpoint.ts';
|
||||
import Text from './misc/Text.ts';
|
||||
import Thumbnail from './misc/Thumbnail.ts';
|
||||
import AccessibilityData, { type AccessibilitySupportedDatas } from './misc/AccessibilityData.ts';
|
||||
|
||||
export default class ReelItem extends YTNode {
|
||||
static type = 'ReelItem';
|
||||
|
||||
id: string;
|
||||
title: Text;
|
||||
thumbnails: Thumbnail[];
|
||||
views: Text;
|
||||
endpoint: NavigationEndpoint;
|
||||
accessibility_label?: string;
|
||||
public id: string;
|
||||
public title: Text;
|
||||
public thumbnails: Thumbnail[];
|
||||
public views: Text;
|
||||
public endpoint: NavigationEndpoint;
|
||||
public accessibility?: AccessibilitySupportedDatas;
|
||||
|
||||
constructor(data: RawNode) {
|
||||
super();
|
||||
@@ -21,6 +22,16 @@ export default class ReelItem extends YTNode {
|
||||
this.thumbnails = Thumbnail.fromResponse(data.thumbnail);
|
||||
this.views = new Text(data.viewCountText);
|
||||
this.endpoint = new NavigationEndpoint(data.navigationEndpoint);
|
||||
this.accessibility_label = data.accessibility.accessibilityData.label;
|
||||
|
||||
if ('accessibility' in data
|
||||
&& 'accessibilityData' in data.accessibility) {
|
||||
this.accessibility = {
|
||||
accessibility_data: new AccessibilityData(data.accessibility.accessibilityData)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
get label() {
|
||||
return this.accessibility?.accessibility_data?.label;
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ export default class RichGrid extends YTNode {
|
||||
|
||||
header: YTNode;
|
||||
contents: ObservedArray<YTNode>;
|
||||
target_id?: string;
|
||||
|
||||
constructor(data: RawNode) {
|
||||
super();
|
||||
@@ -13,5 +14,8 @@ export default class RichGrid extends YTNode {
|
||||
// (Daniel Wykerd) XXX: reflowOptions aren't parsed, I think its only used internally for layout
|
||||
this.header = Parser.parseItem(data.header);
|
||||
this.contents = Parser.parseArray(data.contents);
|
||||
|
||||
if (Reflect.has(data, 'targetId'))
|
||||
this.target_id = data.targetId;
|
||||
}
|
||||
}
|
||||
@@ -4,10 +4,17 @@ import { Parser, type RawNode } from '../index.ts';
|
||||
export default class RichSection extends YTNode {
|
||||
static type = 'RichSection';
|
||||
|
||||
content: YTNode;
|
||||
public content: YTNode;
|
||||
public full_bleed: boolean;
|
||||
public target_id?: string;
|
||||
|
||||
constructor(data: RawNode) {
|
||||
super();
|
||||
this.content = Parser.parseItem(data.content);
|
||||
this.full_bleed = !!data.fullBleed;
|
||||
|
||||
if ('targetId' in data) {
|
||||
this.target_id = data.targetId;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { YTNode, type ObservedArray } from '../helpers.ts';
|
||||
import { type ObservedArray, YTNode } from '../helpers.ts';
|
||||
import { Parser, type RawNode } from '../index.ts';
|
||||
import NavigationEndpoint from './NavigationEndpoint.ts';
|
||||
import Text from './misc/Text.ts';
|
||||
@@ -6,22 +6,50 @@ import Text from './misc/Text.ts';
|
||||
export default class RichShelf extends YTNode {
|
||||
static type = 'RichShelf';
|
||||
|
||||
title: Text;
|
||||
contents: ObservedArray<YTNode>;
|
||||
endpoint?: NavigationEndpoint;
|
||||
subtitle?: Text;
|
||||
|
||||
public title: Text;
|
||||
public contents: ObservedArray<YTNode>;
|
||||
public endpoint?: NavigationEndpoint;
|
||||
public subtitle?: Text;
|
||||
public is_expanded: boolean;
|
||||
public is_bottom_divider_hidden: boolean;
|
||||
public is_top_divider_hidden: boolean;
|
||||
public layout_sizing?: 'RICH_GRID_LAYOUT_SIZING_UNSPECIFIED'
|
||||
| 'RICH_GRID_LAYOUT_SIZING_STANDARD'
|
||||
| 'RICH_GRID_LAYOUT_SIZING_COMPACT'
|
||||
| 'RICH_GRID_LAYOUT_SIZING_EXTRA_COMPACT'
|
||||
| 'RICH_GRID_LAYOUT_SIZING_TINY';
|
||||
public icon_type?: string;
|
||||
public menu: YTNode | null;
|
||||
public next_button: YTNode | null;
|
||||
public previous_button: YTNode | null;
|
||||
|
||||
constructor(data: RawNode) {
|
||||
super();
|
||||
this.title = new Text(data.title);
|
||||
this.contents = Parser.parseArray(data.contents);
|
||||
|
||||
if (Reflect.has(data, 'endpoint')) {
|
||||
this.is_expanded = !!data.is_expanded;
|
||||
this.is_bottom_divider_hidden = !!data.isBottomDividerHidden;
|
||||
this.is_top_divider_hidden = !!data.isTopDividerHidden;
|
||||
|
||||
if ('endpoint' in data) {
|
||||
this.endpoint = new NavigationEndpoint(data.endpoint);
|
||||
}
|
||||
|
||||
if (Reflect.has(data, 'subtitle')) {
|
||||
if ('subtitle' in data) {
|
||||
this.subtitle = new Text(data.subtitle);
|
||||
}
|
||||
|
||||
if ('layoutSizing' in data) {
|
||||
this.layout_sizing = data.layoutSizing;
|
||||
}
|
||||
|
||||
if ('icon' in data) {
|
||||
this.icon_type = data.icon.iconType;
|
||||
}
|
||||
|
||||
this.menu = Parser.parseItem(data.menu);
|
||||
this.next_button = Parser.parseItem(data.nextButton);
|
||||
this.previous_button = Parser.parseItem(data.previousButton);
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,15 @@
|
||||
import { Parser, type RawNode } from '../index.ts';
|
||||
import { type ObservedArray, YTNode } from '../helpers.ts';
|
||||
import UniversalWatchCard from './UniversalWatchCard.ts';
|
||||
|
||||
export default class SecondarySearchContainer extends YTNode {
|
||||
static type = 'SecondarySearchContainer';
|
||||
|
||||
contents: ObservedArray<YTNode>;
|
||||
public target_id?: string;
|
||||
public contents: ObservedArray<UniversalWatchCard>;
|
||||
|
||||
constructor(data: RawNode) {
|
||||
super();
|
||||
this.contents = Parser.parseArray(data.contents);
|
||||
this.contents = Parser.parseArray(data.contents, [ UniversalWatchCard ]);
|
||||
}
|
||||
}
|
||||
14
deno/src/parser/classes/SectionHeaderView.ts
Normal file
14
deno/src/parser/classes/SectionHeaderView.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { YTNode } from '../helpers.ts';
|
||||
import type { RawNode } from '../index.ts';
|
||||
import Text from './misc/Text.ts';
|
||||
|
||||
export default class SectionHeaderView extends YTNode {
|
||||
static type = 'SectionHeaderView';
|
||||
|
||||
public headline: Text;
|
||||
|
||||
constructor(data: RawNode) {
|
||||
super();
|
||||
this.headline = Text.fromAttributed(data.headline);
|
||||
}
|
||||
}
|
||||
@@ -34,11 +34,12 @@ export default class SegmentedLikeDislikeButtonView extends YTNode {
|
||||
if (toggle_button.default_button) {
|
||||
this.short_like_count = toggle_button.default_button.title;
|
||||
|
||||
this.like_count = parseInt(toggle_button.default_button.accessibility_text.replace(/\D/g, ''));
|
||||
if (toggle_button.default_button.accessibility_text)
|
||||
this.like_count = parseInt(toggle_button.default_button.accessibility_text.replace(/\D/g, ''));
|
||||
} else if (toggle_button.toggled_button) {
|
||||
this.short_like_count = toggle_button.toggled_button.title;
|
||||
|
||||
this.like_count = parseInt(toggle_button.toggled_button.accessibility_text.replace(/\D/g, ''));
|
||||
if (toggle_button.toggled_button.accessibility_text)
|
||||
this.like_count = parseInt(toggle_button.toggled_button.accessibility_text.replace(/\D/g, ''));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { YTNode } from '../helpers.ts';
|
||||
import { Parser } from '../index.ts';
|
||||
import type { RawNode } from '../types/RawResponse.ts';
|
||||
import type { RawNode } from '../types/index.ts';
|
||||
import BadgeView from './BadgeView.ts';
|
||||
import Text from './misc/Text.ts';
|
||||
import Thumbnail from './misc/Thumbnail.ts';
|
||||
|
||||
@@ -1,42 +1,40 @@
|
||||
import { YTNode } from '../helpers.ts';
|
||||
import type { RawNode } from '../index.ts';
|
||||
import NavigationEndpoint from './NavigationEndpoint.ts';
|
||||
import AccessibilityData, { type AccessibilitySupportedDatas } from './misc/AccessibilityData.ts';
|
||||
|
||||
export interface SubMenuItem {
|
||||
title: string;
|
||||
selected: boolean;
|
||||
continuation: string;
|
||||
endpoint: NavigationEndpoint;
|
||||
subtitle: string | null;
|
||||
}
|
||||
|
||||
export default class SortFilterSubMenu extends YTNode {
|
||||
static type = 'SortFilterSubMenu';
|
||||
|
||||
title?: string;
|
||||
icon_type?: string;
|
||||
label?: string;
|
||||
tooltip?: string;
|
||||
|
||||
sub_menu_items?: {
|
||||
title: string;
|
||||
selected: boolean;
|
||||
continuation: string;
|
||||
endpoint: NavigationEndpoint;
|
||||
subtitle: string | null;
|
||||
}[];
|
||||
public title?: string;
|
||||
public icon_type?: string;
|
||||
public tooltip?: string;
|
||||
public sub_menu_items?: SubMenuItem[];
|
||||
public accessibility?: AccessibilitySupportedDatas;
|
||||
|
||||
constructor(data: RawNode) {
|
||||
super();
|
||||
if (Reflect.has(data, 'title')) {
|
||||
if ('title' in data) {
|
||||
this.title = data.title;
|
||||
}
|
||||
|
||||
if (Reflect.has(data, 'icon')) {
|
||||
if ('icon' in data) {
|
||||
this.icon_type = data.icon.iconType;
|
||||
}
|
||||
|
||||
if (Reflect.has(data, 'accessibility')) {
|
||||
this.label = data.accessibility.accessibilityData.label;
|
||||
}
|
||||
|
||||
if (Reflect.has(data, 'tooltip')) {
|
||||
if ('tooltip' in data) {
|
||||
this.tooltip = data.tooltip;
|
||||
}
|
||||
|
||||
if (Reflect.has(data, 'subMenuItems')) {
|
||||
if ('subMenuItems' in data) {
|
||||
this.sub_menu_items = data.subMenuItems.map((item: RawNode) => ({
|
||||
title: item.title,
|
||||
selected: item.selected,
|
||||
@@ -45,5 +43,16 @@ export default class SortFilterSubMenu extends YTNode {
|
||||
subtitle: item.subtitle || null
|
||||
}));
|
||||
}
|
||||
|
||||
if ('accessibility' in data
|
||||
&& 'accessibilityData' in data.accessibility) {
|
||||
this.accessibility = {
|
||||
accessibility_data: new AccessibilityData(data.accessibility.accessibilityData)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
get label(): string | undefined {
|
||||
return this.accessibility?.accessibility_data?.label;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { YTNode, type ObservedArray } from '../helpers.ts';
|
||||
import { type ObservedArray, YTNode } from '../helpers.ts';
|
||||
import { Parser, type RawNode } from '../index.ts';
|
||||
import ExpandableVideoDescriptionBody from './ExpandableVideoDescriptionBody.ts';
|
||||
import HorizontalCardList from './HorizontalCardList.ts';
|
||||
@@ -7,16 +7,20 @@ import VideoDescriptionInfocardsSection from './VideoDescriptionInfocardsSection
|
||||
import VideoDescriptionMusicSection from './VideoDescriptionMusicSection.ts';
|
||||
import VideoDescriptionTranscriptSection from './VideoDescriptionTranscriptSection.ts';
|
||||
import VideoDescriptionCourseSection from './VideoDescriptionCourseSection.ts';
|
||||
import ReelShelf from './ReelShelf.ts';
|
||||
import VideoAttributesSectionView from './VideoAttributesSectionView.ts';
|
||||
import HowThisWasMadeSectionView from './HowThisWasMadeSectionView.ts';
|
||||
import ReelShelf from './ReelShelf.ts';
|
||||
import ExpandableMetadata from './ExpandableMetadata.ts';
|
||||
import MerchandiseShelf from './MerchandiseShelf.ts';
|
||||
|
||||
export default class StructuredDescriptionContent extends YTNode {
|
||||
static type = 'StructuredDescriptionContent';
|
||||
|
||||
items: ObservedArray<
|
||||
public items: ObservedArray<
|
||||
VideoDescriptionHeader | ExpandableVideoDescriptionBody | VideoDescriptionMusicSection |
|
||||
VideoDescriptionInfocardsSection | VideoDescriptionTranscriptSection | VideoDescriptionTranscriptSection |
|
||||
VideoDescriptionCourseSection | HorizontalCardList | ReelShelf | VideoAttributesSectionView
|
||||
VideoDescriptionInfocardsSection | VideoDescriptionTranscriptSection |
|
||||
VideoDescriptionCourseSection | HorizontalCardList | ReelShelf | VideoAttributesSectionView |
|
||||
HowThisWasMadeSectionView | ExpandableMetadata | MerchandiseShelf
|
||||
>;
|
||||
|
||||
constructor(data: RawNode) {
|
||||
@@ -24,7 +28,8 @@ export default class StructuredDescriptionContent extends YTNode {
|
||||
this.items = Parser.parseArray(data.items, [
|
||||
VideoDescriptionHeader, ExpandableVideoDescriptionBody, VideoDescriptionMusicSection,
|
||||
VideoDescriptionInfocardsSection, VideoDescriptionCourseSection, VideoDescriptionTranscriptSection,
|
||||
VideoDescriptionTranscriptSection, HorizontalCardList, ReelShelf, VideoAttributesSectionView
|
||||
VideoDescriptionTranscriptSection, HorizontalCardList, ReelShelf, VideoAttributesSectionView,
|
||||
HowThisWasMadeSectionView, ExpandableMetadata, MerchandiseShelf
|
||||
]);
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user