Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(PlayerEndpoint): Workaround for "The following content is not available on this app" (Android) #624

Merged
merged 6 commits into from
Mar 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 28 additions & 12 deletions src/core/Session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,25 +33,25 @@ export interface Context {
hl: string;
gl: string;
remoteHost?: string;
screenDensityFloat: number;
screenHeightPoints: number;
screenPixelDensity: number;
screenWidthPoints: number;
visitorData: string;
screenDensityFloat?: number;
screenHeightPoints?: number;
screenPixelDensity?: number;
screenWidthPoints?: number;
visitorData?: string;
clientName: string;
clientVersion: string;
clientScreen?: string,
androidSdkVersion?: string;
androidSdkVersion?: number;
osName: string;
osVersion: string;
platform: string;
clientFormFactor: string;
userInterfaceTheme: string;
userInterfaceTheme?: string;
timeZone: string;
userAgent?: string;
browserName?: string;
browserVersion?: string;
originalUrl: string;
originalUrl?: string;
deviceMake: string;
deviceModel: string;
utcOffsetMinutes: number;
Expand All @@ -73,6 +73,10 @@ export interface Context {
thirdParty?: {
embedUrl: string;
};
request?: {
useSsl: boolean;
internalExperimentFlags: any[];
};
}

export interface SessionOptions {
Expand Down Expand Up @@ -327,11 +331,17 @@ export default class Session extends EventEmitter {
},
user: {
enableSafetyMode: options.enable_safety_mode,
lockedSafetyMode: false,
onBehalfOfUser: options.on_behalf_of_user
lockedSafetyMode: false
},
request: {
useSsl: true,
internalExperimentFlags: []
}
};

if (options.on_behalf_of_user)
context.user.onBehalfOfUser = options.on_behalf_of_user;

return { context, api_key, api_version };
}

Expand Down Expand Up @@ -366,11 +376,17 @@ export default class Session extends EventEmitter {
},
user: {
enableSafetyMode: options.enable_safety_mode,
lockedSafetyMode: false,
onBehalfOfUser: options.on_behalf_of_user
lockedSafetyMode: false
},
request: {
useSsl: true,
internalExperimentFlags: []
}
};

if (options.on_behalf_of_user)
context.user.onBehalfOfUser = options.on_behalf_of_user;

return { context, api_key: Constants.CLIENTS.WEB.API_KEY, api_version: Constants.CLIENTS.WEB.API_VERSION };
}

Expand Down
3 changes: 2 additions & 1 deletion src/core/endpoints/PlayerEndpoint.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { encodeShortsParam } from '../../proto/index.js';
import type { IPlayerRequest, PlayerEndpointOptions } from '../../types/index.js';

export const PATH = '/player';
Expand Down Expand Up @@ -43,7 +44,7 @@ export function build(opts: PlayerEndpointOptions): IPlayerRequest {
client: opts.client,
playlistId: opts.playlist_id,
// Workaround streaming URLs returning 403 or getting throttled when using Android based clients.
params: is_android ? '2AMBCgIQBg' : opts.params
params: is_android ? encodeShortsParam() : opts.params
LuanRT marked this conversation as resolved.
Show resolved Hide resolved
}
};
}
75 changes: 75 additions & 0 deletions src/proto/generated/messages/youtube/(ShortsParam)/Field1.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import {
tsValueToJsonValueFns,
jsonValueToTsValueFns,
} from "../../../runtime/json/scalar.js";
import {
WireMessage,
} from "../../../runtime/wire/index.js";
import {
default as serialize,
} from "../../../runtime/wire/serialize.js";
import {
tsValueToWireValueFns,
wireValueToTsValueFns,
} from "../../../runtime/wire/scalar.js";
import {
default as deserialize,
} from "../../../runtime/wire/deserialize.js";

export declare namespace $.youtube.ShortsParam {
export type Field1 = {
p1: number;
}
}

export type Type = $.youtube.ShortsParam.Field1;

export function getDefaultValue(): $.youtube.ShortsParam.Field1 {
return {
p1: 0,
};
}

export function createValue(partialValue: Partial<$.youtube.ShortsParam.Field1>): $.youtube.ShortsParam.Field1 {
return {
...getDefaultValue(),
...partialValue,
};
}

