Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

youtubei.js

Package Overview
Dependencies
Maintainers
1
Versions
124
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

youtubei.js - npm Package Compare versions

Comparing version 1.3.6 to 1.3.7-md

289

lib/Actions.js

@@ -11,6 +11,6 @@ 'use strict';

*
* @param {Innertube} session A valid Innertube session.
* @param {string} engagement_type Type of engagement.
* @param {object} args Engagement arguments.
* @returns {Promise.<object>} { success: boolean, status_code: number } | { success: boolean, status_code: number, message: string }
* @param {Innertube} session - A valid Innertube session.
* @param {string} engagement_type - Type of engagement.
* @param {object} args - Engagement arguments.
* @returns {Promise.<{ success: boolean; status_code: number; data: object; message?: string }>}
*/

@@ -20,4 +20,3 @@ async function engage(session, engagement_type, args = {}) {

let data;
const data = { context: session.context };
switch (engagement_type) {

@@ -27,30 +26,19 @@ case 'like/like':

case 'like/removelike':
data = {
context: session.context,
target: {
videoId: args.video_id
}
};
data.target = {
videoId: args.video_id
}
break;
case 'subscription/subscribe':
case 'subscription/unsubscribe':
data = {
context: session.context,
channelIds: [args.channel_id],
params: engagement_type == 'subscription/subscribe' ? 'EgIIAhgA' : 'CgIIAhgA'
};
data.channelIds = [args.channel_id];
data.params = engagement_type == 'subscription/subscribe' ? 'EgIIAhgA' : 'CgIIAhgA';
break;
case 'comment/create_comment':
data = {
context: session.context,
commentText: args.text,
createCommentParams: Utils.encodeCommentParams(args.video_id)
};
data.commentText = args.text;
data.createCommentParams = Utils.encodeCommentParams(args.video_id);
break;
default:
}
const response = await Axios.post(`${Constants.URLS.YT_BASE_URL}/youtubei/v1/${engagement_type}${session.logged_in && session.cookie.length < 1 ? '' : `?key=${session.key}`}`,
JSON.stringify(data), Constants.INNERTUBE_REQOPTS({ session, id: args.video_id, data })).catch((error) => error);
const response = await session.YTRequester.post(`/${engagement_type}`, JSON.stringify(data));
if (response instanceof Error) return { success: false, status_code: response.response.status, message: response.message };

@@ -70,40 +58,24 @@

* @param {object} args - Action argumenets.
* @returns {Promise.<object>} { success: boolean, status_code: number, data: object } | { success: boolean, status_code: number, message: string }
* @returns {Promise.<{ success: boolean; status_code: number; data: object; message?: string }>}
*/
async function browse(session, action_type, args = {}) {
if (!session.logged_in && (action_type != 'lyrics' || action_type != 'music_playlist'))
if (!session.logged_in && action_type !== 'lyrics' && action_type !== 'music_playlist')
throw new Error('You are not signed-in');
let data;
const data = { context: session.context };
switch (action_type) {
case 'account_notifications':
data = {
context: session.context,
browseId: 'SPaccount_notifications'
};
data.browseId = 'SPaccount_notifications';
break;
case 'account_privacy':
data = {
context: session.context,
browseId: 'SPaccount_privacy'
};
data.browseId = 'SPaccount_privacy';
break;
case 'history':
data = {
context: session.context,
browseId: 'FEhistory'
}
data.browseId = 'FEhistory';
break;
case 'home_feed':
data = {
context: session.context,
browseId: 'FEwhat_to_watch'
};
data.browseId = 'FEwhat_to_watch';
break;
case 'subscriptions_feed':
data = {
context: session.context,
browseId: 'FEsubscriptions'
};
data.browseId = 'FEsubscriptions';
break;

@@ -114,24 +86,17 @@ case 'lyrics':

context.client.originalUrl = Constants.URLS.YT_MUSIC_URL;
context.client.originalUrl = Constants.URLS.YT_MUSIC;
context.client.clientVersion = Constants.YTMUSIC_VERSION;
context.client.clientName = 'WEB_REMIX';
data = {
context,
browseId: args.browse_id
}
data.context = context;
data.browseId = args.browse_id;
break;
case 'playlist':
data = {
context: session.context,
browseId: args.browse_id
}
data.browseId = args.browse_id;
break;
default:
}
const client_domain = args.ytmusic && Constants.URLS.YT_MUSIC_URL || Constants.URLS.YT_BASE_URL;
const response = await Axios.post(`${client_domain}/youtubei/v1/browse${session.logged_in && session.cookie.length < 1 ? '' : `?key=${session.key}`}`,
JSON.stringify(data), Constants.INNERTUBE_REQOPTS({ session, ytmusic: args.ytmusic })).catch((error) => error);
const requester = args.ytmusic && session.YTMRequester || session.YTRequester;
const response = await requester.post('/browse', JSON.stringify(data));
if (response instanceof Error) return { success: false, status_code: response.response.status, message: response.message };

@@ -153,3 +118,3 @@

* @param {object} args - Action argumenets.
* @returns {Promise.<object>} { success: boolean, status_code: number, data: object } | { success: boolean, status_code: number, message: string }
* @returns {Promise.<{ success: boolean; status_code: number; data: object; message?: string }>}
*/

@@ -159,16 +124,11 @@ async function account(session, action_type, args = {}) {

let data;
const data = {};
switch (action_type) {
case 'account/account_menu':
data = { context: session.context };
data.context = session.context;
break;
case 'account/set_setting':
data = {
context: session.context,
newValue: {
boolValue: args.new_value
},
settingItemId: args.setting_item_id
}
data.context = session.context;
data.newValue = { boolValue: args.new_value };
data.settingItemId = arts.setting_item_id;
break;

@@ -178,6 +138,4 @@ default:

}
const response = await Axios.post(`${Constants.URLS.YT_BASE_URL}/youtubei/v1/${action_type}${session.logged_in && session.cookie.length < 1 ? '' : `?key=${session.key}`}`,
JSON.stringify(data), Constants.INNERTUBE_REQOPTS({ session })).catch((error) => error);
const response = await session.YTRequester.post(`/${action_type}`, JSON.stringify(data));
if (response instanceof Error) return { success: false, status_code: response.response.status, message: response.message };

@@ -193,3 +151,3 @@

/**
* Accesses YouTube Music endpoints under /youtubei/v1/music/.
* Accesses YouTube Music endpoints (/youtubei/v1/music/).
*

@@ -200,3 +158,3 @@ * @param {Innertube} session - A valid Innertube session.

* @todo Implement more actions.
* @returns
* @returns {Promise.<{ success: boolean; status_code: number; data: object; message?: string }>}
*/

@@ -206,3 +164,3 @@ async function music(session, action_type, args) {

context.client.originalUrl = Constants.URLS.YT_MUSIC_URL;
context.client.originalUrl = Constants.URLS.YT_MUSIC;
context.client.clientVersion = Constants.YTMUSIC_VERSION;

@@ -215,6 +173,4 @@ context.client.clientName = 'WEB_REMIX';

case 'get_search_suggestions':
data = {
context,
input: args.input || ''
};
data.context = context;
data.input = args.input || '';
break;

@@ -224,6 +180,4 @@ default:

}
const response = await Axios.post(`${Constants.URLS.YT_MUSIC_URL}/youtubei/v1/music/${action_type}${session.logged_in && session.cookie.length < 1 ? '' : `?key=${session.key}`}`,
JSON.stringify(data), Constants.INNERTUBE_REQOPTS({ session, ytmusic: true })).catch((error) => error);
const response = await session.YTMRequester.post(`/music/${action_type}`, JSON.stringify(data));
if (response instanceof Error) return { success: false, status_code: response.response.status, message: response.message };

@@ -239,3 +193,3 @@

/**
* Performs searches on YouTube.
* Searches a given query on YouTube/YTMusic.
*

@@ -245,3 +199,3 @@ * @param {Innertube} session - A valid Innertube session.

* @param {object} args - Search arguments.
* @returns {Promise.<object>} - { success: boolean, status_code: number, data: object } | { success: boolean, status_code: number, message: string }
* @returns {Promise.<{ success: boolean; status_code: number; data: object; message?: string }>}
*/

@@ -251,11 +205,7 @@ async function search(session, client, args = {}) {

let data;
const data = { context: session.context };
switch (client) {
case 'YOUTUBE':
data = {
context: session.context,
params: Utils.encodeFilter(args.options.period, args.options.duration, args.options.order),
query: args.query
};
data.params = Utils.encodeFilter(args.options.period, args.options.duration, args.options.order);
data.query = args.query;
break;

@@ -265,10 +215,8 @@ case 'YTMUSIC':

context.client.originalUrl = Constants.URLS.YT_MUSIC_URL;
context.client.originalUrl = Constants.URLS.YT_MUSIC;
context.client.clientVersion = Constants.YTMUSIC_VERSION;
context.client.clientName = 'WEB_REMIX';
data = {
context: context,
query: args.query
};
data.context = context;
data.query = args.query;
break;

@@ -278,6 +226,5 @@ default:

}
const response = await Axios.post(`${client === 'YOUTUBE' && Constants.URLS.YT_BASE_URL || Constants.URLS.YT_MUSIC_URL}/youtubei/v1/search${session.logged_in && session.cookie.length < 1 ? '' : `?key=${session.key}`}`,
JSON.stringify(data), Constants.INNERTUBE_REQOPTS({ session, ytmusic: client === 'YTMUSIC' })).catch((error) => error);
const requester = client == 'YOUTUBE' && session.YTRequester || session.YTMRequester;
const response = await requester.post('/search', JSON.stringify(data));
if (response instanceof Error) return { success: false, status_code: response.response.status, message: response.message };

@@ -298,3 +245,3 @@

* @param {object} args - Action arguments.
* @returns {Promise.<object>} - { success: boolean, status_code: number, data: object } | { success: boolean, status_code: number, message: string }
* @returns {Promise.<{ success: boolean; status_code: number; data: object; message?: string }>}
*/

@@ -304,29 +251,20 @@ async function notifications(session, action_type, args = {}) {

let data;
const data = {};
switch (action_type) {
case 'modify_channel_preference':
let pref_types = { PERSONALIZED: 1, ALL: 2, NONE: 3 };
data = {
context: session.context,
params: Utils.encodeNotificationPref(args.channel_id, pref_types[args.pref.toUpperCase()])
};
const pref_types = { PERSONALIZED: 1, ALL: 2, NONE: 3 };
data.context = session.context;
data.params = Utils.encodeNotificationPref(args.channel_id, pref_types[args.pref.toUpperCase()]);
break;
case 'get_notification_menu':
data = {
context: session.context,
notificationsMenuRequestType: 'NOTIFICATIONS_MENU_REQUEST_TYPE_INBOX'
};
data.context = session.context;
data.notificationsMenuRequestType = 'NOTIFICATIONS_MENU_REQUEST_TYPE_INBOX';
break;
case 'get_unseen_count':
data = {
context: session.context
};
data.context = session.context;
break;
default:
}
const response = await Axios.post(`${Constants.URLS.YT_BASE_URL}/youtubei/v1/notification/${action_type}${session.logged_in && session.cookie.length < 1 ? '' : `?key=${session.key}`}`,
JSON.stringify(data), Constants.INNERTUBE_REQOPTS({ session })).catch((error) => error);
const response = await session.YTRequester.post(`/notification/${action_type}`, JSON.stringify(data)).catch((err) => err);
if (response instanceof Error) return { success: false, status_code: response.response.status, message: response.message };

@@ -348,40 +286,29 @@ if (action_type === 'modify_channel_preference') return { success: true, status_code: response.status };

* @param {object} args - Action arguments.
* @returns {Promise.<object>} - { success: boolean, status_code: number, data: object } | { success: boolean, status_code: number, message: string }
* @returns {Promise.<{ success: boolean; data: object; message?: string }>}
*/
async function livechat(session, action_type, args = {}) {
let data;
const data = {};
switch (action_type) {
case 'live_chat/get_live_chat':
data = {
context: session.context,
continuation: args.ctoken
};
data.context = session.context;
data.continuation = args.ctoken;
break;
case 'live_chat/send_message':
data = {
context: session.context,
params: Utils.encodeMessageParams(args.channel_id, args.video_id),
clientMessageId: `ytjs-${Uuid.v4()}`,
richMessage: {
textSegments: [{ text: args.text }]
}
};
data.context = session.context;
data.params = Utils.encodeMessageParams(args.channel_id, args.video_id);
data.clientMessageId = `ytjs-${Uuid.v4()}`;
data.richMessage = {
textSegments: [{ text: args.text }]
}
break;
case 'live_chat/get_item_context_menu':
data = {
context: session.context
};
data.context = session.context;
break;
case 'live_chat/moderate':
data = {
context: session.context,
params: args.cmd_params
};
data.context = session.context;
data.params = args.cmd_params;
break;
case 'updated_metadata':
data = {
context: session.context,
videoId: args.video_id
};
data.context = session.context;
data.videoId = args.video_id;
args.continuation && (data.continuation = args.continuation);

@@ -391,8 +318,6 @@ break;

}
const response = await Axios.post(`${Constants.URLS.YT_BASE_URL}/youtubei/v1/${action_type}${session.logged_in && session.cookie.length < 1 ? '' : `?key=${session.key}`}`,
JSON.stringify(data), Constants.INNERTUBE_REQOPTS({ session, params: args.params })).catch((error) => error);
const response = await session.YTRequester.post(`/${action_type}`, JSON.stringify(data)).catch((err) => err);
if (response instanceof Error) return { success: false, message: response.message };
return { success: true, data: response.data };

@@ -402,3 +327,3 @@ }

/**
* Gets detailed data for a video.
* Retrieves video data.
*

@@ -410,11 +335,4 @@ * @param {Innertube} session - A valid Innertube session.

async function getVideoInfo(session, args = {}) {
let response;
!args.desktop &&
(response = await Axios.get(`${Constants.URLS.YT_WATCH_PAGE}?v=${args.id}&t=8s&pbj=1`, Constants.INNERTUBE_REQOPTS({ session, id: args.id, desktop: false })).catch((error) => error)) ||
(response = await Axios.post(`${Constants.URLS.YT_BASE_URL}/youtubei/v1/player${session.logged_in && session.cookie.length < 1 ? '' : `?key=${session.key}`}`,
JSON.stringify(Constants.VIDEO_INFO_REQBODY(args.id, session.sts, session.context)), Constants.INNERTUBE_REQOPTS({ session, id: args.id, desktop: true })).catch((error) => error));
const response = await session.YTRequester.post(`/player`, JSON.stringify(Constants.VIDEO_INFO_REQBODY(args.id, session.sts, session.context))).catch((err) => err);
if (response instanceof Error) throw new Error(`Could not get video info: ${response.message}`);
return response.data;

@@ -428,3 +346,3 @@ }

* @param {object} args - Continuation arguments.
* @returns {Promise.<object>} - { success: boolean, status_code: number, data: object } | { success: boolean, status_code: number, message: string }
* @returns {Promise.<{ success: boolean; status_code: number; data: object; message?: string }>}
*/

@@ -440,3 +358,3 @@ async function getContinuation(session, args = {}) {

context.client.originalUrl = Constants.URLS.YT_MUSIC_URL;
context.client.originalUrl = Constants.URLS.YT_MUSIC;
context.client.clientVersion = Constants.YTMUSIC_VERSION;

@@ -459,9 +377,12 @@ context.client.clientName = 'WEB_REMIX';

}
const requester = args.ytmusic && session.YTMRequester || session.YTRequester;
const response = await requester.post('/next', JSON.stringify(data));
if (response instanceof Error) return {
success: false,
status_code: response.response.status,
message: response.message
};
const client_domain = args.ytmusic && Constants.URLS.YT_MUSIC_URL || Constants.URLS.YT_BASE_URL;
const response = await Axios.post(`${client_domain}/youtubei/v1/next${session.logged_in && session.cookie.length < 1 ? '' : `?key=${session.key}`}`,
JSON.stringify(data), Constants.INNERTUBE_REQOPTS({ session, ytmusic: args.ytmusic })).catch((error) => error);
if (response instanceof Error) return { success: false, status_code: response.response.status, message: response.message };
return {

@@ -477,5 +398,5 @@ success: true,

*
* @param {Innertube} session
* @param {string} query
* @returns
* @param {Innertube} session - A valid innertube session
* @param {string} query - Search query
* @returns {Promise.<{ success: boolean; status_code: number; data: object; message?: string }>}
*/

@@ -486,3 +407,7 @@ async function getYTSearchSuggestions(session, query) {

if (response instanceof Error) return { success: false, status_code: response.response.status, message: response.message };
if (response instanceof Error) return {
success: false,
status_code: response.response.status,
message: response.message
};

@@ -496,2 +421,2 @@ return {

module.exports = { engage, browse, account, music, search, notifications, livechat, getVideoInfo, getContinuation, getYTSearchSuggestions };
module.exports = { engage, browse, account, music, search, notifications, livechat, getVideoInfo, getContinuation, getYTSearchSuggestions };

@@ -7,7 +7,7 @@ 'use strict';

URLS: {
YT_BASE_URL: 'https://www.youtube.com',
YT_MUSIC_URL: 'https://music.youtube.com',
YT_MOBILE_URL: 'https://m.youtube.com',
YT_WATCH_PAGE: 'https://m.youtube.com/watch',
YT_SUGGESTIONS: 'https://suggestqueries.google.com/complete/'
YT_BASE: 'https://www.youtube.com',
YT_BASE_API: 'https://www.youtube.com/youtubei/',
YT_SUGGESTIONS: 'https://suggestqueries.google.com/complete/',
YT_MUSIC: 'https://music.youtube.com',
YT_MUSIC_BASE_API: 'https://music.youtube.com/youtubei/'
},

@@ -37,5 +37,5 @@ OAUTH: {

'Accept-Language': 'en-US,en',
'Accept-Encoding': 'gzip',
'Upgrade-Insecure-Requests': 1
}
'Accept-Encoding': 'gzip'
},
};

@@ -51,32 +51,26 @@ },

},
INNERTUBE_REQOPTS: (info) => {
info.desktop === undefined && (info.desktop = true);
const origin = info.ytmusic && 'https://music.youtube.com' ||
info.desktop && 'https://www.youtube.com' || 'https://m.youtube.com';
INNERTUBE_HEADERS: (info) => {
const origin = info.ytmusic && 'https://music.youtube.com' || 'https://www.youtube.com';
let req_opts = {
params: info.params || {},
headers: {
'accept': '*/*',
'user-agent': Utils.getRandomUserAgent(info.desktop ? 'desktop' : 'mobile').userAgent,
'content-type': 'application/json',
'accept-language': 'en-US,en;q=0.9',
'x-goog-authuser': 0,
'x-goog-visitor-id': info.session.context.client.visitorData || '',
'x-youtube-client-name': info.desktop ? 1 : 2,
'x-youtube-client-version': info.session.context.client.clientVersion,
'x-youtube-chrome-connected': 'source=Chrome,mode=0,enable_account_consistency=true,supervised=false,consistency_enabled_by_default=false',
'x-origin': origin,
'origin': origin,
}
const headers = {
'accept': '*/*',
'user-agent': Utils.getRandomUserAgent('desktop').userAgent,
'content-type': 'application/json',
'accept-language': 'en-US,en;q=0.9',
'x-goog-authuser': 0,
'x-goog-visitor-id': info.session.context.client.visitorData || '',
'x-youtube-client-name': 1,
'x-youtube-client-version': info.session.context.client.clientVersion,
'x-youtube-chrome-connected': 'source=Chrome,mode=0,enable_account_consistency=true,supervised=false,consistency_enabled_by_default=false',
'x-origin': origin,
'origin': origin
};
info.id && (req_opts.headers.referer = (info.desktop ? 'https://www.youtube.com' : 'https://m.youtube.com') + '/watch?v=' + info.id);
if (info.session.logged_in && info.desktop) {
req_opts.headers.Cookie = info.session.cookie;
req_opts.headers.authorization = info.session.cookie.length < 1 ? `Bearer ${info.session.access_token}` : info.session.auth_apisid;
if (info.session.logged_in) {
headers.Cookie = info.session.cookie;
headers.authorization = info.session.cookie.length && info.session.auth_apisid || `Bearer ${info.session.access_token}`;
}
return req_opts;
return headers
},

@@ -104,3 +98,3 @@ VIDEO_INFO_REQBODY: (id, sts, context) => {

METADATA_KEYS: [
'embed', 'view_count', 'average_rating',
'embed', 'view_count', 'average_rating', 'allow_ratings',
'length_seconds', 'channel_id', 'channel_url',

@@ -115,3 +109,3 @@ 'external_channel_id', 'is_live_content', 'is_family_safe',

'is_owner_viewing', 'is_unplugged_corpus',
'is_crawlable', 'allow_ratings', 'author'
'is_crawlable', 'author'
],

@@ -118,0 +112,0 @@ ACCOUNT_SETTINGS: {

@@ -37,3 +37,3 @@ 'use strict';

async #init() {
const response = await Axios.get(Constants.URLS.YT_BASE_URL, Constants.DEFAULT_HEADERS(this)).catch((error) => error);
const response = await Axios.get(Constants.URLS.YT_BASE, Constants.DEFAULT_HEADERS(this)).catch((error) => error);
if (response instanceof Error) throw new Error(`Could not retrieve Innertube session: ${response.message}`);

@@ -44,3 +44,4 @@

if (data.INNERTUBE_CONTEXT) {
this.key = data.INNERTUBE_API_KEY;
this.key = data.INNERTUBE_API_KEY;
this.version = data.INNERTUBE_API_VERSION;
this.context = data.INNERTUBE_CONTEXT;

@@ -56,4 +57,4 @@

/**
* @event auth - Fired when signing in to an account.
* @event update-credentials - Fired when the access token is no longer valid.
* @event Innertube#auth - Fired when signing in to an account.
* @event Innertube#update-credentials - Fired when the access token is no longer valid.
* @type {EventEmitter}

@@ -65,8 +66,23 @@ */

await this.#player.init();
if (this.logged_in && this.cookie.length > 1) {
if (this.logged_in && this.cookie.length) {
this.auth_apisid = Utils.getStringBetweenStrings(this.cookie, 'PAPISID=', ';');
this.auth_apisid = Utils.generateSidAuth(this.auth_apisid);
}
// Axios instances
this.YTRequester = Axios.create({
baseURL: Constants.URLS.YT_BASE_API + this.version,
timeout: 15000,
headers: Constants.INNERTUBE_HEADERS({ session: this, ytmusic: false }),
params: { key: this.key }
});
this.YTMRequester = Axios.create({
baseURL: Constants.URLS.YT_MUSIC_BASE_API + this.version,
timeout: 15000,
headers: Constants.INNERTUBE_HEADERS({ session: this, ytmusic: true }),
params: { key: this.key }
});
this.#initMethods();

@@ -269,4 +285,12 @@ } else {

this.logged_in = true;
// API key is not needed if logged in via OAuth
delete this.YTRequester.defaults.params.key;
delete this.YTMRequester.defaults.params.key;
// Update default headers
this.YTRequester.defaults.headers = Constants.INNERTUBE_HEADERS({ session: this, ytmusic: false });
this.YTMRequester.defaults.headers = Constants.INNERTUBE_HEADERS({ session: this, ytmusic: true });
resolve();
resolve();
} else {

@@ -279,2 +303,9 @@ oauth.on('auth', (data) => {

this.logged_in = true;
delete this.YTRequester.defaults.params.key;
delete this.YTMRequester.defaults.params.key;
this.YTRequester.defaults.headers = Constants.INNERTUBE_HEADERS({ session: this, ytmusic: false });
this.YTMRequester.defaults.headers = Constants.INNERTUBE_HEADERS({ session: this, ytmusic: true });
resolve();

@@ -382,26 +413,30 @@ } else {

const data = await Actions.getVideoInfo(this, { id: video_id, is_desktop: false });
const refined_data = new Parser(this, data, { client: 'YOUTUBE', data_type: 'VIDEO_INFO', desktop_v: false }).parse();
const data = await Actions.getVideoInfo(this, { id: video_id });
const continuation = await Actions.getContinuation(this, { video_id });
data.continuation = continuation.data;
const details = new Parser(this, data, { client: 'YOUTUBE', data_type: 'VIDEO_INFO' }).parse();
if (refined_data.metadata.is_live_content) {
if (details.metadata.is_live_content) {
const data_continuation = await Actions.getContinuation(this, { video_id });
if (data_continuation.data.contents.twoColumnWatchNextResults.conversationBar) {
refined_data.getLivechat = () => new Livechat(this, data_continuation.data.contents.twoColumnWatchNextResults.conversationBar.liveChatRenderer.continuations[0].reloadContinuationData.continuation, refined_data.metadata.channel_id, video_id);
details.getLivechat = () => new Livechat(this, data_continuation.data.contents.twoColumnWatchNextResults.conversationBar.liveChatRenderer.continuations[0].reloadContinuationData.continuation, details.metadata.channel_id, video_id);
} else {
refined_data.getLivechat = () => { };
details.getLivechat = () => { };
}
} else {
refined_data.getLivechat = () => { };
details.getLivechat = () => { };
}
// Functions
details.like = () => Actions.engage(this, 'like/like', { video_id });
details.dislike = () => Actions.engage(this, 'like/dislike', { video_id });
details.removeLike = () => Actions.engage(this, 'like/removelike', { video_id });
details.subscribe = () => Actions.engage(this, 'subscription/subscribe', { channel_id: details.metadata.channel_id });
details.unsubscribe = () => Actions.engage(this, 'subscription/unsubscribe', { channel_id: details.metadata.channel_id });
details.comment = (text) => Actions.engage(this, 'comment/create_comment', { video_id, text });
details.getComments = () => this.getComments(video_id);
details.changeNotificationPreferences = (type) => Actions.notifications(this, 'modify_channel_preference', { channel_id: details.metadata.channel_id, pref: type || 'NONE' });
refined_data.like = () => Actions.engage(this, 'like/like', { video_id });
refined_data.dislike = () => Actions.engage(this, 'like/dislike', { video_id });
refined_data.removeLike = () => Actions.engage(this, 'like/removelike', { video_id });
refined_data.subscribe = () => Actions.engage(this, 'subscription/subscribe', { channel_id: refined_data.metadata.channel_id });
refined_data.unsubscribe = () => Actions.engage(this, 'subscription/unsubscribe', { channel_id: refined_data.metadata.channel_id });
refined_data.comment = (text) => Actions.engage(this, 'comment/create_comment', { video_id, text });
refined_data.getComments = () => this.getComments(video_id);
refined_data.changeNotificationPreferences = (type) => Actions.notifications(this, 'modify_channel_preference', { channel_id: refined_data.metadata.channel_id, pref: type || 'NONE' });
return refined_data;
return details;
}

@@ -518,11 +553,23 @@

title: item.videoRenderer.title.runs.map((run) => run.text).join(' '),
channel: item.videoRenderer.shortBylineText && item.videoRenderer.shortBylineText.runs[0].text || 'N/A',
description: item.videoRenderer.descriptionSnippet && item.videoRenderer.descriptionSnippet.runs[0].text || 'N/A',
channel: {
name: item.videoRenderer.shortBylineText && item.videoRenderer.shortBylineText.runs[0].text || 'N/A',
url: item.videoRenderer.shortBylineText && `${Constants.URLS.YT_BASE}${item.videoRenderer.shortBylineText.runs[0].navigationEndpoint.commandMetadata.webCommandMetadata.url}` || 'N/A',
},
metadata: {
view_count: item.videoRenderer.viewCountText && item.videoRenderer.viewCountText.simpleText || 'N/A',
short_view_count_text: {
simple_text: item.videoRenderer.shortViewCountText && item.videoRenderer.shortViewCountText.simpleText || 'N/A',
accessibility_label: item.videoRenderer.shortViewCountText && (item.videoRenderer.shortViewCountText.accessibility && item.videoRenderer.shortViewCountText.accessibility.accessibilityData.label || 'N/A') || 'N/A',
},
thumbnail: item.videoRenderer.thumbnail && item.videoRenderer.thumbnail.thumbnails.slice(-1)[0] || [],
moving_thumbnail: item.videoRenderer.richThumbnail && item.videoRenderer.richThumbnail.movingThumbnailRenderer.movingThumbnailDetails.thumbnails[0] || [],
published: item.videoRenderer.publishedTimeText && item.videoRenderer.publishedTimeText.simpleText || 'N/A',
duration: item.videoRenderer.lengthText && item.videoRenderer.lengthText.simpleText || 'N/A',
badges: item.videoRenderer.badges && item.videoRenderer.badges.map((badge) => badge.metadataBadgeRenderer.label) || 'N/A',
owner_badges: item.videoRenderer.ownerBadges && item.videoRenderer.ownerBadges.map((badge) => badge.metadataBadgeRenderer.tooltip) || 'N/A'
duration: {
seconds: Utils.timeToSeconds(item.videoRenderer.lengthText && item.videoRenderer.lengthText.simpleText || '0'),
simple_text: item.videoRenderer.lengthText && item.videoRenderer.lengthText.simpleText || 'N/A',
accessibility_label: item.videoRenderer.lengthText && item.videoRenderer.lengthText.accessibility.accessibilityData.label || 'N/A'
},
badges: item.videoRenderer.badges && item.videoRenderer.badges.map((badge) => badge.metadataBadgeRenderer.label) || [],
owner_badges: item.videoRenderer.ownerBadges && item.videoRenderer.ownerBadges.map((badge) => badge.metadataBadgeRenderer.tooltip) || []
}

@@ -538,3 +585,3 @@ };

/**
* Returns YouTube's home feed.
* Returns YouTube's home feed (aka recommendations).
* @returns {Promise.<[{ id: string; title: string; channel: string; metadata: {} }]>}

@@ -547,3 +594,3 @@ */

const contents = response.data.contents.twoColumnBrowseResultsRenderer.tabs[0].tabRenderer.content.richGridRenderer.contents;
return contents.map((item) => {

@@ -556,8 +603,21 @@ const content = item.richItemRenderer && item.richItemRenderer.content.videoRenderer &&

title: content.videoRenderer.title.runs.map((run) => run.text).join(' '),
channel: content.videoRenderer.shortBylineText && content.videoRenderer.shortBylineText.runs[0].text || 'N/A',
description: content.videoRenderer.descriptionSnippet && content.videoRenderer.descriptionSnippet.runs[0].text || 'N/A',
channel: {
name: content.videoRenderer.shortBylineText && content.videoRenderer.shortBylineText.runs[0].text || 'N/A',
url: content.videoRenderer.shortBylineText && `${Constants.URLS.YT_BASE}${content.videoRenderer.shortBylineText.runs[0].navigationEndpoint.commandMetadata.webCommandMetadata.url}` || 'N/A',
},
metadata: {
view_count: content.videoRenderer.viewCountText && content.videoRenderer.viewCountText.simpleText || 'N/A',
thumbnail: content.videoRenderer.thumbnail && content.videoRenderer.thumbnail.thumbnails.slice(-1)[0] || [],
moving_thumbnail: content.videoRenderer.richThumbnail && content.videoRenderer.richThumbnail.movingThumbnailRenderer.movingThumbnailDetails.thumbnails[0] || [],
short_view_count_text: {
simple_text: content.videoRenderer.shortViewCountText && content.videoRenderer.shortViewCountText.simpleText || 'N/A',
accessibility_label: content.videoRenderer.shortViewCountText && (content.videoRenderer.shortViewCountText.accessibility && content.videoRenderer.shortViewCountText.accessibility.accessibilityData.label || 'N/A') || 'N/A',
},
thumbnail: content.videoRenderer.thumbnail && content.videoRenderer.thumbnail.thumbnails.slice(-1)[0] || {},
moving_thumbnail: content.videoRenderer.richThumbnail && content.videoRenderer.richThumbnail.movingThumbnailRenderer.movingThumbnailDetails.thumbnails[0] || {},
published: content.videoRenderer.publishedTimeText && content.videoRenderer.publishedTimeText.simpleText || 'N/A',
duration: {
seconds: Utils.timeToSeconds(content.videoRenderer.lengthText && content.videoRenderer.lengthText.simpleText || '0'),
simple_text: content.videoRenderer.lengthText && content.videoRenderer.lengthText.simpleText || 'N/A',
accessibility_label: content.videoRenderer.lengthText && content.videoRenderer.lengthText.accessibility.accessibilityData.label || 'N/A'
},
badges: content.videoRenderer.badges && content.videoRenderer.badges.map((badge) => badge.metadataBadgeRenderer.label) || [],

@@ -594,9 +654,17 @@ owner_badges: content.videoRenderer.ownerBadges && content.videoRenderer.ownerBadges.map((badge) => badge.metadataBadgeRenderer.tooltip) || []

title: item.gridVideoRenderer.title.runs.map((run) => run.text).join(' '),
channel: item.gridVideoRenderer.shortBylineText && item.gridVideoRenderer.shortBylineText.runs[0].text || 'N/A',
channel: {
name: item.gridVideoRenderer.shortBylineText && item.gridVideoRenderer.shortBylineText.runs[0].text || 'N/A',
url: item.gridVideoRenderer.shortBylineText && `${Constants.URLS.YT_BASE}${item.gridVideoRenderer.shortBylineText.runs[0].navigationEndpoint.commandMetadata.webCommandMetadata.url}` || 'N/A',
},
metadata: {
view_count: item.gridVideoRenderer.viewCountText && item.gridVideoRenderer.viewCountText.simpleText || 'N/A',
short_view_count_text: {
simple_text: item.gridVideoRenderer.shortViewCountText && item.gridVideoRenderer.shortViewCountText.simpleText || 'N/A',
accessibility_label: item.gridVideoRenderer.shortViewCountText && (item.gridVideoRenderer.shortViewCountText.accessibility && item.gridVideoRenderer.shortViewCountText.accessibility.accessibilityData.label || 'N/A') || 'N/A',
},
thumbnail: item.gridVideoRenderer.thumbnail && item.gridVideoRenderer.thumbnail.thumbnails.slice(-1)[0] || [],
moving_thumbnail: item.gridVideoRenderer.richThumbnail && item.gridVideoRenderer.richThumbnail.movingThumbnailRenderer.movingThumbnailDetails.thumbnails[0] || {},
published: item.gridVideoRenderer.publishedTimeText && item.gridVideoRenderer.publishedTimeText.simpleText || 'N/A',
badges: item.gridVideoRenderer.badges && item.gridVideoRenderer.badges.map((badge) => badge.metadataBadgeRenderer.label) || 'N/A',
owner_badges: item.gridVideoRenderer.ownerBadges && item.gridVideoRenderer.ownerBadges.map((badge) => badge.metadataBadgeRenderer.tooltip) || 'N/A'
badges: item.gridVideoRenderer.badges && item.gridVideoRenderer.badges.map((badge) => badge.metadataBadgeRenderer.label) || [],
owner_badges: item.gridVideoRenderer.ownerBadges && item.gridVideoRenderer.ownerBadges.map((badge) => badge.metadataBadgeRenderer.tooltip) || []
}

@@ -733,3 +801,3 @@ };

const data = await Actions.getVideoInfo(this, { id, desktop: true });
const data = await Actions.getVideoInfo(this, { id });
const streaming_data = this.#chooseFormat(options, data);

@@ -762,3 +830,3 @@ if (!streaming_data.selected_format) throw new Error('Could not find any suitable format.');

const stream = new Stream.PassThrough();
Actions.getVideoInfo(this, { id, desktop: true }).then(async (video_data) => {
Actions.getVideoInfo(this, { id }).then(async (video_data) => {
if (video_data.playabilityStatus.status === 'LOGIN_REQUIRED')

@@ -892,2 +960,2 @@ return stream.emit('error', { message: 'You must login to download age-restricted videos.', error_type: 'LOGIN_REQUIRED', playability_status: video_data.playabilityStatus.status });

module.exports = Innertube;
module.exports = Innertube;

@@ -14,6 +14,5 @@ 'use strict';

this.oauth_code_url = `${Constants.URLS.YT_BASE_URL}/o/oauth2/device/code`;
this.oauth_token_url = `${Constants.URLS.YT_BASE_URL}/o/oauth2/token`;
this.guide_url = `${Constants.URLS.YT_BASE_URL}/youtubei/v1/guide`;
this.oauth_code_url = `${Constants.URLS.YT_BASE}/o/oauth2/device/code`;
this.oauth_token_url = `${Constants.URLS.YT_BASE}/o/oauth2/token`;
this.model_name = Constants.OAUTH.MODEL_NAME;

@@ -128,3 +127,3 @@ this.grant_type = Constants.OAUTH.GRANT_TYPE;

* Gets a new access token using a refresh token.
* @returns {object.<{ credentials: { access_token: string; refresh_token: string; expires: Date }; status: string }>}
* @returns {Promise.<{ credentials: { access_token: string; refresh_token: string; expires: Date }; status: string }>}
*/

@@ -177,3 +176,3 @@ async refreshAccessToken() {

// This request is made to get the auth script url, hard-coding it isn't viable as it changes overtime.
const yttv_response = await Axios.get(`${Constants.URLS.YT_BASE_URL}/tv`, Constants.OAUTH.HEADERS).catch((error) => error);
const yttv_response = await Axios.get(`${Constants.URLS.YT_BASE}/tv`, Constants.OAUTH.HEADERS).catch((error) => error);
if (yttv_response instanceof Error) throw new Error(`Could not extract client identity: ${yttv_response.message}`);

@@ -183,4 +182,4 @@

const url_body = this.auth_script_regex.exec(yttv_response.data)[1];
const script_url = `${Constants.URLS.YT_BASE_URL}/${url_body}`;
const script_url = `${Constants.URLS.YT_BASE}/${url_body}`;
const response = await Axios.get(script_url, Constants.DEFAULT_HEADERS).catch((error) => error);

@@ -204,2 +203,2 @@ if (response instanceof Error) throw new Error(`Could not extract client identity: ${response.message}`);

module.exports = OAuth;
module.exports = OAuth;

@@ -48,8 +48,8 @@ 'use strict';

return {
id: video.videoId,
title: video.title.runs[0].text,
description: video.detailedMetadataSnippets && video.detailedMetadataSnippets[0].snippetText.runs.map((item) => item.text).join('') || 'N/A',
channel_url: `${Constants.URLS.YT_BASE}${video.ownerText.runs[0].navigationEndpoint.commandMetadata.webCommandMetadata.url}`,
author: video.ownerText.runs[0].text,
id: video.videoId,
url: `https://youtu.be/${video.videoId}`,
channel_url: `${Constants.URLS.YT_BASE_URL}${video.ownerText.runs[0].navigationEndpoint.commandMetadata.webCommandMetadata.url}`,
metadata: {

@@ -211,21 +211,16 @@ view_count: video.viewCountText && video.viewCountText.simpleText || 'N/A',

}
/**
* Video data is parsed dynamically, so if youtube decides to add something new we won't have to change anything here.
*/
#parseVideoInfo() {
const desktop_v = this.args.desktop_v;
const playability_status = this.data.playabilityStatus;
const playability_status = desktop_v && this.data.playabilityStatus ||
this.data[2].playerResponse.playabilityStatus;
if (playability_status.status == 'ERROR')
throw new Error(`Could not retrieve details for this video: ${playability_status.status} - ${playability_status.reason}`);
throw new Error(`Could not retrieve video details: ${playability_status.status} - ${playability_status.reason}`);
const details = desktop_v && this.data.videoDetails ||
this.data[2].playerResponse.videoDetails;
const microformat = desktop_v && this.data.microformat.playerMicroformatRenderer ||
this.data[2].playerResponse.microformat.playerMicroformatRenderer;
const streaming_data = desktop_v && this.data.streamingData ||
this.data[2].playerResponse.streamingData;
const details = this.data.videoDetails;
const microformat = this.data.microformat.playerMicroformatRenderer;
const streaming_data = this.data.streamingData;
const response = {

@@ -241,3 +236,4 @@ id: '',

const dt_raw_data = Object.entries(details);
// Extracts most of the metadata
mf_raw_data.forEach((entry) => {

@@ -254,3 +250,4 @@ const key = Utils.camelToSnake(entry[0]);

});
// Extracts extra details
dt_raw_data.forEach((entry) => {

@@ -269,20 +266,48 @@ const key = Utils.camelToSnake(entry[0]);

});
if (!desktop_v) {
const dislike_available = this.data[3].response.contents.singleColumnWatchNextResults.results.results.contents[1]
.slimVideoMetadataSectionRenderer.contents[1].slimVideoActionBarRenderer.buttons[1].slimMetadataToggleButtonRenderer
.button.toggleButtonRenderer.defaultText.accessibility && true || false;
response.metadata.likes = parseInt(this.data[3].response.contents.singleColumnWatchNextResults.results.results.contents[1]
.slimVideoMetadataSectionRenderer.contents[1].slimVideoActionBarRenderer.buttons[0].slimMetadataToggleButtonRenderer
.button.toggleButtonRenderer.defaultText.accessibility.accessibilityData.label.replace(/\D/g, ''));
response.metadata.dislikes = dislike_available && parseInt(this.data[3].response.contents.singleColumnWatchNextResults.results.results.contents[1]
.slimVideoMetadataSectionRenderer.contents[1].slimVideoActionBarRenderer.buttons[1].slimMetadataToggleButtonRenderer
.button.toggleButtonRenderer.defaultText.accessibility.accessibilityData.label.replace(/\D/g, '')) || 0;
// Data continuation is only required in getDetails()
if (this.data.continuation) {
const primary_info_renderer = this.data.continuation.contents.twoColumnWatchNextResults
.results.results.contents.find((item) => item.videoPrimaryInfoRenderer).videoPrimaryInfoRenderer;
const secondary_info_renderer = this.data.continuation.contents.twoColumnWatchNextResults
.results.results.contents.find((item) => item.videoSecondaryInfoRenderer).videoSecondaryInfoRenderer;
const like_btn = primary_info_renderer.videoActions.menuRenderer
.topLevelButtons.find((item) => item.toggleButtonRenderer.defaultIcon.iconType == 'LIKE');
const dislike_btn = primary_info_renderer.videoActions.menuRenderer
.topLevelButtons.find((item) => item.toggleButtonRenderer.defaultIcon.iconType == 'DISLIKE');
const notification_toggle_btn = secondary_info_renderer.subscribeButton.subscribeButtonRenderer
?.notificationPreferenceButton?.subscriptionNotificationToggleButtonRenderer;
// These will always be false if logged out.
response.metadata.is_liked = like_btn.toggleButtonRenderer.isToggled;
response.metadata.is_disliked = dislike_btn.toggleButtonRenderer.isToggled;
response.metadata.is_subscribed = secondary_info_renderer.subscribeButton.subscribeButtonRenderer?.subscribed || false;
response.metadata.subscriber_count = secondary_info_renderer.owner.videoOwnerRenderer?.subscriberCountText?.simpleText || 'N/A';
response.metadata.current_notification_preference = notification_toggle_btn?.states.find((state) => state.stateId == notification_toggle_btn.currentStateId)
.state.buttonRenderer.icon.iconType || 'N/A';
// Simpler version of publish_date
response.metadata.publish_date_text = primary_info_renderer.dateText.simpleText;
// Only parse like count if it's enabled
if (response.metadata.allow_ratings) {
response.metadata.likes = {
count: parseInt(like_btn.toggleButtonRenderer.defaultText.accessibility.accessibilityData.label.replace(/\D/g, '')),
short_count_text: like_btn.toggleButtonRenderer.defaultText.simpleText
};
}
response.metadata.owner_badges = secondary_info_renderer.owner.videoOwnerRenderer?.badges?.map((badge) => badge.metadataBadgeRenderer.tooltip) || [];
}
streaming_data && streaming_data.adaptiveFormats &&
(response.metadata.available_qualities = [...new Set(streaming_data.adaptiveFormats.filter(v => v.qualityLabel)
.map(v => v.qualityLabel).sort((a, b) => +a.replace(/\D/gi, '') - +b.replace(/\D/gi, '')))]) ||
(response.metadata.available_qualities = []);
response.metadata.available_qualities = [...new Set(streaming_data.adaptiveFormats.filter(v => v.qualityLabel)
.map(v => v.qualityLabel).sort((a, b) => + a.replace(/\D/gi, '') - + b.replace(/\D/gi, '')))];
return response;

@@ -292,2 +317,2 @@ }

module.exports = Parser;
module.exports = Parser;

@@ -21,3 +21,3 @@ 'use strict';

} else {
const response = await Axios.get(`${Constants.URLS.YT_BASE_URL}${this.session.player_url}`, { path: this.session.playerUrl, headers: { 'content-type': 'text/javascript', 'user-agent': Utils.getRandomUserAgent('desktop').userAgent } }).catch((error) => error);
const response = await Axios.get(`${Constants.URLS.YT_BASE}${this.session.player_url}`, { path: this.session.playerUrl, headers: { 'content-type': 'text/javascript', 'user-agent': Utils.getRandomUserAgent('desktop').userAgent } }).catch((error) => error);
if (response instanceof Error) throw new Error('Could not download player script: ' + response.message);

@@ -24,0 +24,0 @@

{
"name": "youtubei.js",
"version": "1.3.6",
"version": "1.3.7-md",
"description": "An object-oriented library that allows you to search, get detailed info about videos, subscribe, unsubscribe, like, dislike, comment, download videos and much more!",

@@ -5,0 +5,0 @@ "main": "index.js",

@@ -93,5 +93,5 @@ <h1 align=center>YouTube.js</h1>

{
id: string,
title: string,
description: string,
id: string,
url: string,

@@ -215,3 +215,3 @@ metadata:{

Get details about a specific video:
Get details about a given video:

@@ -251,2 +251,3 @@ ```js

external_channel_id: string,
allow_ratings: boolean,
is_live_content: boolean,

@@ -256,2 +257,9 @@ is_family_safe: boolean,

is_private: boolean,
is_liked: boolean,
is_disliked: boolean,
is_subscribed: boolean,
subscriber_count: string,
current_notification_preference: string,
likes: { count: number, short_count_text: string },
publish_date_text: string,
has_ypc_metadata: boolean,

@@ -334,5 +342,7 @@ category: string,

title: string,
description: string,
channel: string,
metadata: {
view_count: string,
short_view_count_text: { simple_text: string, accessibility_label: string },
thumbnail: {

@@ -342,11 +352,16 @@ url: string,

height: number
},
moving_thumbnail: {
},
moving_thumbnail: {
url: string,
widt: number,
width: number,
height: number
},
published: string,
badges: [Array],
owner_badges: [Array]
},
published: string,
duration: {
seconds: number,
simple_text: string,
accessibility_label: string
},
badges: string,
owner_badges: [Array]
}

@@ -379,15 +394,17 @@ }

view_count: string,
short_view_count_text: { simple_text: string, accessibility_label: string },
thumbnail: {
url: string,
width: number,
height: number
url: string,
width: number,
height: number
},
moving_thumbnail: {
url: string,
width: number,
height: number
url: string,
width: number,
height: number
},
published: string,
badges: [Array],
badges: string,
owner_badges: [Array]
}
}

@@ -403,15 +420,17 @@ //...

view_count: string,
short_view_count_text: { simple_text: string, accessibility_label: string },
thumbnail: {
url: string,
width: number,
height: number
url: string,
width: number,
height: number
},
moving_thumbnail: {
url: string,
width: number,
height: number
url: string,
width: number,
height: number
},
published: string,
badges: [Array],
badges: string,
owner_badges: [Array]
}
}

@@ -421,3 +440,2 @@ //...

this_week: [
{
id: string,

@@ -429,14 +447,15 @@ title: string,

thumbnail: {
url: string,
width: number,
height: number
url: string,
width: number,
height: number
},
moving_thumbnail: {
url: string,
width: number,
height: number
url: string,
width: number,
height: number
},
published: string,
badges: [Array],
badges: string,
owner_badges: [Array]
}
}

@@ -466,5 +485,10 @@ // ...

title: string,
channel: string,
description: string,
channel: {
name: string,
url: string
},
metadata: {
view_count: string,
short_view_count_text: { simple_text: string, accessibility_label: string },
thumbnail: {

@@ -481,3 +505,7 @@ url: string,

published: string,
duration: string,
duration: {
seconds: number,
simple_text: string,
accessibility_label: string
},
badges: string,

@@ -618,3 +646,3 @@ owner_badges: [Array]

The library makes it easy to interact with YouTube programatically. However, don't forget that you must be signed in to use the following features!
The library makes it easy to interact with YouTube programmatically. However, don't forget that you must be signed in to use the following features!

@@ -643,3 +671,3 @@ * Subscribe/Unsubscribe:

// Options: ALL | NONE | PERSONALIZED
await youtube.interact.changeNotificationPreferences('VIDEO_ID', 'ALL');
await youtube.interact.changeNotificationPreferences('CHANNEL_ID', 'ALL');
```

@@ -646,0 +674,0 @@

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc