From e6d2556e03eff802d1f30ee6e9fe40f1e4c0b3a9 Mon Sep 17 00:00:00 2001 From: Luan Date: Sat, 11 Jan 2025 19:25:39 -0300 Subject: [PATCH 1/2] feat(Innertube): Add `getAttestationChallenge` YouTube has recently started using this InnerTube endpoint to fetch challenges for WebPo generation. --- src/Innertube.ts | 25 +++++++++++++++++++++++-- src/core/Actions.ts | 7 +++++-- src/parser/parser.ts | 19 +++++++++++++++++++ src/parser/types/ParsedResponse.ts | 28 ++++++++++++++++++++++------ src/parser/types/RawResponse.ts | 15 +++++++++++++++ src/types/Misc.ts | 1 + 6 files changed, 85 insertions(+), 10 deletions(-) diff --git a/src/Innertube.ts b/src/Innertube.ts index 83ae3ab911..cd199d8dce 100644 --- a/src/Innertube.ts +++ b/src/Innertube.ts @@ -25,7 +25,14 @@ import * as Constants from './utils/Constants.js'; import { generateRandomString, InnertubeError, throwIfMissing, u8ToBase64 } from './utils/Utils.js'; import type { ApiResponse } from './core/Actions.js'; -import type { DownloadOptions, FormatOptions, InnerTubeClient, InnerTubeConfig, SearchFilters } from './types/index.js'; +import { + DownloadOptions, + EngagementType, + FormatOptions, + InnerTubeClient, + InnerTubeConfig, + SearchFilters +} from './types/index.js'; import type { IBrowseResponse, IParsedResponse } from './parser/index.js'; import type Format from './parser/classes/misc/Format.js'; @@ -480,7 +487,7 @@ export default class Innertube { } /** - * Get comments for a community post. + * Gets the comments of a post. */ async getPostComments(post_id: string, channel_id: string, sort_by?: 'TOP_COMMENTS' | 'NEWEST_FIRST'): Promise { throwIfMissing({ post_id, channel_id }); @@ -531,6 +538,20 @@ export default class Innertube { return new Comments(this.actions, response.data); } + /** + * Fetches an attestation challenge. + */ + async getAttestationChallenge(engagement_type: EngagementType, ids?: Record[]) { + const payload: Record = { + engagementType: engagement_type, + }; + + if (ids) + payload.ids = ids; + + return this.actions.execute('/att/get', { parse: true, ...payload }); + } + /** * Utility method to call an endpoint without having to use {@link Actions}. */ diff --git a/src/core/Actions.ts b/src/core/Actions.ts index 3fd90320e7..0af24c9854 100644 --- a/src/core/Actions.ts +++ b/src/core/Actions.ts @@ -1,5 +1,6 @@ import type { IBrowseResponse, + IGetChallengeResponse, IGetNotificationsMenuResponse, INextResponse, IParsedResponse, @@ -28,6 +29,7 @@ export type InnertubeEndpoint = | '/reel' | '/updated_metadata' | '/notification/get_notification_menu' + | '/att/get' | string; export type ParsedResponse = @@ -38,7 +40,8 @@ export type ParsedResponse = T extends '/updated_metadata' ? IUpdatedMetadataResponse : T extends '/navigation/resolve_url' ? IResolveURLResponse : T extends '/notification/get_notification_menu' ? IGetNotificationsMenuResponse : - IParsedResponse; + T extends '/att/get' ? IGetChallengeResponse : + IParsedResponse; export default class Actions { public session: Session; @@ -117,7 +120,7 @@ export default class Actions { if (this.#needsLogin(data.browseId) && !this.session.logged_in) throw new InnertubeError('You must be signed in to perform this operation.'); } - + if (Reflect.has(data, 'skip_auth_check')) delete data.skip_auth_check; diff --git a/src/parser/parser.ts b/src/parser/parser.ts index a1cb864041..d1c6e6183e 100644 --- a/src/parser/parser.ts +++ b/src/parser/parser.ts @@ -479,6 +479,25 @@ export function parseResponse(data: if (engagement_panels.length) { parsed_data.engagement_panels = engagement_panels; } + + if (data.bgChallenge) { + const interpreter_url = { + private_do_not_access_or_else_trusted_resource_url_wrapped_value: data.bgChallenge.interpreterUrl.privateDoNotAccessOrElseTrustedResourceUrlWrappedValue, + private_do_not_access_or_else_safe_script_wrapped_value: data.bgChallenge.interpreterUrl.privateDoNotAccessOrElseSafeScriptWrappedValue + }; + + parsed_data.bg_challenge = { + interpreter_url, + interpreter_hash: data.bgChallenge.interpreterHash, + program: data.bgChallenge.program, + global_name: data.bgChallenge.globalName, + client_experiments_state_blob: data.bgChallenge.clientExperimentsStateBlob + }; + } + + if (data.challenge) { + parsed_data.challenge = data.challenge; + } if (data.playerResponse) { parsed_data.player_response = parseResponse(data.playerResponse); diff --git a/src/parser/types/ParsedResponse.ts b/src/parser/types/ParsedResponse.ts index f8b9824736..22a78f2460 100644 --- a/src/parser/types/ParsedResponse.ts +++ b/src/parser/types/ParsedResponse.ts @@ -25,6 +25,8 @@ import type OpenPopupAction from '../classes/actions/OpenPopupAction.js'; export interface IParsedResponse { background?: MusicThumbnail; + challenge?: string; + bg_challenge?: IBotguardChallenge; actions?: SuperParsedResult; actions_memo?: Memo; contents?: SuperParsedResult; @@ -78,6 +80,19 @@ export interface IParsedResponse { watch_next_response?: INextResponse; } +export interface ITrustedResource { + private_do_not_access_or_else_trusted_resource_url_wrapped_value?: string; + private_do_not_access_or_else_safe_script_wrapped_value?: string; +} + +export interface IBotguardChallenge { + interpreter_url: ITrustedResource; + interpreter_hash: string; + program: string; + global_name: string; + client_experiments_state_blob: string; +} + export interface IPlaybackTracking { videostats_watchtime_url: string; videostats_playback_url: string; @@ -121,11 +136,12 @@ export interface IStreamingData { } export type IPlayerResponse = Pick; -export type INextResponse = Pick -export type IBrowseResponse = Pick +export type INextResponse = Pick; +export type IBrowseResponse = Pick; export type ISearchResponse = Pick; export type IResolveURLResponse = Pick; -export type IGetTranscriptResponse = Pick -export type IGetNotificationsMenuResponse = Pick -export type IUpdatedMetadataResponse = Pick -export type IGuideResponse = Pick +export type IGetTranscriptResponse = Pick; +export type IGetNotificationsMenuResponse = Pick; +export type IUpdatedMetadataResponse = Pick; +export type IGuideResponse = Pick; +export type IGetChallengeResponse = Pick; \ No newline at end of file diff --git a/src/parser/types/RawResponse.ts b/src/parser/types/RawResponse.ts index c48406dc20..75a3cf986c 100644 --- a/src/parser/types/RawResponse.ts +++ b/src/parser/types/RawResponse.ts @@ -37,9 +37,24 @@ export interface IRawPlayerConfig { }; } +export interface IRawTrustedResource { + privateDoNotAccessOrElseTrustedResourceUrlWrappedValue?: string; + privateDoNotAccessOrElseSafeScriptWrappedValue?: string; +} + +export interface IRawBotguardChallenge { + interpreterUrl: IRawTrustedResource; + interpreterHash: string; + program: string; + globalName: string; + clientExperimentsStateBlob: string; +} + export interface IRawResponse { responseContext?: IResponseContext; background?: RawNode; + bgChallenge?: IRawBotguardChallenge; + challenge?: string; contents?: RawData; onResponseReceivedActions?: RawNode[]; onResponseReceivedEndpoints?: RawNode[]; diff --git a/src/types/Misc.ts b/src/types/Misc.ts index f6943b39d4..877fa130ed 100644 --- a/src/types/Misc.ts +++ b/src/types/Misc.ts @@ -2,6 +2,7 @@ import type { SessionOptions } from '../core/index.js'; export type InnerTubeConfig = SessionOptions; export type InnerTubeClient = 'IOS' | 'WEB' | 'MWEB' | 'ANDROID' | 'YTMUSIC' | 'YTMUSIC_ANDROID' | 'YTSTUDIO_ANDROID' | 'TV' | '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'; From cfee460cc4118b3725b017da0510fd1340546f20 Mon Sep 17 00:00:00 2001 From: Luan Date: Sat, 11 Jan 2025 19:39:18 -0300 Subject: [PATCH 2/2] chore: lint --- src/Innertube.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Innertube.ts b/src/Innertube.ts index cd199d8dce..4790b9b076 100644 --- a/src/Innertube.ts +++ b/src/Innertube.ts @@ -25,7 +25,7 @@ import * as Constants from './utils/Constants.js'; import { generateRandomString, InnertubeError, throwIfMissing, u8ToBase64 } from './utils/Utils.js'; import type { ApiResponse } from './core/Actions.js'; -import { +import type { DownloadOptions, EngagementType, FormatOptions, @@ -543,13 +543,13 @@ export default class Innertube { */ async getAttestationChallenge(engagement_type: EngagementType, ids?: Record[]) { const payload: Record = { - engagementType: engagement_type, + engagementType: engagement_type }; if (ids) payload.ids = ids; - return this.actions.execute('/att/get', { parse: true, ...payload }); + return this.actions.execute('/att/get', { parse: true, ...payload }); } /**