export function encodeJson(value: $.youtube.ShortsParam.Field1): unknown {
const result: any = {};
if (value.p1 !== undefined) result.p1 = tsValueToJsonValueFns.int32(value.p1);
return result;
}

export function decodeJson(value: any): $.youtube.ShortsParam.Field1 {
const result = getDefaultValue();
if (value.p1 !== undefined) result.p1 = jsonValueToTsValueFns.int32(value.p1);
return result;
}

export function encodeBinary(value: $.youtube.ShortsParam.Field1): Uint8Array {
const result: WireMessage = [];
if (value.p1 !== undefined) {
const tsValue = value.p1;
result.push(
[1, tsValueToWireValueFns.int32(tsValue)],
);
}
return serialize(result);
}

export function decodeBinary(binary: Uint8Array): $.youtube.ShortsParam.Field1 {
const result = getDefaultValue();
const wireMessage = deserialize(binary);
const wireFields = new Map(wireMessage);
field: {
const wireValue = wireFields.get(1);
if (wireValue === undefined) break field;
const value = wireValueToTsValueFns.int32(wireValue);
if (value === undefined) break field;
result.p1 = value;
}
return result;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export type { Type as Field1 } from "./Field1.js";
100 changes: 100 additions & 0 deletions src/proto/generated/messages/youtube/ShortsParam.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import {
Type as Field1,
encodeJson as encodeJson_1,
decodeJson as decodeJson_1,
encodeBinary as encodeBinary_1,
decodeBinary as decodeBinary_1,
} from "./(ShortsParam)/Field1.js";
import {
tsValueToJsonValueFns,
jsonValueToTsValueFns,
} from "../../runtime/json/scalar.js";
import {
WireMessage,
WireType,
} from "../../runtime/wire/index.js";
import {
default as serialize,
} from "../../runtime/wire/serialize.js";
import {
tsValueToWireValueFns,
wireValueToTsValueFns,
} from "../../runtime/wire/scalar.js";
import {
default as deserialize,
} from "../../runtime/wire/deserialize.js";

export declare namespace $.youtube {
export type ShortsParam = {
f1?: Field1;
p59: number;
}
}

export type Type = $.youtube.ShortsParam;

export function getDefaultValue(): $.youtube.ShortsParam {
return {
f1: undefined,
p59: 0,
};
}

export function createValue(partialValue: Partial<$.youtube.ShortsParam>): $.youtube.ShortsParam {
return {
...getDefaultValue(),
...partialValue,
};
}

export function encodeJson(value: $.youtube.ShortsParam): unknown {
const result: any = {};
if (value.f1 !== undefined) result.f1 = encodeJson_1(value.f1);
if (value.p59 !== undefined) result.p59 = tsValueToJsonValueFns.int32(value.p59);
return result;
}

export function decodeJson(value: any): $.youtube.ShortsParam {
const result = getDefaultValue();
if (value.f1 !== undefined) result.f1 = decodeJson_1(value.f1);
if (value.p59 !== undefined) result.p59 = jsonValueToTsValueFns.int32(value.p59);
return result;
}

export function encodeBinary(value: $.youtube.ShortsParam): Uint8Array {
const result: WireMessage = [];
if (value.f1 !== undefined) {
const tsValue = value.f1;
result.push(
[1, { type: WireType.LengthDelimited as const, value: encodeBinary_1(tsValue) }],
);
}
if (value.p59 !== undefined) {
const tsValue = value.p59;
result.push(
[59, tsValueToWireValueFns.int32(tsValue)],
);
}
return serialize(result);
}

export function decodeBinary(binary: Uint8Array): $.youtube.ShortsParam {
const result = getDefaultValue();
const wireMessage = deserialize(binary);
const wireFields = new Map(wireMessage);
field: {
const wireValue = wireFields.get(1);
if (wireValue === undefined) break field;
const value = wireValue.type === WireType.LengthDelimited ? decodeBinary_1(wireValue.value) : undefined;
if (value === undefined) break field;
result.f1 = value;
}
field: {
const wireValue = wireFields.get(59);
if (wireValue === undefined) break field;
const value = wireValueToTsValueFns.int32(wireValue);
if (value === undefined) break field;
result.p59 = value;
}
return result;
}
1 change: 1 addition & 0 deletions src/proto/generated/messages/youtube/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@ export type { Type as MusicSearchFilter } from "./MusicSearchFilter.js";
export type { Type as SearchFilter } from "./SearchFilter.js";
export type { Type as Hashtag } from "./Hashtag.js";
export type { Type as ReelSequence } from "./ReelSequence.js";
export type { Type as ShortsParam } from "./ShortsParam.js";
11 changes: 11 additions & 0 deletions src/proto/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import * as NotificationPreferences from './generated/messages/youtube/Notificat
import * as InnertubePayload from './generated/messages/youtube/InnertubePayload.js';
import * as Hashtag from './generated/messages/youtube/Hashtag.js';
import * as ReelSequence from './generated/messages/youtube/ReelSequence.js';
import * as ShortsParam from './generated/messages/youtube/ShortsParam.js';

export function encodeVisitorData(id: string, timestamp: number): string {
const buf = VisitorData.encodeBinary({ id, timestamp });
Expand Down Expand Up @@ -341,4 +342,14 @@ export function encodeReelSequence(short_id: string): string {
feature3: 0
});
return encodeURIComponent(u8ToBase64(buf));
}

