Compare commits

...

2 Commits

Author SHA1 Message Date
LuanRT
f21db117e8 chore: v17.0.1 release 2026-03-16 19:03:11 +00:00
absidue
cc09231db2 chore: v17.0.0 release 2026-03-16 18:38:10 +00:00
52 changed files with 1838 additions and 922 deletions

View File

@@ -1,6 +1,6 @@
{
"name": "youtubei.js",
"version": "16.0.1",
"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",
@@ -108,14 +108,13 @@
"devDependencies": {
"@eslint/js": "^9.37.0",
"@types/estree": "^1.0.6",
"@types/glob": "^8.1.0",
"@types/node": "^24.0.14",
"@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": "^16.4.0",
"globals": "^17.0.0",
"replace": "^1.2.2",
"rimraf": "^6.0.1",
"ts-patch": "^3.0.2",
@@ -141,4 +140,4 @@
"downloader",
"ytmusic"
]
}
}

View File

@@ -1,7 +1,7 @@
// Code generated by protoc-gen-ts_proto. DO NOT EDIT.
// versions:
// protoc-gen-ts_proto v2.2.0
// protoc v5.29.2
// protoc-gen-ts_proto v2.7.7
// protoc v6.33.5
// source: misc/common.proto
/* eslint-disable */
@@ -56,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;
@@ -105,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;
@@ -158,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;
@@ -204,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;
@@ -250,25 +259,27 @@ 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;
@@ -293,18 +304,19 @@ export const FormatXTags: MessageFns<FormatXTags> = {
decode(input: BinaryReader | Uint8Array, length?: number): FormatXTags {
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 = createBaseFormatXTags();
while (reader.pos < end) {
const tag = reader.uint32();
switch (tag >>> 3) {
case 1:
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

View File

@@ -1,7 +1,7 @@
// Code generated by protoc-gen-ts_proto. DO NOT EDIT.
// versions:
// protoc-gen-ts_proto v2.2.0
// protoc v5.29.2
// 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;

View File

@@ -1,7 +1,7 @@
// Code generated by protoc-gen-ts_proto. DO NOT EDIT.
// versions:
// protoc-gen-ts_proto v2.2.0
// protoc v5.29.2
// 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

View File

@@ -1,7 +1,7 @@
// Code generated by protoc-gen-ts_proto. DO NOT EDIT.
// versions:
// protoc-gen-ts_proto v2.2.0
// protoc v5.29.2
// 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;

View File

@@ -1,7 +1,7 @@
// Code generated by protoc-gen-ts_proto. DO NOT EDIT.
// versions:
// protoc-gen-ts_proto v2.2.0
// protoc v5.29.2
// 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;

View File

@@ -1,7 +1,7 @@
// Code generated by protoc-gen-ts_proto. DO NOT EDIT.
// versions:
// protoc-gen-ts_proto v2.2.0
// protoc v5.29.2
// 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;

View File

@@ -1,7 +1,7 @@
// Code generated by protoc-gen-ts_proto. DO NOT EDIT.
// versions:
// protoc-gen-ts_proto v2.2.0
// protoc v5.29.2
// 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;

View File

@@ -1,7 +1,7 @@
// Code generated by protoc-gen-ts_proto. DO NOT EDIT.
// versions:
// protoc-gen-ts_proto v2.2.0
// protoc v5.29.2
// 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;

View File

@@ -1,7 +1,7 @@
// Code generated by protoc-gen-ts_proto. DO NOT EDIT.
// versions:
// protoc-gen-ts_proto v2.2.0
// protoc v5.29.2
// 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;

View File

@@ -1,7 +1,7 @@
// Code generated by protoc-gen-ts_proto. DO NOT EDIT.
// versions:
// protoc-gen-ts_proto v2.2.0
// protoc v5.29.2
// 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;

View File

@@ -1,7 +1,7 @@
// Code generated by protoc-gen-ts_proto. DO NOT EDIT.
// versions:
// protoc-gen-ts_proto v2.2.0
// protoc v5.29.2
// 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;

View File

@@ -1,7 +1,7 @@
// Code generated by protoc-gen-ts_proto. DO NOT EDIT.
// versions:
// protoc-gen-ts_proto v2.2.0
// protoc v5.29.2
// 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;

View File

@@ -1,7 +1,7 @@
// Code generated by protoc-gen-ts_proto. DO NOT EDIT.
// versions:
// protoc-gen-ts_proto v2.2.0
// protoc v5.29.2
// 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;

View File

@@ -1,7 +1,7 @@
// Code generated by protoc-gen-ts_proto. DO NOT EDIT.
// versions:
// protoc-gen-ts_proto v2.2.0
// protoc v5.29.2
// 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;

View File

@@ -1,7 +1,7 @@
// Code generated by protoc-gen-ts_proto. DO NOT EDIT.
// versions:
// protoc-gen-ts_proto v2.2.0
// protoc v5.29.2
// 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();

View File

@@ -1,7 +1,7 @@
// Code generated by protoc-gen-ts_proto. DO NOT EDIT.
// versions:
// protoc-gen-ts_proto v2.2.0
// protoc v5.29.2
// 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;

View File

@@ -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 {

View File

@@ -49,7 +49,7 @@ import {
SearchFilter_Filters_Duration,
SearchFilter_Filters_SearchType,
SearchFilter_Filters_UploadDate,
SearchFilter_SortBy
SearchFilter_Prioritize
} from '../protos/generated/misc/params.ts';
/**
@@ -205,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) {
@@ -364,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 });
@@ -393,7 +387,7 @@ export default class Innertube {
const browse_endpoint = new NavigationEndpoint({ browseEndpoint: { browseId: id } });
let response = await browse_endpoint.call<IBrowseResponse>(this.#session.actions, { parse: true });
if (response.on_response_received_actions?.[0].is(NavigateAction)) {
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 });
}

View File

@@ -4,12 +4,13 @@ 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 { nMatcher, sigMatcher, timestampMatcher } from '../utils/javascript/matchers.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';
@@ -71,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}.`);
@@ -87,13 +88,11 @@ export default class Player {
const player_js = await player_res.text();
const sigFunctionName = 'sigFunction';
const nFunctionName = 'nFunction';
const nsigFunctionName = 'nsigFunction';
const timestampVarName = 'signatureTimestampVar';
const extractions: ExtractionConfig[] = [
{ friendlyName: sigFunctionName, match: sigMatcher },
{ friendlyName: nFunctionName, match: nMatcher },
{ friendlyName: nsigFunctionName, match: nsigMatcher },
{ friendlyName: timestampVarName, match: timestampMatcher, collectDependencies: false }
];
@@ -110,12 +109,8 @@ export default class Player {
Log.warn(TAG, 'Failed to extract signature timestamp.');
}
if (!result.exported.includes(sigFunctionName)) {
Log.warn(TAG, 'Failed to extract signature decipher function.');
}
if (!result.exported.includes(nFunctionName)) {
Log.warn(TAG, 'Failed to extract n decipher function.');
if (!result.exported.includes(nsigFunctionName)) {
Log.warn(TAG, 'Failed to extract n/sig decipher function.');
}
const signatureTimestamp = result.exportedRawValues?.[timestampVarName];
@@ -145,10 +140,11 @@ export default class Player {
const sp = args.get('sp');
if (this.data && ((signature_cipher || cipher) || n)) {
const eval_args: { sig?: string | null; n?: string | null } = {};
const eval_args: { sig?: string | null; n?: string | null; sp?: string | null } = {};
if (signature_cipher || cipher) {
eval_args.sig = s;
eval_args.sp = sp;
}
if (n) {
@@ -161,7 +157,12 @@ export default class Player {
}
if (Object.keys(eval_args).length > 0) {
const result = await Platform.shim.eval(this.data, eval_args) as Record<string, unknown>;
// 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.');

View File

@@ -26,6 +26,7 @@ export enum ClientType {
MUSIC = 'WEB_REMIX',
IOS = 'iOS',
ANDROID = 'ANDROID',
ANDROID_VR = 'ANDROID_VR',
ANDROID_MUSIC = 'ANDROID_MUSIC',
ANDROID_CREATOR = 'ANDROID_CREATOR',
TV = 'TVHTML5',

View File

@@ -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);
}
}

View File

@@ -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
};
}
}

View File

@@ -23,14 +23,16 @@ export default class ContentMetadataView extends YTNode {
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: 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;
}
}

View File

@@ -26,13 +26,13 @@ export default class DescriptionPreviewView extends YTNode {
constructor(data: RawNode) {
super();
if (Reflect.has(data, 'description'))
if ('description' in data)
this.description = Text.fromAttributed(data.description);
if (Reflect.has(data, 'maxLines'))
if ('maxLines' in data)
this.max_lines = parseInt(data.maxLines);
if (Reflect.has(data, 'truncationText'))
if ('truncationText' in data)
this.truncation_text = Text.fromAttributed(data.truncationText);
this.always_show_truncation_text = !!data.alwaysShowTruncationText;

View File

@@ -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);
}

View File

@@ -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;
}
}
}

View File

@@ -11,22 +11,28 @@ import Text from './misc/Text.ts';
export default class ListItemView extends YTNode {
static type = 'ListItemView';
public title: Text;
public subtitle: Text;
public title?: Text;
public subtitle?: Text;
public leading_accessory: AvatarView | null;
public renderer_context: RendererContext;
public trailing_buttons?: ObservedArray<SubscribeButtonView>;
public renderer_context?: RendererContext;
public trailing_buttons: ObservedArray<SubscribeButtonView>;
constructor(data: RawNode) {
super();
this.title = Text.fromAttributed(data.title);
this.subtitle = Text.fromAttributed(data.subtitle);
this.leading_accessory = Parser.parseItem(data.leadingAccessory, AvatarView);
this.renderer_context = new RendererContext(data.rendererContext);
if ('trailingButtons' in data) {
this.trailing_buttons = Parser.parseArray(data.trailingButtons.buttons, SubscribeButtonView);
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);
}
}

View 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);
}
}
}

View File

@@ -10,6 +10,8 @@ import VideoDescriptionCourseSection from './VideoDescriptionCourseSection.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';
@@ -18,7 +20,7 @@ export default class StructuredDescriptionContent extends YTNode {
VideoDescriptionHeader | ExpandableVideoDescriptionBody | VideoDescriptionMusicSection |
VideoDescriptionInfocardsSection | VideoDescriptionTranscriptSection |
VideoDescriptionCourseSection | HorizontalCardList | ReelShelf | VideoAttributesSectionView |
HowThisWasMadeSectionView
HowThisWasMadeSectionView | ExpandableMetadata | MerchandiseShelf
>;
constructor(data: RawNode) {
@@ -27,7 +29,7 @@ export default class StructuredDescriptionContent extends YTNode {
VideoDescriptionHeader, ExpandableVideoDescriptionBody, VideoDescriptionMusicSection,
VideoDescriptionInfocardsSection, VideoDescriptionCourseSection, VideoDescriptionTranscriptSection,
VideoDescriptionTranscriptSection, HorizontalCardList, ReelShelf, VideoAttributesSectionView,
HowThisWasMadeSectionView
HowThisWasMadeSectionView, ExpandableMetadata, MerchandiseShelf
]);
}
}

View File

@@ -10,52 +10,65 @@ interface ButtonContent {
endpoint: NavigationEndpoint;
}
interface BellAccessibilityData {
off_label?: string;
all_label?: string;
occasional_label?: string;
disabled_label?: string;
}
interface ButtonStyle {
unsubscribed_state_style?: string;
subscribed_state_style?: string;
}
export default class SubscribeButtonView extends YTNode {
static type = 'SubscribeButtonView';
public subscribe_button_content: ButtonContent;
public unsubscribe_button_content: ButtonContent;
public disable_notification_bell: boolean;
public button_style: {
unsubscribed_state_style: string;
subscribed_state_style: string;
};
public button_style?: ButtonStyle;
public is_signed_out: boolean;
public background_style: string;
public disable_subscribe_button: boolean;
public on_show_subscription_options: NavigationEndpoint;
public on_show_subscription_options?: NavigationEndpoint;
public channel_id: string;
public enable_subscribe_button_post_click_animation: boolean;
public bell_accessiblity_data: {
off_label: string;
all_label: string;
occasional_label: string;
disabled_label: string;
};
public bell_accessibility_data?: BellAccessibilityData;
constructor(data: RawNode) {
super();
this.subscribe_button_content = this.#parseButtonContent(data.subscribeButtonContent);
this.unsubscribe_button_content = this.#parseButtonContent(data.unsubscribeButtonContent);
this.disable_notification_bell = data.disableNotificationBell;
this.button_style = {
unsubscribed_state_style: data.buttonStyle.unsubscribedStateStyle,
subscribed_state_style: data.buttonStyle.subscribedStateStyle
};
if ('buttonStyle' in data) {
this.button_style = {
unsubscribed_state_style: data.buttonStyle?.unsubscribedStateStyle,
subscribed_state_style: data.buttonStyle?.subscribedStateStyle
};
}
this.is_signed_out = data.isSignedOut;
this.background_style = data.backgroundStyle;
this.disable_subscribe_button = data.disableSubscribeButton;
this.on_show_subscription_options = new NavigationEndpoint(data.onShowSubscriptionOptions);
if ('onShowSubscriptionOptions' in data) {
this.on_show_subscription_options = new NavigationEndpoint(data.onShowSubscriptionOptions);
}
this.channel_id = data.channelId;
this.enable_subscribe_button_post_click_animation = data.enableSubscribeButtonPostClickAnimation;
this.bell_accessiblity_data = {
off_label: data.bellAccessibilityData.offLabel,
all_label: data.bellAccessibilityData.allLabel,
occasional_label: data.bellAccessibilityData.occasionalLabel,
disabled_label: data.bellAccessibilityData.disabledLabel
};
if ('bellAccessibilityData' in data) {
this.bell_accessibility_data = {
off_label: data.bellAccessibilityData?.offLabel,
all_label: data.bellAccessibilityData?.allLabel,
occasional_label: data.bellAccessibilityData?.occasionalLabel,
disabled_label: data.bellAccessibilityData?.disabledLabel
};
}
}
#parseButtonContent(data: RawNode): ButtonContent {

View File

@@ -0,0 +1,28 @@
import type { ObservedArray } from '../helpers.ts';
import { YTNode } from '../helpers.ts';
import { Parser, type RawNode } from '../index.ts';
import DislikeButtonView from './DislikeButtonView.ts';
import LikeButtonView from './LikeButtonView.ts';
import VideoSummaryParagraphView from './VideoSummaryParagraphView.ts';
export default class VideoSummaryContentView extends YTNode {
static type = 'VideoSummaryContentView';
public dislike_button_view?: DislikeButtonView | null;
public like_button_view?: LikeButtonView | null;
public paragraphs: ObservedArray<VideoSummaryParagraphView>;
constructor(data: RawNode) {
super();
if ('dislikeButtonViewModel' in data) {
this.dislike_button_view = Parser.parseItem(data.dislikeButtonViewModel, DislikeButtonView);
}
if ('likeButtonViewModel' in data) {
this.like_button_view = Parser.parseItem(data.likeButtonViewModel, LikeButtonView);
}
this.paragraphs = Parser.parseArray(data.paragraphs, VideoSummaryParagraphView);
}
}

View File

@@ -0,0 +1,14 @@
import { YTNode } from '../helpers.ts';
import { type RawNode } from '../index.ts';
import { Text } from '../misc.ts';
export default class VideoSummaryParagraphView extends YTNode {
static type = 'VideoSummaryParagraphView';
public text: Text;
constructor(data: RawNode) {
super();
this.text = Text.fromAttributed(data.text);
}
}

View File

@@ -5,16 +5,35 @@ import type { RawNode } from '../index.ts';
export default class VideoViewCount extends YTNode {
static type = 'VideoViewCount';
public original_view_count: string;
public short_view_count: Text;
public extra_short_view_count: Text;
public view_count: Text;
public original_view_count?: number;
public unlabeled_view_count_value?: Text;
public short_view_count?: Text;
public extra_short_view_count?: Text;
public view_count?: Text;
public is_live: boolean;
constructor(data: RawNode) {
super();
this.original_view_count = data.originalViewCount;
this.short_view_count = new Text(data.shortViewCount);
this.extra_short_view_count = new Text(data.extraShortViewCount);
this.view_count = new Text(data.viewCount);
if ('originalViewCount' in data) {
this.original_view_count = parseInt(data.originalViewCount);
}
if ('unlabeledViewCountValue' in data) {
this.unlabeled_view_count_value = new Text(data.unlabeledViewCountValue);
}
if ('shortViewCount' in data) {
this.short_view_count = new Text(data.shortViewCount);
}
if ('extraShortViewCount' in data) {
this.extra_short_view_count = new Text(data.extraShortViewCount);
}
if ('viewCount' in data) {
this.view_count = new Text(data.viewCount);
}
this.is_live = !!data.isLive;
}
}

View File

@@ -1,23 +1,57 @@
import Text from '../misc/Text.ts';
import type Text from '../misc/Text.ts';
import { YTNode } from '../../helpers.ts';
import type { RawNode } from '../../index.ts';
import { Parser, type RawNode } from '../../index.ts';
import VideoViewCount from '../VideoViewCount.ts';
export default class UpdateViewershipAction extends YTNode {
static type = 'UpdateViewershipAction';
view_count: Text;
extra_short_view_count: Text;
original_view_count: number;
unlabeled_view_count_value: Text;
is_live: boolean;
public view_count_node: VideoViewCount | null;
/**
* @deprecated Use `view_count_node.view_count` instead.
*/
get view_count(): Text | undefined {
return this.view_count_node?.view_count;
}
/**
* @deprecated Use `view_count_node.extra_short_view_count` instead.
*/
get extra_short_view_count(): Text | undefined {
return this.view_count_node?.extra_short_view_count;
}
/**
* @deprecated Use `view_count_node.short_view_count` instead.
*/
get short_view_count(): Text | undefined {
return this.view_count_node?.short_view_count;
}
/**
* @deprecated Use `view_count_node.original_view_count` instead.
*/
get original_view_count(): number | undefined {
return this.view_count_node?.original_view_count;
}
/**
* @deprecated Use `view_count_node.unlabeled_view_count_value` instead.
*/
get unlabeled_view_count_value(): Text | undefined {
return this.view_count_node?.unlabeled_view_count_value;
}
/**
* @deprecated Use `view_count_node.is_live` instead.
*/
get is_live(): boolean | undefined {
return this.view_count_node?.is_live;
}
constructor(data: RawNode) {
super();
const view_count_renderer = data.viewCount.videoViewCountRenderer;
this.view_count = new Text(view_count_renderer.viewCount);
this.extra_short_view_count = new Text(view_count_renderer.extraShortViewCount);
this.original_view_count = parseInt(view_count_renderer.originalViewCount);
this.unlabeled_view_count_value = new Text(view_count_renderer.unlabeledViewCountValue);
this.is_live = view_count_renderer.isLive;
this.view_count_node = Parser.parseItem(data.viewCount, VideoViewCount);
}
}

View File

@@ -7,16 +7,17 @@ import ButtonView from '../ButtonView.ts';
import MenuServiceItem from './MenuServiceItem.ts';
import DownloadButton from '../DownloadButton.ts';
import MenuServiceItemDownload from './MenuServiceItemDownload.ts';
import ListItemView from '../ListItemView.ts';
export default class MenuFlexibleItem extends YTNode {
static type = 'MenuFlexibleItem';
public menu_item: MenuServiceItem | MenuServiceItemDownload | null;
public menu_item: ListItemView | MenuServiceItem | MenuServiceItemDownload | null;
public top_level_button: DownloadButton | ButtonView | Button | null;
constructor(data: RawNode) {
super();
this.menu_item = Parser.parseItem(data.menuItem, [ MenuServiceItem, MenuServiceItemDownload ]);
this.menu_item = Parser.parseItem(data.menuItem, [ ListItemView, MenuServiceItem, MenuServiceItemDownload ]);
this.top_level_button = Parser.parseItem(data.topLevelButton, [ DownloadButton, ButtonView, Button ]);
}
}

View File

@@ -63,6 +63,8 @@ export default class Format {
public loudness_db?: number;
public signature_cipher?: string;
public is_drc?: boolean;
public is_vb?: boolean;
public is_sr?: boolean;
public drm_track_type?: string;
public distinct_params?: string;
public track_absolute_loudness_lkfs?: number;
@@ -212,15 +214,16 @@ export default class Format {
id: data.captionTrack.id
};
if (this.has_audio || this.has_text) {
const xtags = this.xtags
? FormatXTags.decode(base64ToU8(decodeURIComponent(this.xtags).replace(/-/g, '+').replace(/_/g, '/'))).xtags
: [];
const xtags = this.xtags
? FormatXTags.decode(base64ToU8(decodeURIComponent(this.xtags).replace(/-/g, '+').replace(/_/g, '/'))).xtags
: [];
if (this.has_audio || this.has_text) {
this.language = xtags.find((tag) => tag.key === 'lang')?.value || null;
if (this.has_audio) {
this.is_drc = !!data.isDrc || xtags.some((tag) => tag.key === 'drc' && tag.value === '1');
this.is_vb = !!data.isVb || xtags.some((tag) => tag.key === 'vb' && tag.value === '1');
const audio_content = xtags.find((tag) => tag.key === 'acont')?.value;
this.is_dubbed = audio_content === 'dubbed';
@@ -235,6 +238,10 @@ export default class Format {
this.language = this.caption_track.language_code;
}
}
if (this.has_video) {
this.is_sr = xtags.some((tag) => tag.key === 'sr' && tag.value === '1');
}
}
/**

View File

@@ -364,6 +364,7 @@ export { default as PageIntroduction } from './classes/PageIntroduction.ts';
export { default as PanelFooterView } from './classes/PanelFooterView.ts';
export { default as PivotButton } from './classes/PivotButton.ts';
export { default as PlayerAnnotationsExpanded } from './classes/PlayerAnnotationsExpanded.ts';
export { default as PlayerCaptchaView } from './classes/PlayerCaptchaView.ts';
export { default as PlayerCaptionsTracklist } from './classes/PlayerCaptionsTracklist.ts';
export { default as PlayerControlsOverlay } from './classes/PlayerControlsOverlay.ts';
export { default as PlayerErrorMessage } from './classes/PlayerErrorMessage.ts';
@@ -528,6 +529,8 @@ export { default as VideoMetadataCarouselView } from './classes/VideoMetadataCar
export { default as VideoOwner } from './classes/VideoOwner.ts';
export { default as VideoPrimaryInfo } from './classes/VideoPrimaryInfo.ts';
export { default as VideoSecondaryInfo } from './classes/VideoSecondaryInfo.ts';
export { default as VideoSummaryContentView } from './classes/VideoSummaryContentView.ts';
export { default as VideoSummaryParagraphView } from './classes/VideoSummaryParagraphView.ts';
export { default as VideoViewCount } from './classes/VideoViewCount.ts';
export { default as ViewCountFactoid } from './classes/ViewCountFactoid.ts';
export { default as WatchCardCompactVideo } from './classes/WatchCardCompactVideo.ts';

View File

@@ -87,7 +87,8 @@ const IGNORED_LIST = new Set([
'StatementBanner',
'GuideSigninPromo',
'AdsEngagementPanelContent',
'MiniGameCardView'
'MiniGameCardView',
'GenAiFeedbackFormView'
]);
const RUNTIME_NODES = new Map<string, YTNodeConstructor>(Object.entries(YTNodes));

View File

@@ -5,7 +5,8 @@ import Button from '../classes/Button.ts';
import type { Actions, ApiResponse } from '../../core/index.ts';
import type { IBrowseResponse } from '../types/index.ts';
import type Video from '../classes/Video.ts';
import Video from '../classes/Video.ts';
import LockupView from '../classes/LockupView.ts';
// TODO: make feed actions usable
export default class History extends Feed<IBrowseResponse> {
@@ -33,20 +34,27 @@ export default class History extends Feed<IBrowseResponse> {
*/
async removeVideo(video_id: string, pages_to_load: number = 1): Promise<boolean> {
let pagesToLoad = pages_to_load;
// eslint-disable-next-line @typescript-eslint/no-this-alias
let currentHistory: History = this;
while (pagesToLoad > 0) {
let feedbackToken;
for (const section of currentHistory.sections) {
for (const section of this.sections) {
for (const content of section.contents) {
const video = content as Video;
if (video.video_id === video_id && video.menu) {
feedbackToken = video.menu.top_level_buttons[0].as(Button).endpoint.payload.feedbackToken;
break;
if (content.is(Video)) {
if (content.video_id === video_id && content.menu) {
feedbackToken = content.menu.top_level_buttons[0].as(Button).endpoint.payload.feedbackToken;
break;
}
} else if (content.is(LockupView)) {
if (content.content_id === video_id) {
const listItems = content.metadata?.menu_button?.on_tap?.payload.panelLoadingStrategy.inlineContent.sheetViewModel.content.listViewModel.listItems;
const listItem = listItems.find((video: { listItemViewModel: { title: { content: string; }; }; }) => video.listItemViewModel?.title.content === 'Remove from watch history');
feedbackToken = listItem.listItemViewModel.rendererContext.commandContext.onTap.innertubeCommand.feedbackEndpoint.feedbackToken;
break;
}
}
}
if (feedbackToken) {
break;
}
@@ -66,7 +74,7 @@ export default class History extends Feed<IBrowseResponse> {
if (--pagesToLoad > 0) {
try {
currentHistory = await currentHistory.getContinuation();
Object.assign(this, await this.getContinuation());
} catch {
throw new Error('Unable to find video in watch history');
}

View File

@@ -1,20 +1,20 @@
import type { SessionOptions } from '../core/index.ts';
export type InnerTubeConfig = SessionOptions;
export type InnerTubeClient = 'IOS' | 'WEB' | 'MWEB' | 'ANDROID' | 'YTMUSIC' | 'YTMUSIC_ANDROID' | 'YTSTUDIO_ANDROID' | 'TV' | 'TV_SIMPLY' |'TV_EMBEDDED' | 'YTKIDS' | 'WEB_EMBEDDED' | 'WEB_CREATOR';
export type InnerTubeClient = 'IOS' | 'WEB' | 'MWEB' | 'ANDROID' | 'ANDROID_VR' | 'YTMUSIC' | 'YTMUSIC_ANDROID' | 'YTSTUDIO_ANDROID' | 'TV' | 'TV_SIMPLY' |'TV_EMBEDDED' | 'YTKIDS' | 'WEB_EMBEDDED' | 'WEB_CREATOR';
export type EngagementType = 'ENGAGEMENT_TYPE_UNBOUND' | 'ENGAGEMENT_TYPE_VIDEO_LIKE' | 'ENGAGEMENT_TYPE_VIDEO_DISLIKE' | 'ENGAGEMENT_TYPE_SUBSCRIBE' | 'ENGAGEMENT_TYPE_PLAYBACK' | 'ENGAGEMENT_TYPE_YPC_GET_PREMIUM_PAGE' | 'ENGAGEMENT_TYPE_YPC_GET_DOWNLOAD_ACTION';
export type UploadDate = 'all' | 'hour' | 'today' | 'week' | 'month' | 'year';
export type SearchType = 'all' | 'video' | 'channel' | 'playlist' | 'movie';
export type Duration = 'all' | 'short' | 'medium' | 'long';
export type SortBy = 'relevance' | 'rating' | 'upload_date' | 'view_count';
export type UploadDate = 'all' | 'today' | 'week' | 'month' | 'year';
export type SearchType = 'all' | 'video' | 'shorts' | 'channel' | 'playlist' | 'movie';
export type Duration = 'all' | 'over_twenty_mins' | 'under_three_mins' | 'three_to_twenty_mins';
export type Prioritize = 'relevance' | 'popularity';
export type Feature = 'hd' | 'subtitles' | 'creative_commons' | '3d' | 'live' | 'purchased' | '4k' | '360' | 'location' | 'hdr' | 'vr180';
export type SearchFilters = {
upload_date?: UploadDate;
type?: SearchType;
duration?: Duration;
sort_by?: SortBy;
prioritize?: Prioritize;
features?: Feature[];
};

View File

@@ -9,7 +9,7 @@ export interface StreamingInfoOptions {
*/
captions_format?: 'vtt' | 'ttml';
/**
* The label to use for the non-DRC streams when a video has DRC and streams.
* The label to use for the non-DRC/VB streams when a video has DRC and Voice Boost streams.
*
* Defaults to `"Original"`
*/
@@ -27,7 +27,19 @@ export interface StreamingInfoOptions {
* Defaults to `(audio_track_display_name) => audio_track_display_name + " (Stable Volume)"`
*/
label_drc_multiple?: (audio_track_display_name: string) => string;
/**
* The label to use for the VB (Voice Boost) streams when a video has VB streams.
*
* Defaults to `"Voice Boost"`
*/
label_vb?: string;
/**
* A function that generates the label to use for the Voice Boost streams when a video has multiple audio tracks and Voice Boost streams.
* The non-Voice Boost streams use the unmodified audio track label provided by YouTube.
*
* Defaults to `(audio_track_display_name) => audio_track_display_name + " (Voice Boost)"`
*/
label_vb_multiple?: (audio_track_display_name: string) => string;
/**
* If `true`, the generated manifest will contain URLs that are suitable for use with the SABR protocol.
*/

View File

@@ -33,7 +33,7 @@ export const CLIENTS = {
},
WEB: {
NAME: 'WEB',
VERSION: '2.20250222.10.00',
VERSION: '2.20260206.01.00',
API_KEY: 'AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8',
API_VERSION: 'v1',
STATIC_VISITOR_ID: '6zpwvWUNAco',
@@ -41,12 +41,12 @@ export const CLIENTS = {
},
MWEB: {
NAME: 'MWEB',
VERSION: '2.20250224.01.00',
VERSION: '2.20260205.04.01',
API_VERSION: 'v1'
},
WEB_KIDS: {
NAME: 'WEB_KIDS',
VERSION: '2.20250221.11.00'
VERSION: '2.20260205.00.00'
},
YTMUSIC: {
NAME: 'WEB_REMIX',
@@ -54,9 +54,17 @@ export const CLIENTS = {
},
ANDROID: {
NAME: 'ANDROID',
VERSION: '19.35.36',
SDK_VERSION: 33,
USER_AGENT: 'com.google.android.youtube/19.35.36(Linux; U; Android 13; en_US; SM-S908E Build/TP1A.220624.014) gzip'
VERSION: '21.03.36',
SDK_VERSION: 36,
USER_AGENT: 'com.google.android.youtube/21.03.36(Linux; U; Android 16; en_US; SM-S908E Build/TP1A.220624.014) gzip'
},
ANDROID_VR: {
NAME: 'ANDROID_VR',
VERSION: '1.65.10',
SDK_VERSION: 32,
DEVICE_MAKE: 'Oculus',
DEVICE_MODEL: 'Quest 3',
USER_AGENT: 'com.google.android.apps.youtube.vr.oculus/1.65.10 (Linux; U; Android 12L; eureka-user Build/SQ3A.220605.009.A1) gzip'
},
YTSTUDIO_ANDROID: {
NAME: 'ANDROID_CREATOR',
@@ -68,7 +76,7 @@ export const CLIENTS = {
},
TV: {
NAME: 'TVHTML5',
VERSION: '7.20250219.14.00',
VERSION: '7.20260311.12.00',
USER_AGENT: 'Mozilla/5.0 (ChromiumStylePlatform) Cobalt/Version'
},
TV_SIMPLY: {
@@ -81,7 +89,7 @@ export const CLIENTS = {
},
WEB_EMBEDDED: {
NAME: 'WEB_EMBEDDED_PLAYER',
VERSION: '1.20250219.01.00',
VERSION: '1.20260206.01.00',
API_KEY: 'AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8',
API_VERSION: 'v1',
STATIC_VISITOR_ID: '6zpwvWUNAco'
@@ -106,6 +114,7 @@ export const CLIENT_NAME_IDS = {
ANDROID: '3',
ANDROID_CREATOR: '14',
ANDROID_MUSIC: '21',
ANDROID_VR: '28',
TVHTML5: '7',
TVHTML5_SIMPLY: '74',
TVHTML5_SIMPLY_EMBEDDED_PLAYER: '85',
@@ -124,4 +133,4 @@ export const INNERTUBE_HEADERS_BASE = {
'content-type': 'application/json'
} as const;
export const SUPPORTED_CLIENTS = [ 'IOS', 'WEB', 'MWEB', 'YTKIDS', 'YTMUSIC', 'ANDROID', 'YTSTUDIO_ANDROID', 'YTMUSIC_ANDROID', 'TV', 'TV_SIMPLY', 'TV_EMBEDDED', 'WEB_EMBEDDED', 'WEB_CREATOR' ];
export const SUPPORTED_CLIENTS = [ 'IOS', 'WEB', 'MWEB', 'YTKIDS', 'YTMUSIC', 'ANDROID', 'ANDROID_VR', 'YTSTUDIO_ANDROID', 'YTMUSIC_ANDROID', 'TV', 'TV_SIMPLY', 'TV_EMBEDDED', 'WEB_EMBEDDED', 'WEB_CREATOR' ];

View File

@@ -254,6 +254,18 @@ export default class HTTPClient {
ctx.client.clientFormFactor = 'SMALL_FORM_FACTOR';
ctx.client.clientName = Constants.CLIENTS.ANDROID.NAME;
break;
case 'ANDROID_VR':
ctx.client.androidSdkVersion = 32;
ctx.client.osName = 'Android';
ctx.client.osVersion = '12L';
ctx.client.platform = 'MOBILE';
ctx.client.userAgent = Constants.CLIENTS.ANDROID_VR.USER_AGENT;
ctx.client.deviceMake = Constants.CLIENTS.ANDROID_VR.DEVICE_MAKE;
ctx.client.deviceModel = Constants.CLIENTS.ANDROID_VR.DEVICE_MODEL;
ctx.client.clientVersion = Constants.CLIENTS.ANDROID_VR.VERSION;
ctx.client.clientFormFactor = 'SMALL_FORM_FACTOR';
ctx.client.clientName = Constants.CLIENTS.ANDROID_VR.NAME;
break;
case 'YTMUSIC_ANDROID':
ctx.client.clientVersion = Constants.CLIENTS.YTMUSIC_ANDROID.VERSION;
ctx.client.clientFormFactor = 'SMALL_FORM_FACTOR';

View File

@@ -157,15 +157,22 @@ interface DrcLabels {
label_drc_multiple: (audio_track_display_name: string) => string;
}
interface VbLabels {
label_original: string;
label_vb: string;
label_vb_multiple: (audio_track_display_name: string) => string;
}
function getFormatGroupings(formats: Format[], is_post_live_dvr: boolean) {
const group_info = new Map<string, Format[]>();
const has_multiple_audio_tracks = formats.some((fmt) => !!fmt.audio_track);
for (const format of formats) {
if ((!format.index_range || !format.init_range) && !format.is_type_otf && !is_post_live_dvr) {
if (((!format.index_range || !format.init_range) && !format.is_type_otf && !is_post_live_dvr)) {
continue;
}
const mime_type = format.mime_type.split(';')[0];
// Codec without any profile or level information
@@ -177,8 +184,9 @@ function getFormatGroupings(formats: Format[], is_post_live_dvr: boolean) {
const audio_track_id = format.audio_track?.id || '';
const drc = format.is_drc ? 'drc' : '';
const vb = format.is_vb ? 'vb' : '';
const group_id = `${mime_type}-${just_codec}-${color_info}-${audio_track_id}-${drc}`;
const group_id = `${mime_type}-${just_codec}-${color_info}-${audio_track_id}-${drc}-${vb}`;
if (!group_info.has(group_id)) {
group_info.set(group_id, []);
@@ -324,7 +332,7 @@ async function getSegmentInfo(
is_sabr?: boolean
) {
let transformed_url = '';
if (is_sabr) {
const formatKey = `${format.itag || ''}:${format.xtags || ''}`;
transformed_url = `sabr://${format.has_video ? 'video' : 'audio'}?key=${formatKey}`;
@@ -333,7 +341,7 @@ async function getSegmentInfo(
url.searchParams.set('cpn', cpn || '');
transformed_url = url_transformer(url).toString();
}
if (format.is_type_otf) {
if (!actions)
throw new InnertubeError('Unable to get segment durations for this OTF stream without an Actions instance', { format });
@@ -417,6 +425,10 @@ async function getAudioRepresentation(
uid_parts.push('drc');
}
if (format.is_vb) {
uid_parts.push('vb');
}
const rep: AudioRepresentation = {
uid: uid_parts.join('-'),
bitrate: format.bitrate,
@@ -444,7 +456,7 @@ function getTrackRoles(format: Format, has_drc_streams: boolean) {
if (format.is_descriptive)
roles.push('description');
if (format.is_drc)
if (format.is_drc || format.is_vb)
roles.push('enhanced-audio-intelligibility');
return roles;
@@ -458,6 +470,7 @@ async function getAudioSet(
cpn?: string,
shared_post_live_dvr_info?: SharedPostLiveDvrInfo,
drc_labels?: DrcLabels,
vb_labels?: VbLabels,
is_sabr?: boolean
) {
const first_format = formats[0];
@@ -465,17 +478,27 @@ async function getAudioSet(
const hoisted: string[] = [];
const has_drc_streams = !!drc_labels;
const has_vb_streams = !!vb_labels;
let track_name;
if (audio_track) {
if (has_drc_streams && first_format.is_drc) {
track_name = drc_labels.label_drc_multiple(audio_track.display_name);
} else if (has_vb_streams && first_format.is_vb) {
track_name = vb_labels.label_vb_multiple(audio_track.display_name);
} else {
track_name = audio_track.display_name;
}
} else if (has_drc_streams) {
track_name = first_format.is_drc ? drc_labels.label_drc : drc_labels.label_original;
} else if (has_drc_streams || has_vb_streams) {
if (has_drc_streams && first_format.is_drc) {
track_name = drc_labels.label_drc;
} else if (has_vb_streams && first_format.is_vb) {
track_name = vb_labels.label_vb;
} else {
// Both use the same param, so it doesn't matter which one is defined here.
track_name = (drc_labels || vb_labels)?.label_original;
}
}
const set: AudioSet = {
@@ -868,8 +891,23 @@ export async function getStreamingInfo(
});
let drc_labels: DrcLabels | undefined;
let vb_labels: VbLabels | undefined;
if (audio_groups.flat().some((format) => format.is_drc)) {
let hasDrc = false;
let hasVb = false;
for (const ag of audio_groups.flat()) {
if (hasDrc === false && ag.is_drc) {
hasDrc = true;
}
if (hasVb === false && ag.is_vb) {
hasVb = true;
}
}
// TODO: Put these audio fields in a shared object to reduce dups.
if (hasDrc) {
drc_labels = {
label_original: options?.label_original || 'Original',
label_drc: options?.label_drc || 'Stable Volume',
@@ -877,7 +915,15 @@ export async function getStreamingInfo(
};
}
const audio_sets = await Promise.all(audio_groups.map((formats) => getAudioSet(formats, url_transformer, actions, player, cpn, shared_post_live_dvr_info, drc_labels, options?.is_sabr)));
if (hasVb) {
vb_labels = {
label_original: options?.label_original || 'Original',
label_vb: options?.label_vb || 'Voice Boost',
label_vb_multiple: options?.label_vb_multiple || ((display_name) => `${display_name} (Voice Boost)`)
};
}
const audio_sets = await Promise.all(audio_groups.map((formats) => getAudioSet(formats, url_transformer, actions, player, cpn, shared_post_live_dvr_info, drc_labels, vb_labels, options?.is_sabr)));
const video_sets = await Promise.all(video_groups.map((formats) => getVideoSet(formats, url_transformer, player, actions, cpn, shared_post_live_dvr_info, options?.is_sabr)));
@@ -908,7 +954,7 @@ export async function getStreamingInfo(
text_sets = getTextSets(caption_tracks, options.captions_format, url_transformer);
}
const info : StreamingInfo = {
const info: StreamingInfo = {
getDuration,
audio_sets,
video_sets,

View File

@@ -261,4 +261,34 @@ export function getCookie(cookies: string, name: string, matchWholeName = false)
const regex = matchWholeName ? `(^|\\s?)\\b${name}\\b=([^;]+)` : `(^|s?)${name}=([^;]+)`;
const match = cookies.match(new RegExp(regex));
return match ? match[2] : undefined;
}
export function getNsigProcessorFn(n?: string | null, sp?: string | null, s?: string | null) {
return `function process(n = "", sp = "", s = "") {
const mockStreamingURL = "https://ytjs.googlevideo.com/videoplayback?expire=1234567890&"+"n="+encodeURIComponent(n);
const urlCtorFunction = exportedVars.nsigFunction || (() => { throw new Error('No n/sig decipher function extracted') });
const urlCtor = urlCtorFunction(mockStreamingURL, sp, s);
const proto = Object.getPrototypeOf(urlCtor);
const properties = Object.getOwnPropertyNames(proto);
const methodBlacklist = ['constructor', 'clone', 'set', 'get'];
for (const prop of properties) {
if (methodBlacklist.includes(prop))
continue;
if (typeof urlCtor[prop] === 'function')
urlCtor[prop]();
}
const sigResult = urlCtor.get(sp);
const nResult = urlCtor.get('n');
return {
sig: sigResult ? decodeURIComponent(sigResult) : undefined,
n: nResult ? decodeURIComponent(nResult) : undefined
};
}
return process("${n || ''}", "${sp || ''}", "${s || ''}");`;
}

View File

@@ -1,6 +1,5 @@
import type { ESTree } from 'meriyah';
import { parseScript } from 'meriyah';
import { WALK_STOP, jsBuiltIns, memberBaseName, memberToString, walkAst } from './helpers.ts';
import { parseScript, type ESTree } from 'meriyah';
import { jsBuiltIns, memberBaseName, memberToString, walkAst } from './helpers.ts';
export interface ExtractionConfig {
/**
@@ -14,9 +13,20 @@ export interface ExtractionConfig {
collectDependencies?: boolean;
/**
* When `true`, traversal stops once the extraction is matched and all its dependencies (when `collectDependencies=true`) resolve.
* Only useful for small functions/vars without too many dependencies.
* Only useful for small functions/vars without too many dependencies. Deeper dependency trees will usually have the unresolvable
* member expression here and there, for example:
* ```js
* var Vmi = g.dX.window, Wr = Vmi?.yt?.config_ || Vmi?.ytcfg?.data_ || {};
* ```
*
* Since `Vmi.ytcfg` is a dependency, it will never resolve because it comes from `g.dX.window`, which is an external object we don't have access to.
* In cases like this, `stopWhenReady` option does nothing useful.
*/
stopWhenReady?: boolean;
/**
* If `true`, dependency collection is limited to the match context node itself.
*/
onlyProcessMatchContext?: boolean;
/**
* Name for easier identification of extractions.
*/
@@ -35,6 +45,7 @@ export interface VariableMetadata {
node?: any;
dependencies: Set<string>;
dependents: Set<string>;
prototypeAliases: Map<string, Set<VariableMetadata>>;
predeclared: boolean;
}
@@ -64,9 +75,10 @@ export class JsAnalyzer {
private readonly hasExtractions: boolean;
private readonly extractionStates: ExtractionState[];
private readonly dependentsTracker: Map<string, Set<string>> = new Map();
private pendingPrototypeAliasBinding: [string, VariableMetadata] | null = null;
public declaredVariables: Map<string, VariableMetadata> = new Map();
public iifeParamName: string | null = null;
public readonly declaredVariables: Map<string, VariableMetadata> = new Map();
/**
* Creates a new instance over the provided source.
@@ -135,7 +147,37 @@ export class JsAnalyzer {
const left = assignment.left;
const right = assignment.right;
// Detect things like `a.b = g.c.prototype` so later `a.b.foo = ...` can be attributed back to `g.c`.
if (
right.type === 'MemberExpression' &&
!right.computed &&
right.property.type === 'Identifier' &&
right.property.name === 'prototype'
) {
const prototypeSourceExpr = memberToString(right, this.source);
const aliasTargetExpr = left.type === 'Identifier' ? left.name : memberToString(left, this.source);
if (prototypeSourceExpr) {
const prototypeOwnerMeta = this.declaredVariables.get(
prototypeSourceExpr.replace('.prototype', '')
);
if (aliasTargetExpr && prototypeOwnerMeta) {
const aliasedPrototypeMembers = new Set<VariableMetadata>();
const aliasExpr = `${aliasTargetExpr}.`; // Had to add a dot here so we can detect it later when matching member expressions..
// Activate an alias binding context, so subsequent member assignments to the alias (`a.b.foo = ...`) can be tracked.
// NOTE: This assumes that the alias members come right after this declaration and are grouped together in the code, hehe :)
this.pendingPrototypeAliasBinding = [ aliasExpr, prototypeOwnerMeta ];
prototypeOwnerMeta.prototypeAliases.set(aliasExpr, aliasedPrototypeMembers);
}
}
}
if (left.type === 'Identifier') {
// This identifier existing means it was a pre-declared and
// we just got to it.
const existingVariable = this.declaredVariables.get(left.name);
if (!existingVariable) continue;
@@ -148,6 +190,33 @@ export class JsAnalyzer {
if (this.onMatch(existingVariable.node, existingVariable)) return;
} else if (assignment.left.type === 'MemberExpression') {
const memberName = memberToString(assignment.left, this.source);
const activeAliasExpr = this.pendingPrototypeAliasBinding?.[0];
// While an alias binding is active, collect member assignments made through the alias (`g.q.foo = ...`).
if (activeAliasExpr && (memberName?.includes(activeAliasExpr) || memberName === activeAliasExpr.slice(0, -1))) {
const aliasOwnerMeta = this.declaredVariables.get(this.pendingPrototypeAliasBinding?.[1].name || '');
if (aliasOwnerMeta) {
const existingAliasedMembers = aliasOwnerMeta.prototypeAliases.get(activeAliasExpr);
const aliasedMemberMeta: VariableMetadata = {
name: memberName,
node: currentNode,
dependents: this.dependentsTracker.get(memberName) || new Set<string>(),
predeclared: false,
prototypeAliases: new Map<string, Set<VariableMetadata>>(),
dependencies: this.findDependencies(right, memberName)
};
if (existingAliasedMembers) {
existingAliasedMembers.add(aliasedMemberMeta);
} else {
aliasOwnerMeta.prototypeAliases.set(activeAliasExpr, new Set([ aliasedMemberMeta ]));
}
}
} else {
this.pendingPrototypeAliasBinding = null;
}
if (!memberName || this.declaredVariables.has(memberName)) continue;
const metadata: VariableMetadata = {
@@ -155,6 +224,7 @@ export class JsAnalyzer {
node: currentNode,
dependents: this.dependentsTracker.get(memberName) || new Set<string>(),
predeclared: false,
prototypeAliases: new Map<string, Set<VariableMetadata>>(),
dependencies: this.findDependencies(right, memberName)
};
@@ -174,6 +244,8 @@ export class JsAnalyzer {
break;
}
case 'VariableDeclaration': {
this.pendingPrototypeAliasBinding = null;
for (const declaration of currentNode.declarations) {
if (declaration.id.type !== 'Identifier') continue;
@@ -181,6 +253,7 @@ export class JsAnalyzer {
name: declaration.id.name,
node: declaration,
dependents: this.dependentsTracker.get(declaration.id.name) || new Set<string>(),
prototypeAliases: new Map<string, Set<VariableMetadata>>(),
dependencies: new Set(),
predeclared: false
};
@@ -188,9 +261,8 @@ export class JsAnalyzer {
const init = declaration.init;
if (!init && currentNode.kind === 'var') {
metadata.predeclared = true;
metadata.predeclared = true; // "var x, y, z;"
} else if (init && this.needsDependencyAnalysis(init)) {
// "var x, y, z;"
metadata.dependencies = this.findDependencies(init, metadata.name);
}
@@ -225,6 +297,7 @@ export class JsAnalyzer {
case 'ConditionalExpression':
case 'ObjectExpression':
case 'SequenceExpression':
case 'ClassExpression':
case 'Identifier':
return true;
default:
@@ -248,9 +321,23 @@ export class JsAnalyzer {
for (const state of this.extractionStates) {
if (!state.node) {
if (node.type === 'VariableDeclarator' && !node.init) continue;
result = state.config.match(node);
if (!result) continue;
state.node = node;
matched = true;
if (metadata) {
state.metadata = metadata;
state.dependents = metadata.dependents;
state.dependencies = metadata.dependencies;
if (typeof result !== 'boolean')
state.matchContext = result;
}
this.refreshExtractionState(state);
} else if (state.node !== node) {
// Use this as a chance to refresh readiness in case dependencies were resolved since last time
// we checked.
@@ -259,21 +346,7 @@ export class JsAnalyzer {
if (this.shouldStopTraversal()) {
return true;
}
continue;
}
matched = true;
if (metadata) {
state.metadata = metadata;
state.dependents = metadata.dependents;
state.dependencies = metadata.dependencies;
if (typeof result !== 'boolean')
state.matchContext = result;
}
this.refreshExtractionState(state);
}
if (!matched) return false;
@@ -423,6 +496,9 @@ export class JsAnalyzer {
walkAst(rootNode, {
enter: (n, parent) => {
switch (n.type) {
// Note for anybody debugging this in the future:
// *DO NOT* add MethodDefinition here.
// MethodDefinition.value is a FunctionExpression, so it is already handled...
case 'FunctionDeclaration':
case 'FunctionExpression':
case 'ArrowFunctionExpression': {
@@ -456,9 +532,12 @@ export class JsAnalyzer {
break;
}
case 'VariableDeclaration': {
const scope = currentScope();
// var hoists to function scope...
const targetScope = n.kind === 'var'
? scopeStack.findLast((s) => s.type === 'function') ?? currentScope()
: currentScope();
for (const d of n.declarations) {
collectBindingIdentifiers(d.id, scope.names);
collectBindingIdentifiers(d.id, targetScope.names);
}
break;
}
@@ -477,8 +556,10 @@ export class JsAnalyzer {
// Ignore if it's a property name (e.g., "obj.prop" or "{prop: 1}"", we don't care about the "prop" name itself).
if (parent?.type === 'Property' && parent.key === n && !parent.computed) return;
// Ignore class method names. They are declarations, not external dependencies.
if (parent?.type === 'MethodDefinition' && parent.key === n && !parent.computed) return;
if (parent?.type === 'MemberExpression' && parent.property === n && !parent.computed) {
if (parent.object.type === 'ThisExpression') return; // Skip 'this.property' stuff.
if (parent.object.type === 'ThisExpression') return; // Skip 'this.property', etc.
const full = memberToString(parent, this.source);
if (!full) return;
@@ -489,6 +570,7 @@ export class JsAnalyzer {
dependencies.add(full);
} else if (parent.object.type === 'Identifier') {
const baseName = parent.object.name;
const declaredBaseVariable = this.declaredVariables.get(baseName);
if (
(declaredBaseVariable || baseName === this.iifeParamName) &&
@@ -509,6 +591,10 @@ export class JsAnalyzer {
return;
}
if (parent?.type === 'MetaProperty') {
return; // Skip stuff like "new.target" or "import.meta"
}
if (isInScope(n.name) || jsBuiltIns.has(n.name)) return;
// It's a free variable, so it's a dependency.
@@ -528,6 +614,12 @@ export class JsAnalyzer {
}
break;
}
case 'ForStatement':
case 'ForInStatement':
case 'ForOfStatement': {
scopeStack.push({ names: new Set<string>(), type: 'block' });
break;
}
}
},
leave: (n: any) => {
@@ -537,6 +629,9 @@ export class JsAnalyzer {
case 'ArrowFunctionExpression':
case 'BlockStatement':
case 'CatchClause':
case 'ForStatement':
case 'ForInStatement':
case 'ForOfStatement':
if (scopeStack.length > 1) scopeStack.pop();
break;
}

View File

@@ -89,6 +89,8 @@ export class JsExtractor {
if (!node) return true;
switch (node.type) {
case 'ClassExpression':
return true;
case 'Literal': {
const literal = node as ESTree.Literal & { regex?: unknown };
return (
@@ -168,6 +170,12 @@ export class JsExtractor {
}
return this.isSafeInitializer(node.object, mode);
}
// Allow cases such as a.b = c.prototype;
if (!node.computed && node.property.type === 'Identifier' && node.property.name === 'prototype') {
return true;
}
return false;
}
case 'LogicalExpression':
@@ -269,16 +277,17 @@ export class JsExtractor {
initSource = initializerFallback;
} else {
const left = assignmentTarget?.left;
const isPrototypeAlias = init?.type === 'MemberExpression' && !init.computed && init.property.type === 'Identifier' && init.property.name === 'prototype';
// Often useless..
// e.g. `xyz.someProp = ...`
if (left?.type === 'MemberExpression' && init) {
// Skip things we don't need.
if (!isPrototypeAlias && left?.type === 'MemberExpression' && init) {
if (
canDisallow &&
left.object.type === 'Identifier' &&
init.type !== 'FunctionExpression' &&
init.type !== 'ArrowFunctionExpression' &&
init.type !== 'LogicalExpression'
init.type !== 'LogicalExpression' &&
init.type !== 'ClassExpression'
) {
return `${indent}// Skipped ${memberToString(left, source)} assignment.`;
}
@@ -287,7 +296,7 @@ export class JsExtractor {
// e.g. `someVar = someOtherVarFuncOrCall`
initSource = extractNodeSource(init, source)
?.trim()
.replace(/;\s*$/, '') || 'kk';
.replace(/;\s*$/, '') || 'undefined // [JsExtractor] Failed to extract initializer source.';
}
}
@@ -333,10 +342,18 @@ export class JsExtractor {
predeclaredVarSet.add(name);
}
const visit = (metadata?: VariableMetadata, depth: number = 0) => {
const visit = (metadata?: VariableMetadata, depth: number = 0, whitelistedDep?: string) => {
if (!metadata || depth > maxDepth) return;
for (const dependency of metadata.dependencies) {
if (whitelistedDep && whitelistedDep !== dependency) {
// If we haven't yet encountered the whitelisted dependency, skip this one.
// And if we have, delete the whitelist var so that all subsequent dependencies are included.
if (!seen.has(whitelistedDep))
continue;
whitelistedDep = undefined;
}
if (seen.has(dependency))
continue;
@@ -352,13 +369,19 @@ export class JsExtractor {
registerPredeclaredVar(dependency);
}
// Usually not used by anything we care about. Less code = better.
// e.g. `x.y = ...`
if (!dependency.includes('.')) {
visit(dependencyMetadata, depth + 1);
}
visit(dependencyMetadata, depth + 1, whitelistedDep);
snippets.push(this.renderNode(dependencyMetadata.node, shouldPredeclare, config));
if (dependencyMetadata.prototypeAliases.size > 0) {
for (const [ , aliasMembers ] of dependencyMetadata.prototypeAliases) {
for (const member of aliasMembers) {
// This is deeper than the first visit, so no need to pass the whitelist, we want all deps of the member to be included.
visit(member, depth);
snippets.push(this.renderNode(member.node, shouldPredeclare, config));
}
}
}
}
};
@@ -372,13 +395,26 @@ export class JsExtractor {
snippets.push(`${indent}//#region --- start [${fname || 'Unknown'}] ---`);
const shouldPredeclare = (forceVarPredeclaration || extraction.metadata.predeclared) && !shouldSkip;
const onlyProcessMatchContext = extraction.config.onlyProcessMatchContext;
if (shouldPredeclare) {
registerPredeclaredVar(extraction.metadata.name);
}
if (extraction.config.collectDependencies && !shouldSkip) {
visit(extraction.metadata);
let whitelistedDep;
const matchContextNode = extraction.matchContext;
if (matchContextNode?.type === 'NewExpression' && onlyProcessMatchContext) {
if (matchContextNode.callee.type === 'Identifier') {
whitelistedDep = matchContextNode.callee.name;
} else if (matchContextNode.callee.type === 'MemberExpression') {
whitelistedDep = memberToString(matchContextNode.callee, this.analyzer.getSource()) || undefined;
}
}
visit(extraction.metadata, undefined, whitelistedDep);
}
if (extraction.matchContext && fname) {
@@ -402,7 +438,10 @@ export class JsExtractor {
}
if (!shouldSkip) {
snippets.push(this.renderNode(extraction.metadata.node, shouldPredeclare, config));
if (!onlyProcessMatchContext) {
snippets.push(this.renderNode(extraction.metadata.node, shouldPredeclare, config));
}
snippets.push(`${indent}//#endregion --- end [${fname || 'Unknown'}] ---\n`);
}
}
@@ -410,12 +449,16 @@ export class JsExtractor {
const output = [];
// Not required by any means, but add it anyway, "just in case".
output.push('const window = Object.assign({}, globalThis);');
output.push('const document = {};');
output.push('const self = window;\n');
output.push('const __jsExtractorGlobal = typeof globalThis !== \'undefined\' ? globalThis :');
output.push(`${indent}typeof self !== 'undefined' ? self :`);
output.push(`${indent}typeof window !== 'undefined' ? window :`);
output.push(`${indent}typeof global !== 'undefined' ? global : {};\n`);
output.push(`const exportedVars = (function(${this.analyzer.iifeParamName}) {`);
output.push(`${indent}const window = typeof __jsExtractorGlobal.window !== 'undefined' ? __jsExtractorGlobal.window : Object.create(null);`);
output.push(`${indent}const document = typeof __jsExtractorGlobal.document !== 'undefined' ? __jsExtractorGlobal.document : {};`);
output.push(`${indent}const self = typeof __jsExtractorGlobal.self !== 'undefined' ? __jsExtractorGlobal.self : window;\n`);
if (predeclaredVarSet.size > 0) {
output.push(`${indent}var ${Array.from(predeclaredVarSet).join(', ')};\n`);
}
@@ -433,7 +476,7 @@ export class JsExtractor {
if (decl?.node?.type === 'VariableDeclarator' && decl.node.init?.type === 'FunctionExpression') {
currentFunctionNode = decl.node;
}
} else if (node.type === 'CallExpression') {
} else if (node.type === 'CallExpression' || node.type === 'NewExpression' || node.type === 'VariableDeclarator') {
currentFunctionNode = node;
}
@@ -450,8 +493,9 @@ export class JsExtractor {
const rawJson = JSON.stringify(exportedRawValues, null, indent.length);
const rawJsonLines = rawJson.split('\n');
// Indent all lines except the first one...
const formattedRawJson = `${rawJsonLines[0]}\n${rawJsonLines.slice(1).map((line) => indent + line).join('\n')}`;
// Indent all lines except the first one..
const formattedRawJson =
`${rawJsonLines[0]}\n${rawJsonLines.slice(1).map((line) => indent + line).join('\n')}`;
output.push(`${indent}const rawValues = ${formattedRawJson};\n`);

View File

@@ -52,7 +52,7 @@ export interface AstVisitObject {
export type AstVisitor = AstVisitFn | AstVisitObject;
/**
* Performs a non-recursive traversal of an ESTree AST.
* Performs traversal of an ESTree AST.
* @param root - Root AST node to start the traversal from.
* @param visitor - Callbacks invoked when nodes are entered or left.
* @remarks
@@ -132,7 +132,7 @@ export function getNodeSourceRange(node: ESTree.Node | null | undefined): [numbe
if (Array.isArray(node.range)) return node.range;
if (typeof node.start === 'number' && typeof node.end === 'number') return [ node.start, node.end ];
return null;
}
};
/**
* Extracts the source code corresponding to a given AST node.
@@ -143,7 +143,7 @@ export function getNodeSourceRange(node: ESTree.Node | null | undefined): [numbe
export function extractNodeSource(node: ESTree.Node | null | undefined, source: string): string | null {
const range = getNodeSourceRange(node);
return range ? source.slice(range[0], range[1]) : null;
}
};
/**
* Converts a member expression into its dot/bracket string form.
@@ -183,7 +183,7 @@ export function memberToString(memberExpression: ESTree.Node, source: string): s
}
return base ? base + segments.join('') : null;
}
};
/**
* Retrieves the base identifier for a member expression chain.
@@ -203,7 +203,7 @@ export function memberBaseName(memberExpression: ESTree.MemberExpression, source
if (target?.type === 'ThisExpression') return 'this';
return null;
}
};
/**
* Analyzes an AST node to determine if it's a function call or a function
@@ -233,6 +233,14 @@ export function createWrapperFunction(analyzer: JsAnalyzer, name: string, node:
node.id.type === 'Identifier'
) {
return generateWrapper(name, node.id.name, parseFunctionArguments(analyzer, node.init.params));
} else if (
node.type === 'NewExpression' &&
node.callee.type === 'MemberExpression' &&
node.callee.object.type === 'Identifier'
) {
const targetFunction = memberToString(node.callee, analyzer.getSource());
if (!targetFunction) return undefined;
return generateWrapper(name, targetFunction, parseFunctionArguments(analyzer, node.arguments), true);
}
}
@@ -242,10 +250,10 @@ export function createWrapperFunction(analyzer: JsAnalyzer, name: string, node:
* @param targetFunction - The name of the target function to call.
* @param args - The arguments to pass to the target function.
*/
function generateWrapper(functionName: string, targetFunction: string, args: string): string {
function generateWrapper(functionName: string, targetFunction: string, args: string[], useNew: boolean = false): string {
return [
`${indent}function ${functionName}(input) {`,
`${indent}${indent}return ${targetFunction}(${args});`,
`${indent}function ${functionName}(${args.join(', ')}) {`,
`${indent}${indent}return ${useNew ? 'new ' : ''}${targetFunction}(${args.join(', ')});`,
`${indent}}`
].join('\n');
}
@@ -264,9 +272,18 @@ function parseFunctionArguments(analyzer: JsAnalyzer, args: ESTree.Node[]) {
params.push(arg.name);
} else if (arg.type === 'Literal' && (typeof arg.value === 'string' || typeof arg.value === 'number')) {
params.push(JSON.stringify(arg.value));
} else if (arg.type === 'UnaryExpression') {
const argSource = extractNodeSource(arg, analyzer.getSource());
if (argSource) {
params.push(argSource.trim());
}
} else if (arg.type === 'AssignmentPattern' && arg.left.type === 'Identifier') {
params.push(arg.left.name);
} else if (arg.type === 'Identifier') {
params.push(arg.name);
} else if (!params.includes('input'))
params.push('input');
}
return params.join(', ');
return params;
}

View File

@@ -1,68 +1,54 @@
import type { ESTree } from 'meriyah';
import { WALK_STOP, walkAst } from './helpers.ts';
export function sigMatcher(node: ESTree.Node) {
if (node.type === 'VariableDeclarator' && node.id?.type === 'Identifier') {
const idNode = node.id;
const initNode = node.init;
export function nsigMatcher(node: ESTree.Node) {
if (node.type !== 'VariableDeclarator')
return false;
if (idNode.type === 'Identifier' && initNode?.type === 'FunctionExpression' && initNode.params.length === 3) {
const functionInitNode = initNode.body;
if (!functionInitNode || functionInitNode.type !== 'BlockStatement') return false;
const init = node.init;
for (const st of functionInitNode.body) {
if (st?.type === 'ExpressionStatement') {
const expression = st.expression;
if (
expression.type === 'LogicalExpression' &&
expression.operator === '&&' &&
expression.left.type === 'Identifier' &&
expression.right.type === 'SequenceExpression'
) {
const firstExp = expression.right.expressions[0];
if (
firstExp.type === 'AssignmentExpression' &&
firstExp.operator === '=' &&
firstExp.left.type === 'Identifier' &&
firstExp.right.type === 'CallExpression' &&
firstExp.right.callee.type === 'Identifier'
) {
const rightArguments = firstExp.right.arguments;
// sigFn(64, decodeURIComponent(sig))
if (rightArguments.length >= 1) {
const callExpression = rightArguments.find((exp) => exp.type === 'CallExpression');
if (
callExpression?.type === 'CallExpression' &&
callExpression?.callee.type === 'Identifier' &&
callExpression.callee.name === 'decodeURIComponent' &&
callExpression.arguments[0].type === 'Identifier'
) {
return firstExp.right;
}
}
}
}
}
if (!init || init.type !== 'FunctionExpression')
return false;
if (init.params.length < 3)
return false;
const [ url, sigName, sigValue ] = init.params;
if (url.type !== 'Identifier' || sigName.type !== 'AssignmentPattern' || sigValue.type !== 'AssignmentPattern')
return false;
const body = init.body;
const blockStatementBody = body?.body || [];
let hasUrlCtor = false;
let hasSetAlr = false;
for (const statement of blockStatementBody) {
if (statement.type !== 'ExpressionStatement')
continue;
const expr = statement.expression;
if (expr.type === 'AssignmentExpression' && expr.operator === '=' && expr.left.type === 'Identifier' && expr.left.name === url.name) {
const right = expr.right;
if (right.type === 'NewExpression' && right.callee.type === 'MemberExpression') {
hasUrlCtor = true;
}
}
if (expr.type === 'CallExpression' && expr.callee.type === 'MemberExpression') {
const args = expr.arguments;
if (args.length === 2 && args[0].type === 'Literal' && args[0].value === 'alr' && args[1].type === 'Literal' && args[1].value === 'yes') {
hasSetAlr = true;
}
}
}
return false;
}
export function nMatcher(node: ESTree.Node) {
if (node.type !== 'VariableDeclarator')
if (!hasUrlCtor || !hasSetAlr)
return false;
if (
node.id.type === 'Identifier' &&
node.init?.type === 'ArrayExpression' &&
node.init.elements[0]?.type === 'Identifier'
) {
return node.init.elements[0];
}
return false;
return node;
}
export function timestampMatcher(node: ESTree.Node) {