Skip to content

Commit 33c27dd

Browse files
authored
feat(Innertube): Add getAttestationChallenge (#869)
* feat(Innertube): Add `getAttestationChallenge` YouTube has recently started using this InnerTube endpoint to fetch challenges for WebPo generation. * chore: lint
1 parent 50539bc commit 33c27dd

File tree

6 files changed

+85
-10
lines changed

6 files changed

+85
-10
lines changed

src/Innertube.ts

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,14 @@ import * as Constants from './utils/Constants.js';
2525
import { generateRandomString, InnertubeError, throwIfMissing, u8ToBase64 } from './utils/Utils.js';
2626

2727
import type { ApiResponse } from './core/Actions.js';
28-
import type { DownloadOptions, FormatOptions, InnerTubeClient, InnerTubeConfig, SearchFilters } from './types/index.js';
28+
import type {
29+
DownloadOptions,
30+
EngagementType,
31+
FormatOptions,
32+
InnerTubeClient,
33+
InnerTubeConfig,
34+
SearchFilters
35+
} from './types/index.js';
2936
import type { IBrowseResponse, IParsedResponse } from './parser/index.js';
3037
import type Format from './parser/classes/misc/Format.js';
3138

@@ -480,7 +487,7 @@ export default class Innertube {
480487
}
481488

482489
/**
483-
* Get comments for a community post.
490+
* Gets the comments of a post.
484491
*/
485492
async getPostComments(post_id: string, channel_id: string, sort_by?: 'TOP_COMMENTS' | 'NEWEST_FIRST'): Promise<Comments> {
486493
throwIfMissing({ post_id, channel_id });
@@ -531,6 +538,20 @@ export default class Innertube {
531538
return new Comments(this.actions, response.data);
532539
}
533540

541+
/**
542+
* Fetches an attestation challenge.
543+
*/
544+
async getAttestationChallenge(engagement_type: EngagementType, ids?: Record<string, any>[]) {
545+
const payload: Record<string, any> = {
546+
engagementType: engagement_type
547+
};
548+
549+
if (ids)
550+
payload.ids = ids;
551+
552+
return this.actions.execute('/att/get', { parse: true, ...payload });
553+
}
554+
534555
/**
535556
* Utility method to call an endpoint without having to use {@link Actions}.
536557
*/

src/core/Actions.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type {
22
IBrowseResponse,
3+
IGetChallengeResponse,
34
IGetNotificationsMenuResponse,
45
INextResponse,
56
IParsedResponse,
@@ -28,6 +29,7 @@ export type InnertubeEndpoint =
2829
| '/reel'
2930
| '/updated_metadata'
3031
| '/notification/get_notification_menu'
32+
| '/att/get'
3133
| string;
3234

3335
export type ParsedResponse<T> =
@@ -38,7 +40,8 @@ export type ParsedResponse<T> =
3840
T extends '/updated_metadata' ? IUpdatedMetadataResponse :
3941
T extends '/navigation/resolve_url' ? IResolveURLResponse :
4042
T extends '/notification/get_notification_menu' ? IGetNotificationsMenuResponse :
41-
IParsedResponse;
43+
T extends '/att/get' ? IGetChallengeResponse :
44+
IParsedResponse;
4245

4346
export default class Actions {
4447
public session: Session;
@@ -117,7 +120,7 @@ export default class Actions {
117120
if (this.#needsLogin(data.browseId) && !this.session.logged_in)
118121
throw new InnertubeError('You must be signed in to perform this operation.');
119122
}
120-
123+
121124
if (Reflect.has(data, 'skip_auth_check'))
122125
delete data.skip_auth_check;
123126

src/parser/parser.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -479,6 +479,25 @@ export function parseResponse<T extends IParsedResponse = IParsedResponse>(data:
479479
if (engagement_panels.length) {
480480
parsed_data.engagement_panels = engagement_panels;
481481
}
482+
483+
if (data.bgChallenge) {
484+
const interpreter_url = {
485+
private_do_not_access_or_else_trusted_resource_url_wrapped_value: data.bgChallenge.interpreterUrl.privateDoNotAccessOrElseTrustedResourceUrlWrappedValue,
486+
private_do_not_access_or_else_safe_script_wrapped_value: data.bgChallenge.interpreterUrl.privateDoNotAccessOrElseSafeScriptWrappedValue
487+
};
488+
489+
parsed_data.bg_challenge = {
490+
interpreter_url,
491+
interpreter_hash: data.bgChallenge.interpreterHash,
492+
program: data.bgChallenge.program,
493+
global_name: data.bgChallenge.globalName,
494+
client_experiments_state_blob: data.bgChallenge.clientExperimentsStateBlob
495+
};
496+
}
497+
498+
if (data.challenge) {
499+
parsed_data.challenge = data.challenge;
500+
}
482501

483502
if (data.playerResponse) {
484503
parsed_data.player_response = parseResponse(data.playerResponse);

src/parser/types/ParsedResponse.ts

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ import type OpenPopupAction from '../classes/actions/OpenPopupAction.js';
2525

2626
export interface IParsedResponse {
2727
background?: MusicThumbnail;
28+
challenge?: string;
29+
bg_challenge?: IBotguardChallenge;
2830
actions?: SuperParsedResult<YTNode>;
2931
actions_memo?: Memo;
3032
contents?: SuperParsedResult<YTNode>;
@@ -78,6 +80,19 @@ export interface IParsedResponse {
7880
watch_next_response?: INextResponse;
7981
}
8082

83+
export interface ITrustedResource {
84+
private_do_not_access_or_else_trusted_resource_url_wrapped_value?: string;
85+
private_do_not_access_or_else_safe_script_wrapped_value?: string;
86+
}
87+
88+
export interface IBotguardChallenge {
89+
interpreter_url: ITrustedResource;
90+
interpreter_hash: string;
91+
program: string;
92+
global_name: string;
93+
client_experiments_state_blob: string;
94+
}
95+
8196
export interface IPlaybackTracking {
8297
videostats_watchtime_url: string;
8398
videostats_playback_url: string;
@@ -121,11 +136,12 @@ export interface IStreamingData {
121136
}
122137

123138
export type IPlayerResponse = Pick<IParsedResponse, 'captions' | 'cards' | 'endscreen' | 'microformat' | 'annotations' | 'playability_status' | 'streaming_data' | 'player_config' | 'playback_tracking' | 'storyboards' | 'video_details'>;
124-
export type INextResponse = Pick<IParsedResponse, 'contents' | 'contents_memo' | 'continuation_contents' | 'continuation_contents_memo' | 'current_video_endpoint' | 'on_response_received_endpoints' | 'on_response_received_endpoints_memo' | 'player_overlays' | 'engagement_panels'>
125-
export type IBrowseResponse = Pick<IParsedResponse, 'background' | 'continuation_contents' | 'continuation_contents_memo' | 'on_response_received_actions' | 'on_response_received_actions_memo' | 'on_response_received_endpoints' | 'on_response_received_endpoints_memo' | 'contents' | 'contents_memo' | 'header' | 'header_memo' | 'metadata' | 'microformat' | 'alerts' | 'sidebar' | 'sidebar_memo'>
139+
export type INextResponse = Pick<IParsedResponse, 'contents' | 'contents_memo' | 'continuation_contents' | 'continuation_contents_memo' | 'current_video_endpoint' | 'on_response_received_endpoints' | 'on_response_received_endpoints_memo' | 'player_overlays' | 'engagement_panels'>;
140+
export type IBrowseResponse = Pick<IParsedResponse, 'background' | 'continuation_contents' | 'continuation_contents_memo' | 'on_response_received_actions' | 'on_response_received_actions_memo' | 'on_response_received_endpoints' | 'on_response_received_endpoints_memo' | 'contents' | 'contents_memo' | 'header' | 'header_memo' | 'metadata' | 'microformat' | 'alerts' | 'sidebar' | 'sidebar_memo'>;
126141
export type ISearchResponse = Pick<IParsedResponse, 'header' | 'header_memo' | 'contents' | 'contents_memo' | 'on_response_received_commands' | 'continuation_contents' | 'continuation_contents_memo' | 'refinements' | 'estimated_results'>;
127142
export type IResolveURLResponse = Pick<IParsedResponse, 'endpoint'>;
128-
export type IGetTranscriptResponse = Pick<IParsedResponse, 'actions' | 'actions_memo'>
129-
export type IGetNotificationsMenuResponse = Pick<IParsedResponse, 'actions' | 'actions_memo'>
130-
export type IUpdatedMetadataResponse = Pick<IParsedResponse, 'actions' | 'actions_memo' | 'continuation'>
131-
export type IGuideResponse = Pick<IParsedResponse, 'items' | 'items_memo'>
143+
export type IGetTranscriptResponse = Pick<IParsedResponse, 'actions' | 'actions_memo'>;
144+
export type IGetNotificationsMenuResponse = Pick<IParsedResponse, 'actions' | 'actions_memo'>;
145+
export type IUpdatedMetadataResponse = Pick<IParsedResponse, 'actions' | 'actions_memo' | 'continuation'>;
146+
export type IGuideResponse = Pick<IParsedResponse, 'items' | 'items_memo'>;
147+
export type IGetChallengeResponse = Pick<IParsedResponse, 'challenge' | 'bg_challenge'>;

src/parser/types/RawResponse.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,24 @@ export interface IRawPlayerConfig {
3737
};
3838
}
3939

40+
export interface IRawTrustedResource {
41+
privateDoNotAccessOrElseTrustedResourceUrlWrappedValue?: string;
42+
privateDoNotAccessOrElseSafeScriptWrappedValue?: string;
43+
}
44+
45+
export interface IRawBotguardChallenge {
46+
interpreterUrl: IRawTrustedResource;
47+
interpreterHash: string;
48+
program: string;
49+
globalName: string;
50+
clientExperimentsStateBlob: string;
51+
}
52+
4053
export interface IRawResponse {
4154
responseContext?: IResponseContext;
4255
background?: RawNode;
56+
bgChallenge?: IRawBotguardChallenge;
57+
challenge?: string;
4358
contents?: RawData;
4459
onResponseReceivedActions?: RawNode[];
4560
onResponseReceivedEndpoints?: RawNode[];

src/types/Misc.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import type { SessionOptions } from '../core/index.js';
22

33
export type InnerTubeConfig = SessionOptions;
44
export type InnerTubeClient = 'IOS' | 'WEB' | 'MWEB' | 'ANDROID' | 'YTMUSIC' | 'YTMUSIC_ANDROID' | 'YTSTUDIO_ANDROID' | 'TV' | 'TV_EMBEDDED' | 'YTKIDS' | 'WEB_EMBEDDED' | 'WEB_CREATOR';
5+
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';
56

67
export type UploadDate = 'all' | 'hour' | 'today' | 'week' | 'month' | 'year';
78
export type SearchType = 'all' | 'video' | 'channel' | 'playlist' | 'movie';

0 commit comments

Comments
 (0)