Skip to content
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
25 changes: 23 additions & 2 deletions src/Innertube.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -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 });
Expand Down Expand Up @@ -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}.
*/
Expand Down
7 changes: 5 additions & 2 deletions src/core/Actions.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type {
IBrowseResponse,
IGetChallengeResponse,
IGetNotificationsMenuResponse,
INextResponse,
IParsedResponse,
Expand Down Expand Up @@ -28,6 +29,7 @@ export type InnertubeEndpoint =
| '/reel'
| '/updated_metadata'
| '/notification/get_notification_menu'
| '/att/get'
| string;

export type ParsedResponse<T> =
Expand All @@ -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;
Expand Down Expand Up @@ -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;

Expand Down
19 changes: 19 additions & 0 deletions src/parser/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
28 changes: 22 additions & 6 deletions src/parser/types/ParsedResponse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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>;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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'>;
15 changes: 15 additions & 0 deletions src/parser/types/RawResponse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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[];
Expand Down
1 change: 1 addition & 0 deletions src/types/Misc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
Loading