Compare commits

..

18 Commits

Author SHA1 Message Date
LuanRT
2b5027eb06 fix: getLyrics() only working when signed-in 2022-02-06 15:26:16 -03:00
LuanRT
0c9f7135bf docs: oops 2022-02-05 19:25:23 -03:00
LuanRT
ce8a109398 docs: update table of contents 2022-02-05 19:23:07 -03:00
LuanRT
6aaa3360e8 docs: update YouTube Music examples 2022-02-05 19:17:12 -03:00
LuanRT
89c018c431 refactor: move getLyrics to Innertube.js 2022-02-05 19:16:36 -03:00
LuanRT
339a01f3a9 chore(release): v1.3.0 2022-02-05 18:45:30 -03:00
LuanRT
dd3f4c0009 chore: format code & other minor changes 2022-02-05 18:32:25 -03:00
LuanRT
7cd41e1d8a docs: add YouTube Music examples 2022-02-05 18:31:28 -03:00
LuanRT
6ac8561af2 feat: add lyrics support 2022-02-05 18:30:21 -03:00
LuanRT
b4607d531f fix(OAuth): secret not found due to bad regex 2022-02-04 15:20:35 -03:00
LuanRT
b3a1cdc1cd chore: remove ntoken 'translate' func var names 2022-02-03 04:54:37 -03:00
LuanRT
fd662df93d style: remove extra white space 2022-02-02 06:12:38 -03:00
LuanRT
8a1f4b4e55 Merge branch 'main' of https://github.com/LuanRT/YouTube.js 2022-02-02 06:04:49 -03:00
LuanRT
4ff83bdc3f style: add missing semi & rename some variables 2022-02-02 06:04:39 -03:00
LuanRT
c81e8e29ac chore: remove unnecessary condition 2022-02-01 18:49:17 -03:00
LuanRT
d5f884ff9b refactor: move refineNTokenData function to Utils 2022-02-01 16:47:02 -03:00
LuanRT
5517c2f202 fix: ntoken function 'translate2' not being parsed 2022-02-01 16:12:47 -03:00
LuanRT
3493a82765 chore: remove more unused variables 2022-01-31 04:31:45 -03:00
14 changed files with 767 additions and 81 deletions

681
README.md
View File

