mirror of
https://github.com/LuanRT/YouTube.js.git
synced 2026-06-26 16:18:51 +00:00
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
This commit is contained in:
@@ -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 type {
|
||||
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<Comments> {
|
||||
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<string, any>[]) {
|
||||
const payload: Record<string, any> = {
|
||||
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}.
|
||||
*/
|
||||
|
||||
@@ -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<T> =
|
||||
@@ -38,7 +40,8 @@ export type ParsedResponse<T> =
|
||||
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;
|
||||
|
||||
|
||||
@@ -479,6 +479,25 @@ export function parseResponse<T extends IParsedResponse = IParsedResponse>(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);
|
||||
|
||||
@@ -25,6 +25,8 @@ import type OpenPopupAction from '../classes/actions/OpenPopupAction.js';
|
||||
|
||||
export interface IParsedResponse {
|
||||
background?: MusicThumbnail;
|
||||
challenge?: string;
|
||||
bg_challenge?: IBotguardChallenge;
|
||||
actions?: SuperParsedResult<YTNode>;
|
||||
actions_memo?: Memo;
|
||||
contents?: SuperParsedResult<YTNode>;
|
||||
@@ -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<IParsedResponse, 'captions' | 'cards' | 'endscreen' | 'microformat' | 'annotations' | 'playability_status' | 'streaming_data' | 'player_config' | 'playback_tracking' | 'storyboards' | 'video_details'>;
|
||||
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'>
|
||||
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'>
|
||||
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'>;
|
||||
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'>;
|
||||
export type ISearchResponse = Pick<IParsedResponse, 'header' | 'header_memo' | 'contents' | 'contents_memo' | 'on_response_received_commands' | 'continuation_contents' | 'continuation_contents_memo' | 'refinements' | 'estimated_results'>;
|
||||
export type IResolveURLResponse = Pick<IParsedResponse, 'endpoint'>;
|
||||
export type IGetTranscriptResponse = Pick<IParsedResponse, 'actions' | 'actions_memo'>
|
||||
export type IGetNotificationsMenuResponse = Pick<IParsedResponse, 'actions' | 'actions_memo'>
|
||||
export type IUpdatedMetadataResponse = Pick<IParsedResponse, 'actions' | 'actions_memo' | 'continuation'>
|
||||
export type IGuideResponse = Pick<IParsedResponse, 'items' | 'items_memo'>
|
||||
export type IGetTranscriptResponse = Pick<IParsedResponse, 'actions' | 'actions_memo'>;
|
||||
export type IGetNotificationsMenuResponse = Pick<IParsedResponse, 'actions' | 'actions_memo'>;
|
||||
export type IUpdatedMetadataResponse = Pick<IParsedResponse, 'actions' | 'actions_memo' | 'continuation'>;
|
||||
export type IGuideResponse = Pick<IParsedResponse, 'items' | 'items_memo'>;
|
||||
export type IGetChallengeResponse = Pick<IParsedResponse, 'challenge' | 'bg_challenge'>;
|
||||
@@ -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[];
|
||||
|
||||
@@ -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';
|
||||
|
||||
Reference in New Issue
Block a user