export function encodeShortsParam() {
const buf = ShortsParam.encodeBinary({
f1: {
p1: 1
},
p59: 1
});
return encodeURIComponent(u8ToBase64(buf));
}
8 changes: 8 additions & 0 deletions src/proto/youtube.proto
Original file line number Diff line number Diff line change
Expand Up @@ -275,4 +275,12 @@ message ReelSequence {
required Params params = 5;
required int32 feature_2 = 10;
required int32 feature_3 = 13;
}

message ShortsParam {
message Field1 {
int32 p1 = 1;
}
Field1 f1 = 1;
int32 p59 = 59;
}
6 changes: 3 additions & 3 deletions src/utils/Constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,9 @@ export const CLIENTS = Object.freeze({
},
ANDROID: {
NAME: 'ANDROID',
VERSION: '18.06.35',
SDK_VERSION: '29',
USER_AGENT: 'com.google.android.youtube/18.06.35 (Linux; U; Android 10; US)'
VERSION: '18.48.37',
SDK_VERSION: 33,
USER_AGENT: 'com.google.android.youtube/18.48.37(Linux; U; Android 13; en_US; sdk_gphone64_x86_64 Build/UPB4.230623.005) gzip'
},
YTSTUDIO_ANDROID: {
NAME: 'ANDROID_CREATOR',
Expand Down
11 changes: 10 additions & 1 deletion src/utils/HTTPClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,13 +95,22 @@ export default class HTTPClient {
if (Platform.shim.server) {
if (n_body.context.client.clientName === 'ANDROID' || n_body.context.client.clientName === 'ANDROID_MUSIC') {
request_headers.set('User-Agent', Constants.CLIENTS.ANDROID.USER_AGENT);
request_headers.set('X-GOOG-API-FORMAT-VERSION', '2');
} else if (n_body.context.client.clientName === 'iOS') {
request_headers.set('User-Agent', Constants.CLIENTS.iOS.USER_AGENT);
}
}

is_web_kids = n_body.context.client.clientName === 'WEB_KIDS';
request_body = JSON.stringify(n_body);
} else if (content_type === 'application/x-protobuf') {
// Assume it is always an Android request.
if (Platform.shim.server) {
request_headers.set('User-Agent', Constants.CLIENTS.ANDROID.USER_AGENT);
request_headers.set('X-GOOG-API-FORMAT-VERSION', '2');
request_headers.delete('X-Youtube-Client-Version');
request_headers.delete('X-Origin');
}
}

// Authenticate (NOTE: YouTube Kids does not support regular bearer tokens)
Expand Down Expand Up @@ -152,7 +161,7 @@ export default class HTTPClient {
ctx.client.androidSdkVersion = Constants.CLIENTS.ANDROID.SDK_VERSION;
ctx.client.userAgent = Constants.CLIENTS.ANDROID.USER_AGENT;
ctx.client.osName = 'Android';
ctx.client.osVersion = '10';
ctx.client.osVersion = '13';
ctx.client.platform = 'MOBILE';
}

Expand Down
Loading
Loading