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.8 to 1.4.0-b

lib/core/Actions.js

2

examples/index.js

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

const video = await youtube.getDetails(search.videos[0].id).catch((error) => error);
const video = await youtube.getDetails(search.videos[0].id);
console.info('Video details:', video);

@@ -33,0 +33,0 @@

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

const Stream = require('stream');
const OAuth = require('./OAuth');
const Utils = require('./Utils');
const Player = require('./Player');
const Parser = require('./Parser');
const NToken = require('./NToken');
const Actions = require('./Actions');
const Livechat = require('./Livechat');
const Constants = require('./Constants');
const SigDecipher = require('./Sig');
const Parser = require('./parser');
const CancelToken = Axios.CancelToken;
const EventEmitter = require('events');
const CancelToken = Axios.CancelToken;
// Core
const OAuth = require('./core/OAuth');
const Player = require('./core/Player');
const Actions = require('./core/Actions');
const Livechat = require('./core/Livechat');
// Utilities
const Utils = require('./utils/Utils');
const Constants = require('./utils/Constants');
// Deciphers
const NToken = require('./deciphers/NToken');
const SigDecipher = require('./deciphers/Sig');
class Innertube {

@@ -39,55 +45,56 @@ #player;

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}`);
if (response instanceof Error) throw new Utils.InnertubeError('Could not retrieve Innertube session', { status_code: response.status || 0 });
const data = JSON.parse(`{${Utils.getStringBetweenStrings(response.data, 'ytcfg.set({', '});') || ''}}`);
if (data.INNERTUBE_CONTEXT) {
this.key = data.INNERTUBE_API_KEY;
this.version = data.INNERTUBE_API_VERSION;
this.context = data.INNERTUBE_CONTEXT;
try {
const data = JSON.parse(`{${Utils.getStringBetweenStrings(response.data, 'ytcfg.set({', '});')}}`);
if (data.INNERTUBE_CONTEXT) {
this.key = data.INNERTUBE_API_KEY;
this.version = data.INNERTUBE_API_VERSION;
this.context = data.INNERTUBE_CONTEXT;
this.player_url = data.PLAYER_JS_URL;
this.logged_in = data.LOGGED_IN;
this.sts = data.STS;
this.player_url = data.PLAYER_JS_URL;
this.logged_in = data.LOGGED_IN;
this.sts = data.STS;
this.context.client.hl = 'en';
this.context.client.gl = 'US';
this.context.client.hl = 'en';
this.context.client.gl = 'US';
/**
* @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}
*/
this.ev = new EventEmitter();
/**
* @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}
*/
this.ev = new EventEmitter();
this.#player = new Player(this);
await this.#player.init();
this.#player = new Player(this);
await this.#player.init();
if (this.logged_in && this.cookie.length) {
this.auth_apisid = Utils.getStringBetweenStrings(this.cookie, 'PAPISID=', ';');
this.auth_apisid = Utils.generateSidAuth(this.auth_apisid);
}
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 }
});
// 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.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();
} else {
throw new Error('No InnerTubeContext shell provided in ytconfig.');
}
} catch (err) {
this.#initMethods();
} else {
this.#retry_count += 1;
if (this.#retry_count >= 10) throw new Error(`Could not retrieve Innertube session: ${err.message}`);
if (this.#retry_count >= 10)
throw new Utils.ParsingError('No InnerTubeContext shell provided in ytconfig.', {
data_snippet: response.data.slice(0, 300),
status_code: response.status || 0
});
return this.#init();

@@ -243,12 +250,12 @@ }

const response = await Actions.browse(this, type);
const contents = response.data.contents.twoColumnBrowseResultsRenderer.tabs[0]
.tabRenderer.content.sectionListRenderer.contents[1]
.itemSectionRenderer.contents.find((content) => content.settingsOptionsRenderer.options)
.settingsOptionsRenderer.options;
const contents = ({
account_notifications: () => Utils.findNode(response.data, 'contents', 'Your preferences', 13, false).options,
account_privacy: () => Utils.findNode(response.data, 'contents', 'settingsSwitchRenderer', 13, false).options
})[type.trim()]();
const option = contents.find((option) => option.settingsSwitchRenderer.enableServiceEndpoint.setSettingEndpoint.settingItemIdForClient == setting_id);
const setting_item_id = option.settingsSwitchRenderer.enableServiceEndpoint.setSettingEndpoint.settingItemId;
const set_setting = await Actions.account(this, 'account/set_setting', { new_value, setting_item_id });
const set_setting = await Actions.account(this, 'account/set_setting', { new_value: type == 'account_privacy' ? !new_value : new_value, setting_item_id });

@@ -323,6 +330,6 @@ return {

const response = await Actions.account(this, 'account/account_menu');
if (!response.success) throw new Error('Could not get account info');
const menu = response.data.actions[0].openPopupAction.popup.multiPageMenuRenderer;
if (!response.success) throw new Utils.InnertubeError('Could not get account info', response);
const menu = Utils.findNode(response, 'actions', 'multiPageMenuRenderer', 6, false);
return {

@@ -350,13 +357,11 @@ name: menu.header.activeAccountHeaderRenderer.accountName.simpleText,

const response = await Actions.search(this, options.client, { query, options });
if (!response.success) throw new Error(`Could not search on YouTube: ${response.message}`);
if (!response.success) throw new Utils.InnertubeError('Could not search on YouTube', response);
const data = new Parser(this, response.data, {
const parsed_data = new Parser(this, response.data, {
client: options.client,
data_type: 'SEARCH',
query
query
}).parse();
data.getContinuation = () => {};
return data;
return parsed_data;
}

@@ -375,3 +380,3 @@

const response = await Actions.getYTSearchSuggestions(this, input);
if (!response.success) throw new Error('Could not get search suggestions');
if (!response.success) throw new Utils.InnertubeError('Could not get search suggestions', response);

@@ -386,3 +391,4 @@ return response.data[1].map((item) => {

const response = await Actions.music(this, 'get_search_suggestions', { input });
if (!response.success) throw new Error('Could not get search suggestions');
if (!response.success) throw new Utils.InnertubeError('Could not get search suggestions', response);
if (!response.data.contents) return [];

@@ -393,9 +399,7 @@

let suggestion;
item.historySuggestionRenderer &&
(suggestion = item.historySuggestionRenderer.suggestion) ||
(suggestion = item.searchSuggestionRenderer.suggestion);
if (item.historySuggestionRenderer) {
suggestion = item.historySuggestionRenderer.suggestion;
} else {
suggestion = item.searchSuggestionRenderer.suggestion;
}
return {

@@ -416,3 +420,3 @@ text: suggestion.runs.map((run) => run.text).join('').trim(),

async getDetails(video_id) {
if (!video_id) throw new Error('You must provide a video id');
if (!video_id) throw new Utils.MissingParamError('Video id is missing');

@@ -424,3 +428,3 @@ const data = await Actions.getVideoInfo(this, { id: video_id });

const details = new Parser(this, data, { client: 'YOUTUBE', data_type: 'VIDEO_INFO' }).parse();
// Functions

@@ -448,3 +452,3 @@ details.like = () => Actions.engage(this, 'like/like', { video_id });

const response = await Actions.browse(this, 'channel', { browse_id: id });
if (!response.success) throw new Error('Could not retrieve channel info.');
if (!response.success) throw new Utils.InnertubeError('Could not retrieve channel info.', response);

@@ -537,3 +541,4 @@ const tabs = response.data.contents.twoColumnBrowseResultsRenderer.tabs;

const continuation = await Actions.next(this, { video_id: video_id, ytmusic: true });
if (!continuation.success) throw new Utils.InnertubeError('Could not retrieve lyrics', continuation);
const lyrics_tab = continuation.data.contents.singleColumnMusicWatchNextResultsRenderer.tabbedRenderer

@@ -543,3 +548,3 @@ .watchNextTabbedResultsRenderer.tabs.find((obj) => obj.tabRenderer.title == 'Lyrics');

const response = await Actions.browse(this, 'lyrics', { ytmusic: true, browse_id: lyrics_tab.tabRenderer.endpoint.browseEndpoint.browseId });
if (!response.data.contents.sectionListRenderer) throw new Error(response.data.contents.messageRenderer.text.runs[0].text);
if (!response.success || !response.data?.contents?.sectionListRenderer) throw new Utils.UnavailableContentError('Lyrics not available', { video_id });

@@ -571,3 +576,3 @@ const lyrics = response.data.contents.sectionListRenderer.contents[0].musicDescriptionShelfRenderer.description.runs[0].text;

* @param {string} [data] - Video data and continuation token (optional).
* @return {Promise.<[{ comments: []; comment_count: string }]>
* @return {Promise.<[{ comments: []; comment_count?: string }]>
*/

@@ -579,9 +584,9 @@ async getComments(video_id, data = {}) {

const continuation = await Actions.next(this, { video_id });
if (!continuation.success) throw new Utils.InnertubeError('Could not fetch comments section', continuation);
const item_section_renderer = continuation.data.contents.twoColumnWatchNextResults.results.results.contents.find((item) => item.itemSectionRenderer);
comment_section_token = item_section_renderer.itemSectionRenderer.contents[0].continuationItemRenderer.continuationEndpoint.continuationCommand.token;
const secondary_info_renderer = continuation.data.contents.twoColumnWatchNextResults
.results.results.contents.find((item) => item.videoSecondaryInfoRenderer).videoSecondaryInfoRenderer;
const contents = Utils.findNode(continuation.data, 'contents', 'comments-section', 5);
const item_section_renderer = contents.find((item) => item.itemSectionRenderer).itemSectionRenderer;
comment_section_token = item_section_renderer?.contents[0]?.continuationItemRenderer?.continuationEndpoint.continuationCommand.token;
const secondary_info_renderer = contents.find((item) => item.videoSecondaryInfoRenderer).videoSecondaryInfoRenderer;
data.channel_id = secondary_info_renderer.owner.videoOwnerRenderer.navigationEndpoint.browseEndpoint.browseId;

@@ -591,6 +596,6 @@ }

const response = await Actions.next(this, { continuation_token: comment_section_token || data.token });
if (!response.success) throw new Error('Could not fetch comments section');
if (!response.success) throw new Utils.InnertubeError('Could not fetch comments section', response);
const comments_section = { comments: [] };
!data.token && (comments_section.comment_count = response.data.onResponseReceivedEndpoints[0].reloadContinuationItemsCommand.continuationItems && response.data.onResponseReceivedEndpoints[0].reloadContinuationItemsCommand.continuationItems[0].commentsHeaderRenderer.countText.runs[0].text || 'N/A');
!data.token && (comments_section.comment_count = response.data?.onResponseReceivedEndpoints[0]?.reloadContinuationItemsCommand?.continuationItems[0]?.commentsHeaderRenderer?.countText.runs[0]?.text || 'N/A');

@@ -656,46 +661,71 @@ let continuation_token;

* Returns your watch history.
* @returns {Promise.<[{ id: string; title: string; channel: string; metadata: {} }]>}
* @returns {Promise.<{ items: [{ date: string; videos: [] }] }>}
*/
async getHistory() {
const response = await Actions.browse(this, 'history');
const contents = response.data.contents.twoColumnBrowseResultsRenderer.tabs[0]
.tabRenderer.content.sectionListRenderer.contents;
const history = [];
contents.forEach((section) => {
if (!section.itemSectionRenderer) return;
const section_items = section.itemSectionRenderer.contents;
section_items.forEach((item) => {
const content = {
id: item.videoRenderer.videoId,
title: item.videoRenderer.title.runs.map((run) => run.text).join(' '),
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: {
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) || []
}
};
history.push(content);
if (!response.success) throw new Utils.InnertubeError('Could not retrieve watch history', response);
const contents = Utils.findNode(response, 'contents', 'videoRenderer', 9, false)
const history = { items: [] };
const parseItems = (contents) => {
contents.forEach((section) => {
if (!section.itemSectionRenderer) return;
const header = section.itemSectionRenderer.header.itemSectionHeaderRenderer.title;
const section_title = header?.simpleText || header?.runs.map((run) => run.text).join('');
const contents = section.itemSectionRenderer.contents;
const section_items = contents.map((item) => {
return {
id: item?.videoRenderer?.videoId,
title: item?.videoRenderer?.title?.runs?.map((run) => run.text).join(' '),
description: item?.videoRenderer?.descriptionSnippet?.runs[0]?.text || 'N/A',
channel: {
id: item?.videoRenderer?.shortBylineText?.runs[0]?.navigationEndpoint?.browseEndpoint?.browseId,
name: item?.videoRenderer?.shortBylineText?.runs[0]?.text || 'N/A',
url: `${Constants.URLS.YT_BASE}${item?.videoRenderer?.shortBylineText?.runs[0]?.navigationEndpoint?.browseEndpoint?.canonicalBaseUrl}`
},
metadata: {
view_count: item?.videoRenderer?.viewCountText?.simpleText || 'N/A',
short_view_count_text: {
simple_text: item?.videoRenderer?.shortViewCountText?.simpleText || 'N/A',
accessibility_label: item?.videoRenderer?.shortViewCountText?.accessibility?.accessibilityData?.label,
},
thumbnail: item?.videoRenderer?.thumbnail?.thumbnails?.slice(-1)[0] || [],
moving_thumbnail: item?.videoRenderer?.richThumbnail?.movingThumbnailRenderer?.movingThumbnailDetails?.thumbnails[0] || [],
duration: {
seconds: Utils.timeToSeconds(item?.videoRenderer?.lengthText?.simpleText || '0'),
simple_text: item?.videoRenderer?.lengthText?.simpleText || 'N/A',
accessibility_label: item?.videoRenderer?.lengthText?.accessibility?.accessibilityData?.label || 'N/A'
},
badges: item?.videoRenderer?.badges?.map((badge) => badge.metadataBadgeRenderer.label) || [],
owner_badges: item?.videoRenderer?.ownerBadges?.map((badge) => badge.metadataBadgeRenderer.tooltip) || []
}
};
});
history.items.push({
date: section_title,
videos: section_items
});
});
});
return history;
history.getContinuation = async () => {
const citem = contents.find((item) => item.continuationItemRenderer);
const ctoken = citem.continuationItemRenderer.continuationEndpoint.continuationCommand.token;
const response = await Actions.browse(this, 'continuation', { ctoken });
if (!response.success) throw new Utils.InnertubeError('Could not retrieve continuation', response);
history.items = [];
return parseItems(response.data.onResponseReceivedActions[0].appendContinuationItemsAction.continuationItems);
}
return history;
}
return parseItems(contents);
}

@@ -705,41 +735,58 @@

* Returns YouTube's home feed (aka recommendations).
* @returns {Promise.<[{ id: string; title: string; channel: string; metadata: {} }]>}
* @returns {Promise.<{ videos: [{ id: string; title: string; description: string; channel: string; metadata: object }] }>}
*/
async getHomeFeed() {
const response = await Actions.browse(this, 'home_feed');
if (!response.success) throw new Error('Could not get home feed');
if (!response.success) throw new Utils.InnertubeError('Could not retrieve home feed', response);
const contents = response.data.contents.twoColumnBrowseResultsRenderer.tabs[0].tabRenderer.content.richGridRenderer.contents;
const contents = Utils.findNode(response, 'contents', 'videoRenderer', 9, false)
return contents.map((item) => {
const content = item.richItemRenderer && item.richItemRenderer.content.videoRenderer &&
item.richItemRenderer.content || undefined;
const parseItems = (contents) => {
const videos = contents.map((item) => {
const content = item.richItemRenderer && item.richItemRenderer.content.videoRenderer &&
item.richItemRenderer.content;
if (content) return {
id: content.videoRenderer.videoId,
title: content.videoRenderer.title.runs.map((run) => run.text).join(' '),
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',
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) || [],
owner_badges: content.videoRenderer.ownerBadges && content.videoRenderer.ownerBadges.map((badge) => badge.metadataBadgeRenderer.tooltip) || []
if (content) return {
id: content.videoRenderer.videoId,
title: content.videoRenderer.title.runs.map((run) => run.text).join(' '),
description: content?.videoRenderer?.descriptionSnippet?.runs[0]?.text || 'N/A',
channel: {
id: content?.videoRenderer?.shortBylineText?.runs[0]?.navigationEndpoint?.browseEndpoint?.browseId,
name: content?.videoRenderer?.shortBylineText?.runs[0]?.text || 'N/A',
url: `${Constants.URLS.YT_BASE}${content?.videoRenderer?.shortBylineText?.runs[0]?.navigationEndpoint?.browseEndpoint?.canonicalBaseUrl}`
},
metadata: {
view_count: content?.videoRenderer?.viewCountText?.simpleText || 'N/A',
short_view_count_text: {
simple_text: content?.videoRenderer?.shortViewCountText?.simpleText || 'N/A',
accessibility_label: content?.videoRenderer?.shortViewCountText?.accessibility?.accessibilityData?.label || 'N/A',
},
thumbnail: content?.videoRenderer?.thumbnail?.thumbnails.slice(-1)[0] || {},
moving_thumbnail: content?.videoRenderer?.richThumbnail?.movingThumbnailRenderer?.movingThumbnailDetails?.thumbnails[0] || {},
published: content?.videoRenderer?.publishedTimeText?.simpleText || 'N/A',
duration: {
seconds: Utils.timeToSeconds(content?.videoRenderer?.lengthText?.simpleText || '0'),
simple_text: content?.videoRenderer?.lengthText?.simpleText || 'N/A',
accessibility_label: content?.videoRenderer?.lengthText?.accessibility?.accessibilityData?.label || 'N/A'
},
badges: content?.videoRenderer?.badges?.map((badge) => badge.metadataBadgeRenderer.label) || [],
owner_badges: content?.videoRenderer?.ownerBadges?.map((badge) => badge.metadataBadgeRenderer.tooltip) || []
}
}
}).filter((item) => item);
const getContinuation = async () => {
const citem = contents.find((item) => item.continuationItemRenderer);
const ctoken = citem.continuationItemRenderer.continuationEndpoint.continuationCommand.token;
const response = await Actions.browse(this, 'continuation', { ctoken });
if (!response.success) throw new Utils.InnertubeError('Could not retrieve continuation', response);
return parseItems(response.data.onResponseReceivedActions[0].appendContinuationItemsAction.continuationItems);
}
}).filter((video) => video);
return { videos, getContinuation };
}
return parseItems(contents);
}

@@ -749,47 +796,67 @@

* Returns your subscription feed.
* @returns {Promise.<{ today: []; yesterday: []; this_week: [] }>}
* @returns {Promise.<{ items: [{ date: string; videos: [] }] }>}
*/
async getSubscriptionsFeed() {
const response = await Actions.browse(this, 'subscriptions_feed');
if (!response.success) throw new Error('Could not get subscriptions feed');
if (!response.success) throw new Utils.InnertubeError('Could not retrieve subscriptions feed', response);
const contents = Utils.findNode(response, 'contents', 'contents', 9, false);
const subsfeed = { items: [] };
const parseItems = (contents) => {
contents.forEach((section) => {
if (!section.itemSectionRenderer) return;
const contents = response.data.contents.twoColumnBrowseResultsRenderer.tabs[0].tabRenderer.content.sectionListRenderer.contents;
const subscriptions_feed = {};
contents.forEach((section) => {
if (!section.itemSectionRenderer) return;
const section_contents = section.itemSectionRenderer.contents[0];
const section_items = section_contents.shelfRenderer.content.gridRenderer.items;
const key = section_contents.shelfRenderer.title.runs[0].text;
subscriptions_feed[key.toLowerCase().replace(/ +/g, '_')] = [];
section_items.forEach((item) => {
const content = {
id: item.gridVideoRenderer.videoId,
title: item.gridVideoRenderer.title.runs.map((run) => run.text).join(' '),
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) || [],
owner_badges: item.gridVideoRenderer.ownerBadges && item.gridVideoRenderer.ownerBadges.map((badge) => badge.metadataBadgeRenderer.tooltip) || []
}
};
subscriptions_feed[key.toLowerCase().replace(/ +/g, '_')].push(content);
const section_contents = section.itemSectionRenderer.contents[0];
const section_title = section_contents.shelfRenderer.title.runs[0].text;
const section_items = section_contents.shelfRenderer.content.gridRenderer.items;
const items = section_items.map((item) => {
return {
id: item.gridVideoRenderer.videoId,
title: item?.gridVideoRenderer?.title?.runs?.map((run) => run.text).join(' '),
channel: {
id: item?.gridVideoRenderer?.shortBylineText?.runs[0]?.navigationEndpoint?.browseEndpoint?.browseId,
name: item?.gridVideoRenderer?.shortBylineText?.runs[0]?.text || 'N/A',
url: `${Constants.URLS.YT_BASE}${item?.gridVideoRenderer?.shortBylineText?.runs[0]?.navigationEndpoint?.browseEndpoint?.canonicalBaseUrl}`
},
metadata: {
view_count: item?.gridVideoRenderer?.viewCountText?.simpleText || 'N/A',
short_view_count_text: {
simple_text: item?.gridVideoRenderer?.shortViewCountText?.simpleText || 'N/A',
accessibility_label: item?.gridVideoRenderer?.shortViewCountText?.accessibility?.accessibilityData?.label || 'N/A',
},
thumbnail: item?.gridVideoRenderer?.thumbnail?.thumbnails.slice(-1)[0] || [],
moving_thumbnail: item?.gridVideoRenderer?.richThumbnail?.movingThumbnailRenderer?.movingThumbnailDetails?.thumbnails[0] || {},
published: item?.gridVideoRenderer?.publishedTimeText?.simpleText || 'N/A',
badges: item?.gridVideoRenderer?.badges?.map((badge) => badge.metadataBadgeRenderer.label) || [],
owner_badges: item?.gridVideoRenderer?.ownerBadges?.map((badge) => badge.metadataBadgeRenderer.tooltip) || []
}
};
});
subsfeed.items.push({
date: section_title,
videos: items
});
});
});
return subscriptions_feed;
subsfeed.getContinuation = async () => {
const citem = contents.find((item) => item.continuationItemRenderer);
const ctoken = citem.continuationItemRenderer.continuationEndpoint.continuationCommand.token;
const response = await Actions.browse(this, 'continuation', { ctoken });
if (!response.success) throw new Utils.InnertubeError('Could not retrieve continuation', response);
const ccontents = Utils.findNode(response.data, 'onResponseReceivedActions', 'itemSectionRenderer', 4, false);
subsfeed.items = [];
return parseItems(ccontents);
}
return subsfeed;
};
return parseItems(contents);
}

@@ -799,24 +866,41 @@

* Retrieves your notifications.
* @returns {Promise.<[{ title: string; sent_time: string; channel_name: string; channel_thumbnail: {}; video_thumbnail: {}; video_url: string; read: boolean; notification_id: string }]>}
* @returns {Promise.<{ items: [{ title: string; sent_time: string; channel_name: string; channel_thumbnail: {}; video_thumbnail: {}; video_url: string; read: boolean; notification_id: string }] }>}
*/
async getNotifications() {
const response = await Actions.notifications(this, 'get_notification_menu');
if (!response.success) throw new Error('Could not fetch notifications');
if (!response.success) throw new Utils.InnertubeError('Could not fetch notifications', response);
const contents = response.data.actions[0].openPopupAction.popup.multiPageMenuRenderer.sections[0];
if (!contents.multiPageMenuNotificationSectionRenderer) return { error: 'You don\'t have any notification.' };
return contents.multiPageMenuNotificationSectionRenderer.items.map((notification) => {
if (!notification.notificationRenderer) return;
notification = notification.notificationRenderer;
return {
title: notification.shortMessage.simpleText,
sent_time: notification.sentTimeText.simpleText,
channel_name: notification.contextualMenu.menuRenderer.items[1].menuServiceItemRenderer.text.runs[1].text,
channel_thumbnail: notification.thumbnail.thumbnails[0],
video_thumbnail: notification.videoThumbnail.thumbnails[0],
video_url: `https://youtu.be/${notification.navigationEndpoint.watchEndpoint.videoId}`,
read: notification.read,
notification_id: notification.notificationId,
};
}).filter((notification) => notification);
if (!contents.multiPageMenuNotificationSectionRenderer) throw new Utils.InnertubeError('No notifications', response);
const parseItems = (items) => {
const parsed_items = items.map((notification) => {
if (!notification.notificationRenderer) return;
notification = notification.notificationRenderer;
return {
title: notification?.shortMessage?.simpleText,
sent_time: notification?.sentTimeText?.simpleText,
channel_name: notification?.contextualMenu?.menuRenderer?.items[1]?.menuServiceItemRenderer?.text?.runs[1]?.text || 'N/A',
channel_thumbnail: notification?.thumbnail?.thumbnails[0],
video_thumbnail: notification?.videoThumbnail?.thumbnails[0],
video_url: notification.navigationEndpoint.watchEndpoint && `https://youtu.be/${notification.navigationEndpoint.watchEndpoint.videoId}` || 'N/A',
read: notification.read,
notification_id: notification.notificationId,
};
}).filter((notification) => notification);
const getContinuation = async () => {
const citem = items.find((item) => item.continuationItemRenderer);
const ctoken = citem?.continuationItemRenderer?.continuationEndpoint?.getNotificationMenuEndpoint?.ctoken;
const response = await Actions.notifications(this, 'get_notification_menu', { ctoken });
if (!response.success) throw new Utils.InnertubeError('Could not retrieve continuation', response);
return parseItems(response.data.actions[0].appendContinuationItemsAction.continuationItems);
}
return { items: parsed_items, getContinuation };
}
return parseItems(contents.multiPageMenuNotificationSectionRenderer.items);
}

