YouTube.js
A full-featured wrapper around the Innertube API, which is what YouTube itself uses.
Report Bug
·
Request Feature
Table of Contents
-
About The Project
-
Getting Started
-
Usage
- Contributing
- License
- Contact
- Disclaimer
About
Innertube is an API used across all YouTube clients, it was created to simplify the internal structure of the platform in a way that updates, tweaks, and experiments can be easily made. This library handles all the low-level communication with Innertube, providing a simple and efficient way to interact with YouTube programmatically.
And huge thanks to @gatecrasher777 for his research on the workings of the Innertube API!
Features
- Search videos, playlists, music, albums, artists, etc.
- Subscribe, unsubscribe, like, dislike, post comments, replies, and etc.
- Get subscriptions/home feed, notifications, watch history, and more.
- Easily sign in to any Google Account.
- Fetch live chat & live stats.
- Manage account settings.
- Manage playlists.
- Download videos.
~ And more!
Do note that you must be signed in to perform actions that involve an account; such as commenting, liking/disliking videos, sending messages to a live chat, etc.
Getting Started
Prerequisites
Installation
- NPM:
npm install youtubei.js@latest
- Yarn:
yarn add youtubei.js@latest
- Git (bleeding-edge version):
npm install git+https://github.com/LuanRT/YouTube.js.git
Usage
Create an Innertube instance (or session):
const Innertube = require('youtubei.js');
const youtube = await new Innertube({ gl: 'US' });
A simple search:
Client: YOUTUBE
| YTMUSIC
const search = await youtube.search('Looking for life on Mars - Documentary', { client: 'YOUTUBE' });
YouTube Output
{
query: string,
corrected_query: string,
estimated_results: number,
videos: [
{
id: string,
url: string,
title: string,
description: string,
metadata:{
view_count: string,
short_view_count_text: {
simple_text: string,
accessibility_label: string
},
thumbnails: [Array],
duration: {
seconds: number,
simple_text: string,
accessibility_label: string
},
published: string,
badges:[Array],
owner_badges:[Array]
}
}
]
}
YTMusic Output
{
query:string,
corrected_query:string,
results:{
top_result:[Array],
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]
},
]
}
}
Search suggestions:
const suggestions = await youtube.getSearchSuggestions('QUERY', { client: 'YOUTUBE' })
Output
[
{
text: string, bold_text: string
},
]
Video info:
const video = await youtube.getDetails('VIDEO_ID');
Output
{
title: string,
description: string,
thumbnail: {
url: string,
width: number,
height: number
},
metadata: {
embed: {
iframeUrl: string,
flashUrl: string,
width: number,
height: number,
flashSecureUrl: string
},
likes: number,
dislikes: number,
view_count: number,
average_rating: number,
length_seconds: number,
channel_id: string,
channel_url: string,
external_channel_id: string,
allow_ratings: boolean,
is_live_content: boolean,
is_family_safe: boolean,
is_unlisted: 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,
category: string,
channel_name: string,
publish_date: string,
upload_date: string,
keywords: [Array]
}
}
Sorting options: TOP_COMMENTS
| NEWEST_FIRST
const comments = await youtube.getComments('VIDEO_ID', 'TOP_COMMENTS');
Alternatively, you can use:
const video = await youtube.getDetails('VIDEO_ID');
const comments = await video.getComments();
Output
{
page_count: number,
comment_count: number,
items: [
{
text: string,
author: {
name: string,
thumbnails: [
{
url: string,
width: number,
height: number
}
],
channel_id: string
},
metadata:{
published: string,
is_liked: boolean,
is_disliked: boolean,
is_pinned: boolean,
is_channel_owner: boolean,
is_reply: boolean,
like_count: number,
reply_count: number,
id: string
}
},
]
}
Reply to, like/dislike, translate and report a comment:
const top_comment = comments.items[0];
await top_comment.like();
await top_comment.dislike();
await top_comment.report();
await top_comment.reply('Nice comment!');
await top_comment.translate('ru');
Comment replies:
const replies = await top_comment.getReplies();
Comments/replies continuation:
const continuation = await comments.getContinuation();
const replies_continuation = await replies.getContinuation();
Home feed:
const homefeed = await youtube.getHomeFeed();
Output
{
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,
duration: {
seconds: number,
simple_text: string,
accessibility_label: string
},
badges: string,
owner_badges: [Array]
}
},
]
}
Continuation:
const continuation = await homefeed.getContinuation();
Watch history:
const history = await youtube.getHistory();
Output
{
items: [
{
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]
}
},
]
},
]
}
Continuation:
const continuation = await history.getContinuation();
Subscriptions feed:
const mysubsfeed = await youtube.getSubscriptionsFeed();
Output
{
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]
}
},
]
},
]
}
Continuation:
const continuation = await mysubsfeed.getContinuation();
Trending content:
const trending = await youtube.getTrending();
Output
{
now: {
content: [
{
title: string,
videos: []
},
]
},
music: { getVideos: Promise.<Array> },
gaming: { getVideos: Promise.<Array> },
movies: { getVideos: Promise.<Array> }
}
Song lyrics:
const search = await youtube.search('Never give you up', { client: 'YTMUSIC' });
const lyrics = await youtube.getLyrics(search.results.songs[0].id);
Notifications:
const notifications = await youtube.getNotifications();
Output
{
items: [
{
title: string,
sent_time: string,
channel_name: string,
channel_thumbnail: {
url: string,
width: number,
height: number
},
video_thumbnail: {
url: string,
width: number,
height: number
},
video_url: string,
read: boolean,
notification_id: string
},
]
}
Continuation:
const continuation = await notifications.getContinuation();
Unseen notifications count:
const unread_notis_count = await youtube.getUnseenNotificationsCount();
Get playlist:
Client: YOUTUBE
| YTMUSIC
const playlist = await youtube.getPlaylist('PLAYLIST_ID', { client: 'YOUTUBE' });
YouTube Output
{
title: string,
description: string,
total_items: string,
last_updated: string,
views: string,
items: [
{
id: string,
title: string,
author: string,
duration: {
seconds: number,
simple_text: string,
accessibility_label: string
},
thumbnails: [Array]
},
]
}
YouTube Music Output
{
title: string,
description: string,
total_items: number,
duration: string,
year: string,
items: [
{
id: string,
title: string,
author: string,
duration: {
seconds: number,
simple_text: string
},
thumbnails: [Array]
},
}
Interactions:
Don't forget that you must be signed in to use some of the following methods!
-
Subscribe/Unsubscribe:
await youtube.interact.subscribe('CHANNEL_ID');
await youtube.interact.unsubscribe('CHANNEL_ID');
-
Like/Dislike:
await youtube.interact.like('VIDEO_ID');
await youtube.interact.dislike('VIDEO_ID');
await youtube.interact.removeLike('VIDEO_ID');
-
Comment:
await youtube.interact.comment('VIDEO_ID', 'Haha, nice video!');
-
Playlists:
const videos = [
'VIDEO_ID1',
'VIDEO_ID2',
'VIDEO_ID3'
];
await youtube.playlist.create('My Playlist', videos);
await youtube.playlist.delete('PLAYLIST_ID');
await youtube.playlist.addVideos('PLAYLIST_ID', videos);
await youtube.playlist.removeVideos('PLAYLIST_ID', videos);
-
Translate (does not require sign in)
await youtube.interact.translate('Hi mom!', 'ru');
-
Change notification preferences:
Options: ALL
| NONE
| PERSONALIZED
await youtube.interact.setNotificationPreferences('CHANNEL_ID', 'ALL');
Response schema:
{
success: boolean,
status_code: number,
playlist_id?: string,
translated_content?: string,
data?: object
}
Account Settings
It is also possible to manage account settings:
Notification settings:
Options: ON
| OFF
-
Subscription notifications:
await youtube.account.settings.notifications.setSubscriptions('ON');
-
Recommended content notifications:
await youtube.account.settings.notifications.setRecommendedVideos('ON');
-
Channel activity notifications:
await youtube.account.settings.notifications.setChannelActivity('ON');
-
Comment replies notifications:
await youtube.account.settings.notifications.setCommentReplies('ON');
-
Channel mention notifications:
await youtube.account.settings.notifications.setSharedContent('ON');
Privacy settings:
Options: ON
| OFF
Live chats:
Currently, the library can retrieve live chat messages, stats and also send messages.
const Innertube = require('youtubei.js');
async function start() {
const youtube = await new Innertube();
const search = await youtube.search('Lofi girl live');
const video = await youtube.getDetails(search.videos[0].id);
const livechat = video.getLivechat();
livechat.on('update-metadata', (data) => {
console.info('Info:', data);
});
livechat.on('chat-update', (message) => {
console.info(`- ${message.author.name}\n${message.text}\n\n`);
if(message.text == '!info') {
livechat.sendMessage('Hello! This message was sent from YouTube.js');
}
});
}
start();
Stop fetching the live chat:
livechat.stop();
Delete a message:
const msg = await livechat.sendMessage('Nice livestream!');
await msg.deleteMessage();
Downloading videos:
const options = {
format: string,
quality: string,
type: string,
range: { start: number, end: number }
};
const stream = youtube.download('VIDEO_ID', options);
Options:
-
format:
mp4
| webm
etc.. (note: only formats provided by YouTube are available)
-
quality:
144p
, 240p
, 360p
, 480p
, 720p
, 1080p
etc..
-
type:
video
| audio
| videoandaudio
-
range: indicates which bytes should be downloaded
- start: an integer indicating the beginning of the range
- end: an integer indicating the end of the range
Cancel a download:
stream.cancel();
Example:
const fs = require('fs');
const Innertube = require('youtubei.js');
async function start() {
const youtube = await new Innertube();
const search = await youtube.search('Looking for life on Mars - documentary');
const stream = youtube.download(search.videos[0].id, {
format: 'mp4',
quality: '360p',
type: 'videoandaudio'
});
stream.pipe(fs.createWriteStream(`./${search.videos[0].title}.mp4`));
stream.on('start', () => {
console.info('[DOWNLOADER]', 'Starting download now!');
});
stream.on('info', (info) => {
console.info('[DOWNLOADER]', `Downloading ${info.video_details.title} by ${info.video_details.metadata.channel_name}`);
});
stream.on('progress', (info) => {
process.stdout.clearLine();
process.stdout.cursorTo(0);
process.stdout.write(`[DOWNLOADER] Downloaded ${info.percentage}% (${info.downloaded_size}MB) of ${info.size}MB`);
});
stream.on('end', () => {
process.stdout.clearLine();
process.stdout.cursorTo(0);
console.info('[DOWNLOADER]', 'Done!');
});
stream.on('error', (err) => console.error('[ERROR]', err));
}
start();
Alternatively, you can get the deciphered streaming data and handle the download yourself:
const streaming_data = await youtube.getStreamingData('VIDEO_ID', options);
Output
{
selected_format: {
itag: number,
mimeType: string,
bitrate: number,
initRange: { start: string, end: string },
indexRange: { start: string, end: string },
lastModified: string,
contentLength: string,
quality: string,
projectionType: string,
averageBitrate: number,
highReplication: boolean,
audioQuality: string,
approxDurationMs: string,
audioSampleRate: string,
audioChannels: number,
loudnessDb: number,
url: string,
has_audio: boolean,
has_video: boolean
},
formats: [
{
itag: number,
mimeType: string,
bitrate: number,
initRange: { start: string, end: string },
indexRange: { start: string, end: string },
lastModified: string,
contentLength: string,
quality: string,
projectionType: string,
averageBitrate: number,
highReplication: boolean,
audioQuality: string,
approxDurationMs: string,
audioSampleRate: string,
audioChannels: number,
loudnessDb: number,
url: string,
has_audio: boolean,
has_video: boolean
}
]
}
Signing in:
When signing in to your account, you have two options:
- Use OAuth 2.0; easy, simple & reliable.
- Cookies; usually more complicated to get and unreliable.
OAuth:
const fs = require('fs');
const Innertube = require('youtubei.js');
const creds_path = './yt_oauth_creds.json';
async function start() {
const creds = fs.existsSync(creds_path) && JSON.parse(fs.readFileSync(creds_path).toString()) || {};
const youtube = await new Innertube();
youtube.ev.on('auth', (data) => {
if (data.status === 'AUTHORIZATION_PENDING') {
console.info(`Hello!\nOn your phone or computer, go to ${data.verification_url} and enter the code ${data.code}`);
} else if (data.status === 'SUCCESS') {
fs.writeFileSync(creds_path, JSON.stringify(data.credentials));
console.info('Successfully signed-in, enjoy!');
}
});
youtube.ev.on('update-credentials', (data) => {
fs.writeFileSync(creds_path, JSON.stringify(data.credentials));
console.info('Credentials updated!', data);
});
await youtube.signIn(creds);
}
start();
Sign out:
const response = await youtube.signOut();
if (response.success) {
console.log('You have successfully signed out');
}
Cookies:
const Innertube = require('youtubei.js');
async function start() {
const youtube = await new Innertube({ cookie: '...' });
}
start();
Contributing
Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change.
Please make sure to update tests as appropriate.
Contributors
Contact
LuanRT - @lrt_nooneknows - luan.lrt4@gmail.com
Project Link: https://github.com/LuanRT/YouTube.js
Disclaimer
This project is not affiliated with, endorsed, or sponsored by YouTube or any of their affiliates or subsidiaries.
All trademarks, logos and brand names are the property of their respective owners.
Should you have any questions or concerns please contact me directly via email.
License
Distributed under the MIT License.
(back to top)