@@ -20,7 +20,7 @@ As of now, this is one of the most advanced & stable YouTube libraries out there
- Get detailed info about any video
- Fetch live chat & live stats in real time
- Get notifications
- Get subscriptions feed
- Get subscriptions/home feed
- Change notification preferences for a channel
- Subscribe/Unsubscribe/Like/Dislike/Comment etc
- Easily sign in to any Google Account
@@ -40,17 +40,17 @@ npm install youtubei.js
## Usage
[1. Basic Usage](https://github.com/LuanRT/YouTube.js#usage)
[1. Basic Usage](#usage)
[2. Interactions](https://github.com/LuanRT/YouTube.js#interactions)
[2. Interactions](#interactions)
[3. Live chats](https://github.com/LuanRT/YouTube.js#fetching-live-chats)
[3. Live chats](#fetching-live-chats)
[4. Downloading videos](https://github.com/LuanRT/YouTube.js#downloading-videos)
[4. Downloading videos](#downloading-videos)
[5. Signing-in](https://github.com/LuanRT/YouTube.js#signing-in)
[5. Signing-in](#signing-in)
[6. Disclaimer](https://github.com/LuanRT/YouTube.js#disclaimer)
[6. Disclaimer](#disclaimer)
First of all we're gonna start by initializing the Innertube instance.
And to make things faster, you should do this only once and reuse the Innertube object when needed.
@@ -62,11 +62,15 @@ const youtube = await new Innertube();
Doing a simple search:
```js
// YouTube:
const search = await youtube.search('Looking for life on Mars - Documentary');
// YTMusic:
const search = await youtube.search('Never Gonna Give You Up', { client: 'YTMUSIC' });
```
<details>
<summary>Output</summary>
<summary>YT Search Output</summary>
<p>
```js
@@ -124,9 +128,233 @@ const search = await youtube.search('Looking for life on Mars - Documentary');
</p>
</details>
<details>
<summary>YTMusic Search Output</summary>
<p>
Get details about a specific video:
```js
{
"songs":[
{
"id":"AhCWdFBd_fM",
"title":"Never Gonna Give You Up",
"artist":"Barry White",
"album":"Barry White - His Greatest Hits",
"duration":"4:50",
"thumbnail":{
"thumbnails":[
{
"url":"https://lh3.googleusercontent.com/S69XTGGI63L8rJJlCx7rJgp0decIo3EdDKGWR_y8B3SE9fEYp-VGu3ygZ3DrlS3tTTyqN8CUfpajjsA=w60-h60-l90-rj",
"width":60,
"height":60
},
{
"url":"https://lh3.googleusercontent.com/S69XTGGI63L8rJJlCx7rJgp0decIo3EdDKGWR_y8B3SE9fEYp-VGu3ygZ3DrlS3tTTyqN8CUfpajjsA=w120-h120-l90-rj",
"width":120,
"height":120
}
]
}
},
{
"id":"C5ITdcx-wRA",
"title":"Never Gonna Give You Up",
"artist":"Home Free",
"album":"Never Gonna Give You Up",
"duration":"3:03",
"thumbnail":{
"thumbnails":[
{
"url":"https://lh3.googleusercontent.com/isnABE6M8Dzq5WG7ZrJRGdKVmF8M8xJDJTM6aMf7d0C-XpqGIHFd1n7i6lgZM_K7LbHXuEuRaXhfnqc=w60-h60-l90-rj",
"width":60,
"height":60
},
{
"url":"https://lh3.googleusercontent.com/isnABE6M8Dzq5WG7ZrJRGdKVmF8M8xJDJTM6aMf7d0C-XpqGIHFd1n7i6lgZM_K7LbHXuEuRaXhfnqc=w120-h120-l90-rj",
"width":120,
"height":120
}
]
}
},
{
"id":"XxWzZxuo6Vk",
"title":"Never Gonna Give You Up",
"artist":"Barry White",
"album":"Dinah Washington",
"duration":"The Unforgettable Voices: 30 Best Of Dinah Washington & Barry White",
"thumbnail":{
"thumbnails":[
{
"url":"https://lh3.googleusercontent.com/KXnlog1Me9ONfcdtT1O4fHr2uRqYrhH1cHWksFmzNpJwzX8lNr_S5TlQsE8ICtihijkGCLp9y8ypI7BiOA=w60-h60-l90-rj",
"width":60,
"height":60
},
{
"url":"https://lh3.googleusercontent.com/KXnlog1Me9ONfcdtT1O4fHr2uRqYrhH1cHWksFmzNpJwzX8lNr_S5TlQsE8ICtihijkGCLp9y8ypI7BiOA=w120-h120-l90-rj",
"width":120,
"height":120
}
]
}
}
],
"videos":[
{
"id":"dMbZfdrzILA",
"title":"Rick Astley - Never Gonna Give You Up • TopPop",
"author":"TopPop",
"views":"313K views",
"duration":"3:25",
"thumbnail":{
"thumbnails":[
{
"url":"https://i.ytimg.com/vi/dMbZfdrzILA/sddefault.jpg?sqp=-oaymwEWCJADEOEBIAQqCghqEJQEGHgg6AJIWg&rs=AMzJL3kLVbR-3q7t0AIjayar6gbsUe-OoQ",
"width":400,
"height":225
}
]
}
},
{
"id":"IO9XlQrEt2Y",
"title":"Never Gonna Give You Up",
"author":"Rick Astley",
"views":"7.1M views",
"duration":"2:58",
"thumbnail":{
"thumbnails":[
{
"url":"https://i.ytimg.com/vi/IO9XlQrEt2Y/sddefault.jpg?sqp=-oaymwEWCJADEOEBIAQqCghqEJQEGHgg6AJIWg&rs=AMzJL3mqRLVI-_NB9-t7Qy-j2J030ejrKA",
"width":400,
"height":225
}
]
}
},
{
"id":"a9WHZ5M8I8w",
"title":"Rick Astley - Never Gonna Give You Up - Festival de Viña del Mar 2016 HD",
"author":"FESTIVALDEVINACHILE",
"views":"2.8M views",
"duration":"8:13",
"thumbnail":{
"thumbnails":[
{
"url":"https://i.ytimg.com/vi/a9WHZ5M8I8w/sddefault.jpg?sqp=-oaymwEWCJADEOEBIAQqCghqEJQEGHgg6AJIWg&rs=AMzJL3mJDPxemHrcREbbK-9uBM3SIRm-Og",
"width":400,
"height":225
}
]
}
}
],
"albums":[
{
"title":"Never Gonna Give You Up",
"author":"Royal Reggie",
"year":"2022",
"thumbnail":{
"thumbnails":[
{
"url":"https://lh3.googleusercontent.com/DeMUgtcPxCOye_JYJaywQjuJHZjdzSYqU-231p8ZADV1EmPC5w3poMQRfm3_U83wLQR0nUbXnJ_Ujyq8=w60-h60-l90-rj",
"width":60,
"height":60
},
{
"url":"https://lh3.googleusercontent.com/DeMUgtcPxCOye_JYJaywQjuJHZjdzSYqU-231p8ZADV1EmPC5w3poMQRfm3_U83wLQR0nUbXnJ_Ujyq8=w120-h120-l90-rj",
"width":120,
"height":120
},
{
"url":"https://lh3.googleusercontent.com/DeMUgtcPxCOye_JYJaywQjuJHZjdzSYqU-231p8ZADV1EmPC5w3poMQRfm3_U83wLQR0nUbXnJ_Ujyq8=w226-h226-l90-rj",
"width":226,
"height":226
},
{
"url":"https://lh3.googleusercontent.com/DeMUgtcPxCOye_JYJaywQjuJHZjdzSYqU-231p8ZADV1EmPC5w3poMQRfm3_U83wLQR0nUbXnJ_Ujyq8=w544-h544-l90-rj",
"width":544,
"height":544
}
]
}
},
{
"title":"Never Gonna Give You Up",
"author":"Carlos Marin",
"year":"2020",
"thumbnail":{
"thumbnails":[
{
"url":"https://lh3.googleusercontent.com/i6JslNpviVqPZfLCBvLvmDyscy8ZQ2CShRz38hGj-PUz_H4wLTR0gkKU6tQoREDoARwKBl1krytJdBQ=w60-h60-l90-rj",
"width":60,
"height":60
},
{
"url":"https://lh3.googleusercontent.com/i6JslNpviVqPZfLCBvLvmDyscy8ZQ2CShRz38hGj-PUz_H4wLTR0gkKU6tQoREDoARwKBl1krytJdBQ=w120-h120-l90-rj",
"width":120,
"height":120
},
{
"url":"https://lh3.googleusercontent.com/i6JslNpviVqPZfLCBvLvmDyscy8ZQ2CShRz38hGj-PUz_H4wLTR0gkKU6tQoREDoARwKBl1krytJdBQ=w226-h226-l90-rj",
"width":226,
"height":226
},
{
"url":"https://lh3.googleusercontent.com/i6JslNpviVqPZfLCBvLvmDyscy8ZQ2CShRz38hGj-PUz_H4wLTR0gkKU6tQoREDoARwKBl1krytJdBQ=w544-h544-l90-rj",
"width":544,
"height":544
}
]
}
},
{
"title":"Never Gonna Give You Up",
"author":"Magnus Carlsson",
"year":"2021",
"thumbnail":{
"thumbnails":[
{
"url":"https://lh3.googleusercontent.com/43cP--d3Eqj3L5FeBbYFkdsnisf7nYb5-Rq4yZDf1kvH6_pEk4RW4Nd004L4ZwG6VzzT4JzFDVPS-8w=w60-h60-l90-rj",
"width":60,
"height":60
},
{
"url":"https://lh3.googleusercontent.com/43cP--d3Eqj3L5FeBbYFkdsnisf7nYb5-Rq4yZDf1kvH6_pEk4RW4Nd004L4ZwG6VzzT4JzFDVPS-8w=w120-h120-l90-rj",
"width":120,
"height":120
},
{
"url":"https://lh3.googleusercontent.com/43cP--d3Eqj3L5FeBbYFkdsnisf7nYb5-Rq4yZDf1kvH6_pEk4RW4Nd004L4ZwG6VzzT4JzFDVPS-8w=w226-h226-l90-rj",
"width":226,
"height":226
},
{
"url":"https://lh3.googleusercontent.com/43cP--d3Eqj3L5FeBbYFkdsnisf7nYb5-Rq4yZDf1kvH6_pEk4RW4Nd004L4ZwG6VzzT4JzFDVPS-8w=w544-h544-l90-rj",
"width":544,
"height":544
}
]
}
}
]
}
```
</p>
</details>
<br>
Find lyrics for a song, directly from YTMusic!
```js
const search = await youtube.search('7 Years', { client: 'YTMUSIC' });
const lyrics = await search.songs[0].getLyrics();
// Or:
const lyrics = await youtube.getLyrics(search.songs[0].id);
```
Get video details:
```js
const video = await youtube.getDetails(search.videos[0].id);
@@ -276,9 +504,442 @@ const comments_continuation = await comments.getContinuation();
</p>
</details>
Get home feed:
```js
const home_feed = await youtube.getHomeFeed();
```
<details>
<summary>Output</summary>
<p>
```js
[
{
"id":"TVcKu_c1C7E",
"title":"The Things We Don't Know About the Universe",
"channel":"Sciencephile the AI",
"metadata":{
"view_count":"233,835 views",
"thumbnail":{
"url":"https://i.ytimg.com/vi/TVcKu_c1C7E/hq720.jpg?sqp=-oaymwEcCNAFEJQDSFXyq4qpAw4IARUAAIhCGAFwAcABBg==&rs=AOn4CLByf2X2pKs1wbWyLIx-FnXFt1PVZw",
"width":720,
"height":404
},
"moving_thumbnail":{
"url":"https://i.ytimg.com/an_webp/TVcKu_c1C7E/mqdefault_6s.webp?du=3000&sqp=COGe-48G&rs=AOn4CLDslQ2RYSgaF3aEY8pcuyIzvfLJbQ",
"width":320,
"height":180
},
"published":"3 months ago",
"badges":[
],
"owner_badges":[
"Verified"
]
}
},
{
"id":"1Tdx9ZZuf_w",
"title":"Life is Strange: Before The Storm Remastered Episode 1 Gameplay Walkthrough",
"channel":"DomTheBomb",
"metadata":{
"view_count":"N/A",
"thumbnail":{
"url":"https://i.ytimg.com/vi/1Tdx9ZZuf_w/hq720_live.jpg?sqp=CMzD-48G-oaymwEcCNAFEJQDSFXyq4qpAw4IARUAAIhCGAFwAcABBg==&rs=AOn4CLC9N4GPouauc-Jw3Y69uCXWVEfBiQ",
"width":720,
"height":404
},
"moving_thumbnail":[
],
"published":"N/A",
"badges":[
"LIVE NOW"
],
"owner_badges":[
"Verified"
]
}
},
{
"id":"C776bXWlgSk",
"title":"Probably my Most BRUTAL BIRDEATER TARANTULA ever .",
"channel":"Exotics Lair",
"metadata":{
"view_count":"346,246 views",
"thumbnail":{
"url":"https://i.ytimg.com/vi/C776bXWlgSk/hqdefault.jpg?sqp=-oaymwEcCOADEI4CSFXyq4qpAw4IARUAAIhCGAFwAcABBg==&rs=AOn4CLDgWQrLwmFTG-KzLUYRdR9KFA5bDw",
"width":480,
"height":270
},
"moving_thumbnail":{
"url":"https://i.ytimg.com/an_webp/C776bXWlgSk/mqdefault_6s.webp?du=3000&sqp=COal-48G&rs=AOn4CLAfsyW-NUXvgFqAHBJr_3pJDZlfhA",
"width":320,
"height":180
},
"published":"5 months ago",
"badges":[
],
"owner_badges":[
"Verified"
]
}
},
{
"id":"nrvnpFCcZeA",
"title":"Run Windows 11 on phone! And play PC games?!!!",
"channel":"Geekerwan",
"metadata":{
"view_count":"519,217 views",
"thumbnail":{
"url":"https://i.ytimg.com/vi/nrvnpFCcZeA/hqdefault.jpg?sqp=-oaymwEcCOADEI4CSFXyq4qpAw4IARUAAIhCGAFwAcABBg==&rs=AOn4CLA0ny_9_K9iLL_u7grHZ4PoAJkH4Q",
"width":480,
"height":270
},
"moving_thumbnail":{
"url":"https://i.ytimg.com/an_webp/nrvnpFCcZeA/mqdefault_6s.webp?du=3000&sqp=CKCR-48G&rs=AOn4CLB9atuuBjdMCWTHgP-v4N75agxvPA",
"width":320,
"height":180
},
"published":"1 month ago",
"badges":[
],
"owner_badges":[
]
}
},
{
"id":"owtvGIQDdiU",
"title":"This Is What The Clouds Of Titan Sound Like! Huygens Probe Sound Recording 2005 (4K UHD)",
"channel":"V101 Science",
"metadata":{
"view_count":"744,350 views",
"thumbnail":{
"url":"https://i.ytimg.com/vi/owtvGIQDdiU/hq720.jpg?sqp=-oaymwEcCNAFEJQDSFXyq4qpAw4IARUAAIhCGAFwAcABBg==&rs=AOn4CLDbQLvaaOO3jW9bGY1SaQAdxIUJOQ",
"width":720,
"height":404
},
"moving_thumbnail":{
"url":"https://i.ytimg.com/an_webp/owtvGIQDdiU/mqdefault_6s.webp?du=3000&sqp=CImy-48G&rs=AOn4CLDXD3UPQ6iix8orhjJs3yDPpJlckQ",
"width":320,
"height":180
},
"published":"1 year ago",
"badges":[
],
"owner_badges":[
"Verified"
]
}
},
{
"id":"pI7ytZ1oyY4",
"title":"My Pet Praying Mantis Passed Away. Goodbye, Kiwi..",
"channel":"제발돼라 PleaseBee",
"metadata":{
"view_count":"3,311,251 views",
"thumbnail":{
"url":"https://i.ytimg.com/vi/pI7ytZ1oyY4/hq720.jpg?sqp=-oaymwEcCNAFEJQDSFXyq4qpAw4IARUAAIhCGAFwAcABBg==&rs=AOn4CLDisjK7ZecKov3aFUsE-Xe5x92VEQ",
"width":720,
"height":404
},
"moving_thumbnail":{
"url":"https://i.ytimg.com/an_webp/pI7ytZ1oyY4/mqdefault_6s.webp?du=3000&sqp=CKbE-48G&rs=AOn4CLD_wWCno1Q6EqhLYp7kMkCPc0y0dg",
"width":320,
"height":180
},
"published":"3 weeks ago",
"badges":[
],
"owner_badges":[
]
}
},
{
"id":"dXdoim96v5A",
"title":"8-bit CPU control logic: Part 1",
"channel":"Ben Eater",
"metadata":{
"view_count":"718,780 views",
"thumbnail":{
"url":"https://i.ytimg.com/vi/dXdoim96v5A/hq720.jpg?sqp=-oaymwEcCNAFEJQDSFXyq4qpAw4IARUAAIhCGAFwAcABBg==&rs=AOn4CLCBSzOjvO-0645gRSb8WezGKYSvUg",
"width":720,
"height":404
},
"moving_thumbnail":{
"url":"https://i.ytimg.com/an_webp/dXdoim96v5A/mqdefault_6s.webp?du=3000&sqp=CKCO-48G&rs=AOn4CLArMCkZu073j8k_QRHCm_4IuB2Uyw",
"width":320,
"height":180
},
"published":"4 years ago",
"badges":[
],
"owner_badges":[
"Verified"
]
}
},
{
"id":"AawLM81gIHo",
"title":"playlist to study like a medieval philosopher having the truth revealed by divine grace",
"channel":"Filosofia Acadêmica",
"metadata":{
"view_count":"3,182,206 views",
"thumbnail":{
"url":"https://i.ytimg.com/vi/AawLM81gIHo/hqdefault.jpg?sqp=-oaymwEcCOADEI4CSFXyq4qpAw4IARUAAIhCGAFwAcABBg==&rs=AOn4CLA_6jX1WlJ8JgjdzJ4vBdmajwkZmA",
"width":480,
"height":270
},
"moving_thumbnail":[
],
"published":"4 months ago",
"badges":[
],
"owner_badges":[
]
}
},
{
"id":"ILyQ4A3ZYu8",
"title":"LIGHTWEIGHT Mirrorless Macro Photography w/ 7Artisans 60mm f/2.8 mkII macro",
"channel":"Thomas Shahan",
"metadata":{
"view_count":"26,594 views",
"thumbnail":{
"url":"https://i.ytimg.com/vi/ILyQ4A3ZYu8/hq720.jpg?sqp=-oaymwEcCNAFEJQDSFXyq4qpAw4IARUAAIhCGAFwAcABBg==&rs=AOn4CLAYn-kG_hpBdu0BZ0QLeb9EdnkXfg",
"width":720,
"height":404
},
"moving_thumbnail":{
"url":"https://i.ytimg.com/an_webp/ILyQ4A3ZYu8/mqdefault_6s.webp?du=3000&sqp=CMit-48G&rs=AOn4CLBdzzV12LamKVEyPdPUXeHqLwqOzg",
"width":320,
"height":180
},
"published":"5 months ago",
"badges":[
],
"owner_badges":[
]
}
},
{
"id":"8E2l4K0OvMc",
"title":"Linkin Park & Eminem - Soldiers (2021) Official Music Video",
"channel":"zwieR.Z.",
"metadata":{
"view_count":"2,949,402 views",
"thumbnail":{
"url":"https://i.ytimg.com/vi/8E2l4K0OvMc/hq720.jpg?sqp=-oaymwEcCNAFEJQDSFXyq4qpAw4IARUAAIhCGAFwAcABBg==&rs=AOn4CLAqJt07Iwc5IFmUv-HazVO9OZWzFw",
"width":720,
"height":404
},
"moving_thumbnail":[
],
"published":"1 month ago",
"badges":[
],
"owner_badges":[
"Verified"
]
}
},
{
"id":"XFqn3uy238E",
"title":"...And We'll Do it Again",
"channel":"Kurzgesagt In a Nutshell",
"metadata":{
"view_count":"8,782,886 views",
"thumbnail":{
"url":"https://i.ytimg.com/vi/XFqn3uy238E/hq720.jpg?sqp=-oaymwEcCNAFEJQDSFXyq4qpAw4IARUAAIhCGAFwAcABBg==&rs=AOn4CLATvhGBLlbbTmMV4aDwmsBRqlh3rQ",
"width":720,
"height":404
},
"moving_thumbnail":{
"url":"https://i.ytimg.com/an_webp/XFqn3uy238E/mqdefault_6s.webp?du=3000&sqp=COGu-48G&rs=AOn4CLCghw3iwEHMhpOZU2Sd79eQAUZg9g",
"width":320,
"height":180
},
"published":"1 month ago",
"badges":[
],
"owner_badges":[
"Verified"
]
}
},
{
"id":"Ks7mmrer37g",
"title":"Using 5000 Minecarts to LAG a Pay-To-Win Server in Minecraft - Part 1",
"channel":"The Horizon",
"metadata":{
"view_count":"3,351,437 views",
"thumbnail":{
"url":"https://i.ytimg.com/vi/Ks7mmrer37g/hq720.jpg?sqp=-oaymwEcCNAFEJQDSFXyq4qpAw4IARUAAIhCGAFwAcABBg==&rs=AOn4CLA16Zi1QZ5YACpDMM0We5k5C6Cc0w",
"width":720,
"height":404
},
"moving_thumbnail":{
"url":"https://i.ytimg.com/an_webp/Ks7mmrer37g/mqdefault_6s.webp?du=3000&sqp=COip-48G&rs=AOn4CLDY3tU8sodjfeSM61JtfSovZIcmww",
"width":320,
"height":180
},
"published":"13 days ago",
"badges":[
],
"owner_badges":[
"Verified"
]
}
},
{
"id":"y7fMTCJaWjQ",
"title":"2004 PC Power On",
"channel":"hardrivethrutown",
"metadata":{
"view_count":"336,545 views",
"thumbnail":{
"url":"https://i.ytimg.com/vi/y7fMTCJaWjQ/hq720.jpg?sqp=-oaymwEcCNAFEJQDSFXyq4qpAw4IARUAAIhCGAFwAcABBg==&rs=AOn4CLB2GeuZo6-IUkJKl6M0VYhtAowNqQ",
"width":720,
"height":404
},
"moving_thumbnail":{
"url":"https://i.ytimg.com/an_webp/y7fMTCJaWjQ/mqdefault_6s.webp?du=3000&sqp=CKjB-48G&rs=AOn4CLAUUDj7VcH8uIysPlgcVZ7rx2AFaQ",
"width":320,
"height":180
},
"published":"1 month ago",
"badges":[
],
"owner_badges":[
]
}
},
{
"id":"oq2-RJH_3NE",
"title":"What if we replace the Moon with other Space Objects?",
"channel":"Sciencephile the AI",
"metadata":{
"view_count":"128,961 views",
"thumbnail":{
"url":"https://i.ytimg.com/vi/oq2-RJH_3NE/hq720.jpg?sqp=-oaymwEcCNAFEJQDSFXyq4qpAw4IARUAAIhCGAFwAcABBg==&rs=AOn4CLBuCNxw_LAFkvrGfnWZ5rI_fI2cuw",
"width":720,
"height":404
},
"moving_thumbnail":{
"url":"https://i.ytimg.com/an_webp/oq2-RJH_3NE/mqdefault_6s.webp?du=3000&sqp=CNSk-48G&rs=AOn4CLAKV2gb8ZMH6_pEGroEC6FIZmyTjg",
"width":320,
"height":180
},
"published":"7 days ago",
"badges":[
],
"owner_badges":[
"Verified"
]
}
},
{
"id":"pB-nGyqJMy0",
"title":"for 4 years I thought PISSY was a boy ~ Maybe thats why SHEs always PISSED at me !!!",
"channel":"Exotics Lair",
"metadata":{
"view_count":"974,222 views",
"thumbnail":{
"url":"https://i.ytimg.com/vi/pB-nGyqJMy0/hqdefault.jpg?sqp=-oaymwEcCOADEI4CSFXyq4qpAw4IARUAAIhCGAFwAcABBg==&rs=AOn4CLAf7vp-L054e_PSMrs5z_GSoIKC1w",
"width":480,
"height":270
},
"moving_thumbnail":{
"url":"https://i.ytimg.com/an_webp/pB-nGyqJMy0/mqdefault_6s.webp?du=3000&sqp=CMqL-48G&rs=AOn4CLC-yzDWXwJo45NudEmzQBMQ_8nsNg",
"width":320,
"height":180
},
"published":"1 year ago",
"badges":[
],
"owner_badges":[
"Verified"
]
}
},
{
"id":"RW4s2WtwoUY",
"title":"This Is Why Crabs Hate Stingrays",
"channel":"WATOP",
"metadata":{
"view_count":"4,329,223 views",
"thumbnail":{
"url":"https://i.ytimg.com/vi/RW4s2WtwoUY/hq720.jpg?sqp=-oaymwEcCNAFEJQDSFXyq4qpAw4IARUAAIhCGAFwAcABBg==&rs=AOn4CLD5iz1haM6niPfCZxeZFfublo_tPQ",
"width":720,
"height":404
},
"moving_thumbnail":{
"url":"https://i.ytimg.com/an_webp/RW4s2WtwoUY/mqdefault_6s.webp?du=3000&sqp=CMS1-48G&rs=AOn4CLBkPjwEFtz6e6iNOpB13tWcuik4pw",
"width":320,
"height":180
},
"published":"8 months ago",
"badges":[
],
"owner_badges":[
"Verified"
]
}
},
{
"id":"0wj8ueoUjt8",
"title":"RagnBone Man & Calvin Harris - Giant (Live at BRITs 2019)",
"channel":"Rag'n'Bone Man",
"metadata":{
"view_count":"5,636,727 views",
"thumbnail":{
"url":"https://i.ytimg.com/vi/0wj8ueoUjt8/hq720.jpg?sqp=-oaymwEcCNAFEJQDSFXyq4qpAw4IARUAAIhCGAFwAcABBg==&rs=AOn4CLDgmKnz4ilXGAdfU3zBBhZVPe9ERw",
"width":720,
"height":404
},
"moving_thumbnail":[
],
"published":"2 years ago",
"badges":[
],
"owner_badges":[
"Official Artist Channel"
]
}
}
]
```
</p>
</details>
Get subscriptions feed:
```js
const mysubfeed = await youtube.getSubscriptionsFeed();
const mysubsfeed = await youtube.getSubscriptionsFeed();
```
<details>

View File

@@ -63,8 +63,8 @@ async function engage(session, engagement_type, args = {}) {
* @param {string} action_type Type of action.
* @returns {object} { success: boolean, status_code: number, data: object } | { success: boolean, status_code: number, message: string }
*/
async function browse(session, action_type, args) {
if (!session.logged_in) throw new Error('You are not signed-in');
async function browse(session, action_type, args = {}) {
if (!session.logged_in && action_type != 'lyrics') throw new Error('You are not signed-in');
let data;
switch (action_type) { // TODO: Handle more actions
@@ -80,12 +80,24 @@ async function browse(session, action_type, args) {
browseId: 'FEsubscriptions'
};
break;
case 'lyrics':
const yt_music_context = JSON.parse(JSON.stringify(session.context)); // deep copy the context obj so we don't accidentally change it
yt_music_context.client.originalUrl = Constants.URLS.YT_MUSIC_URL;
yt_music_context.client.clientVersion = '1.20211213.00.00';
yt_music_context.client.clientName = 'WEB_REMIX';
data = {
context: yt_music_context,
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(`${Constants.URLS.YT_BASE_URL}/youtubei/v1/browse${session.logged_in && session.cookie.length < 1 ? '' : `?key=${session.key}`}`,
JSON.stringify(data), Constants.INNERTUBE_REQOPTS({ session })).catch((error) => error);
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);
if (response instanceof Error) return { success: false, status_code: response.response.status, message: response.message };
@@ -266,7 +278,6 @@ async function getVideoInfo(session, args = {}) {
return response.data;
}
/**
* Requests continuation for previously performed actions.
*

View File

@@ -48,8 +48,8 @@ module.exports = {
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';
info.desktop && 'https://www.youtube.com' || 'https://m.youtube.com';
let req_opts = {
params: info.params || {},
headers: {
@@ -105,14 +105,14 @@ module.exports = {
'owner_profile_url'
],
BLACKLISTED_KEYS: [
'is_owner_viewing', 'is_unplugged_corpus',
'is_owner_viewing', 'is_unplugged_corpus',
'is_crawlable', 'allow_ratings', 'author'
],
BASE64_DIALECT: {
NORMAL: 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_'.split(''),
REVERSE: '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_'.split('')
},
FUNCS_REGEX: /d\.push\(e\)|d\.reverse\(\)|d\[0\]\)\[0\]\)|f=d\[0];d\[0\]|d\.length;d\.splice\(e,1\)|function\(\){for\(var|function\(d,e,f\){var k=f|function\(d\){for\(var|reverse\(\)\.forEach|unshift\(d\.pop\(\)\)|function\(d,e\){for\(var f/,
FUNCS_REGEX: /d\.push\(e\)|d\.reverse\(\)|d\[0\]\)\[0\]\)|f=d\[0];d\[0\]|d\.length;d\.splice\(e,1\)|function\(\){for\(var|function\(d,e,f\){var|function\(d\){for\(var|reverse\(\)\.forEach|unshift\(d\.pop\(\)\)|function\(d,e\){for\(var f/,
FUNCS: {
PUSH: 'd.push(e)',
REVERSE_1: 'd.reverse()',
@@ -124,16 +124,6 @@ module.exports = {
ROTATE_2: 'unshift(d.pop())',
BASE64_DIA: 'function(){for(var',
TRANSLATE_1: 'function(d,e){for(var f',
TRANSLATE_2: 'function(d,e,f){var k=f'
},
// Just a helper function, felt like Utils.js wasn't the right place for it:
formatNTransformData: (data) => {
return data
.replace(/function\(d,e\)/g, '"function(d,e)').replace(/function\(d\)/g, '"function(d)')
.replace(/function\(\)/g, '"function()').replace(/function\(d,e,f\)/g, '"function(d,e,f)')
.replace(/\[function\(d,e,f\)/g, '["function(d,e,f)').replace(/,b,/g, ',"b",')
.replace(/,b/g, ',"b"').replace(/b,/g, '"b",').replace(/b]/g, '"b"]')
.replace(/\[b/g, '["b"').replace(/}]/g, '"]').replace(/},/g, '}",')
.replace(/""/g, '').replace(/length]\)}"/g, 'length])}');
TRANSLATE_2: 'function(d,e,f){var'
}
};

View File

@@ -150,6 +150,25 @@ class Innertube {
return refined_data;
}
/**
* Retrieves the lyrics for a given song
*
* @param {string} id
* @returns {string} Song lyrics
*/
async getLyrics(id) {
const data_continuation = await Actions.getContinuation(this, { video_id: id, ytmusic: true });
const lyrics_tab = data_continuation.data.contents.singleColumnMusicWatchNextResultsRenderer.tabbedRenderer
.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);
const lyrics = response.data.contents.sectionListRenderer.contents[0].musicDescriptionShelfRenderer.description.runs[0].text;
return lyrics
}
/**
* Gets the comments section of a video.
*
@@ -228,8 +247,8 @@ class Innertube {
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',
badges: content.videoRenderer.badges && content.videoRenderer.badges.map((badge) => badge.metadataBadgeRenderer.label) || 'N/A',
owner_badges: content.videoRenderer.ownerBadges && content.videoRenderer.ownerBadges.map((badge) => badge.metadataBadgeRenderer.tooltip) || '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) || []
}
}
}).filter((video) => video);

View File

@@ -1,8 +1,6 @@
'use strict';
const Axios = require('axios');
const Actions = require('./Actions');
const Constants = require('./Constants');
const EventEmitter = require('events');
class Livechat extends EventEmitter {

View File

@@ -6,8 +6,8 @@ const Constants = require('./Constants');
class NToken {
constructor(raw_code) {
this.raw_code = raw_code;
this.null_placeholder_regex = /c\[(.*?)\]=c/g;
this.transformation_calls_regex = /c\[(.*?)\]\((.+?)\)/g;
this.placeholders_regex = /c\[(.*?)\]=c/g;
this.calls_regex = /c\[(.*?)\]\((.+?)\)/g;
}
/**
@@ -41,21 +41,21 @@ class NToken {
return el;
});
// Fills the null placeholders with a copy of the transformations array
const null_placeholder_positions = [...this.raw_code.matchAll(this.null_placeholder_regex)].map((item) => parseInt(item[1]));
null_placeholder_positions.forEach((pos) => transformations[pos] = transformations);
// Fills all placeholders with the transformations array
const placeholder_indexes = [...this.raw_code.matchAll(this.placeholders_regex)].map((item) => parseInt(item[1]));
placeholder_indexes.forEach((i) => transformations[i] = transformations);
// Parses and emulates calls to the functions of the transformations array
const transformation_calls = [...Utils.getStringBetweenStrings(this.raw_code.replace(/\n/g, ''), 'try{', '}catch')
.matchAll(this.transformation_calls_regex)].map((params) => ({ index: params[1], params: params[2] }));
const function_calls = [...Utils.getStringBetweenStrings(this.raw_code.replace(/\n/g, ''), 'try{', '}catch')
.matchAll(this.calls_regex)].map((params) => ({ index: params[1], params: params[2] }));
transformation_calls.forEach((data) => {
function_calls.forEach((data) => {
const param_index = data.params.split(',').map((param) => param.match(/c\[(.*?)\]/)[1]);
const base64_dia = (param_index[2] && transformations[param_index[2]]());
transformations[data.index](transformations[param_index[0]], transformations[param_index[1]], base64_dia);
});
} catch (err) {
console.error(`Could not transform n-token (${n}), download may be throttled:`, err)
console.error(`Could not transform n-token (${n}), download may be throttled:`, err.message);
return n;
}
return n_token.join('');
@@ -71,7 +71,7 @@ class NToken {
*/
#getTransformationData() {
const data = `[${Utils.getStringBetweenStrings(this.raw_code.replace(/\n/g, ''), 'c=[', '];c')}]`;
return JSON.parse(Constants.formatNTransformData(data));
return JSON.parse(Utils.refineNTokenData(data));
}
/**

View File

@@ -20,7 +20,7 @@ class OAuth extends EventEmitter {
this.scope = Constants.OAUTH.SCOPE;
this.auth_script_regex = /<script id=\"base-js\" src=\"(.*?)\" nonce=".*?"><\/script>/;
this.identity_regex = /.+?={};var .+?={clientId:\"(?<id>.+?)\",si:\"(?<secret>.+?)\"},/;
this.identity_regex = /.+?={};var .+?={clientId:\"(?<id>.+?)\",.+?:\"(?<secret>.+?)\"},/;
if (auth_info.access_token) return;
this.#requestAuthCode();

View File

@@ -77,14 +77,6 @@ class Parser {
const tabs = this.data.contents.tabbedSearchResultsRenderer.tabs;
const contents = tabs[0].tabRenderer.content.sectionListRenderer.contents;
/**
* WIP
**/
const getLyrics = async (id) => {
// const data_continuation = await Actions.getContinuation(this.session, { video_id: id, ytmusic: true });
return undefined;
}
const songs_ms = contents.find((content) => content.musicShelfRenderer.title.runs[0].text == 'Songs');
const songs = songs_ms.musicShelfRenderer.contents.map((item) => {
const list_item = item.musicResponsiveListItemRenderer;
@@ -95,7 +87,7 @@ class Parser {
album: list_item.flexColumns[1].musicResponsiveListItemFlexColumnRenderer.text.runs[4].text,
duration: list_item.flexColumns[1].musicResponsiveListItemFlexColumnRenderer.text.runs[6].text,
thumbnail: list_item.thumbnail.musicThumbnailRenderer.thumbnail,
getLyrics: () => getLyrics(list_item.playlistItemData.videoId)
getLyrics: () => this.session.getLyrics(list_item.playlistItemData.videoId)
};
});
@@ -109,7 +101,7 @@ class Parser {
views: list_item.flexColumns[1].musicResponsiveListItemFlexColumnRenderer.text.runs[4].text,
duration: list_item.flexColumns[1].musicResponsiveListItemFlexColumnRenderer.text.runs[6].text,
thumbnail: list_item.thumbnail.musicThumbnailRenderer.thumbnail,
getLyrics: () => getLyrics(list_item.playlistItemData.videoId)
getLyrics: () => this.session.getLyrics(list_item.playlistItemData.videoId)
};
});
@@ -133,7 +125,7 @@ class Parser {
const playability_status = desktop_v && this.data.playabilityStatus ||
this.data[2].playerResponse.playabilityStatus;
if (playability_status.status == 'ERROR')
if (playability_status.status == 'ERROR')
throw new Error(`Could not retrieve details for this video: ${playability_status.status} - ${playability_status.reason}`);
const details = desktop_v && this.data.videoDetails ||

View File

@@ -25,7 +25,7 @@ class Player {
// Caches the current player so we don't have to download it all the time.
Fs.mkdirSync(this.tmp_cache_dir, { recursive: true });
Fs.writeFileSync(`${this.tmp_cache_dir}/${this.player_name}.js`, response.data);
} catch (err) {}
} catch (err) { }
this.sig_decipher_sc = this.#getSigDecipherCode(response.data);
this.ntoken_sc = this.#getNEncoder(response.data);

View File

@@ -12,9 +12,9 @@ class SigDecipher {
this.actions_regex = /;.{2}\.(.{2})\(.*?,(.*?)\)/g;
}
/**
* Deciphers signature.
*/
/**
* Deciphers signature.
*/
decipher() {
const args = QueryString.parse(this.url);
const functions = this.#getFunctions();

View File

@@ -156,9 +156,9 @@ function encodeFilter(period, duration, order) {
const youtube_proto = Proto(Fs.readFileSync(`${__dirname}/proto/youtube.proto`));
const periods = { 'any': null, 'hour': 1, 'day': 2, 'week': 3, 'month': 4, 'year': 5 };
const durations = { 'any': null, 'short' : 1, 'long': 2 };
const orders = { 'relevance': null, 'rating': 1, 'age': 2, 'views' : 3 };
const durations = { 'any': null, 'short': 1, 'long': 2 };
const orders = { 'relevance': null, 'rating': 1, 'age': 2, 'views': 3 };
const search_filter_buff = youtube_proto.SearchFilter.encode({
number: orders[order],
filter: {
@@ -167,8 +167,23 @@ function encodeFilter(period, duration, order) {
param_2: durations[duration]
}
});
return encodeURIComponent(Buffer.from(search_filter_buff).toString('base64'));
}
module.exports = { getRandomUserAgent, generateSidAuth, getStringBetweenStrings, camelToSnake, timeToSeconds, encodeMessageParams, encodeCommentParams, encodeNotificationPref, encodeFilter };
/**
* Turns the ntoken transform data into a valid json array
*
* @param {string} data
*/
function refineNTokenData(data) {
return data
.replace(/function\(d,e\)/g, '"function(d,e)').replace(/function\(d\)/g, '"function(d)')
.replace(/function\(\)/g, '"function()').replace(/function\(d,e,f\)/g, '"function(d,e,f)')
.replace(/\[function\(d,e,f\)/g, '["function(d,e,f)').replace(/,b,/g, ',"b",')
.replace(/,b/g, ',"b"').replace(/b,/g, '"b",').replace(/b]/g, '"b"]')
.replace(/\[b/g, '["b"').replace(/}]/g, '"]').replace(/},/g, '}",')
.replace(/""/g, '').replace(/length]\)}"/g, 'length])}');
}
module.exports = { getRandomUserAgent, generateSidAuth, getStringBetweenStrings, camelToSnake, timeToSeconds, encodeMessageParams, encodeCommentParams, encodeNotificationPref, encodeFilter, refineNTokenData };

26
package-lock.json generated
View File

@@ -1,6 +1,6 @@
{
"name": "youtubei.js",
"version": "1.2.9",
"version": "1.3.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
@@ -77,9 +77,9 @@
"integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8="
},
"node_modules/multiformats": {
"version": "9.6.2",
"resolved": "https://registry.npmjs.org/multiformats/-/multiformats-9.6.2.tgz",
"integrity": "sha512-1dKng7RkBelbEZQQD2zvdzYKgUmtggpWl+GXQBYhnEGGkV6VIYfWgV3VSeyhcUFFEelI5q4D0etCJZ7fbuiamQ=="
"version": "9.6.3",
"resolved": "https://registry.npmjs.org/multiformats/-/multiformats-9.6.3.tgz",
"integrity": "sha512-yfXKI66fL0nFzt0nJl26i4wV1qAqbAEIBvfFbkbsne9GrLz6IHvHUoRyxUtlJcdP181ssOgjama6E/VSk4pbrA=="
},
"node_modules/protocol-buffers-schema": {
"version": "3.6.0",
@@ -127,9 +127,9 @@
}
},
"node_modules/user-agents": {
"version": "1.0.912",
"resolved": "https://registry.npmjs.org/user-agents/-/user-agents-1.0.912.tgz",
"integrity": "sha512-vvFMlK3XcaF/ZRF1Ky4IrBASSNXflZys+5ArPRanNeggWtOYeD2k3FD/6wrv9h7lFlvh5KS+X45E/siw26+EJg==",
"version": "1.0.918",
"resolved": "https://registry.npmjs.org/user-agents/-/user-agents-1.0.918.tgz",
"integrity": "sha512-rHqVET1f+DsrIY8ejUSSgjRKb8qJN8//TJ7K2jIgSDR45OJiZWYVMTtg4qkMAKzkaXhcc6BeQQE2M70cXvHqWw==",
"dependencies": {
"dot-json": "^1.2.2",
"lodash.clonedeep": "^4.5.0"
@@ -189,9 +189,9 @@
"integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8="
},
"multiformats": {
"version": "9.6.2",
"resolved": "https://registry.npmjs.org/multiformats/-/multiformats-9.6.2.tgz",
"integrity": "sha512-1dKng7RkBelbEZQQD2zvdzYKgUmtggpWl+GXQBYhnEGGkV6VIYfWgV3VSeyhcUFFEelI5q4D0etCJZ7fbuiamQ=="
"version": "9.6.3",
"resolved": "https://registry.npmjs.org/multiformats/-/multiformats-9.6.3.tgz",
"integrity": "sha512-yfXKI66fL0nFzt0nJl26i4wV1qAqbAEIBvfFbkbsne9GrLz6IHvHUoRyxUtlJcdP181ssOgjama6E/VSk4pbrA=="
},
"protocol-buffers-schema": {
"version": "3.6.0",
@@ -239,9 +239,9 @@
}
},
"user-agents": {
"version": "1.0.912",
"resolved": "https://registry.npmjs.org/user-agents/-/user-agents-1.0.912.tgz",
"integrity": "sha512-vvFMlK3XcaF/ZRF1Ky4IrBASSNXflZys+5ArPRanNeggWtOYeD2k3FD/6wrv9h7lFlvh5KS+X45E/siw26+EJg==",
"version": "1.0.918",
"resolved": "https://registry.npmjs.org/user-agents/-/user-agents-1.0.918.tgz",
"integrity": "sha512-rHqVET1f+DsrIY8ejUSSgjRKb8qJN8//TJ7K2jIgSDR45OJiZWYVMTtg4qkMAKzkaXhcc6BeQQE2M70cXvHqWw==",
"requires": {
"dot-json": "^1.2.2",
"lodash.clonedeep": "^4.5.0"

View File

@@ -1,6 +1,6 @@
{
"name": "youtubei.js",
"version": "1.2.9",
"version": "1.3.0",
"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!",
"main": "index.js",
"scripts": {

View File

@@ -10,7 +10,7 @@ let failed_tests = 0;
async function performTests() {
const youtube = await new Innertube().catch((error) => error);
assert(youtube instanceof Error ? false : true, `should retrieve Innertube configuration data`, youtube);
assert(!(youtube instanceof Error), `should retrieve Innertube configuration data`, youtube);
if (!(youtube instanceof Error)) {
const search = await youtube.search('Carl Sagan - Documentary').catch((error) => error);
@@ -58,4 +58,4 @@ function assert(outcome, description, data) {
return outcome;
}
performTests();
performTests();