From ddc35db72eae23f2f58b0099501910cfa818684c Mon Sep 17 00:00:00 2001 From: LuanRT Date: Fri, 18 Jul 2025 15:25:50 +0000 Subject: [PATCH] chore: v15.0.0 release --- README.md | 1 - deno/package.json | 27 +- deno/protos/generated/misc/params.ts | 250 +++---------- deno/protos/misc/params.proto | 29 +- deno/src/Innertube.ts | 55 +-- deno/src/core/Session.ts | 22 +- deno/src/core/clients/Kids.ts | 14 +- deno/src/core/clients/Music.ts | 31 +- deno/src/core/mixins/MediaInfo.ts | 2 - .../classes/CompositeVideoPrimaryInfo.ts | 11 + deno/src/parser/classes/DismissableDialog.ts | 20 ++ .../DismissableDialogContentSection.ts | 16 + deno/src/parser/classes/GridShelfView.ts | 27 ++ deno/src/parser/classes/HypePointsFactoid.ts | 14 + deno/src/parser/classes/SectionHeaderView.ts | 14 + deno/src/parser/classes/ToggleFormField.ts | 24 ++ deno/src/parser/classes/VideoCard.ts | 1 - .../parser/classes/VideoDescriptionHeader.ts | 5 +- .../parser/classes/comments/CommentView.ts | 12 +- .../comments/VoiceReplyContainerView.ts | 18 + deno/src/parser/nodes.ts | 8 + deno/src/parser/parser.ts | 6 +- deno/src/parser/youtube/Channel.ts | 4 +- deno/src/parser/ytmusic/Artist.ts | 8 +- deno/src/platform/react-native.ts | 2 +- deno/src/types/FormatUtils.ts | 8 +- deno/src/types/GetVideoInfoOptions.ts | 13 + deno/src/types/index.ts | 3 +- deno/src/utils/DashManifest.js | 335 +++++++++++------- deno/src/utils/DashManifest.tsx | 23 +- deno/src/utils/HTTPClient.ts | 16 +- deno/src/utils/StreamingInfo.ts | 8 + 32 files changed, 583 insertions(+), 444 deletions(-) create mode 100644 deno/src/parser/classes/CompositeVideoPrimaryInfo.ts create mode 100644 deno/src/parser/classes/DismissableDialog.ts create mode 100644 deno/src/parser/classes/DismissableDialogContentSection.ts create mode 100644 deno/src/parser/classes/GridShelfView.ts create mode 100644 deno/src/parser/classes/HypePointsFactoid.ts create mode 100644 deno/src/parser/classes/SectionHeaderView.ts create mode 100644 deno/src/parser/classes/ToggleFormField.ts create mode 100644 deno/src/parser/classes/comments/VoiceReplyContainerView.ts create mode 100644 deno/src/types/GetVideoInfoOptions.ts diff --git a/README.md b/README.md index efa4c464..8619287b 100644 --- a/README.md +++ b/README.md @@ -50,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 */); ``` diff --git a/deno/package.json b/deno/package.json index 4000daf2..034dbcf7 100644 --- a/deno/package.json +++ b/deno/package.json @@ -1,6 +1,6 @@ { "name": "youtubei.js", - "version": "14.0.0", + "version": "15.0.0", "description": "A JavaScript client for YouTube's private API, known as InnerTube.", "type": "module", "types": "./dist/src/platform/lib.d.ts", @@ -28,11 +28,11 @@ }, "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", @@ -71,17 +71,16 @@ "Absidue (https://github.com/absidue)" ], "scripts": { - "test": "jest --verbose", + "test": "vitest run --reporter verbose", "lint": "eslint ./src", "lint:fix": "eslint --fix ./src", - "clean:source-maps": "rimraf ./bundle/browser.js.map ./bundle/node.cjs.map ./bundle/cf-worker.js.map ./bundle/react-native.js.map", - "clean:build-output": "rimraf ./dist ./bundle/browser.js ./bundle/node.cjs ./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:node && npm run bundle:browser && 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 --sourcemap --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 --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", @@ -105,7 +104,7 @@ "@bufbuild/protobuf": "^2.0.0", "jintr": "^3.3.1", "tslib": "^2.5.0", - "undici": "^5.19.1" + "undici": "^6.21.3" }, "overrides": { "typescript": "^5.0.0" @@ -114,24 +113,22 @@ "@eslint/js": "^9.9.0", "@types/estree": "^1.0.6", "@types/glob": "^8.1.0", - "@types/jest": "^28.1.7", - "@types/node": "^17.0.45", + "@types/node": "^24.0.14", "cpy-cli": "^4.2.0", - "esbuild": "^0.14.49", + "esbuild": "^0.25.6", "eslint": "^9.9.0", "glob": "^8.0.3", "globals": "^15.9.0", - "jest": "^29.7.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" + "typescript-eslint": "^8.2.0", + "vitest": "^3.2.4" }, "bugs": { "url": "https://github.com/LuanRT/YouTube.js/issues" diff --git a/deno/protos/generated/misc/params.ts b/deno/protos/generated/misc/params.ts index 30eda8e3..a09e298c 100644 --- a/deno/protos/generated/misc/params.ts +++ b/deno/protos/generated/misc/params.ts @@ -237,18 +237,13 @@ export interface NextParams { } export interface CommunityPostParams { - f0: string; f1: CommunityPostParams_Field1 | undefined; - f2: CommunityPostParams_Field2 | undefined; } export interface CommunityPostParams_Field1 { + ucid1: string; postId: string; -} - -export interface CommunityPostParams_Field2 { - p1: number; - p2: number; + ucid2: string; } export interface CommunityPostCommentsParamContainer { @@ -262,28 +257,19 @@ export interface CommunityPostCommentsParamContainer_Container { export interface CommunityPostCommentsParam { title: string; - postContainer: CommunityPostCommentsParam_PostContainer | undefined; - f0: CommunityPostCommentsParam_Field2 | undefined; commentDataContainer: CommunityPostCommentsParam_CommentDataContainer | undefined; } -export interface CommunityPostCommentsParam_PostContainer { - postId: string; -} - -export interface CommunityPostCommentsParam_Field2 { - f0: number; - f1: number; -} - export interface CommunityPostCommentsParam_CommentDataContainer { commentData: CommunityPostCommentsParam_CommentDataContainer_CommentData | undefined; + f0: number; title: string; } export interface CommunityPostCommentsParam_CommentDataContainer_CommentData { sortBy: number; f0: number; + f1: number; postId: string; channelId: string; } @@ -2132,19 +2118,13 @@ export const NextParams: MessageFns = { }; function createBaseCommunityPostParams(): CommunityPostParams { - return { f0: "", f1: undefined, f2: undefined }; + return { f1: undefined }; } export const CommunityPostParams: MessageFns = { encode(message: CommunityPostParams, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { - if (message.f0 !== "") { - writer.uint32(18).string(message.f0); - } if (message.f1 !== undefined) { - CommunityPostParams_Field1.encode(message.f1, writer.uint32(202).fork()).join(); - } - if (message.f2 !== undefined) { - CommunityPostParams_Field2.encode(message.f2, writer.uint32(362).fork()).join(); + CommunityPostParams_Field1.encode(message.f1, writer.uint32(450).fork()).join(); } return writer; }, @@ -2156,27 +2136,13 @@ export const CommunityPostParams: MessageFns = { while (reader.pos < end) { const tag = reader.uint32(); switch (tag >>> 3) { - case 2: - if (tag !== 18) { - break; - } - - message.f0 = reader.string(); - continue; - case 25: - if (tag !== 202) { + case 56: + if (tag !== 450) { break; } message.f1 = CommunityPostParams_Field1.decode(reader, reader.uint32()); continue; - case 45: - if (tag !== 362) { - break; - } - - message.f2 = CommunityPostParams_Field2.decode(reader, reader.uint32()); - continue; } if ((tag & 7) === 4 || tag === 0) { break; @@ -2188,13 +2154,19 @@ export const CommunityPostParams: MessageFns = { }; function createBaseCommunityPostParams_Field1(): CommunityPostParams_Field1 { - return { postId: "" }; + return { ucid1: "", postId: "", ucid2: "" }; } export const CommunityPostParams_Field1: MessageFns = { encode(message: CommunityPostParams_Field1, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { + if (message.ucid1 !== "") { + writer.uint32(18).string(message.ucid1); + } if (message.postId !== "") { - writer.uint32(178).string(message.postId); + writer.uint32(26).string(message.postId); + } + if (message.ucid2 !== "") { + writer.uint32(90).string(message.ucid2); } return writer; }, @@ -2206,58 +2178,26 @@ export const CommunityPostParams_Field1: MessageFns while (reader.pos < end) { const tag = reader.uint32(); switch (tag >>> 3) { - case 22: - if (tag !== 178) { + case 2: + if (tag !== 18) { + break; + } + + message.ucid1 = reader.string(); + continue; + case 3: + if (tag !== 26) { break; } message.postId = reader.string(); continue; - } - if ((tag & 7) === 4 || tag === 0) { - break; - } - reader.skip(tag & 7); - } - return message; - }, -}; - -function createBaseCommunityPostParams_Field2(): CommunityPostParams_Field2 { - return { p1: 0, p2: 0 }; -} - -export const CommunityPostParams_Field2: MessageFns = { - encode(message: CommunityPostParams_Field2, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { - if (message.p1 !== 0) { - writer.uint32(16).uint32(message.p1); - } - if (message.p2 !== 0) { - writer.uint32(24).uint32(message.p2); - } - return writer; - }, - - decode(input: BinaryReader | Uint8Array, length?: number): CommunityPostParams_Field2 { - const reader = input instanceof BinaryReader ? input : new BinaryReader(input); - let end = length === undefined ? reader.len : reader.pos + length; - const message = createBaseCommunityPostParams_Field2(); - while (reader.pos < end) { - const tag = reader.uint32(); - switch (tag >>> 3) { - case 2: - if (tag !== 16) { + case 11: + if (tag !== 90) { break; } - message.p1 = reader.uint32(); - continue; - case 3: - if (tag !== 24) { - break; - } - - message.p2 = reader.uint32(); + message.ucid2 = reader.string(); continue; } if ((tag & 7) === 4 || tag === 0) { @@ -2356,7 +2296,7 @@ export const CommunityPostCommentsParamContainer_Container: MessageFns = { @@ -2364,12 +2304,6 @@ export const CommunityPostCommentsParam: MessageFns if (message.title !== "") { writer.uint32(18).string(message.title); } - if (message.postContainer !== undefined) { - CommunityPostCommentsParam_PostContainer.encode(message.postContainer, writer.uint32(202).fork()).join(); - } - if (message.f0 !== undefined) { - CommunityPostCommentsParam_Field2.encode(message.f0, writer.uint32(362).fork()).join(); - } if (message.commentDataContainer !== undefined) { CommunityPostCommentsParam_CommentDataContainer.encode(message.commentDataContainer, writer.uint32(426).fork()) .join(); @@ -2391,20 +2325,6 @@ export const CommunityPostCommentsParam: MessageFns message.title = reader.string(); continue; - case 25: - if (tag !== 202) { - break; - } - - message.postContainer = CommunityPostCommentsParam_PostContainer.decode(reader, reader.uint32()); - continue; - case 45: - if (tag !== 362) { - break; - } - - message.f0 = CommunityPostCommentsParam_Field2.decode(reader, reader.uint32()); - continue; case 53: if (tag !== 426) { break; @@ -2425,90 +2345,8 @@ export const CommunityPostCommentsParam: MessageFns }, }; -function createBaseCommunityPostCommentsParam_PostContainer(): CommunityPostCommentsParam_PostContainer { - return { postId: "" }; -} - -export const CommunityPostCommentsParam_PostContainer: MessageFns = { - encode(message: CommunityPostCommentsParam_PostContainer, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { - if (message.postId !== "") { - writer.uint32(178).string(message.postId); - } - return writer; - }, - - decode(input: BinaryReader | Uint8Array, length?: number): CommunityPostCommentsParam_PostContainer { - const reader = input instanceof BinaryReader ? input : new BinaryReader(input); - let end = length === undefined ? reader.len : reader.pos + length; - const message = createBaseCommunityPostCommentsParam_PostContainer(); - while (reader.pos < end) { - const tag = reader.uint32(); - switch (tag >>> 3) { - case 22: - if (tag !== 178) { - break; - } - - message.postId = reader.string(); - continue; - } - if ((tag & 7) === 4 || tag === 0) { - break; - } - reader.skip(tag & 7); - } - return message; - }, -}; - -function createBaseCommunityPostCommentsParam_Field2(): CommunityPostCommentsParam_Field2 { - return { f0: 0, f1: 0 }; -} - -export const CommunityPostCommentsParam_Field2: MessageFns = { - encode(message: CommunityPostCommentsParam_Field2, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { - if (message.f0 !== 0) { - writer.uint32(16).int32(message.f0); - } - if (message.f1 !== 0) { - writer.uint32(24).int32(message.f1); - } - return writer; - }, - - decode(input: BinaryReader | Uint8Array, length?: number): CommunityPostCommentsParam_Field2 { - const reader = input instanceof BinaryReader ? input : new BinaryReader(input); - let end = length === undefined ? reader.len : reader.pos + length; - const message = createBaseCommunityPostCommentsParam_Field2(); - while (reader.pos < end) { - const tag = reader.uint32(); - switch (tag >>> 3) { - case 2: - if (tag !== 16) { - break; - } - - message.f0 = reader.int32(); - continue; - case 3: - if (tag !== 24) { - break; - } - - message.f1 = reader.int32(); - continue; - } - if ((tag & 7) === 4 || tag === 0) { - break; - } - reader.skip(tag & 7); - } - return message; - }, -}; - function createBaseCommunityPostCommentsParam_CommentDataContainer(): CommunityPostCommentsParam_CommentDataContainer { - return { commentData: undefined, title: "" }; + return { commentData: undefined, f0: 0, title: "" }; } export const CommunityPostCommentsParam_CommentDataContainer: MessageFns< @@ -2522,6 +2360,9 @@ export const CommunityPostCommentsParam_CommentDataContainer: MessageFns< CommunityPostCommentsParam_CommentDataContainer_CommentData.encode(message.commentData, writer.uint32(34).fork()) .join(); } + if (message.f0 !== 0) { + writer.uint32(56).int32(message.f0); + } if (message.title !== "") { writer.uint32(66).string(message.title); } @@ -2545,6 +2386,13 @@ export const CommunityPostCommentsParam_CommentDataContainer: MessageFns< reader.uint32(), ); continue; + case 7: + if (tag !== 56) { + break; + } + + message.f0 = reader.int32(); + continue; case 8: if (tag !== 66) { break; @@ -2563,7 +2411,7 @@ export const CommunityPostCommentsParam_CommentDataContainer: MessageFns< }; function createBaseCommunityPostCommentsParam_CommentDataContainer_CommentData(): CommunityPostCommentsParam_CommentDataContainer_CommentData { - return { sortBy: 0, f0: 0, postId: "", channelId: "" }; + return { sortBy: 0, f0: 0, f1: 0, postId: "", channelId: "" }; } export const CommunityPostCommentsParam_CommentDataContainer_CommentData: MessageFns< @@ -2577,7 +2425,10 @@ export const CommunityPostCommentsParam_CommentDataContainer_CommentData: Messag writer.uint32(48).int32(message.sortBy); } if (message.f0 !== 0) { - writer.uint32(216).int32(message.f0); + writer.uint32(120).int32(message.f0); + } + if (message.f1 !== 0) { + writer.uint32(200).int32(message.f1); } if (message.postId !== "") { writer.uint32(234).string(message.postId); @@ -2605,13 +2456,20 @@ export const CommunityPostCommentsParam_CommentDataContainer_CommentData: Messag message.sortBy = reader.int32(); continue; - case 27: - if (tag !== 216) { + case 15: + if (tag !== 120) { break; } message.f0 = reader.int32(); continue; + case 25: + if (tag !== 200) { + break; + } + + message.f1 = reader.int32(); + continue; case 29: if (tag !== 234) { break; diff --git a/deno/protos/misc/params.proto b/deno/protos/misc/params.proto index 669762d7..010cc3ff 100644 --- a/deno/protos/misc/params.proto +++ b/deno/protos/misc/params.proto @@ -231,19 +231,13 @@ message NextParams { } message CommunityPostParams { - string f0 = 2; - message Field1 { - string post_id = 22; + string ucid1 = 2; + string post_id = 3; + string ucid2 = 11; } - message Field2 { - uint32 p1 = 2; - uint32 p2 = 3; - } - - Field1 f1 = 25; - Field2 f2 = 45; + Field1 f1 = 56; } message CommunityPostCommentsParamContainer { @@ -256,29 +250,20 @@ message CommunityPostCommentsParamContainer { } message CommunityPostCommentsParam { - message PostContainer { - string post_id = 22; - } - - message Field2 { - int32 f0 = 2; - int32 f1 = 3; - } - message CommentDataContainer { message CommentData { int32 sortBy = 6; - int32 f0 = 27; + 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; - PostContainer post_container = 25; - Field2 f0 = 45; CommentDataContainer comment_data_container = 53; } diff --git a/deno/src/Innertube.ts b/deno/src/Innertube.ts index 4bfec056..f92507b9 100644 --- a/deno/src/Innertube.ts +++ b/deno/src/Innertube.ts @@ -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,10 +17,10 @@ import { Search, VideoInfo } from './parser/youtube/index.ts'; - import { ShortFormVideoInfo } from './parser/ytshorts/index.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'; @@ -29,12 +30,12 @@ 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, @@ -70,7 +71,7 @@ export default class Innertube { return new Innertube(await Session.create(config)); } - async getInfo(target: string | NavigationEndpoint, client?: InnerTubeClient): Promise { + async getInfo(target: string | NavigationEndpoint, options?: GetVideoInfoOptions): Promise { throwIfMissing({ target }); const payload = { @@ -96,10 +97,14 @@ export default class Innertube { signatureTimestamp: session.player?.sts } }, - client + client: options?.client }; - if (session.po_token) { + if (options?.po_token) { + extra_payload.serviceIntegrityDimensions = { + poToken: options.po_token + }; + } else if (session.po_token) { extra_payload.serviceIntegrityDimensions = { poToken: session.po_token }; @@ -115,7 +120,7 @@ export default class Innertube { return new VideoInfo(response, session.actions, cpn); } - async getBasicInfo(video_id: string, client?: InnerTubeClient): Promise { + async getBasicInfo(video_id: string, options?: GetVideoInfoOptions): Promise { throwIfMissing({ video_id }); const watch_endpoint = new NavigationEndpoint({ @@ -137,10 +142,14 @@ export default class Innertube { signatureTimestamp: session.player?.sts } }, - client + client: options?.client }; - if (session.po_token) { + if (options?.po_token) { + extra_payload.serviceIntegrityDimensions = { + poToken: options.po_token + }; + } else if (session.po_token) { extra_payload.serviceIntegrityDimensions = { poToken: session.po_token }; @@ -445,7 +454,7 @@ export default class Innertube { * @param options - Format options. */ async getStreamingData(video_id: string, options: FormatOptions = {}): Promise { - const info = await this.getBasicInfo(video_id, options?.client); + const info = await this.getBasicInfo(video_id, options); const format = info.chooseFormat(options); format.url = format.decipher(this.#session.player); @@ -460,7 +469,7 @@ export default class Innertube { * @param options - Download options. */ async download(video_id: string, options?: DownloadOptions): Promise> { - const info = await this.getBasicInfo(video_id, options?.client); + const info = await this.getBasicInfo(video_id, options); return info.download(options); } @@ -482,19 +491,16 @@ export default class Innertube { async getPost(post_id: string, channel_id: string) : Promise> { throwIfMissing({ post_id, channel_id }); const writer = CommunityPostParams.encode({ - f0: 'community', f1: { - postId: post_id - }, - f2: { - p1: 1, - p2: 1 + 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: channel_id, params: params } }); + 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); @@ -512,22 +518,17 @@ export default class Innertube { }; const writer1 = CommunityPostCommentsParam.encode({ - title: 'community', - postContainer: { - postId: post_id - }, - f0: { - f0: 1, - f1: 1 - }, + title: 'posts', commentDataContainer: { title: 'comments-section', commentData: { sortBy: SORT_OPTIONS[sort_by || 'TOP_COMMENTS'], - f0: 1, + f0: 2, + f1: 0, channelId: channel_id, postId: post_id - } + }, + f0: 0 } }); diff --git a/deno/src/core/Session.ts b/deno/src/core/Session.ts index 22918f39..d23454b7 100644 --- a/deno/src/core/Session.ts +++ b/deno/src/core/Session.ts @@ -1,8 +1,8 @@ -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, Log, LZW, ProtoUtils } from '../utils/index.ts'; import { generateRandomString, getRandomUserAgent, @@ -203,7 +203,7 @@ 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; /** @@ -352,11 +352,7 @@ export default class Session extends EventEmitter { if (client) { result.context.client.clientName = client.NAME; result.context.client.clientVersion = client.VERSION; - } else { - Log.warn(TAG, `Unknown client name: ${session_args.client_name}. Using default WEB client.`); - result.context.client.clientName = ClientType.WEB; - result.context.client.clientVersion = Constants.CLIENTS.WEB.VERSION; - } + } else Log.warn(TAG, `Unknown client name: ${session_args.client_name}.`); } result.context.client.timeZone = session_args.time_zone; @@ -424,7 +420,7 @@ export default class Session extends EventEmitter { user_agent: user_agent, visitor_data: visitor_data || ProtoUtils.encodeVisitorData(generateRandomString(11), Math.floor(Date.now() / 1000)), client_name: client_name, - client_version: Object.values(Constants.CLIENTS).filter((v) => v.NAME === client_name)[0]?.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', @@ -566,9 +562,9 @@ export default class Session extends EventEmitter { visitor_data: options.visitor_data || device_info[13], user_agent: options.user_agent, client_name: options.client_name, - client_version: Object.values(Constants.CLIENTS).filter( - (v) => v.NAME === options.client_name - )[0]?.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, diff --git a/deno/src/core/clients/Kids.ts b/deno/src/core/clients/Kids.ts index fbd4cf84..1a5ace57 100644 --- a/deno/src/core/clients/Kids.ts +++ b/deno/src/core/clients/Kids.ts @@ -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,7 +19,7 @@ export default class Kids { return new Search(this.#session.actions, response); } - async getInfo(video_id: string): Promise { + async getInfo(video_id: string, options?: Omit): Promise { const payload = { videoId: video_id }; const watch_endpoint = new NavigationEndpoint({ watchEndpoint: payload }); const watch_next_endpoint = new NavigationEndpoint({ watchNextEndpoint: payload }); @@ -38,7 +38,11 @@ export default class Kids { client: 'YTKIDS' }; - if (session.po_token) { + if (options?.po_token) { + extra_payload.serviceIntegrityDimensions = { + poToken: options.po_token + }; + } else if (session.po_token) { extra_payload.serviceIntegrityDimensions = { poToken: session.po_token }; diff --git a/deno/src/core/clients/Music.ts b/deno/src/core/clients/Music.ts index 92a0ba85..c1974394 100644 --- a/deno/src/core/clients/Music.ts +++ b/deno/src/core/clients/Music.ts @@ -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,19 +42,20 @@ 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 { + getInfo(target: string | MusicTwoRowItem | MusicResponsiveListItem | NavigationEndpoint, options?: Omit): Promise { 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.#fetchInfoFromEndpoint(target, options); } - return this.#fetchInfoFromVideoId(target); + return this.#fetchInfoFromVideoId(target, options); } - async #fetchInfoFromVideoId(video_id: string): Promise { + async #fetchInfoFromVideoId(video_id: string, options?: GetVideoInfoOptions): Promise { const payload = { videoId: video_id, racyCheckOk: true, contentCheckOk: true }; const watch_endpoint = new NavigationEndpoint({ watchEndpoint: payload }); const watch_next_endpoint = new NavigationEndpoint({ watchNextEndpoint: payload }); @@ -71,7 +72,11 @@ export default class Music { client: 'YTMUSIC' }; - if (this.#session.po_token) { + 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 }; @@ -87,7 +92,7 @@ export default class Music { return new TrackInfo(response, this.#actions, cpn); } - async #fetchInfoFromEndpoint(endpoint?: NavigationEndpoint): Promise { + async #fetchInfoFromEndpoint(endpoint?: NavigationEndpoint, options?: GetVideoInfoOptions): Promise { if (!endpoint) throw new Error('This item does not have an endpoint.'); @@ -103,7 +108,11 @@ export default class Music { client: 'YTMUSIC' }; - if (this.#session.po_token) { + 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 }; diff --git a/deno/src/core/mixins/MediaInfo.ts b/deno/src/core/mixins/MediaInfo.ts index 90430990..ce0b9585 100644 --- a/deno/src/core/mixins/MediaInfo.ts +++ b/deno/src/core/mixins/MediaInfo.ts @@ -104,8 +104,6 @@ export default class MediaInfo { async toDash(options: { url_transformer?: URLTransformer; format_filter?: FormatFilter; - include_thumbnails?: boolean; - captions_format?: string; manifest_options?: DashOptions; } = {}): Promise { const player_response = this.#page[0]; diff --git a/deno/src/parser/classes/CompositeVideoPrimaryInfo.ts b/deno/src/parser/classes/CompositeVideoPrimaryInfo.ts new file mode 100644 index 00000000..0fc806af --- /dev/null +++ b/deno/src/parser/classes/CompositeVideoPrimaryInfo.ts @@ -0,0 +1,11 @@ +import { YTNode } from '../helpers.ts'; +import type { RawNode } from '../types/index.ts'; + +export default class CompositeVideoPrimaryInfo extends YTNode { + static type = 'CompositeVideoPrimaryInfo'; + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + constructor(data: RawNode) { + super(); + } +} diff --git a/deno/src/parser/classes/DismissableDialog.ts b/deno/src/parser/classes/DismissableDialog.ts new file mode 100644 index 00000000..34435542 --- /dev/null +++ b/deno/src/parser/classes/DismissableDialog.ts @@ -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; + 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; + } +} diff --git a/deno/src/parser/classes/DismissableDialogContentSection.ts b/deno/src/parser/classes/DismissableDialogContentSection.ts new file mode 100644 index 00000000..4a6495e7 --- /dev/null +++ b/deno/src/parser/classes/DismissableDialogContentSection.ts @@ -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); + } +} diff --git a/deno/src/parser/classes/GridShelfView.ts b/deno/src/parser/classes/GridShelfView.ts new file mode 100644 index 00000000..d2bee93b --- /dev/null +++ b/deno/src/parser/classes/GridShelfView.ts @@ -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; + 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; + } +} diff --git a/deno/src/parser/classes/HypePointsFactoid.ts b/deno/src/parser/classes/HypePointsFactoid.ts new file mode 100644 index 00000000..463edac9 --- /dev/null +++ b/deno/src/parser/classes/HypePointsFactoid.ts @@ -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); + } +} \ No newline at end of file diff --git a/deno/src/parser/classes/SectionHeaderView.ts b/deno/src/parser/classes/SectionHeaderView.ts new file mode 100644 index 00000000..1688d94b --- /dev/null +++ b/deno/src/parser/classes/SectionHeaderView.ts @@ -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); + } +} \ No newline at end of file diff --git a/deno/src/parser/classes/ToggleFormField.ts b/deno/src/parser/classes/ToggleFormField.ts new file mode 100644 index 00000000..f9ceab12 --- /dev/null +++ b/deno/src/parser/classes/ToggleFormField.ts @@ -0,0 +1,24 @@ +import { type RawNode } from '../index.ts'; +import { YTNode } from '../helpers.ts'; +import NavigationEndpoint from './NavigationEndpoint.ts'; + +export default class ToggleFormField extends YTNode { + static type = 'ToggleFormField'; + + public label: Text; + public toggled: boolean; + public toggle_on_action?: NavigationEndpoint; + public toggle_off_action?: NavigationEndpoint; + + constructor(data: RawNode) { + super(); + this.label = new Text(data.label); + this.toggled = data.toggled; + + if ('toggleOnAction' in data) + this.toggle_on_action = new NavigationEndpoint(data.toggleOnAction); + + if ('toggleOffAction' in data) + this.toggle_off_action = new NavigationEndpoint(data.toggleOffAction); + } +} \ No newline at end of file diff --git a/deno/src/parser/classes/VideoCard.ts b/deno/src/parser/classes/VideoCard.ts index 88760b7b..e6667745 100644 --- a/deno/src/parser/classes/VideoCard.ts +++ b/deno/src/parser/classes/VideoCard.ts @@ -7,7 +7,6 @@ export default class VideoCard extends Video { static type = 'VideoCard'; public metadata_text?: Text; - public byline_text?: Text; constructor(data: RawNode) { super(data); diff --git a/deno/src/parser/classes/VideoDescriptionHeader.ts b/deno/src/parser/classes/VideoDescriptionHeader.ts index 93ebba94..beb9af0b 100644 --- a/deno/src/parser/classes/VideoDescriptionHeader.ts +++ b/deno/src/parser/classes/VideoDescriptionHeader.ts @@ -5,6 +5,7 @@ import Factoid from './Factoid.ts'; import NavigationEndpoint from './NavigationEndpoint.ts'; import UploadTimeFactoid from './UploadTimeFactoid.ts'; import ViewCountFactoid from './ViewCountFactoid.ts'; +import HypePointsFactoid from './HypePointsFactoid.ts'; export default class VideoDescriptionHeader extends YTNode { static type = 'VideoDescriptionHeader'; @@ -12,7 +13,7 @@ export default class VideoDescriptionHeader extends YTNode { channel: Text; channel_navigation_endpoint?: NavigationEndpoint; channel_thumbnail: Thumbnail[]; - factoids: ObservedArray; + factoids: ObservedArray; publish_date: Text; title: Text; views: Text; @@ -25,6 +26,6 @@ export default class VideoDescriptionHeader extends YTNode { this.channel_thumbnail = Thumbnail.fromResponse(data.channelThumbnail); this.publish_date = new Text(data.publishDate); this.views = new Text(data.views); - this.factoids = Parser.parseArray(data.factoid, [ Factoid, ViewCountFactoid, UploadTimeFactoid ]); + this.factoids = Parser.parseArray(data.factoid, [ Factoid, HypePointsFactoid, ViewCountFactoid, UploadTimeFactoid ]); } } diff --git a/deno/src/parser/classes/comments/CommentView.ts b/deno/src/parser/classes/comments/CommentView.ts index e0627bbc..f8a1c7c2 100644 --- a/deno/src/parser/classes/comments/CommentView.ts +++ b/deno/src/parser/classes/comments/CommentView.ts @@ -1,8 +1,10 @@ +import { Parser } from '../../index.ts'; import { YTNode } from '../../helpers.ts'; import NavigationEndpoint from '../NavigationEndpoint.ts'; import Author from '../misc/Author.ts'; import Text from '../misc/Text.ts'; import CommentReplyDialog from './CommentReplyDialog.ts'; +import VoiceReplyContainerView from './VoiceReplyContainerView.ts'; import { InnertubeError } from '../../../utils/Utils.ts'; import * as ProtoUtils from '../../../utils/ProtoUtils.ts'; @@ -63,6 +65,8 @@ export default class CommentView extends YTNode { public is_disliked?: boolean; public is_hearted?: boolean; + public voice_reply_container?: VoiceReplyContainerView | null; + constructor(data: RawNode) { super(); @@ -78,7 +82,7 @@ export default class CommentView extends YTNode { }; } - applyMutations(comment?: RawNode, toolbar_state?: RawNode, toolbar_surface?: RawNode) { + applyMutations(comment?: RawNode, toolbar_state?: RawNode, toolbar_surface?: RawNode, comment_surface?: RawNode) { if (comment) { this.content = Text.fromAttributed(comment.properties.content); this.published_time = comment.properties.publishedTime; @@ -129,6 +133,12 @@ export default class CommentView extends YTNode { this.reply_command = new NavigationEndpoint(toolbar_surface.replyCommand); } } + + if (comment_surface) { + if ('voiceReplyContainerViewModel' in comment_surface) { + this.voice_reply_container = Parser.parseItem(comment_surface.voiceReplyContainerViewModel, VoiceReplyContainerView); + } + } } /** diff --git a/deno/src/parser/classes/comments/VoiceReplyContainerView.ts b/deno/src/parser/classes/comments/VoiceReplyContainerView.ts new file mode 100644 index 00000000..d186076b --- /dev/null +++ b/deno/src/parser/classes/comments/VoiceReplyContainerView.ts @@ -0,0 +1,18 @@ +import { YTNode } from '../../helpers.ts'; +import Text from '../misc/Text.ts'; + +import type { RawNode } from '../../index.ts'; + +export default class VoiceReplyContainerView extends YTNode { + static type = 'VoiceReplyContainerView'; + + voice_reply_unavailable_text : Text; + transcript_text : Text; + + constructor(data: RawNode) { + super(); + + this.voice_reply_unavailable_text = Text.fromAttributed(data.voiceReplyUnavailableText); + this.transcript_text = Text.fromAttributed(data.transcriptText); + } +} \ No newline at end of file diff --git a/deno/src/parser/nodes.ts b/deno/src/parser/nodes.ts index 5408c075..39083c66 100644 --- a/deno/src/parser/nodes.ts +++ b/deno/src/parser/nodes.ts @@ -101,6 +101,7 @@ export { default as CreatorHeart } from './classes/comments/CreatorHeart.ts'; export { default as EmojiPicker } from './classes/comments/EmojiPicker.ts'; export { default as PdgCommentChip } from './classes/comments/PdgCommentChip.ts'; export { default as SponsorCommentBadge } from './classes/comments/SponsorCommentBadge.ts'; +export { default as VoiceReplyContainerView } from './classes/comments/VoiceReplyContainerView.ts'; export { default as CompactChannel } from './classes/CompactChannel.ts'; export { default as CompactLink } from './classes/CompactLink.ts'; export { default as CompactMix } from './classes/CompactMix.ts'; @@ -108,6 +109,7 @@ export { default as CompactMovie } from './classes/CompactMovie.ts'; export { default as CompactPlaylist } from './classes/CompactPlaylist.ts'; export { default as CompactStation } from './classes/CompactStation.ts'; export { default as CompactVideo } from './classes/CompactVideo.ts'; +export { default as CompositeVideoPrimaryInfo } from './classes/CompositeVideoPrimaryInfo.ts'; export { default as ConfirmDialog } from './classes/ConfirmDialog.ts'; export { default as ContentMetadataView } from './classes/ContentMetadataView.ts'; export { default as ContentPreviewImageView } from './classes/ContentPreviewImageView.ts'; @@ -124,6 +126,8 @@ export { default as DialogHeaderView } from './classes/DialogHeaderView.ts'; export { default as DialogView } from './classes/DialogView.ts'; export { default as DidYouMean } from './classes/DidYouMean.ts'; export { default as DislikeButtonView } from './classes/DislikeButtonView.ts'; +export { default as DismissableDialog } from './classes/DismissableDialog.ts'; +export { default as DismissableDialogContentSection } from './classes/DismissableDialogContentSection.ts'; export { default as DownloadButton } from './classes/DownloadButton.ts'; export { default as Dropdown } from './classes/Dropdown.ts'; export { default as DropdownItem } from './classes/DropdownItem.ts'; @@ -186,6 +190,7 @@ export { default as GridHeader } from './classes/GridHeader.ts'; export { default as GridMix } from './classes/GridMix.ts'; export { default as GridMovie } from './classes/GridMovie.ts'; export { default as GridPlaylist } from './classes/GridPlaylist.ts'; +export { default as GridShelfView } from './classes/GridShelfView.ts'; export { default as GridShow } from './classes/GridShow.ts'; export { default as GridVideo } from './classes/GridVideo.ts'; export { default as GuideCollapsibleEntry } from './classes/GuideCollapsibleEntry.ts'; @@ -205,6 +210,7 @@ export { default as HorizontalCardList } from './classes/HorizontalCardList.ts'; export { default as HorizontalList } from './classes/HorizontalList.ts'; export { default as HorizontalMovieList } from './classes/HorizontalMovieList.ts'; export { default as HowThisWasMadeSectionView } from './classes/HowThisWasMadeSectionView.ts'; +export { default as HypePointsFactoid } from './classes/HypePointsFactoid.ts'; export { default as IconLink } from './classes/IconLink.ts'; export { default as ImageBannerView } from './classes/ImageBannerView.ts'; export { default as IncludingResultsFor } from './classes/IncludingResultsFor.ts'; @@ -417,6 +423,7 @@ export { default as SearchSubMenu } from './classes/SearchSubMenu.ts'; export { default as SearchSuggestion } from './classes/SearchSuggestion.ts'; export { default as SearchSuggestionsSection } from './classes/SearchSuggestionsSection.ts'; export { default as SecondarySearchContainer } from './classes/SecondarySearchContainer.ts'; +export { default as SectionHeaderView } from './classes/SectionHeaderView.ts'; export { default as SectionList } from './classes/SectionList.ts'; export { default as SegmentedLikeDislikeButton } from './classes/SegmentedLikeDislikeButton.ts'; export { default as SegmentedLikeDislikeButtonView } from './classes/SegmentedLikeDislikeButtonView.ts'; @@ -482,6 +489,7 @@ export { default as TimedMarkerDecoration } from './classes/TimedMarkerDecoratio export { default as TitleAndButtonListHeader } from './classes/TitleAndButtonListHeader.ts'; export { default as ToggleButton } from './classes/ToggleButton.ts'; export { default as ToggleButtonView } from './classes/ToggleButtonView.ts'; +export { default as ToggleFormField } from './classes/ToggleFormField.ts'; export { default as ToggleMenuServiceItem } from './classes/ToggleMenuServiceItem.ts'; export { default as Tooltip } from './classes/Tooltip.ts'; export { default as TopicChannelDetails } from './classes/TopicChannelDetails.ts'; diff --git a/deno/src/parser/parser.ts b/deno/src/parser/parser.ts index 663c27e6..a9fe63d6 100644 --- a/deno/src/parser/parser.ts +++ b/deno/src/parser/parser.ts @@ -844,7 +844,11 @@ export function applyCommentsMutations(memo: Memo, mutations: RawNode[]) { const engagement_toolbar = mutations.find((mutation) => mutation.entityKey === comment_view.keys.toolbar_surface) ?.payload?.engagementToolbarSurfaceEntityPayload; - comment_view.applyMutations(comment_mutation, toolbar_state_mutation, engagement_toolbar); + const comment_surface_mutation = mutations + .find((mutation) => mutation.payload?.commentSurfaceEntityPayload?.key === comment_view.keys.comment_surface) + ?.payload?.commentSurfaceEntityPayload; + + comment_view.applyMutations(comment_mutation, toolbar_state_mutation, engagement_toolbar, comment_surface_mutation); } } } diff --git a/deno/src/parser/youtube/Channel.ts b/deno/src/parser/youtube/Channel.ts index d020e530..d802425b 100644 --- a/deno/src/parser/youtube/Channel.ts +++ b/deno/src/parser/youtube/Channel.ts @@ -194,7 +194,7 @@ export default class Channel extends TabbedFeed { } async getCommunity(): Promise { - const tab = await this.getTabByURL('community'); + const tab = await this.getTabByURL('posts'); return new Channel(this.actions, tab.page, true); } @@ -283,7 +283,7 @@ export default class Channel extends TabbedFeed { } get has_community(): boolean { - return this.hasTabWithURL('community'); + return this.hasTabWithURL('posts'); } get has_about(): boolean { diff --git a/deno/src/parser/ytmusic/Artist.ts b/deno/src/parser/ytmusic/Artist.ts index da00fc59..79fd7e05 100644 --- a/deno/src/parser/ytmusic/Artist.ts +++ b/deno/src/parser/ytmusic/Artist.ts @@ -38,13 +38,13 @@ export default class Artist { if (!music_shelves.length) throw new InnertubeError('Could not find any node of type MusicShelf.'); - const shelf = music_shelves.find((shelf) => shelf.title.toString() === 'Songs') as MusicShelf; + const shelf = music_shelves.find((shelf) => shelf.title?.text === 'Top songs') as MusicShelf; if (!shelf) - throw new InnertubeError('Could not find target shelf (Songs).'); + throw new InnertubeError('Could not find target shelf (Top songs).'); if (!shelf.endpoint) - throw new InnertubeError('Target shelf (Songs) did not have an endpoint.'); + throw new InnertubeError('Target shelf (Top songs) did not have an endpoint.'); const page = await shelf.endpoint.call(this.#actions, { client: 'YTMUSIC', parse: true }); return page.contents_memo?.getType(MusicPlaylistShelf)?.[0]; @@ -53,4 +53,4 @@ export default class Artist { get page(): IBrowseResponse { return this.#page; } -} \ No newline at end of file +} diff --git a/deno/src/platform/react-native.ts b/deno/src/platform/react-native.ts index 334cfa90..46873b36 100644 --- a/deno/src/platform/react-native.ts +++ b/deno/src/platform/react-native.ts @@ -2,7 +2,7 @@ import type { ICache } from '../types/Cache.ts'; import { Platform } from '../utils/Utils.ts'; import sha1Hash from './polyfills/web-crypto.ts'; -import package_json from '../../package.json' assert { type: 'json' }; +import package_json from '../../package.json' with { type: 'json' }; import evaluate from './jsruntime/jinter.ts'; class Cache implements ICache { diff --git a/deno/src/types/FormatUtils.ts b/deno/src/types/FormatUtils.ts index 6106f171..159d2933 100644 --- a/deno/src/types/FormatUtils.ts +++ b/deno/src/types/FormatUtils.ts @@ -1,10 +1,10 @@ -import type { InnerTubeClient } from '../types/index.ts'; +import type { GetVideoInfoOptions } from '../types/index.ts'; import type { Format } from '../parser/misc.ts'; export type URLTransformer = (url: URL) => URL; export type FormatFilter = (format: Format) => boolean; -export interface FormatOptions { +export interface FormatOptions extends GetVideoInfoOptions { /** * Video or audio itag */ @@ -29,10 +29,6 @@ export interface FormatOptions { * Video or audio codec, e.g. 'avc', 'vp9', 'av01' for video, 'opus', 'mp4a' for audio */ codec?: string; - /** - * InnerTube client. - */ - client?: InnerTubeClient; } export interface DownloadOptions extends FormatOptions { diff --git a/deno/src/types/GetVideoInfoOptions.ts b/deno/src/types/GetVideoInfoOptions.ts new file mode 100644 index 00000000..4998f5bb --- /dev/null +++ b/deno/src/types/GetVideoInfoOptions.ts @@ -0,0 +1,13 @@ +import { type InnerTubeClient } from './Misc.ts'; + +export interface GetVideoInfoOptions { + /** + * InnerTube client. + */ + client?: InnerTubeClient; + /** + * Proof of Origin token, bound to the video ID being requested. + * If not provided, session bound token will be used. + */ + po_token?: string; +} \ No newline at end of file diff --git a/deno/src/types/index.ts b/deno/src/types/index.ts index 03464dde..0aca46b4 100644 --- a/deno/src/types/index.ts +++ b/deno/src/types/index.ts @@ -3,4 +3,5 @@ export type { default as PlatformShim } from './PlatformShim.ts'; export * from './Cache.ts'; export * from './PlatformShim.ts'; export * from './Misc.ts'; -export * from './FormatUtils.ts'; \ No newline at end of file +export * from './FormatUtils.ts'; +export * from './GetVideoInfoOptions.ts'; \ No newline at end of file diff --git a/deno/src/utils/DashManifest.js b/deno/src/utils/DashManifest.js index 5a8a2c29..edd6cd08 100644 --- a/deno/src/utils/DashManifest.js +++ b/deno/src/utils/DashManifest.js @@ -4,33 +4,44 @@ import * as DashUtils from "./DashUtils.ts"; import { getStreamingInfo } from "./StreamingInfo.ts"; import { InnertubeError } from "./Utils.ts"; async function OTFPostLiveDvrSegmentInfo({ info }) { - if (!info.is_oft && !info.is_post_live_dvr) - return null; + if (!info.is_oft && !info.is_post_live_dvr) return null; const template = await info.getSegmentTemplate(); - return /* @__PURE__ */ DashUtils.createElement("segmentTemplate", { - startNumber: template.init_url ? "1" : "0", - timescale: "1000", - initialization: template.init_url, - media: template.media_url - }, /* @__PURE__ */ DashUtils.createElement("segmentTimeline", null, template.timeline.map((segment_duration) => /* @__PURE__ */ DashUtils.createElement("s", { - d: segment_duration.duration, - r: segment_duration.repeat_count - })))); + return /* @__PURE__ */ DashUtils.createElement( + "segmentTemplate", + { + startNumber: template.init_url ? "1" : "0", + timescale: "1000", + initialization: template.init_url, + media: template.media_url + }, + /* @__PURE__ */ DashUtils.createElement("segmentTimeline", null, template.timeline.map((segment_duration) => /* @__PURE__ */ DashUtils.createElement( + "s", + { + d: segment_duration.duration, + r: segment_duration.repeat_count + } + ))) + ); } __name(OTFPostLiveDvrSegmentInfo, "OTFPostLiveDvrSegmentInfo"); function SegmentInfo({ info }) { if (info.is_oft || info.is_post_live_dvr) { - return /* @__PURE__ */ DashUtils.createElement(OTFPostLiveDvrSegmentInfo, { - info - }); + return /* @__PURE__ */ DashUtils.createElement(OTFPostLiveDvrSegmentInfo, { info }); } - return /* @__PURE__ */ DashUtils.createElement(DashUtils.Fragment, null, /* @__PURE__ */ DashUtils.createElement("baseURL", null, info.base_url), /* @__PURE__ */ DashUtils.createElement("segmentBase", { - indexRange: `${info.index_range.start}-${info.index_range.end}` - }, /* @__PURE__ */ DashUtils.createElement("initialization", { - range: `${info.init_range.start}-${info.init_range.end}` - }))); + return /* @__PURE__ */ DashUtils.createElement(DashUtils.Fragment, null, /* @__PURE__ */ DashUtils.createElement("baseURL", null, info.base_url), /* @__PURE__ */ DashUtils.createElement("segmentBase", { indexRange: `${info.index_range.start}-${info.index_range.end}` }, /* @__PURE__ */ DashUtils.createElement("initialization", { range: `${info.init_range.start}-${info.init_range.end}` }))); } __name(SegmentInfo, "SegmentInfo"); +function getDrmSystemId(drm_family) { + switch (drm_family) { + case "WIDEVINE": + return "edef8ba9-79d6-4ace-a3c8-27dcd51d21ed"; + case "PLAYREADY": + return "9a04f079-9840-4286-ab92-e65be0885f95"; + default: + return null; + } +} +__name(getDrmSystemId, "getDrmSystemId"); async function DashManifest({ streamingData, isPostLiveDvr, @@ -50,120 +61,190 @@ async function DashManifest({ image_sets, text_sets } = getStreamingInfo(streamingData, isPostLiveDvr, transformURL, rejectFormat, cpn, player, actions, storyboards, captionTracks, options); - return /* @__PURE__ */ DashUtils.createElement("mPD", { - xmlns: "urn:mpeg:dash:schema:mpd:2011", - minBufferTime: "PT1.500S", - profiles: "urn:mpeg:dash:profile:isoff-main:2011", - type: "static", - mediaPresentationDuration: `PT${await getDuration()}S`, - "xmlns:xsi": "http://www.w3.org/2001/XMLSchema-instance", - "xsi:schemaLocation": "urn:mpeg:dash:schema:mpd:2011 http://standards.iso.org/ittf/PubliclyAvailableStandards/MPEG-DASH_schema_files/DASH-MPD.xsd" - }, /* @__PURE__ */ DashUtils.createElement("period", null, audio_sets.map((set, index) => /* @__PURE__ */ DashUtils.createElement("adaptationSet", { - id: index, - mimeType: set.mime_type, - startWithSAP: "1", - subsegmentAlignment: "true", - lang: set.language, - codecs: set.codecs, - audioSamplingRate: set.audio_sample_rate, - contentType: "audio" - }, set.track_roles && set.track_roles.map((role) => /* @__PURE__ */ DashUtils.createElement("role", { - schemeIdUri: "urn:mpeg:dash:role:2011", - value: role - })), set.track_name && /* @__PURE__ */ DashUtils.createElement("label", { - id: index - }, set.track_name), set.channels && /* @__PURE__ */ DashUtils.createElement("audioChannelConfiguration", { - schemeIdUri: "urn:mpeg:dash:23003:3:audio_channel_configuration:2011", - value: set.channels - }), set.representations.map((rep) => /* @__PURE__ */ DashUtils.createElement("representation", { - id: rep.uid, - bandwidth: rep.bitrate, - codecs: rep.codecs, - audioSamplingRate: rep.audio_sample_rate - }, rep.channels && /* @__PURE__ */ DashUtils.createElement("audioChannelConfiguration", { - schemeIdUri: "urn:mpeg:dash:23003:3:audio_channel_configuration:2011", - value: rep.channels - }), /* @__PURE__ */ DashUtils.createElement(SegmentInfo, { - info: rep.segment_info - }))))), video_sets.map((set, index) => /* @__PURE__ */ DashUtils.createElement("adaptationSet", { - id: index + audio_sets.length, - mimeType: set.mime_type, - startWithSAP: "1", - subsegmentAlignment: "true", - codecs: set.codecs, - maxPlayoutRate: "1", - frameRate: set.fps, - contentType: "video" - }, set.color_info.primaries && /* @__PURE__ */ DashUtils.createElement("supplementalProperty", { - schemeIdUri: "urn:mpeg:mpegB:cicp:ColourPrimaries", - value: set.color_info.primaries - }), set.color_info.transfer_characteristics && /* @__PURE__ */ DashUtils.createElement("supplementalProperty", { - schemeIdUri: "urn:mpeg:mpegB:cicp:TransferCharacteristics", - value: set.color_info.transfer_characteristics - }), set.color_info.matrix_coefficients && /* @__PURE__ */ DashUtils.createElement("supplementalProperty", { - schemeIdUri: "urn:mpeg:mpegB:cicp:MatrixCoefficients", - value: set.color_info.matrix_coefficients - }), set.representations.map((rep) => /* @__PURE__ */ DashUtils.createElement("representation", { - id: rep.uid, - bandwidth: rep.bitrate, - width: rep.width, - height: rep.height, - codecs: rep.codecs, - frameRate: rep.fps - }, /* @__PURE__ */ DashUtils.createElement(SegmentInfo, { - info: rep.segment_info - }))))), image_sets.map(async (set, index) => { - return /* @__PURE__ */ DashUtils.createElement("adaptationSet", { - id: index + audio_sets.length + video_sets.length, - mimeType: await set.getMimeType(), - contentType: "image" - }, set.representations.map(async (rep) => /* @__PURE__ */ DashUtils.createElement("representation", { - id: `thumbnails_${rep.thumbnail_width}x${rep.thumbnail_height}`, - bandwidth: await rep.getBitrate(), - width: rep.sheet_width, - height: rep.sheet_height - }, /* @__PURE__ */ DashUtils.createElement("essentialProperty", { - schemeIdUri: "http://dashif.org/thumbnail_tile", - value: `${rep.columns}x${rep.rows}` - }), /* @__PURE__ */ DashUtils.createElement("segmentTemplate", { - media: rep.template_url, - duration: rep.template_duration, - startNumber: "0" - })))); - }), text_sets.map((set, index) => { - return /* @__PURE__ */ DashUtils.createElement("adaptationSet", { - id: index + audio_sets.length + video_sets.length + image_sets.length, - mimeType: set.mime_type, - lang: set.language, - contentType: "text" - }, set.track_roles.map((role) => /* @__PURE__ */ DashUtils.createElement("role", { - schemeIdUri: "urn:mpeg:dash:role:2011", - value: role - })), /* @__PURE__ */ DashUtils.createElement("label", { - id: index + audio_sets.length - }, set.track_name), /* @__PURE__ */ DashUtils.createElement("representation", { - id: set.representation.uid, - bandwidth: "0" - }, /* @__PURE__ */ DashUtils.createElement("baseURL", null, set.representation.base_url))); - }))); + return /* @__PURE__ */ DashUtils.createElement( + "mPD", + { + xmlns: "urn:mpeg:dash:schema:mpd:2011", + minBufferTime: "PT1.500S", + profiles: "urn:mpeg:dash:profile:isoff-main:2011", + type: "static", + mediaPresentationDuration: `PT${await getDuration()}S`, + "xmlns:xsi": "http://www.w3.org/2001/XMLSchema-instance", + "xsi:schemaLocation": "urn:mpeg:dash:schema:mpd:2011 http://standards.iso.org/ittf/PubliclyAvailableStandards/MPEG-DASH_schema_files/DASH-MPD.xsd" + }, + /* @__PURE__ */ DashUtils.createElement("period", null, audio_sets.map((set, index) => /* @__PURE__ */ DashUtils.createElement( + "adaptationSet", + { + id: index, + mimeType: set.mime_type, + startWithSAP: "1", + subsegmentAlignment: "true", + lang: set.language, + codecs: set.codecs, + audioSamplingRate: set.audio_sample_rate, + contentType: "audio" + }, + set.drm_families && set.drm_families.map((drm_family) => /* @__PURE__ */ DashUtils.createElement("contentProtection", { schemeIdUri: `urn:uuid:${getDrmSystemId(drm_family)}` })), + set.track_roles && set.track_roles.map((role) => /* @__PURE__ */ DashUtils.createElement( + "role", + { + schemeIdUri: "urn:mpeg:dash:role:2011", + value: role + } + )), + set.track_name && /* @__PURE__ */ DashUtils.createElement("label", { id: index }, set.track_name), + set.channels && /* @__PURE__ */ DashUtils.createElement( + "audioChannelConfiguration", + { + schemeIdUri: "urn:mpeg:dash:23003:3:audio_channel_configuration:2011", + value: set.channels + } + ), + set.representations.map((rep) => /* @__PURE__ */ DashUtils.createElement( + "representation", + { + id: rep.uid, + bandwidth: rep.bitrate, + codecs: rep.codecs, + audioSamplingRate: rep.audio_sample_rate + }, + rep.channels && /* @__PURE__ */ DashUtils.createElement( + "audioChannelConfiguration", + { + schemeIdUri: "urn:mpeg:dash:23003:3:audio_channel_configuration:2011", + value: rep.channels + } + ), + /* @__PURE__ */ DashUtils.createElement(SegmentInfo, { info: rep.segment_info }) + )) + )), video_sets.map((set, index) => /* @__PURE__ */ DashUtils.createElement( + "adaptationSet", + { + id: index + audio_sets.length, + mimeType: set.mime_type, + startWithSAP: "1", + subsegmentAlignment: "true", + codecs: set.codecs, + maxPlayoutRate: "1", + frameRate: set.fps, + contentType: "video" + }, + set.drm_families && set.drm_families.map((drm_family) => /* @__PURE__ */ DashUtils.createElement("contentProtection", { schemeIdUri: `urn:uuid:${getDrmSystemId(drm_family)}` })), + set.color_info.primaries && /* @__PURE__ */ DashUtils.createElement( + "supplementalProperty", + { + schemeIdUri: "urn:mpeg:mpegB:cicp:ColourPrimaries", + value: set.color_info.primaries + } + ), + set.color_info.transfer_characteristics && /* @__PURE__ */ DashUtils.createElement( + "supplementalProperty", + { + schemeIdUri: "urn:mpeg:mpegB:cicp:TransferCharacteristics", + value: set.color_info.transfer_characteristics + } + ), + set.color_info.matrix_coefficients && /* @__PURE__ */ DashUtils.createElement( + "supplementalProperty", + { + schemeIdUri: "urn:mpeg:mpegB:cicp:MatrixCoefficients", + value: set.color_info.matrix_coefficients + } + ), + set.representations.map((rep) => /* @__PURE__ */ DashUtils.createElement( + "representation", + { + id: rep.uid, + bandwidth: rep.bitrate, + width: rep.width, + height: rep.height, + codecs: rep.codecs, + frameRate: rep.fps + }, + /* @__PURE__ */ DashUtils.createElement(SegmentInfo, { info: rep.segment_info }) + )) + )), image_sets.map(async (set, index) => { + return /* @__PURE__ */ DashUtils.createElement( + "adaptationSet", + { + id: index + audio_sets.length + video_sets.length, + mimeType: await set.getMimeType(), + contentType: "image" + }, + set.representations.map(async (rep) => /* @__PURE__ */ DashUtils.createElement( + "representation", + { + id: `thumbnails_${rep.thumbnail_width}x${rep.thumbnail_height}`, + bandwidth: await rep.getBitrate(), + width: rep.sheet_width, + height: rep.sheet_height + }, + /* @__PURE__ */ DashUtils.createElement( + "essentialProperty", + { + schemeIdUri: "http://dashif.org/thumbnail_tile", + value: `${rep.columns}x${rep.rows}` + } + ), + /* @__PURE__ */ DashUtils.createElement( + "segmentTemplate", + { + media: rep.template_url, + duration: rep.template_duration, + startNumber: "0" + } + ) + )) + ); + }), text_sets.map((set, index) => { + return /* @__PURE__ */ DashUtils.createElement( + "adaptationSet", + { + id: index + audio_sets.length + video_sets.length + image_sets.length, + mimeType: set.mime_type, + lang: set.language, + contentType: "text" + }, + set.track_roles.map((role) => /* @__PURE__ */ DashUtils.createElement( + "role", + { + schemeIdUri: "urn:mpeg:dash:role:2011", + value: role + } + )), + /* @__PURE__ */ DashUtils.createElement("label", { id: index + audio_sets.length }, set.track_name), + /* @__PURE__ */ DashUtils.createElement( + "representation", + { + id: set.representation.uid, + bandwidth: "0" + }, + /* @__PURE__ */ DashUtils.createElement("baseURL", null, set.representation.base_url) + ) + ); + })) + ); } __name(DashManifest, "DashManifest"); function toDash(streaming_data, is_post_live_dvr = false, url_transformer = (url) => url, format_filter, cpn, player, actions, storyboards, caption_tracks, options) { if (!streaming_data) throw new InnertubeError("Streaming data not available"); return DashUtils.renderToString( - /* @__PURE__ */ DashUtils.createElement(DashManifest, { - streamingData: streaming_data, - isPostLiveDvr: is_post_live_dvr, - transformURL: url_transformer, - options, - rejectFormat: format_filter, - cpn, - player, - actions, - storyboards, - captionTracks: caption_tracks - }) + /* @__PURE__ */ DashUtils.createElement( + DashManifest, + { + streamingData: streaming_data, + isPostLiveDvr: is_post_live_dvr, + transformURL: url_transformer, + options, + rejectFormat: format_filter, + cpn, + player, + actions, + storyboards, + captionTracks: caption_tracks + } + ) ); } __name(toDash, "toDash"); diff --git a/deno/src/utils/DashManifest.tsx b/deno/src/utils/DashManifest.tsx index a1702bb2..cfdc072e 100644 --- a/deno/src/utils/DashManifest.tsx +++ b/deno/src/utils/DashManifest.tsx @@ -65,6 +65,17 @@ function SegmentInfo({ info }: { info: FSegmentInfo }) { ; } +function getDrmSystemId(drm_family?: string): string | null { + switch (drm_family) { + case 'WIDEVINE': + return 'edef8ba9-79d6-4ace-a3c8-27dcd51d21ed'; + case 'PLAYREADY': + return '9a04f079-9840-4286-ab92-e65be0885f95'; + default: + return null; + } +} + async function DashManifest({ streamingData, isPostLiveDvr, @@ -109,6 +120,11 @@ async function DashManifest({ audioSamplingRate={set.audio_sample_rate} contentType="audio" > + { + set.drm_families && set.drm_families.map((drm_family) => ( + + )) + } { set.track_roles && set.track_roles.map((role) => ( + { + set.drm_families && set.drm_families.map((drm_family) => ( + + )) + } { set.color_info.primaries && ); -} +} \ No newline at end of file diff --git a/deno/src/utils/HTTPClient.ts b/deno/src/utils/HTTPClient.ts index 41f5c8c4..5f0f9b5e 100644 --- a/deno/src/utils/HTTPClient.ts +++ b/deno/src/utils/HTTPClient.ts @@ -203,16 +203,22 @@ export default class HTTPClient { if (!client) return; - if (!Constants.SUPPORTED_CLIENTS.includes(client.toUpperCase())) { + const clientName = client.toUpperCase(); + + if (!Constants.SUPPORTED_CLIENTS.includes(clientName)) { throw new InnertubeError(`Invalid client: ${client}`, { available_innertube_clients: Constants.SUPPORTED_CLIENTS }); } + if (clientName !== 'WEB') { + delete ctx.client.configInfo; + } + if ( - client === 'ANDROID' || - client === 'YTMUSIC_ANDROID' || - client === 'YTSTUDIO_ANDROID' + clientName === 'ANDROID' || + clientName === 'YTMUSIC_ANDROID' || + clientName === 'YTSTUDIO_ANDROID' ) { ctx.client.androidSdkVersion = Constants.CLIENTS.ANDROID.SDK_VERSION; ctx.client.userAgent = Constants.CLIENTS.ANDROID.USER_AGENT; @@ -221,7 +227,7 @@ export default class HTTPClient { ctx.client.platform = 'MOBILE'; } - switch (client.toUpperCase()) { + switch (clientName) { case 'MWEB': ctx.client.clientVersion = Constants.CLIENTS.MWEB.VERSION; ctx.client.clientName = Constants.CLIENTS.MWEB.NAME; diff --git a/deno/src/utils/StreamingInfo.ts b/deno/src/utils/StreamingInfo.ts index 591c43fc..dc3e2ff9 100644 --- a/deno/src/utils/StreamingInfo.ts +++ b/deno/src/utils/StreamingInfo.ts @@ -32,6 +32,8 @@ export interface AudioSet { track_name?: string; track_roles?: ('main' | 'dub' | 'description' | 'enhanced-audio-intelligibility' | 'alternate')[]; channels?: number; + drm_families?: string[]; + drm_track_type?: string; representations: AudioRepresentation[]; } @@ -81,6 +83,8 @@ export interface VideoSet { color_info: ColorInfo; codecs?: string; fps?: number; + drm_families?: string[]; + drm_track_type?: string; representations: VideoRepresentation[] } @@ -481,6 +485,8 @@ function getAudioSet( track_name, track_roles: getTrackRoles(first_format, has_drc_streams), channels: hoistAudioChannelsIfPossible(formats, hoisted), + drm_families: first_format.drm_families, + drm_track_type: first_format.drm_track_type, representations: formats.map((format) => getAudioRepresentation(format, hoisted, url_transformer, actions, player, cpn, shared_post_live_dvr_info, is_sabr)) }; @@ -596,6 +602,8 @@ function getVideoSet( color_info, codecs: hoistCodecsIfPossible(formats, hoisted), fps: hoistNumberAttributeIfPossible(formats, 'fps', hoisted), + drm_families: first_format.drm_families, + drm_track_type: first_format.drm_track_type, representations: formats.map((format) => getVideoRepresentation(format, url_transformer, hoisted, player, actions, cpn, shared_post_live_dvr_info, is_sabr)) };