@@ -830,3 +914,3 @@

const response = await Actions.notifications(this, 'get_unseen_count');
if (!response.success) throw new Error('Could not get unseen notifications count');
if (!response.success) throw new Utils.InnertubeError('Could not get unseen notifications count', response);
return response.data.unseenCount;

@@ -861,3 +945,3 @@ }

if (url_components.searchParams.get('n')) {
url_components.searchParams.set('n', new NToken(this.#player.ntoken_sc).transform(url_components.searchParams.get('n')));
url_components.searchParams.set('n', new NToken(this.#player.ntoken_sc, url_components.searchParams.get('n')).transform());
}

@@ -923,3 +1007,3 @@

const streaming_data = this.#chooseFormat(options, data);
if (!streaming_data.selected_format) throw new Error('Could not find any suitable format.');
if (!streaming_data.selected_format) throw new Utils.NoStreamingDataError('Could not find any suitable format.', { id, options });

@@ -940,3 +1024,3 @@ return streaming_data;

download(id, options = {}) {
if (!id) throw new Error('Missing video id');
if (!id) throw new Utils.MissingParamError('Video id is missing');

@@ -961,4 +1045,4 @@ options.quality = options.quality || '360p';

return stream.emit('error', { message: 'Could not find any suitable format.', type: 'FORMAT_UNAVAILABLE' });
const video_details = new Parser(this, video_data, { client: 'YOUTUBE', data_type: 'VIDEO_INFO', desktop_v: true }).parse();
const video_details = new Parser(this, video_data, { client: 'YOUTUBE', data_type: 'VIDEO_INFO' }).parse();
stream.emit('info', { video_details, selected_format: format, formats });

@@ -965,0 +1049,0 @@

{
"name": "youtubei.js",
"version": "1.3.8",
"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!",
"version": "1.4.0-b",
"description": "A full-featured library that allows you to get detailed info about any video, subscribe, unsubscribe, like, dislike, comment, search, download videos/music and much more!",
"main": "index.js",
"author": "LuanRT <luan.lrt4@gmail.com> (https://github.com/LuanRT)",
"funding": "https://ko-fi.com/luanrt",
"license": "MIT",
"engines": {
"node": ">=14"
},
"scripts": {
"test": "node test"
},
"author": "LuanRT",
"funding": "https://ko-fi.com/luanrt",
"license": "MIT",
"directories": {

@@ -18,2 +21,3 @@ "example": "examples",

"axios": "^0.21.4",
"flat": "^5.0.2",
"protons": "^2.0.3",

@@ -27,2 +31,6 @@ "user-agents": "^1.0.778",

},
"bugs": {
"url": "https://github.com/LuanRT/YouTube.js/issues"
},
"homepage": "https://github.com/LuanRT/YouTube.js#readme",
"keywords": [

@@ -37,4 +45,4 @@ "yt",

"innertubeapi",
"downloader",
"livechat",
"downloader",
"dislike",

@@ -46,7 +54,3 @@ "search",

"dl"
],
"bugs": {
"url": "https://github.com/LuanRT/YouTube.js/issues"
},
"homepage": "https://github.com/LuanRT/YouTube.js#readme"
]
}
<h1 align=center>YouTube.js</h1>
<p align=center><i>An object-oriented wrapper around the Innertube API, which is what YouTube itself uses.</i><p>
<p align=center>
<a href=https://github.com/LuanRT/YouTube.js/issues>Report Bug</a>
<i>A full-featured wrapper around the Innertube API, which is what YouTube itself uses.</i>
<p>
<p align=center>
<a href="https://github.com/LuanRT/YouTube.js/issues">Report Bug</a>
·
<a href=https://github.com/LuanRT/YouTube.js/issues>Request Feature
</a>
<br/>
<br/>
<img src=https://github.com/LuanRT/YouTube.js/actions/workflows/node.js.yml/badge.svg>
<img src=https://img.shields.io/npm/v/youtubei.js?color=%2335C757>
<img src=https://www.codefactor.io/repository/github/luanrt/youtube.js/badge>
<a href="https://github.com/LuanRT/YouTube.js/issues">Request Feature</a>
<br/>
<br/>
<!-- PROJECT SHIELDS -->
<img src="https://github.com/LuanRT/YouTube.js/actions/workflows/node.js.yml/badge.svg">
<img src="https://img.shields.io/npm/v/youtubei.js?color=%2335C757">
<img src="https://www.codefactor.io/repository/github/luanrt/youtube.js/badge">
<img src="https://img.shields.io/npm/dm/youtubei.js">
<a href="https://saythanks.io/to/LuanRT">
<img src="https://img.shields.io/badge/Say%20Thanks-!-1EAEDB.svg">
</a>
</p>
<!-- TABLE OF CONTENTS -->
<details>
<summary>Table of Contents</summary>
<ol>
<li>
<a href="#about">About The Project</a>
<ul>
<li><a href="#features">Features</a></li>
</ul>
</li>
<li>
<a href="#getting-started">Getting Started</a>
<ul>
<li><a href="#prerequisites">Prerequisites</a></li>
<li><a href="#installation">Installation</a></li>
</ul>
</li>
<li>
<a href="#usage">Usage</a>
<ul>
<li><a href="#interactions">Interactions</a></li>
<li><a href="#live-chats">Livechats</a></li>
<li><a href="#downloading-videos">Downloading videos</a></li>
<li><a href="#signing-in">Signing in</a></li>
</ul>
</li>
<li><a href="#contributing">Contributing</a></li>
<li><a href="#license">License</a></li>
<li><a href="#contact">Contact</a></li>
<li><a href="#disclaimer">Disclaimer</a></li>
</ol>
</details>
<!-- ABOUT THE PROJECT -->
## About
Innertube is an API used across all YouTube clients, it was [made to simplify](https://gizmodo.com/how-project-innertube-helped-pull-youtube-out-of-the-gu-1704946491) the internal structure of the platform and make it easy to push updates. This library takes advantage of that API, therefore providing a simple & efficient way to interact with YouTube programmatically.

@@ -22,3 +65,3 @@

#### What can it do?
### Features

@@ -30,5 +73,2 @@ As of now, this is one of the most advanced & stable YouTube libraries out there, here's a short summary of its features:

- Fetch live chat & live stats in real time
- Get notifications
- Get watch history
- Get subscriptions/home feed
- Change notification preferences for a channel

@@ -38,2 +78,5 @@ - Subscribe/Unsubscribe/Like/Dislike/Comment etc

- Change an account's settings.
- Get subscriptions/home feed
- Get notifications
- Get watch history
- Download videos

@@ -43,26 +86,31 @@

#### Do I need an API key to use this?
### Do I need an API key to use this?
No, YouTube.js does not use any official API so no API keys are required.
## Installation
<!-- GETTING STARTED -->
## Getting Started
```bash
npm install youtubei.js
```
### Prerequisites
- [NodeJS](https://nodejs.org) v14 or greater
## Usage
To verify things are set up
properly, you can run this:
```bash
node --version
```
[1. Getting Started](#usage)
[2. Interactions](#interactions)
### Installation
- NPM:
```bash
npm install youtubei.js@latest
```
- Yarn:
```bash
yarn add youtubei.js@latest
```
[3. Live chats](#fetching-live-chats)
[4. Downloading videos](#downloading-videos)
[5. Signing-in](#signing-in)
[6. Disclaimer](#disclaimer)
<!-- USAGE -->
## Usage
First of all we're gonna start by initializing the Innertube instance.

@@ -76,9 +124,11 @@ And to make things faster, you should do this only once and reuse the Innertube object when needed.

Doing a simple search:
### Doing a simple search
YouTube:
```js
// YouTube:
const search = await youtube.search('Looking for life on Mars - Documentary');
```
// YTMusic:
YTMusic:
```js
const search = await youtube.search('Interstellar Main Theme', { client: 'YTMUSIC' });

@@ -88,3 +138,3 @@ ```

<details>
<summary>YouTube Search Output</summary>
<summary>YouTube Output</summary>
<p>

@@ -100,5 +150,5 @@

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

@@ -130,3 +180,3 @@ view_count: string,

<details>
<summary>YouTube Music Search Output</summary>
<summary>YTMusic Output</summary>
<p>

@@ -137,57 +187,70 @@

{
songs: [
{
id: string,
title: string,
artist: string,
album: string,
duration: string,
thumbnail: {
thumbnails: [Array]
}
},
//...
],
videos: [
{
id: string,
title: string,
author: string,
views: string,
duration: string,
thumbnail: {
thumbnails: [Array]
}
}
//...
],
albums: [
{
title: string,
author: string,
year: string,
thumbnail: {
thumbnails: [Array]
}
},
//...
],
playlists: [
{
title: string,
description: string,
total_items: number,
duration: string,
year: string,
items: [
{
id: string,
title: string,
author: string,
duration: string,
thumbnail: [Array]
}
]
}
]
query:string,
corrected_query:string,
results:{
top_result:[Array], // Can be anything; video, playlist, artist etc..
songs:[
{
id:string,
title:string,
artist:string,
album:string,
duration:string,
thumbnails:[
Array
]
},
//...
],
videos:[
{
id:string,
title:string,
author:string,
views:string,
duration:string,
thumbnails:[Array]
},
//...
],
albums:[
{
id:string,
title:string,
author:string,
year:string,
thumbnails:[Array]
},
//...
],
featured_playlists:[
{
id:string,
title:string,
author:string,
channel_id:string,
total_items:number
},
//...
],
community_playlists:[
{
id:string,
title:string,
author:string,
channel_id:string,
total_items:number
},
//...
],
artists:[
{
id:string,
name:string,
subscribers:string,
thumbnails:[Array]
},
//...
]
}
}

@@ -200,3 +263,3 @@ ```

Get search suggestions:
### Get search suggestions:
```js

@@ -224,3 +287,3 @@ const suggestions = await youtube.getSearchSuggestions('QUERY', {

Get details about a given video:
### Get video info:

@@ -285,23 +348,13 @@ ```js

Get comments:
### Get comments:
```js
const response = await youtube.getComments('VIDEO_ID');
```
Alternatively you can use:
// Or:
```js
const video = await youtube.getDetails('VIDEO_ID');
const response = await video.getComments();
// Get comment replies:
const replies = await response.comments[0].getReplies();
// Like, dislike, reply (same logic for replies):
await response.comments[0].like();
await response.comments[0].dislike();
await response.comments[0].reply('Nice comment!');
// Get comments continuation (same logic for replies):
const continuation = await response.getContinuation();
```
<details>

@@ -347,52 +400,24 @@ <summary>Output</summary>

Get home feed:
Reply to, like and dislike comments:
```js
const homefeed = await youtube.getHomeFeed();
await response.comments[0].like();
await response.comments[0].dislike();
await response.comments[0].reply('Nice comment!');
```
<details>
<summary>Output</summary>
<p>
Get comment replies:
```js
[
{
id: string,
title: string,
description: string,
channel: string,
metadata: {
view_count: string,
short_view_count_text: { simple_text: string, accessibility_label: string },
thumbnail: {
url: string,
width: number,
height: number
},
moving_thumbnail: {
url: string,
width: number,
height: number
},
published: string,
duration: {
seconds: number,
simple_text: string,
accessibility_label: string
},
badges: string,
owner_badges: [Array]
}
}
// ...
]
const replies = await response.comments[0].getReplies();
```
</p>
</details>
Get comments/replies continuation:
```js
const continuation = await response.getContinuation();
const replies_continuation = await replies.getContinuation();
```
Get subscriptions feed:
### Get home feed:
```js
const mysubsfeed = await youtube.getSubscriptionsFeed();
const homefeed = await youtube.getHomeFeed();
```
<details>

@@ -404,11 +429,16 @@ <summary>Output</summary>

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

@@ -424,57 +454,75 @@ width: number,

published: string,
duration: {
seconds: number,
simple_text: string,
accessibility_label: string
},
badges: string,
owner_badges: [Array]
}
}
//...
],
yesterday: [
}
},
// ...
]
}
```
</p>
</details>
Get continuation:
```js
const continuation = await homefeed.getContinuation();
````
### Get watch history:
```js
const history = await youtube.getHistory();
```
<details>
<summary>Output</summary>
<p>
```js
{
items: [
{
id: string,
title: string,
channel: string,
metadata: {
view_count: string,
short_view_count_text: { simple_text: string, accessibility_label: string },
thumbnail: {
url: string,
width: number,
height: number
date: string,
videos: [
{
id: string,
title: string,
channel: {
id: string,
name: string,
url: string
},
metadata: {
view_count: string,
short_view_count_text: {
simple_text: string,
accessibility_label: string
},
thumbnail: {
url: string,
width: number,
height: number
},
moving_thumbnail: {
url: string,
width: number,
height: number
},
published: string,
badges: [Array],
owner_badges: [Array]
}
},
moving_thumbnail: {
url: string,
width: number,
height: number
},
published: string,
badges: string,
owner_badges: [Array]
}
}
//...
]
},
//...
],
this_week: [
id: string,
title: string,
channel: string,
metadata: {
view_count: string,
thumbnail: {
url: string,
width: number,
height: number
},
moving_thumbnail: {
url: string,
width: number,
height: number
},
published: string,
badges: string,
owner_badges: [Array]
}
}
// ...
]
}
}
```

@@ -485,8 +533,12 @@

Get watch history:
Get continuation:
```js
const history = await youtube.getHistory();
const continuation = await history.getContinuation();
````
### Get subscriptions feed:
```js
const mysubsfeed = await youtube.getSubscriptionsFeed();
```
<details>

@@ -497,35 +549,43 @@ <summary>Output</summary>

```js
[
{
id: string,
title: string,
description: string,
channel: {
name: string,
url: string
},
metadata: {
view_count: string,
short_view_count_text: { simple_text: string, accessibility_label: string },
thumbnail: {
url: string,
width: number,
height: number
{
items: [
{
date: string,
videos: [
{
id: string,
title: string,
description: string,
channel: {
id: string,
name: string,
url: string
},
metadata: {
view_count: string,
short_view_count_text: {
simple_text: string,
accessibility_label: string
},
thumbnail: {
url: string,
width: number,
height: number
},
moving_thumbnail: {
url: string,
width: number,
height: number
},
published: string,
badges: [Array],
owner_badges: [Array]
}
},
//...
]
},
moving_thumbnail: {
url: string,
width: number,
height: number
},
published: string,
duration: {
seconds: number,
simple_text: string,
accessibility_label: string
},
badges: string,
owner_badges: [Array]
}
}
]
//...
]
}
```

@@ -536,4 +596,9 @@

Get notifications:
Get continuation:
```js
const continuation = await mysubsfeed.getContinuation();
````
### Get notifications:
```js

@@ -548,4 +613,5 @@ const notifications = await youtube.getNotifications();

```js
[
{
{
items: [
{
title: string,

@@ -567,5 +633,6 @@ sent_time: string,

notification_id: string
},
//...
]
},
//...
]
}
```

@@ -576,4 +643,9 @@

Get unseen notifications count:
Get continuation:
```js
const continuation = await notifications.getContinuation();
````
### Get unseen notifications count:
```js

@@ -583,26 +655,23 @@ const notifications = await youtube.getUnseenNotificationsCount();

Get song lyrics:
### Get song lyrics:
```js
const search = await youtube.search('Never give you up', { client: 'YTMUSIC' });
const lyrics = await youtube.getLyrics(search.songs[0].id);
const lyrics = await youtube.getLyrics(search.results.songs[0].id);
```
Get playlist:
### Get playlist:
YouTube (default):
```js
const search = await youtube.search('Interstellar Soundtrack', {
client: 'YTMUSIC'
});
const playlist = await youtube.getPlaylist('PLAYLIST_ID');
```
// YouTube Music
const playlist = await youtube.getPlaylist(search.playlists[0].id, {
YouTube Music:
```js
const playlist = await youtube.getPlaylist('PLAYLIST_ID', {
client: 'YTMUSIC'
});
// YouTube (default)
const playlist = await youtube.getPlaylist(search.playlists[0].id);
```
<details>
<summary>YouTube Music Output</summary>
<summary>YouTube Output</summary>
<p>

@@ -614,5 +683,5 @@

description: string,
total_items: number,
duration: string,
year: string,
total_items: string,
last_updated: string,
views: string,
items: [

@@ -623,6 +692,11 @@ {

author: string,
duration: string,
thumbnail: [Array]
duration: {
seconds: number,
simple_text: string,
accessibility_label: string
},
thumbnails: [Array]
},
//...
]
}

@@ -635,3 +709,3 @@ ```

<details>
<summary>YouTube Output</summary>
<summary>YouTube Music Output</summary>
<p>

@@ -643,5 +717,5 @@

description: string,
total_items: string,
last_updated: string,
views: string,
total_items: number,
duration: string,
year: string,
items: [

@@ -654,9 +728,7 @@ {

seconds: number,
simple_text: string,
accessibility_label: string
simple_text: string
},
thumbnail: [Array]
thumbnails: [Array]
},
//...
]
}

@@ -674,25 +746,24 @@ ```

* Subscribe/Unsubscribe:
```js
await youtube.interact.subscribe('CHANNEL_ID');
await youtube.interact.unsubscribe('CHANNEL_ID');
```
```js
await youtube.interact.subscribe('CHANNEL_ID');
await youtube.interact.unsubscribe('CHANNEL_ID');
```
* Like/Dislike:
```js
await youtube.interact.like('VIDEO_ID');
await youtube.interact.dislike('VIDEO_ID');
await youtube.interact.removeLike('VIDEO_ID');
```
```js
await youtube.interact.like('VIDEO_ID');
await youtube.interact.dislike('VIDEO_ID');
await youtube.interact.removeLike('VIDEO_ID');
```
* Comment:
```js
await youtube.interact.comment('VIDEO_ID', 'Haha, nice video!');
```
```js
await youtube.interact.comment('VIDEO_ID', 'Haha, nice video!');
```
* Change notification preferences:
```js
// Options: ALL | NONE | PERSONALIZED
await youtube.interact.changeNotificationPreferences('CHANNEL_ID', 'ALL');
```
```js
// Options: ALL | NONE | PERSONALIZED
await youtube.interact.changeNotificationPreferences('CHANNEL_ID', 'ALL');
```

@@ -705,26 +776,26 @@ These methods will always return ```{ success: true, status_code: 200 }``` if successful.

* Get account info:
```js
await youtube.account.info();
```
```js
await youtube.account.info();
```
<details>
<summary>Output</summary>
<p>
<details>
<summary>Output</summary>
<p>
```js
{
name: string,
photo: [
{
url: string,
width: number,
height: number
}
],
country: string,
language: string;
}
```
</p>
</details>
```js
{
name: string,
photo: [
{
url: string,
width: number,
height: number
}
],
country: string,
language: string;
}
```
</p>
</details>

@@ -736,25 +807,25 @@ <br>

* Subscription notifications:
```js
await youtube.account.settings.notifications.setSubscriptions(true);
```
```js
await youtube.account.settings.notifications.setSubscriptions(true);
```
* Recommended content notifications:
```js
await youtube.account.settings.notifications.setRecommendedVideos(true);
```
```js
await youtube.account.settings.notifications.setRecommendedVideos(true);
```
* Channel activity notifications:
```js
await youtube.account.settings.notifications.setChannelActivity(true);
```
```js
await youtube.account.settings.notifications.setChannelActivity(true);
```
* Comment replies notifications:
```js
await youtube.account.settings.notifications.setCommentReplies(true);
```
```js
await youtube.account.settings.notifications.setCommentReplies(true);
```
* Channel mention notifications:
```js
await youtube.account.settings.notifications.setSharedContent(true);
```
```js
await youtube.account.settings.notifications.setSharedContent(true);
```

@@ -764,12 +835,12 @@ #### Privacy settings:

* Subscriptions privacy:
```js
await youtube.account.settings.privacy.setSubscriptionsPrivate(true);
```
```js
await youtube.account.settings.privacy.setSubscriptionsPrivate(true);
```
* Saved playlists privacy:
```js
await youtube.account.settings.privacy.setSavedPlaylistsPrivate(true);
```
```js
await youtube.account.settings.privacy.setSavedPlaylistsPrivate(true);
```
### Fetching live chats:
### Live chats:
---

@@ -877,3 +948,3 @@

Cancelling a download:
Cancel a download:
```js

@@ -957,3 +1028,3 @@ stream.cancel();

OAuth:
#### OAuth:

@@ -991,3 +1062,3 @@ ```js

Cookies:
#### Cookies:

@@ -1005,2 +1076,3 @@ ```js

<!-- CONTRIBUTING -->
## Contributing

@@ -1011,2 +1083,11 @@ Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change.

<!-- CONTACT -->
## Contact
LuanRT - [@lrt_nooneknows](https://twitter.com/lrt_nooneknows) - luan.lrt4@gmail.com
Project Link: [https://github.com/LuanRT/YouTube.js](https://github.com/LuanRT/YouTube.js)
<!-- DISCLAIMER -->
## Disclaimer

@@ -1018,3 +1099,6 @@ This project is not affiliated with, endorsed, or sponsored by YouTube or any of their affiliates or subsidiaries.

<!-- LICENSE -->
## License
[MIT](https://choosealicense.com/licenses/mit/)
Distributed under the [MIT](https://choosealicense.com/licenses/mit/) License.
<p align="right">(<a href="#top">back to top</a>)</p>

@@ -5,4 +5,4 @@ 'use strict';

const Innertube = require('..');
const NToken = require('../lib/NToken');
const SigDecipher = require('../lib/Sig');
const NToken = require('../lib/deciphers/NToken');
const SigDecipher = require('../lib/deciphers/Sig');
const Constants = require('./constants');

@@ -17,5 +17,17 @@

if (!(youtube instanceof Error)) {
const search = await youtube.search('Carl Sagan - Documentary').catch((error) => error);
assert(!(search instanceof Error) && search.videos.length >= 1, `should search videos`, search);
const homefeed = await youtube.getHomeFeed();
assert(!(homefeed instanceof Error), `should retrieve recommendations`, homefeed);
const ytsearch = await youtube.search('Carl Sagan - Documentary').catch((error) => error);
assert(!(ytsearch instanceof Error) && ytsearch.videos.length, `should search on YouTube`, ytsearch);
const ytmsearch = await youtube.search('Logic - Obediently Yours', { client: 'YTMUSIC' }).catch((error) => error);
assert(!(ytmsearch instanceof Error), `should search on YouTube Music`, ytmsearch);
const ytsearch_suggestions = await youtube.getSearchSuggestions('test', { client: 'YOUTUBE' });
assert(!(ytsearch_suggestions instanceof Error), `should retrieve YouTube search suggestions`);
const ytmsearch_suggestions = await youtube.getSearchSuggestions('test', { client: 'YTMUSIC' });
assert(!(ytmsearch_suggestions instanceof Error), `should retrieve YouTube Music search suggestions`);
const details = await youtube.getDetails(Constants.test_video_id).catch((error) => error);

@@ -26,3 +38,12 @@ assert(!(details instanceof Error), `should retrieve details for ${Constants.test_video_id}`, details);

assert(!(comments instanceof Error), `should retrieve comments for ${Constants.test_video_id}`, comments);
const ytplaylist = await youtube.getPlaylist(ytmsearch.results.community_playlists[0].id, { client: 'YOUTUBE' });
assert(!(ytplaylist instanceof Error), `should retrieve and parse playlist with YouTube`, ytplaylist);
const ytmplaylist = await youtube.getPlaylist(ytmsearch.results.community_playlists[0].id, { client: 'YTMUSIC' });
assert(!(ytmplaylist instanceof Error), `should retrieve and parse playlist with YouTube Music`, ytmplaylist);
const lyrics = await youtube.getLyrics(ytmsearch.results.songs[0].id);
assert(!(lyrics instanceof Error), `should retrieve song lyrics`, lyrics);
const video = await downloadVideo(Constants.test_video_id, youtube).catch((error) => error);

@@ -32,4 +53,3 @@ assert(!(video instanceof Error), `should download video (${Constants.test_video_id})`, video);

const n_token = new NToken(Constants.n_scramble_sc).transform(Constants.original_ntoken);
const n_token = new NToken(Constants.n_scramble_sc, Constants.original_ntoken).transform();
assert(n_token == Constants.expected_ntoken, `should transform n token into ${Constants.expected_ntoken}`, n_token);

@@ -65,2 +85,2 @@

performTests();
performTests();
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