Skip to content

Commit

Permalink
feat(Player): Add support for Proof of Identity tokens (#708)
Browse files Browse the repository at this point in the history
* Fix different usages of potoken.

* Fix linting.

* Add mention about invidious youtube-trusted-session-generator.

---------

Co-authored-by: Luan <[email protected]>
  • Loading branch information
unixfox and LuanRT authored Aug 8, 2024
1 parent 25d268b commit c9f0ddd
Show file tree
Hide file tree
Showing 6 changed files with 40 additions and 10 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ const youtube = await Innertube.create(/* options */);
| `cache` | `ICache` | Used to cache algorithms, session data, and OAuth2 tokens. | `undefined` |
| `cookie` | `string` | YouTube cookies. | `undefined` |
| `fetch` | `FetchFunction` | Fetch function to use. | `fetch` |
| `po_token` | `string` | Proof of Origin Token. This is an attestation token generated by BotGuard/DroidGuard. It is used to confirm that the request is coming from a genuine device. To get a valid one, please refer to [Invidious' Trusted Session Generator](https://github.com/iv-org/youtube-trusted-session-generator). | `undefined` |

</details>

Expand Down
6 changes: 4 additions & 2 deletions src/Innertube.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,8 @@ export default class Innertube {
video_id: next_payload.videoId,
playlist_id: next_payload?.playlistId,
client: client,
sts: this.#session.player?.sts
sts: this.#session.player?.sts,
po_token: this.#session.po_token
});

const player_response = this.actions.execute(PlayerEndpoint.PATH, player_payload);
Expand All @@ -116,7 +117,8 @@ export default class Innertube {
PlayerEndpoint.PATH, PlayerEndpoint.build({
video_id: video_id,
client: client,
sts: this.#session.player?.sts
sts: this.#session.player?.sts,
po_token: this.#session.po_token
})
);

Expand Down
13 changes: 11 additions & 2 deletions src/core/Player.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export default class Player {
sts: number;
nsig_sc?: string;
sig_sc?: string;
po_token?: string;

constructor(player_id: string, signature_timestamp: number, sig_sc?: string, nsig_sc?: string) {
this.player_id = player_id;
Expand All @@ -20,7 +21,7 @@ export default class Player {
this.sig_sc = sig_sc;
}

static async create(cache: ICache | undefined, fetch: FetchFunction = Platform.shim.fetch): Promise<Player> {
static async create(cache: ICache | undefined, fetch: FetchFunction = Platform.shim.fetch, po_token?: string): Promise<Player> {
const url = new URL('/iframe_api', Constants.URLS.YT_BASE);
const res = await fetch(url);

Expand All @@ -41,6 +42,7 @@ export default class Player {
const cached_player = await Player.fromCache(cache, player_id);
if (cached_player) {
Log.info(TAG, 'Found up-to-date player data in cache.');
cached_player.po_token = po_token;
return cached_player;
}
}
Expand All @@ -67,7 +69,10 @@ export default class Player {

Log.info(TAG, `Got signature timestamp (${sig_timestamp}) and algorithms needed to decipher signatures.`);

return await Player.fromSource(player_id, sig_timestamp, cache, sig_sc, nsig_sc);
const player = await Player.fromSource(player_id, sig_timestamp, cache, sig_sc, nsig_sc);
player.po_token = po_token;

return player;
}

decipher(url?: string, signature_cipher?: string, cipher?: string, this_response_nsig_cache?: Map<string, string>): string {
Expand Down Expand Up @@ -123,6 +128,10 @@ export default class Player {
url_components.searchParams.set('n', nsig);
}

// @NOTE: SABR requests should include the PoToken (not base64d, but as bytes!) in the payload.
if (url_components.searchParams.get('sabr') !== '1' && this.po_token)
url_components.searchParams.set('pot', this.po_token);

const client = url_components.searchParams.get('c');

switch (client) {
Expand Down
18 changes: 13 additions & 5 deletions src/core/Session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,10 @@ export type SessionOptions = {
* Fetch function to use.
*/
fetch?: FetchFunction;
/**
* Proof of Origin Token. This is an attestation token generated by BotGuard/DroidGuard. It is used to confirm that the request is coming from a genuine device.
*/
po_token?: string;
}

export type SessionData = {
Expand Down Expand Up @@ -217,8 +221,9 @@ export default class Session extends EventEmitter {
key: string;
api_version: string;
account_index: number;
po_token?: string;

constructor(context: Context, api_key: string, api_version: string, account_index: number, player?: Player, cookie?: string, fetch?: FetchFunction, cache?: ICache) {
constructor(context: Context, api_key: string, api_version: string, account_index: number, player?: Player, cookie?: string, fetch?: FetchFunction, cache?: ICache, po_token?: string) {
super();
this.http = new HTTPClient(this, cookie, fetch);
this.actions = new Actions(this);
Expand All @@ -230,6 +235,7 @@ export default class Session extends EventEmitter {
this.api_version = api_version;
this.context = context;
this.player = player;
this.po_token = po_token;
}

on(type: 'auth', listener: OAuth2AuthEventHandler): void;
Expand Down Expand Up @@ -263,12 +269,13 @@ export default class Session extends EventEmitter {
options.fetch,
options.on_behalf_of_user,
options.cache,
options.enable_session_cache
options.enable_session_cache,
options.po_token
);

return new Session(
context, api_key, api_version, account_index,
options.retrieve_player === false ? undefined : await Player.create(options.cache, options.fetch),
options.retrieve_player === false ? undefined : await Player.create(options.cache, options.fetch, options.po_token),
options.cookie, options.fetch, options.cache
);
}
Expand Down Expand Up @@ -327,9 +334,10 @@ export default class Session extends EventEmitter {
fetch: FetchFunction = Platform.shim.fetch,
on_behalf_of_user?: string,
cache?: ICache,
enable_session_cache = true
enable_session_cache = true,
po_token?: string
) {
const session_args = { lang, location, time_zone: tz, device_category, client_name, enable_safety_mode, visitor_data, on_behalf_of_user };
const session_args = { lang, location, time_zone: tz, device_category, client_name, enable_safety_mode, visitor_data, on_behalf_of_user, po_token };

let session_data: SessionData | undefined;

Expand Down
5 changes: 4 additions & 1 deletion src/core/endpoints/PlayerEndpoint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,10 @@ export function build(opts: PlayerEndpointOptions): IPlayerRequest {
...{
client: opts.client,
playlistId: opts.playlist_id,
params: opts.params
params: opts.params,
serviceIntegrityDimensions: {
poToken: opts.po_token || ''
}
}
};
}
7 changes: 7 additions & 0 deletions src/types/Endpoints.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ export interface IPlayerRequest {
playlistId?: string;
params?: string;
client?: InnerTubeClient;
serviceIntegrityDimensions?: {
poToken: string
}
}

export type PlayerEndpointOptions = {
Expand All @@ -52,6 +55,10 @@ export type PlayerEndpointOptions = {
* Additional protobuf parameters.
*/
params?: string;
/**
* Token for serviceIntegrityDimensions
*/
po_token?: string;
}

export type NextEndpointOptions = {
Expand Down

0 comments on commit c9f0ddd

Please sign in to comment.