Merge pull request #164 from play-dl/developer

Docs update
This commit is contained in:
Killer069 2021-11-19 14:10:02 +05:30 committed by GitHub
commit 7b6f007258
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 1799 additions and 1077 deletions

1
.gitignore vendored
View File

@ -4,3 +4,4 @@ dist/
examples/node_modules examples/node_modules
examples/package-lock.json examples/package-lock.json
examples/package.json examples/package.json
docs/

View File

@ -1,181 +0,0 @@
# Deezer
## Main
### deezer(url : `string`)
_This returns data from a track | playlist | album url. Accepts share links as well, which it resolves first._
```js
let data = await deezer(url); //Gets the data
console.log(data.type); // Console logs the type of data that you got.
```
## Validate
### dz_validate(url : `string`)
_This checks that given url is Deezer url or not._
**Returns :** `track` | `album` | `playlist` | `search` | `false`
```js
let check = dz_validate(url)
if(!check) // Invalid Deezer URL
if(check === 'track') // Deezer Track URL
if(check === "search") // Given term is a search query. Search it somewhere.
```
## Search
### dz_search(query: `string`, options: `DeezerSearchOptions`)
_Searches for tracks, playlists and albums._
**Returns :** `Deezer[]` an array of tracks, playlists or albums
#### `DeezerSearchOptions`
- **type?** `'track'` | `'playlist'` | `'album'` The type to search for. Defaults to `'track'`.
- **limit?** `number` The maximum number of results to return. Maximum `100`, defaults to `10`.
- **fuzzy?** `boolean` Whether the search should be fuzzy or only return exact matches. Defaults to `true`.
```js
const results = await dz_search(query, {
limit: 1,
type: 'track',
fuzzy: false
}); // Returns an array with one track, using exact matching
```
### dz_advanced_track_search(options: `DeezerAdvancedSearchOptions`)
_Searches Deezer for tracks using the specified metadata._
**Returns :** `DeezerTrack[]` an array of tracks
#### `DeezerAdvancedSearchOptions`
- **limit?** `number` The maximum number of results to return, maximum `100`, defaults to `10`.
- **artist?** `string` The name of the artist
- **album?** `string` The title of the album
- **title?** `string` The title of the track
- **label?** `string` The label that released the track
- **minDurationInSec?** `number` The minimum duration in seconds
- **maxDurationInSec?** `number` The maximum duration in seconds
- **minBpm?** `number` The minimum BPM
- **maxBpm?** `number` The minimum BPM
```js
const results = await dz_advanced_track_search({
limit: 1,
artist: 'Rick Astley',
title: 'Never Gonna Give You Up'
}); // Returns an array with one track
```
## Classes [ Returned by `deezer(url)` function ]
### DeezerTrack
_This is the class for a Deezer track._
##### type `property`
_This will always return as "track" for this class._
##### partial `property`
_Will return true for tracks in search results and false for tracks fetched directly or if the fetch function has been called. This being true means that the optional properties are undefined._
##### toJSON() `function`
_Converts the object to JSON_
##### fetch() `function`
_Fetches the missing data for a partial track._
```js
const track = await deezer(track_url);
await track.fetch() // Fetches the missing data
```
### DeezerPlaylist
_This is the class for a Deezer playlist._
##### fetch() `function`
_This will fetch up to 1000 tracks in a playlist as well as the missing data for a partial playlist._
```js
let data = await deezer(playlist_url)
await data.fetch() // Fetches tracks more than 100 tracks in playlist
```
##### tracksCount `property`
_This will return the total number of tracks in a playlist._
```js
const data = await deezer(playlist_url)
console.log(data.tracksCount) // Total number of tracks in the playlist.
```
##### type `property`
_This will always return as "playlist" for this class._
##### partial `property`
_Will return true for playlists in search results and false for playlists fetched directly or if the fetch function has been called. This being true means that the optional properties are undefined and `tracks` may be empty or partially filled._
##### tracks `property`
_The array of tracks in this album, this is always empty (length of 0) for partial playlists._
```js
const data = await deezer(playlist_url);
if (data.tracks.length !== data.tracksCount) {
await data.fetch();
}
console.log(data.tracks); // returns all tracks in the playlist
```
##### toJSON() `function`
_Converts the object to JSON_
### DeezerAlbum
_This is the class for a Deezer album._
##### type `property`
_This will always return as "track" for this class._
##### tracks `property`
_The array of tracks in this album, this is always empty (length of 0) for partial albums._
##### partial `property`
_Will return true for albums in search results and false for albums fetched directly or if the fetch function has been called. This being true means that the optional properties are undefined._
##### toJSON() `function`
_Converts the object to JSON_
##### fetch() `function`
_Fetches the missing data for a partial album._

View File

@ -1,190 +0,0 @@
# Play-dl commands
For source specific commands :-
- [YouTube](https://github.com/play-dl/play-dl/tree/main/docs/YouTube#youtube)
- [Spotify](https://github.com/play-dl/play-dl/tree/main/docs/Spotify#spotify)
- [SoundCloud](https://github.com/play-dl/play-dl/tree/main/docs/SoundCloud)
### Validate
#### validate(url : `string`)
_This checks all type of urls that are supported by play-dl._
**Returns :** `so_playlist` | `so_track` | `sp_track` | `sp_album` | `sp_playlist` | `dz_track` | `dz_playlist` | `dz_album` | `yt_video` | `yt_playlist` | `search` | `false`
`so` = **SoundCloud**
`sp` = **Spotify**
`yt` = **YouTube**
`dz` = **Deezer**
```js
let check = await validate(url)
if(!check) // Invalid URL
if(check === 'yt_video') // YouTube Video
if(check === 'sp_track') // Spotify Track
if(check === 'so_track') // SoundCloud Track
if(check === 'dz_track') // Deezer Track
if(check === "search") // Given term is not a url. Search this term somewhere.
```
### authorization()
_This creates basic spotify / soundcloud / youtube data to be stored locally._
```js
authorization() //After then you will be asked about type of data you want to create and then follow the steps properly.
```
### setToken(options : `TokenOptions`)
_This sets token without using file._
```js
setToken({
spotify : {
client_id : "ID",
client_secret : "Secret",
refresh_token : "Token",
market : "Country Code"
}
}) // Setting Spotify Token [ To get refresh_token, just run through authorization, and set file save to No ]
setToken({
soundcloud : {
client_id : "ID"
}
}) // Setting SoundCloud Token
setToken({
youtube : {
cookie : "Cookies"
}
}) // Warning : Using setToken for youtube cookies will only update cookies present in memory only.
```
### Search
#### SearchOptions :
- limit : `number` :- Sets total amount of results you want.
- source : {
youtube: `video` | `playlist` | `channel` ;
spotify: `album` | `playlist` | `track` ;
soundcloud: `tracks` | `playlists` | `albums` ;
deezer: `track` | `playlist` | `album` ;
}
#### search(query : `string`, options? : [`SearchOptions`](https://github.com/play-dl/play-dl/tree/main/docs#searchoptions-))
_This is basic to search with any source._
**NOTE :-** If options.source is not specified, then it will default to youtube video search.
```js
let data = await search('Rick Roll', { limit : 1 }) // Searches for youtube video
let data = await search('Rick Roll', { limit : 1, source : { youtube : "video" } }) // Searches for youtube video
let data = await search('Rick Roll', { limit: 1, source : { spotify : "track" } }) // Searches for spotify track.
let data = await search('Rick Roll', { limit: 1, source : { soundcloud : "tracks" } }) // Searches for soundcloud track.
let data = await search('Rick Roll', { limit: 1, source : { deezer : "track" } }) // Searches for a Deezer track.
```
### Stream
**Attaching events to player is important for stream to work.**
#### attachListeners(player : `AudioPlayer`, resource : `YouTubeStream | SoundCloudStream`)
_This is used for attaching pause and playing events to audioPlayer._
```js
let resource = await stream("url")
let player = createAudioPlayer()
attachListeners(player, resource)
```
#### StreamOptions :
- quality : `number` :- Sets quality of stream [ 0 = Lowest, 1 = Medium ]. Leave this empty to get highest audio quality.
- proxy : `Proxy` :- Optional parameter to add support of proxies. As of now, HTTPS proxies are only supported. So make sure to get HTTPS proxies only.
#### stream(url : `string`, options? : [`StreamOptions`](https://github.com/play-dl/play-dl/tree/main/docs#streamoptions-))
_This is basic to create a stream from a youtube or soundcloud url._
```js
let source = await stream("url") // This will create a stream Class. Highest Quality
let source = await stream("url", { quality : 0 }) // Lowest quality
let source = await stream("url", { quality : 1 }) // Next to Lowest quality.
let source = await stream(url, { proxy : ['url'] }) // Accepts a url which has port in it.
let source = await stream(url. {proxy : [{
host : "IP or hostname",
port : 8080
}]
}) // Or add a json containing hostname and port.
let resource = createAudioResource(source.stream, {
inputType : source.type
}) // This creates resource for playing
```
#### stream_from_info(info : `infoData`, options? : [`StreamOptions`](https://github.com/play-dl/play-dl/tree/main/docs#streamoptions-))
_This is basic to create a stream from a info [ from [video_info](https://github.com/play-dl/play-dl#video_infourl--string) function or [soundcloud](https://github.com/play-dl/play-dl/tree/main/docs/SoundCloud#soundcloudurl--string) function [**Only SoundCloudTrack class is allowed**] ]._
**Note :** Here, cookies are required only for retrying purposes.
```js
let source = await stream_from_info(info) // This will create a stream Class from video_info or SoundCoudTrack Class. Highest Quality
let source = await stream_from_info(info, { quality : 0 }) // Lowest quality
let source = await stream_from_info(info, { quality : 1 }) // Next to Lowest quality.
let source = await stream_from_info(info, { proxy : ['url'] }) // Accepts a url which has port in it.
let source = await stream_from_info(info, {proxy : [{
host : "IP or hostname",
port : 8080
}]
}) // Or add a json containing hostname and port.
let resource = createAudioResource(source.stream, {
inputType : source.type
}) // This creates resource for playing
```
#### cookieHeaders(headersCookie : `string[]`)
_This is function to update youtube cookies when using external https module._
```js
const res = ... // You need to get response.
play.cookieHeaders(res.headers['set-cookie']) // Updates YouTube Cookies if cookies exists.
```

View File

@ -1,115 +0,0 @@
# SoundCloud
## Main
### soundcloud(url : `string`)
_This returns data from a track | playlist url._
```js
let data = await soundcloud(url) //Gets the data
console.log(data.type) // Console logs the type of data that you got.
```
### getFreeClientID()
_This returns free client ID._
```js
const client_id = await getFreeClientID()
setToken({
soundcloud : {
client_id : client_id
}
}) // This will set client ID for use in play-dl.
```
## Validate
### so_validate(url : `string`)
_This checks that given url is soundcloud url or not._
**Returns :** `track` | `playlist` | `search` | `false`
```js
let check = await so_validate(url)
if(!check) // Invalid SoundCloud URL
if(check === 'track') // SoundCloud Track URL
if(check === "search") // Given term is not a SoundCloud URL. Search this somewhere.
```
## Classes [ Returned by `soundcloud(url)` function ]
### SoundCloudTrack
_This is class for a soundcloud track._
##### type `property`
_This will always return as "track" for this class._
##### toJSON() `function`
_converts class into a json format_
### SoundCloudPlaylist
_This is a soundcloud playlist class._
##### fetch() `function`
_This will fetch tracks in a playlist._
```js
let data = await soundcloud(playlist_url)
await data.fetch() // Fetches all unfetched tracks in playlist
```
##### tracksCount `property`
_This will give no. of tracks in a playlist._
```js
let data = await soundcloud(playlist_url)
console.log(data.tracksCount) // Returns total tracks count in a playlist
```
#### tracks `property`
_This will give all tracks fetched as array._
```js
let data = await soundcloud(playlist_url)
console.log(data.tracks) // Tracks Array
data.tracks.forEach((track) => {
queue.push(track) // This will push every track in playlist to your queue
})
```
#### total_tracks `property`
_This give total videos that have been fetched so far._
```js
let data = await soundcloud(playlist_url)
console.log(data.total_tracks) // This will tell no. of videos that have been fetched so far.
```
##### type `property`
_This will always return as "playlist" for this class._
##### toJSON() `function`
_converts class into a json format_

View File

@ -1,204 +0,0 @@
# Spotify
## Main
### spotify(url : `string`)
_This returns data from a track | playlist | album url._
```js
let data = await spotify(url) //Gets the data
console.log(data.type) // Console logs the type of data that you got.
```
## Validate
### sp_validate(url : `string`)
_This checks that given url is spotify url or not._
**Returns :** `track` | `album` | `playlist` | `search` | `false`
```js
let check = sp_validate(url)
if(!check) // Invalid Spotify URL
if(check === 'track') // Spotify Track URL
if(check === "search") // Given term is a spotify url. Search it somewhere.
```
### is_expired()
_This tells that whether the access token is expired or not_
**Returns :** `boolean`
```js
if(is_expired()){
await refreshToken()
}
```
### refreshToken()
_This refreshes the access token._
**Returns :** `boolean` for telling whether access token is refreshed or not
```js
await refreshToken()
```
## Classes [ Returned by `spotify(url)` function ]
### SpotifyVideo
_Don't go by the name. This is class for a spotify track._
##### type `property`
_This will always return as "track" for this class._
##### toJSON() `function`
_converts class into a json format_
### SpotifyPlaylist
_This is a spotify playlist class._
##### fetch() `function`
_This will fetch tracks in a playlist upto 1000 tracks only._
```js
let data = await spotify(playlist_url)
await data.fetch() // Fetches tracks more than 100 tracks in playlist
```
##### tracksCount `property`
_This will give no. of tracks in a playlist._
```js
let data = await spotify(playlist_url)
console.log(data.tracksCount) // Returns total tracks count in a playlist
```
##### page(page_number : `number`)
_This will return array of tracks in that page._
> Same as youtube playlist pages
```js
let data = await spotify(playlist_url)
console.log(data.page(1)) //This will give first 100 tracks in playlist.
```
- total_pages `property`
_This give total pages that have been fetched so far._
```js
let data = await spotify(playlist_url)
console.log(data.total_pages) // This will tell no. of pages that have been fetched so far.
for(let i = 1; i <= data.total_pages; i++){
queue.push(data.page(i)) //This will push all tracks to your queue system
}
```
- total_tracks `property`
_This give total videos that have been fetched so far._
```js
let data = await spotify(playlist_url)
console.log(data.total_tracks) // This will tell no. of videos that have been fetched so far.
```
##### type `property`
_This will always return as "playlist" for this class._
##### toJSON() `function`
_converts class into a json format_
### SpotifyAlbum
_This is a spotify albun class._
##### fetch() `function`
_This will fetch tracks in a album upto 500 tracks only._
```js
let data = await spotify(playlist_url)
await data.fetch() // Fetches tracks more than 50 tracks in album
```
##### tracksCount `property`
_This will give no. of tracks in a playlist._
```js
let data = await spotify(playlist_url)
console.log(data.tracksCount) // Returns total tracks count in a album
```
##### page(page_number : `number`)
_This will return array of tracks in that page._
> Same as youtube playlist pages
```js
let data = await spotify(playlist_url)
console.log(data.page(1)) //This will give first 50 tracks in album.
```
- total_pages `property`
_This give total pages that have been fetched so far._
```js
let data = await spotify(playlist_url)
console.log(data.total_pages) // This will tell no. of pages that have been fetched so far.
for(let i = 1; i <= data.total_pages; i++){
queue.push(data.page(i)) //This will push all tracks to your queue system
}
```
- total_tracks `property`
_This give total videos that have been fetched so far._
```js
let data = await spotify(playlist_url)
console.log(data.total_tracks) // This will tell no. of videos that have been fetched so far.
```
##### type `property`
_This will always return as "album" for this class._
##### toJSON() `function`
_converts class into a json format_

View File

@ -1,198 +0,0 @@
# YouTube
## Basic Usage
```js
const youtube = require('play-dl');
// ES6: import youtube from 'play-dl';
const options = {
limit : 1
}
const results = await youtube.search('post malone sunflower', options);
```
## Validate
### yt_validate(url : `string`)
_This will validate url and return type or boolean_
**Returns :** `video` | `playlist` | `search` | `false`
```js
let check = yt_validate(url)
if(!check) // Invalid URL
if(check === "video") //URL is video url
if(check === "playlist") //URL is a playlist url
if(check === "search") // Given term is not a video ID and PlayList ID.
```
## Extract ID
### extractID(url : `string`)
_This will return videoID or playlistID from a url_
**Note :** URL like [this](https://www.youtube.com/watch?v=E2gHczUOCGI&list=PLUt3leKZfbZqLzLwcQMYPBdbe7i7KRCOP&index=2) will return a playlist ID only.
```js
let id = extractID(url)
```
## Video
### InfoOptions
_This are the info options that can be passed as a parameter in `video_info` and `video_basic_info`_
- proxy : Optional parameter to add support of proxies. As of now, HTTPS proxies are only supported. So make sure to get HTTPS proxies only.
- htmldata : `boolean` Set this to true if you are passing a html body as first parameter.
```js
const video = await video_basic_info(url, { proxy : ['url'] }) // Accepts a url which has port in it.
const video = await video_basic_info(url, {proxy : [{
host : "IP or hostname",
port : 8080
}]
}) // Or add a json containing hostname and port.
// Use any https package to use proxy and then do this
const video = await video_basic_info(body, { htmldata : true }) // You can use video_info function also.
```
### video_basic_info(url : `string`, options? : [`InfoOptions`](https://github.com/play-dl/play-dl/tree/main/docs/YouTube#infooptions))
_The basic video details `play-dl` fetches at first from url or videoID._
```js
const video = await video_basic_info(url)
```
### video_info(url : `string`, , options? : [`InfoOptions`](https://github.com/play-dl/play-dl/tree/main/docs/YouTube#infooptions))
_This contains everything with deciphered formats along with `video_details`. It can fetech data from url or videoID._
```js
const video = await video_info(url)
```
- #### format `property`
_This returns all the formats available for a video._
```js
const video = await video_info(url)
console.log(video.format)
```
### decipher_info(data : `InfoData`)
_This contains everything with deciphered formats along with `video_details`. It uses data returned by [`video_basic_info`](https://github.com/play-dl/play-dl/tree/main/docs/YouTube#video_basic_infourl--string-options--infooptions). This function is useful if you use [`video_basic_info`](https://github.com/play-dl/play-dl/tree/main/docs/YouTube#video_basic_infourl--string-options--infooptions) earlier in your code and want to convert the output for use with [`stream_from_info`](https://github.com/play-dl/play-dl/tree/main/docs#stream_from_infoinfo--infodata-options--streamoptions)_
```js
const basic_video = await video_basic_info(url);
const video = await decipher_info(basic_video);
```
## Playlist
### playlist_info(url : `string`, options : `PlaylistOptions`)
_This fetches all details about a playlist from a url or playlistID._
```js
const playlist = await playlist_info(url)
//This only fetches first 100 videos from a playlist
const playlist = await playlist_info(url, { incomplete : true })
//This only fetches first 100 videos from a playlist and also parses playlist with hidden videos
const playlist = await playlist_info(url, { proxy : [''] }) // Same 2 options as mentioned in InfoOptions
```
- #### fetch() `method`
_This fetches and returns all videos from the whole provided playlist ._
```js
const playlist = await playlist_info(url)
//This only fetches first 100 videos from a playlist
await playlist.fetch()
// This one fetches all videos from a playlist.
```
- #### page(page_number : `number`)
_This returns no. of videos from a page._
> Every 100 videos have been divided into pages.
> Example: There are 782 videos in a playlist, so there will be 8 pages.
```js
const playlist = await playlist_info(url);
// This only fetches first 100 videos from a playlist.
await playlist.fetch();
// This one fetches all videos from a playlist.
console.log(playlist.page(1));
// This displays first 100 videos of a playlist
```
- #### total_pages `property`
_This returns total no. of pages that have been fetched so far._
```js
const playlist = await playlist_info(url)
//This only fetches first 100 videos from a playlist.
await playlist.fetch()
// This one fetches all videos from a playlist.
console.log(playlist.total_pages)
// This displays total no. of pages fetched so far.
for(let i = 1; i <= playlist.total_pages; i++){
queue.push(...playlist.page(i))
} // This will push every video in that playlist to your queue
```
- #### total_videos `property`
_This returns total no. of videos that have been fetched so far._
```js
const playlist = await playlist_info(url)
//This only fetches first 100 videos from a playlist.
await playlist.fetch()
// This one fetches all videos from a playlist.
console.log(playlist.total_videos)
// This displays total no. of videos fetched so far.
```
- #### videoCount `property`
_This returns total no. of videos in the provided playlist._
```js
const playlist = await playlist_info(url)
//This only fetches first 100 videos from a playlist.
await playlist.fetch()
// This one fetches all videos from a playlist.
console.log(playlist.videoCount)
// This displays total no. of videos in a playlist.
```

369
package-lock.json generated
View File

@ -1,16 +1,18 @@
{ {
"name": "play-dl", "name": "play-dl",
"version": "1.2.6", "version": "1.3.0",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "play-dl", "name": "play-dl",
"version": "1.2.6", "version": "1.3.0",
"license": "GPL-3.0", "license": "GPL-3.0",
"devDependencies": { "devDependencies": {
"@types/node": "^16.9.4", "@types/node": "^16.9.4",
"prettier": "^2.3.1", "prettier": "^2.3.1",
"typedoc": "^0.22.9",
"typedoc-plugin-missing-exports": "^0.22.4",
"typescript": "^4.4.4" "typescript": "^4.4.4"
}, },
"engines": { "engines": {
@ -23,6 +25,142 @@
"integrity": "sha512-ua7PgUoeQFjmWPcoo9khiPum3Pd60k4/2ZGXt18sm2Slk0W0xZTqt5Y0Ny1NyBiN1EVQ/+FaF9NcY4Qe6rwk5w==", "integrity": "sha512-ua7PgUoeQFjmWPcoo9khiPum3Pd60k4/2ZGXt18sm2Slk0W0xZTqt5Y0Ny1NyBiN1EVQ/+FaF9NcY4Qe6rwk5w==",
"dev": true "dev": true
}, },
"node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"dev": true
},
"node_modules/brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"dev": true,
"dependencies": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
}
},
"node_modules/concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
"dev": true
},
"node_modules/fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
"dev": true
},
"node_modules/glob": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz",
"integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==",
"dev": true,
"dependencies": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
"inherits": "2",
"minimatch": "^3.0.4",
"once": "^1.3.0",
"path-is-absolute": "^1.0.0"
},
"engines": {
"node": "*"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/inflight": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
"integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
"dev": true,
"dependencies": {
"once": "^1.3.0",
"wrappy": "1"
}
},
"node_modules/inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
"dev": true
},
"node_modules/jsonc-parser": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.0.0.tgz",
"integrity": "sha512-fQzRfAbIBnR0IQvftw9FJveWiHp72Fg20giDrHz6TdfB12UH/uue0D3hm57UB5KgAVuniLMCaS8P1IMj9NR7cA==",
"dev": true
},
"node_modules/lru-cache": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
"integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
"dev": true,
"dependencies": {
"yallist": "^3.0.2"
}
},
"node_modules/lunr": {
"version": "2.3.9",
"resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz",
"integrity": "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==",
"dev": true
},
"node_modules/marked": {
"version": "3.0.8",
"resolved": "https://registry.npmjs.org/marked/-/marked-3.0.8.tgz",
"integrity": "sha512-0gVrAjo5m0VZSJb4rpL59K1unJAMb/hm8HRXqasD8VeC8m91ytDPMritgFSlKonfdt+rRYYpP/JfLxgIX8yoSw==",
"dev": true,
"bin": {
"marked": "bin/marked"
},
"engines": {
"node": ">= 12"
}
},
"node_modules/minimatch": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
"dev": true,
"dependencies": {
"brace-expansion": "^1.1.7"
},
"engines": {
"node": "*"
}
},
"node_modules/once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
"dev": true,
"dependencies": {
"wrappy": "1"
}
},
"node_modules/onigasm": {
"version": "2.2.5",
"resolved": "https://registry.npmjs.org/onigasm/-/onigasm-2.2.5.tgz",
"integrity": "sha512-F+th54mPc0l1lp1ZcFMyL/jTs2Tlq4SqIHKIXGZOR/VkHkF9A7Fr5rRr5+ZG/lWeRsyrClLYRq7s/yFQ/XhWCA==",
"dev": true,
"dependencies": {
"lru-cache": "^5.1.1"
}
},
"node_modules/path-is-absolute": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/prettier": { "node_modules/prettier": {
"version": "2.4.1", "version": "2.4.1",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.4.1.tgz", "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.4.1.tgz",
@ -35,6 +173,48 @@
"node": ">=10.13.0" "node": ">=10.13.0"
} }
}, },
"node_modules/shiki": {
"version": "0.9.12",
"resolved": "https://registry.npmjs.org/shiki/-/shiki-0.9.12.tgz",
"integrity": "sha512-VXcROdldv0/Qu0w2XvzU4IrvTeBNs/Kj/FCmtcEXGz7Tic/veQzliJj6tEiAgoKianhQstpYmbPDStHU5Opqcw==",
"dev": true,
"dependencies": {
"jsonc-parser": "^3.0.0",
"onigasm": "^2.2.5",
"vscode-textmate": "5.2.0"
}
},
"node_modules/typedoc": {
"version": "0.22.9",
"resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.22.9.tgz",
"integrity": "sha512-84PjudoXVcap6bwdZFbYIUWlgdz/iLV09ZHwrCzhtHWXaDQG6mlosJ8te6DSThuRkRvQjp46HO+qY/P7Gpm78g==",
"dev": true,
"dependencies": {
"glob": "^7.2.0",
"lunr": "^2.3.9",
"marked": "^3.0.8",
"minimatch": "^3.0.4",
"shiki": "^0.9.12"
},
"bin": {
"typedoc": "bin/typedoc"
},
"engines": {
"node": ">= 12.10.0"
},
"peerDependencies": {
"typescript": "4.0.x || 4.1.x || 4.2.x || 4.3.x || 4.4.x"
}
},
"node_modules/typedoc-plugin-missing-exports": {
"version": "0.22.4",
"resolved": "https://registry.npmjs.org/typedoc-plugin-missing-exports/-/typedoc-plugin-missing-exports-0.22.4.tgz",
"integrity": "sha512-NRagPFyh8Oma5E7rR8534x4d8GQ/87Tp3WYfBJcxradPjXTZ4D2MXD5eHfG98DQCMqYrb+CRZnvO4iaGaqQC7w==",
"dev": true,
"peerDependencies": {
"typedoc": "0.22.x"
}
},
"node_modules/typescript": { "node_modules/typescript": {
"version": "4.4.4", "version": "4.4.4",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.4.4.tgz", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.4.4.tgz",
@ -47,6 +227,24 @@
"engines": { "engines": {
"node": ">=4.2.0" "node": ">=4.2.0"
} }
},
"node_modules/vscode-textmate": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/vscode-textmate/-/vscode-textmate-5.2.0.tgz",
"integrity": "sha512-Uw5ooOQxRASHgu6C7GVvUxisKXfSgW4oFlO+aa+PAkgmH89O3CXxEEzNRNtHSqtXFTl0nAC1uYj0GMSH27uwtQ==",
"dev": true
},
"node_modules/wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
"dev": true
},
"node_modules/yallist": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
"integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
"dev": true
} }
}, },
"dependencies": { "dependencies": {
@ -56,17 +254,184 @@
"integrity": "sha512-ua7PgUoeQFjmWPcoo9khiPum3Pd60k4/2ZGXt18sm2Slk0W0xZTqt5Y0Ny1NyBiN1EVQ/+FaF9NcY4Qe6rwk5w==", "integrity": "sha512-ua7PgUoeQFjmWPcoo9khiPum3Pd60k4/2ZGXt18sm2Slk0W0xZTqt5Y0Ny1NyBiN1EVQ/+FaF9NcY4Qe6rwk5w==",
"dev": true "dev": true
}, },
"balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"dev": true
},
"brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"dev": true,
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
}
},
"concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
"dev": true
},
"fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
"dev": true
},
"glob": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz",
"integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==",
"dev": true,
"requires": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
"inherits": "2",
"minimatch": "^3.0.4",
"once": "^1.3.0",
"path-is-absolute": "^1.0.0"
}
},
"inflight": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
"integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
"dev": true,
"requires": {
"once": "^1.3.0",
"wrappy": "1"
}
},
"inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
"dev": true
},
"jsonc-parser": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.0.0.tgz",
"integrity": "sha512-fQzRfAbIBnR0IQvftw9FJveWiHp72Fg20giDrHz6TdfB12UH/uue0D3hm57UB5KgAVuniLMCaS8P1IMj9NR7cA==",
"dev": true
},
"lru-cache": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
"integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
"dev": true,
"requires": {
"yallist": "^3.0.2"
}
},
"lunr": {
"version": "2.3.9",
"resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz",
"integrity": "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==",
"dev": true
},
"marked": {
"version": "3.0.8",
"resolved": "https://registry.npmjs.org/marked/-/marked-3.0.8.tgz",
"integrity": "sha512-0gVrAjo5m0VZSJb4rpL59K1unJAMb/hm8HRXqasD8VeC8m91ytDPMritgFSlKonfdt+rRYYpP/JfLxgIX8yoSw==",
"dev": true
},
"minimatch": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
"dev": true,
"requires": {
"brace-expansion": "^1.1.7"
}
},
"once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
"dev": true,
"requires": {
"wrappy": "1"
}
},
"onigasm": {
"version": "2.2.5",
"resolved": "https://registry.npmjs.org/onigasm/-/onigasm-2.2.5.tgz",
"integrity": "sha512-F+th54mPc0l1lp1ZcFMyL/jTs2Tlq4SqIHKIXGZOR/VkHkF9A7Fr5rRr5+ZG/lWeRsyrClLYRq7s/yFQ/XhWCA==",
"dev": true,
"requires": {
"lru-cache": "^5.1.1"
}
},
"path-is-absolute": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
"dev": true
},
"prettier": { "prettier": {
"version": "2.4.1", "version": "2.4.1",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.4.1.tgz", "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.4.1.tgz",
"integrity": "sha512-9fbDAXSBcc6Bs1mZrDYb3XKzDLm4EXXL9sC1LqKP5rZkT6KRr/rf9amVUcODVXgguK/isJz0d0hP72WeaKWsvA==", "integrity": "sha512-9fbDAXSBcc6Bs1mZrDYb3XKzDLm4EXXL9sC1LqKP5rZkT6KRr/rf9amVUcODVXgguK/isJz0d0hP72WeaKWsvA==",
"dev": true "dev": true
}, },
"shiki": {
"version": "0.9.12",
"resolved": "https://registry.npmjs.org/shiki/-/shiki-0.9.12.tgz",
"integrity": "sha512-VXcROdldv0/Qu0w2XvzU4IrvTeBNs/Kj/FCmtcEXGz7Tic/veQzliJj6tEiAgoKianhQstpYmbPDStHU5Opqcw==",
"dev": true,
"requires": {
"jsonc-parser": "^3.0.0",
"onigasm": "^2.2.5",
"vscode-textmate": "5.2.0"
}
},
"typedoc": {
"version": "0.22.9",
"resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.22.9.tgz",
"integrity": "sha512-84PjudoXVcap6bwdZFbYIUWlgdz/iLV09ZHwrCzhtHWXaDQG6mlosJ8te6DSThuRkRvQjp46HO+qY/P7Gpm78g==",
"dev": true,
"requires": {
"glob": "^7.2.0",
"lunr": "^2.3.9",
"marked": "^3.0.8",
"minimatch": "^3.0.4",
"shiki": "^0.9.12"
}
},
"typedoc-plugin-missing-exports": {
"version": "0.22.4",
"resolved": "https://registry.npmjs.org/typedoc-plugin-missing-exports/-/typedoc-plugin-missing-exports-0.22.4.tgz",
"integrity": "sha512-NRagPFyh8Oma5E7rR8534x4d8GQ/87Tp3WYfBJcxradPjXTZ4D2MXD5eHfG98DQCMqYrb+CRZnvO4iaGaqQC7w==",
"dev": true,
"requires": {}
},
"typescript": { "typescript": {
"version": "4.4.4", "version": "4.4.4",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.4.4.tgz", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.4.4.tgz",
"integrity": "sha512-DqGhF5IKoBl8WNf8C1gu8q0xZSInh9j1kJJMqT3a94w1JzVaBU4EXOSMrz9yDqMT0xt3selp83fuFMQ0uzv6qA==", "integrity": "sha512-DqGhF5IKoBl8WNf8C1gu8q0xZSInh9j1kJJMqT3a94w1JzVaBU4EXOSMrz9yDqMT0xt3selp83fuFMQ0uzv6qA==",
"dev": true "dev": true
},
"vscode-textmate": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/vscode-textmate/-/vscode-textmate-5.2.0.tgz",
"integrity": "sha512-Uw5ooOQxRASHgu6C7GVvUxisKXfSgW4oFlO+aa+PAkgmH89O3CXxEEzNRNtHSqtXFTl0nAC1uYj0GMSH27uwtQ==",
"dev": true
},
"wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
"dev": true
},
"yallist": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
"integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
"dev": true
} }
} }
} }

View File

@ -43,6 +43,8 @@
"devDependencies": { "devDependencies": {
"@types/node": "^16.9.4", "@types/node": "^16.9.4",
"prettier": "^2.3.1", "prettier": "^2.3.1",
"typedoc": "^0.22.9",
"typedoc-plugin-missing-exports": "^0.22.4",
"typescript": "^4.4.4" "typescript": "^4.4.4"
} }
} }

View File

@ -255,3 +255,5 @@ export async function dz_advanced_track_search(options: DeezerAdvancedSearchOpti
return results; return results;
} }
export { DeezerTrack, DeezerAlbum, DeezerPlaylist }

View File

@ -3,48 +3,130 @@ import { Readable } from 'node:stream';
import { IncomingMessage } from 'node:http'; import { IncomingMessage } from 'node:http';
import { StreamType } from '../YouTube/stream'; import { StreamType } from '../YouTube/stream';
import { Timer } from '../YouTube/classes/LiveStream'; import { Timer } from '../YouTube/classes/LiveStream';
import { PlaylistJSON, SoundTrackJSON } from './constants';
interface SoundCloudUser { export interface SoundCloudUser {
/**
* SoundCloud User Name
*/
name: string; name: string;
/**
* SoundCloud User ID
*/
id: string; id: string;
/**
* SoundCloud User URL
*/
url: string; url: string;
/**
* SoundCloud Class type. == "user"
*/
type: 'track' | 'playlist' | 'user'; type: 'track' | 'playlist' | 'user';
/**
* SoundCloud User Verified status
*/
verified: boolean; verified: boolean;
/**
* SoundCloud User Description
*/
description: string; description: string;
/**
* SoundCloud User First Name
*/
first_name: string; first_name: string;
/**
* SoundCloud User Full Name
*/
full_name: string; full_name: string;
/**
* SoundCloud User Last Name
*/
last_name: string; last_name: string;
/**
* SoundCloud User thumbnail URL
*/
thumbnail: string; thumbnail: string;
} }
interface SoundCloudTrackDeprecated { export interface SoundCloudTrackDeprecated {
/**
* SoundCloud Track fetched status
*/
fetched: boolean; fetched: boolean;
/**
* SoundCloud Track ID
*/
id: number; id: number;
/**
* SoundCloud Class type. == "track"
*/
type: 'track'; type: 'track';
} }
export interface SoundCloudTrackFormat { export interface SoundCloudTrackFormat {
/**
* SoundCloud Track Format Url
*/
url: string; url: string;
/**
* SoundCloud Track Format preset
*/
preset: string; preset: string;
/**
* SoundCloud Track Format Duration
*/
duration: number; duration: number;
/**
* SoundCloud Track Format data containing protocol and mime_type
*/
format: { format: {
protocol: string; protocol: string;
mime_type: string; mime_type: string;
}; };
/**
* SoundCloud Track Format quality
*/
quality: string; quality: string;
} }
/** /**
* SoundCloud Track * SoundCloud Track Class
*/ */
export class SoundCloudTrack { export class SoundCloudTrack {
/**
* SoundCloud Track Name
*/
name: string; name: string;
/**
* SoundCloud Track ID
*/
id: number; id: number;
/**
* SoundCloud Track url
*/
url: string; url: string;
/**
* SoundCloud Track fetched status
*/
fetched: boolean; fetched: boolean;
/**
* SoundCloud Class type. === "track"
*/
type: 'track' | 'playlist' | 'user'; type: 'track' | 'playlist' | 'user';
/**
* SoundCloud Track Duration in seconds
*/
durationInSec: number; durationInSec: number;
/**
* SoundCloud Track Duration in miili seconds
*/
durationInMs: number; durationInMs: number;
/**
* SoundCloud Track formats data
*/
formats: SoundCloudTrackFormat[]; formats: SoundCloudTrackFormat[];
/**
* SoundCloud Track Publisher Data
*/
publisher: { publisher: {
name: string; name: string;
id: number; id: number;
@ -52,8 +134,18 @@ export class SoundCloudTrack {
contains_music: boolean; contains_music: boolean;
writer_composer: string; writer_composer: string;
} | null; } | null;
/**
* SoundCloud Track thumbnail
*/
thumbnail: string; thumbnail: string;
/**
* SoundCloud Track user data
*/
user: SoundCloudUser; user: SoundCloudUser;
/**
* Constructor for SoundCloud Track Class
* @param data JSON parsed track html data
*/
constructor(data: any) { constructor(data: any) {
this.name = data.title; this.name = data.title;
this.id = data.id; this.id = data.id;
@ -86,12 +178,14 @@ export class SoundCloudTrack {
}; };
this.thumbnail = data.artwork_url; this.thumbnail = data.artwork_url;
} }
/**
toJSON() { * Converts class to JSON
* @returns JSON parsed Data
*/
toJSON() : SoundTrackJSON {
return { return {
name: this.name, name: this.name,
id: this.id, id: this.id,
type: this.type,
url: this.url, url: this.url,
fetched: this.fetched, fetched: this.fetched,
durationInMs: this.durationInMs, durationInMs: this.durationInMs,
@ -104,20 +198,59 @@ export class SoundCloudTrack {
} }
} }
/** /**
* SoundCloud Playlist * SoundCloud Playlist Class
*/ */
export class SoundCloudPlaylist { export class SoundCloudPlaylist {
/**
* SoundCloud Playlist Name
*/
name: string; name: string;
/**
* SoundCloud Playlist ID
*/
id: number; id: number;
/**
* SoundCloud Playlist URL
*/
url: string; url: string;
/**
* SoundCloud Class type. == "playlist"
*/
type: 'track' | 'playlist' | 'user'; type: 'track' | 'playlist' | 'user';
/**
* SoundCloud Playlist Sub type. == "album" for soundcloud albums
*/
sub_type: string; sub_type: string;
/**
* SoundCloud Playlist Total Duration in seconds
*/
durationInSec: number; durationInSec: number;
/**
* SoundCloud Playlist Total Duration in milli seconds
*/
durationInMs: number; durationInMs: number;
client_id: string; /**
* SoundCloud Playlist user data
*/
user: SoundCloudUser; user: SoundCloudUser;
/**
* SoundCloud Playlist tracks [ It can be fetched or not fetched ]
*/
tracks: SoundCloudTrack[] | SoundCloudTrackDeprecated[]; tracks: SoundCloudTrack[] | SoundCloudTrackDeprecated[];
/**
* SoundCloud Playlist tracks number
*/
tracksCount: number; tracksCount: number;
/**
* SoundCloud Client ID provided by user
* @private
*/
private client_id: string;
/**
* Constructor for SoundCloud Playlist
* @param data JSON parsed SoundCloud playlist data
* @param client_id Provided SoundCloud Client ID
*/
constructor(data: any, client_id: string) { constructor(data: any, client_id: string) {
this.name = data.title; this.name = data.title;
this.id = data.id; this.id = data.id;
@ -153,8 +286,13 @@ export class SoundCloudPlaylist {
}); });
this.tracks = tracks; this.tracks = tracks;
} }
/**
async fetch(): Promise<void> { * Fetches all unfetched songs in a playlist.
*
* For fetching songs and getting all songs, see `fetched_tracks` property.
* @returns playlist class
*/
async fetch(): Promise<SoundCloudPlaylist> {
const work: any[] = []; const work: any[] = [];
for (let i = 0; i < this.tracks.length; i++) { for (let i = 0; i < this.tracks.length; i++) {
if (!this.tracks[i].fetched) { if (!this.tracks[i].fetched) {
@ -172,9 +310,12 @@ export class SoundCloudPlaylist {
} }
} }
await Promise.allSettled(work); await Promise.allSettled(work);
return this;
} }
/**
get total_tracks() { * Get total no. of fetched tracks
*/
get total_tracks(): number {
let count = 0; let count = 0;
this.tracks.forEach((track) => { this.tracks.forEach((track) => {
if (track instanceof SoundCloudTrack) count++; if (track instanceof SoundCloudTrack) count++;
@ -182,12 +323,35 @@ export class SoundCloudPlaylist {
}); });
return count; return count;
} }
/**
toJSON() { * Get all fetched tracks as a array.
*
* For getting all feetched tracks
*
* ```ts
* const playlist = await play.soundcloud("playlist url")
*
* await playlist.fetch()
*
* const result = playlist.fetched_tracks
* ```
*/
get fetched_tracks(): SoundCloudTrack[] {
let result: SoundCloudTrack[] = [];
this.tracks.forEach((track) => {
if (track instanceof SoundCloudTrack) result.push(track);
else return;
});
return result;
}
/**
* Converts Class to JSON data
* @returns JSON parsed data
*/
toJSON(): PlaylistJSON {
return { return {
name: this.name, name: this.name,
id: this.id, id: this.id,
type: this.type,
sub_type: this.sub_type, sub_type: this.sub_type,
url: this.url, url: this.url,
durationInMs: this.durationInMs, durationInMs: this.durationInMs,
@ -201,18 +365,58 @@ export class SoundCloudPlaylist {
/** /**
* SoundCloud Stream class * SoundCloud Stream class
*/ */
export class Stream { export class SoundCloudStream {
/**
* Readable Stream through which data passes
*/
stream: Readable; stream: Readable;
/**
* Type of audio data that we recieved from normal youtube url.
*/
type: StreamType; type: StreamType;
/**
* Dash Url containing segment urls.
* @private
*/
private url: string; private url: string;
/**
* Total time of downloaded segments data.
* @private
*/
private downloaded_time: number; private downloaded_time: number;
/**
* Timer for looping code every 5 minutes
* @private
*/
private timer: Timer; private timer: Timer;
/**
* Total segments Downloaded so far
* @private
*/
private downloaded_segments: number; private downloaded_segments: number;
/**
* Incoming message that we recieve.
*
* Storing this is essential.
* This helps to destroy the TCP connection completely if you stopped player in between the stream
* @private
*/
private request: IncomingMessage | null; private request: IncomingMessage | null;
/**
* Array of segment time. Useful for calculating downloaded_time.
*/
private time: number[]; private time: number[];
/**
* Array of segment_urls in dash file.
*/
private segment_urls: string[]; private segment_urls: string[];
/**
* Constructor for SoundCloud Stream
* @param url Dash url containing dash file.
* @param type Stream Type
*/
constructor(url: string, type: StreamType = StreamType.Arbitrary) { constructor(url: string, type: StreamType = StreamType.Arbitrary) {
this.stream = new Readable({ highWaterMark: 10 * 1000 * 1000, read() {} }); this.stream = new Readable({ highWaterMark: 5 * 1000 * 1000, read() {} });
this.type = type; this.type = type;
this.url = url; this.url = url;
this.downloaded_time = 0; this.downloaded_time = 0;
@ -229,7 +433,10 @@ export class Stream {
}); });
this.start(); this.start();
} }
/**
* Parses SoundCloud dash file.
* @private
*/
private async parser() { private async parser() {
const response = await request(this.url).catch((err: Error) => { const response = await request(this.url).catch((err: Error) => {
return err; return err;
@ -245,7 +452,9 @@ export class Stream {
}); });
return; return;
} }
/**
* Starts looping of code for getting all segments urls data
*/
private async start() { private async start() {
if (this.stream.destroyed) { if (this.stream.destroyed) {
this.cleanup(); this.cleanup();
@ -253,12 +462,14 @@ export class Stream {
} }
this.time = []; this.time = [];
this.segment_urls = []; this.segment_urls = [];
await this.parser();
this.downloaded_time = 0; this.downloaded_time = 0;
await this.parser();
this.segment_urls.splice(0, this.downloaded_segments); this.segment_urls.splice(0, this.downloaded_segments);
this.loop(); this.loop();
} }
/**
* Main Loop function for getting all segments urls data
*/
private async loop() { private async loop() {
if (this.stream.destroyed) { if (this.stream.destroyed) {
this.cleanup(); this.cleanup();
@ -290,7 +501,11 @@ export class Stream {
this.stream.emit('error', err); this.stream.emit('error', err);
}); });
} }
/**
* This cleans every used variable in class.
*
* This is used to prevent re-use of this class and helping garbage collector to collect it.
*/
private cleanup() { private cleanup() {
this.timer.destroy(); this.timer.destroy();
this.request?.destroy(); this.request?.destroy();
@ -301,11 +516,19 @@ export class Stream {
this.time = []; this.time = [];
this.segment_urls = []; this.segment_urls = [];
} }
/**
* Pauses timer.
* Stops running of loop.
*
* Useful if you don't want to get excess data to be stored in stream.
*/
pause() { pause() {
this.timer.pause(); this.timer.pause();
} }
/**
* Resumes timer.
* Starts running of loop.
*/
resume() { resume() {
this.timer.resume(); this.timer.resume();
} }

View File

@ -0,0 +1,93 @@
import { SoundCloudTrack, SoundCloudTrackDeprecated, SoundCloudTrackFormat, SoundCloudUser } from "./classes";
export interface SoundTrackJSON{
/**
* SoundCloud Track Name
*/
name: string;
/**
* SoundCloud Track ID
*/
id: number;
/**
* SoundCloud Track url
*/
url: string;
/**
* SoundCloud Track fetched status
*/
fetched: boolean;
/**
* SoundCloud Track Duration in seconds
*/
durationInSec: number;
/**
* SoundCloud Track Duration in miili seconds
*/
durationInMs: number;
/**
* SoundCloud Track formats data
*/
formats: SoundCloudTrackFormat[];
/**
* SoundCloud Track Publisher Data
*/
publisher: {
name: string;
id: number;
artist: string;
contains_music: boolean;
writer_composer: string;
} | null;
/**
* SoundCloud Track thumbnail
*/
thumbnail: string;
/**
* SoundCloud Track user data
*/
user: SoundCloudUser;
}
export interface PlaylistJSON{
/**
* SoundCloud Playlist Name
*/
name: string;
/**
* SoundCloud Playlist ID
*/
id: number;
/**
* SoundCloud Playlist URL
*/
url: string;
/**
* SoundCloud Playlist Sub type. == "album" for soundcloud albums
*/
sub_type: string;
/**
* SoundCloud Playlist Total Duration in seconds
*/
durationInSec: number;
/**
* SoundCloud Playlist Total Duration in milli seconds
*/
durationInMs: number;
/**
* SoundCloud Playlist user data
*/
user: SoundCloudUser;
/**
* SoundCloud Playlist tracks [ It can be fetched or not fetched ]
*/
tracks: SoundCloudTrack[] | SoundCloudTrackDeprecated[];
/**
* SoundCloud Playlist tracks number
*/
tracksCount: number;
/**
* SoundCloud Client ID provided by user
* @private
*/
}

View File

@ -1,8 +1,7 @@
import fs from 'node:fs'; import fs from 'node:fs';
import { StreamType } from '../YouTube/stream'; import { StreamType } from '../YouTube/stream';
import { request } from '../Request'; import { request } from '../Request';
import { SoundCloudPlaylist, SoundCloudTrack, SoundCloudTrackFormat, Stream } from './classes'; import { SoundCloudPlaylist, SoundCloudTrack, SoundCloudTrackFormat, SoundCloudStream } from './classes';
let soundData: SoundDataOptions; let soundData: SoundDataOptions;
if (fs.existsSync('.data/soundcloud.data')) { if (fs.existsSync('.data/soundcloud.data')) {
soundData = JSON.parse(fs.readFileSync('.data/soundcloud.data').toString()); soundData = JSON.parse(fs.readFileSync('.data/soundcloud.data').toString());
@ -69,7 +68,7 @@ export async function so_search(
* @param quality Quality to select from * @param quality Quality to select from
* @returns SoundCloud Stream * @returns SoundCloud Stream
*/ */
export async function stream(url: string, quality?: number): Promise<Stream> { export async function stream(url: string, quality?: number): Promise<SoundCloudStream> {
const data = await soundcloud(url); const data = await soundcloud(url);
if (data instanceof SoundCloudPlaylist) throw new Error("Streams can't be created from Playlist url"); if (data instanceof SoundCloudPlaylist) throw new Error("Streams can't be created from Playlist url");
@ -83,7 +82,7 @@ export async function stream(url: string, quality?: number): Promise<Stream> {
const type = HLSformats[quality].format.mime_type.startsWith('audio/ogg') const type = HLSformats[quality].format.mime_type.startsWith('audio/ogg')
? StreamType.OggOpus ? StreamType.OggOpus
: StreamType.Arbitrary; : StreamType.Arbitrary;
return new Stream(s_data.url, type); return new SoundCloudStream(s_data.url, type);
} }
/** /**
* Function to get Free Client ID of soundcloud. * Function to get Free Client ID of soundcloud.
@ -101,11 +100,6 @@ export async function getFreeClientID(): Promise<string> {
const data2 = await request(urls[urls.length - 1]); const data2 = await request(urls[urls.length - 1]);
return data2.split(',client_id:"')[1].split('"')[0]; return data2.split(',client_id:"')[1].split('"')[0];
} }
/**
* Type for SoundCloud Stream
*/
export type SoundCloudStream = Stream;
/** /**
* Function for creating a Stream of soundcloud using a SoundCloud Track Class * Function for creating a Stream of soundcloud using a SoundCloud Track Class
* @param data SoundCloud Track Class * @param data SoundCloud Track Class
@ -122,7 +116,7 @@ export async function stream_from_info(data: SoundCloudTrack, quality?: number):
const type = HLSformats[quality].format.mime_type.startsWith('audio/ogg') const type = HLSformats[quality].format.mime_type.startsWith('audio/ogg')
? StreamType.OggOpus ? StreamType.OggOpus
: StreamType.Arbitrary; : StreamType.Arbitrary;
return new Stream(s_data.url, type); return new SoundCloudStream(s_data.url, type);
} }
/** /**
* Function to check client ID * Function to check client ID
@ -172,3 +166,5 @@ function parseHlsFormats(data: SoundCloudTrackFormat[]) {
export function setSoundCloudToken(options: SoundDataOptions) { export function setSoundCloudToken(options: SoundDataOptions) {
soundData = options; soundData = options;
} }
export { SoundCloudTrack, SoundCloudPlaylist, SoundCloudStream }

View File

@ -1,45 +1,122 @@
import { request } from '../Request'; import { request } from '../Request';
import { SpotifyDataOptions } from '.'; import { SpotifyDataOptions } from '.';
import { AlbumJSON, PlaylistJSON, TrackJSON } from './constants';
interface SpotifyTrackAlbum { export interface SpotifyTrackAlbum {
/**
* Spotify Track Album name
*/
name: string; name: string;
/**
* Spotify Track Album url
*/
url: string; url: string;
/**
* Spotify Track Album id
*/
id: string; id: string;
/**
* Spotify Track Album release date
*/
release_date: string; release_date: string;
/**
* Spotify Track Album release date **precise**
*/
release_date_precision: string; release_date_precision: string;
/**
* Spotify Track Album total tracks number
*/
total_tracks: number; total_tracks: number;
} }
interface SpotifyArtists { export interface SpotifyArtists {
/**
* Spotify Artist Name
*/
name: string; name: string;
/**
* Spotify Artist Url
*/
url: string; url: string;
/**
* Spotify Artist ID
*/
id: string; id: string;
} }
interface SpotifyThumbnail { export interface SpotifyThumbnail {
/**
* Spotify Thumbnail height
*/
height: number; height: number;
/**
* Spotify Thumbnail width
*/
width: number; width: number;
/**
* Spotify Thumbnail url
*/
url: string; url: string;
} }
interface SpotifyCopyright { export interface SpotifyCopyright {
/**
* Spotify Copyright Text
*/
text: string; text: string;
/**
* Spotify Copyright Type
*/
type: string; type: string;
} }
/** /**
* Class for Spotify Track * Spotify Track Class
*/ */
export class SpotifyTrack { export class SpotifyTrack {
/**
* Spotify Track Name
*/
name: string; name: string;
/**
* Spotify Class type. == "track"
*/
type: 'track' | 'playlist' | 'album'; type: 'track' | 'playlist' | 'album';
/**
* Spotify Track ID
*/
id: string; id: string;
/**
* Spotify Track url
*/
url: string; url: string;
/**
* Spotify Track explicit info.
*/
explicit: boolean; explicit: boolean;
/**
* Spotify Track Duration in seconds
*/
durationInSec: number; durationInSec: number;
/**
* Spotify Track Duration in milli seconds
*/
durationInMs: number; durationInMs: number;
/**
* Spotify Track Artists data [ array ]
*/
artists: SpotifyArtists[]; artists: SpotifyArtists[];
/**
* Spotify Track Album data
*/
album: SpotifyTrackAlbum | undefined; album: SpotifyTrackAlbum | undefined;
/**
* Spotify Track Thumbnail Data
*/
thumbnail: SpotifyThumbnail | undefined; thumbnail: SpotifyThumbnail | undefined;
/**
* Constructor for Spotify Track
* @param data
*/
constructor(data: any) { constructor(data: any) {
this.name = data.name; this.name = data.name;
this.id = data.id; this.id = data.id;
@ -72,11 +149,10 @@ export class SpotifyTrack {
else this.thumbnail = data.album.images[0]; else this.thumbnail = data.album.images[0];
} }
toJSON() { toJSON() : TrackJSON {
return { return {
name: this.name, name: this.name,
id: this.id, id: this.id,
type: this.type,
url: this.url, url: this.url,
explicit: this.explicit, explicit: this.explicit,
durationInMs: this.durationInMs, durationInMs: this.durationInMs,
@ -88,20 +164,62 @@ export class SpotifyTrack {
} }
} }
/** /**
* Class for Spotify Playlist * Spotify Playlist Class
*/ */
export class SpotifyPlaylist { export class SpotifyPlaylist {
/**
* Spotify Playlist Name
*/
name: string; name: string;
/**
* Spotify Class type. == "playlist"
*/
type: 'track' | 'playlist' | 'album'; type: 'track' | 'playlist' | 'album';
/**
* Spotify Playlist collaborative boolean.
*/
collaborative: boolean; collaborative: boolean;
/**
* Spotify Playlist Description
*/
description: string; description: string;
/**
* Spotify Playlist URL
*/
url: string; url: string;
/**
* Spotify Playlist ID
*/
id: string; id: string;
/**
* Spotify Playlist Thumbnail Data
*/
thumbnail: SpotifyThumbnail; thumbnail: SpotifyThumbnail;
/**
* Spotify Playlist Owner Artist data
*/
owner: SpotifyArtists; owner: SpotifyArtists;
/**
* Spotify Playlist total tracks Count
*/
tracksCount: number; tracksCount: number;
/**
* Spotify Playlist Spotify data
*
* @private
*/
private spotifyData: SpotifyDataOptions; private spotifyData: SpotifyDataOptions;
/**
* Spotify Playlist fetched tracks Map
*
* @private
*/
private fetched_tracks: Map<string, SpotifyTrack[]>; private fetched_tracks: Map<string, SpotifyTrack[]>;
/**
* Constructor for Spotify Playlist Class
* @param data JSON parsed data of playlist
* @param spotifyData Data about sporify token for furhter fetching.
*/
constructor(data: any, spotifyData: SpotifyDataOptions) { constructor(data: any, spotifyData: SpotifyDataOptions) {
this.name = data.name; this.name = data.name;
this.type = 'playlist'; this.type = 'playlist';
@ -124,7 +242,12 @@ export class SpotifyPlaylist {
this.fetched_tracks.set('1', videos); this.fetched_tracks.set('1', videos);
this.spotifyData = spotifyData; this.spotifyData = spotifyData;
} }
/**
* Fetches Spotify Playlist tracks more than 100 tracks.
*
* For getting all tracks in playlist, see `total_pages` property.
* @returns Playlist Class.
*/
async fetch() { async fetch() {
let fetching: number; let fetching: number;
if (this.tracksCount > 1000) fetching = 1000; if (this.tracksCount > 1000) fetching = 1000;
@ -158,51 +281,131 @@ export class SpotifyPlaylist {
await Promise.allSettled(work); await Promise.allSettled(work);
return this; return this;
} }
/**
* Spotify Playlist tracks are divided in pages.
*
* For example getting data of 101 - 200 videos in a playlist,
*
* ```ts
* const playlist = await play.spotify('playlist url')
*
* await playlist.fetch()
*
* const result = playlist.page(2)
* ```
* @param num Page Number
* @returns
*/
page(num: number) { page(num: number) {
if (!num) throw new Error('Page number is not provided'); if (!num) throw new Error('Page number is not provided');
if (!this.fetched_tracks.has(`${num}`)) throw new Error('Given Page number is invalid'); if (!this.fetched_tracks.has(`${num}`)) throw new Error('Given Page number is invalid');
return this.fetched_tracks.get(`${num}`); return this.fetched_tracks.get(`${num}`) as SpotifyTrack[];
} }
/**
* Spotify Playlist total no of pages in a playlist
*
* For getting all songs in a playlist,
*
* ```ts
* const playlist = await play.spotify('playlist url')
*
* await playlist.fetch()
*
* const result = []
*
* for (let i = 0; i <= playlist.tota_pages; i++) {
* result.push(playlist.page(i))
* }
* ```
*/
get total_pages() { get total_pages() {
return this.fetched_tracks.size; return this.fetched_tracks.size;
} }
/**
* Spotify Playlist total no of tracks that have been fetched so far.
*/
get total_tracks() { get total_tracks() {
const page_number: number = this.total_pages; const page_number: number = this.total_pages;
return (page_number - 1) * 100 + (this.fetched_tracks.get(`${page_number}`) as SpotifyTrack[]).length; return (page_number - 1) * 100 + (this.fetched_tracks.get(`${page_number}`) as SpotifyTrack[]).length;
} }
/**
toJSON() { * Converts Class to JSON
* @returns JSON data
*/
toJSON() : PlaylistJSON{
return { return {
name: this.name, name: this.name,
type: this.type,
collaborative: this.collaborative, collaborative: this.collaborative,
description: this.description, description: this.description,
url: this.url, url: this.url,
id: this.id, id: this.id,
thumbnail: this.thumbnail, thumbnail: this.thumbnail,
owner: this.owner owner: this.owner,
tracksCount : this.tracksCount
}; };
} }
} }
/** /**
* Class for Spotify Album * Spotify Album Class
*/ */
export class SpotifyAlbum { export class SpotifyAlbum {
/**
* Spotify Album Name
*/
name: string; name: string;
/**
* Spotify Class type. == "album"
*/
type: 'track' | 'playlist' | 'album'; type: 'track' | 'playlist' | 'album';
/**
* Spotify Album url
*/
url: string; url: string;
/**
* Spotify Album id
*/
id: string; id: string;
/**
* Spotify Album Thumbnail data
*/
thumbnail: SpotifyThumbnail; thumbnail: SpotifyThumbnail;
/**
* Spotify Album artists [ array ]
*/
artists: SpotifyArtists[]; artists: SpotifyArtists[];
/**
* Spotify Album copyright data [ array ]
*/
copyrights: SpotifyCopyright[]; copyrights: SpotifyCopyright[];
/**
* Spotify Album Release date
*/
release_date: string; release_date: string;
/**
* Spotify Album Release Date **precise**
*/
release_date_precision: string; release_date_precision: string;
/**
* Spotify Album total no of tracks
*/
tracksCount: number; tracksCount: number;
/**
* Spotify Album Spotify data
*
* @private
*/
private spotifyData: SpotifyDataOptions; private spotifyData: SpotifyDataOptions;
/**
* Spotify Album fetched tracks Map
*
* @private
*/
private fetched_tracks: Map<string, SpotifyTrack[]>; private fetched_tracks: Map<string, SpotifyTrack[]>;
/**
* Constructor for Spotify Album Class
* @param data Json parsed album data
* @param spotifyData Spotify credentials
*/
constructor(data: any, spotifyData: SpotifyDataOptions) { constructor(data: any, spotifyData: SpotifyDataOptions) {
this.name = data.name; this.name = data.name;
this.type = 'album'; this.type = 'album';
@ -230,7 +433,12 @@ export class SpotifyAlbum {
this.fetched_tracks.set('1', videos); this.fetched_tracks.set('1', videos);
this.spotifyData = spotifyData; this.spotifyData = spotifyData;
} }
/**
* Fetches Spotify Album tracks more than 50 tracks.
*
* For getting all tracks in album, see `total_pages` property.
* @returns Album Class.
*/
async fetch() { async fetch() {
let fetching: number; let fetching: number;
if (this.tracksCount > 500) fetching = 500; if (this.tracksCount > 500) fetching = 500;
@ -264,25 +472,58 @@ export class SpotifyAlbum {
await Promise.allSettled(work); await Promise.allSettled(work);
return this; return this;
} }
/**
* Spotify Album tracks are divided in pages.
*
* For example getting data of 51 - 100 videos in a album,
*
* ```ts
* const album = await play.spotify('album url')
*
* await album.fetch()
*
* const result = album.page(2)
* ```
* @param num Page Number
* @returns
*/
page(num: number) { page(num: number) {
if (!num) throw new Error('Page number is not provided'); if (!num) throw new Error('Page number is not provided');
if (!this.fetched_tracks.has(`${num}`)) throw new Error('Given Page number is invalid'); if (!this.fetched_tracks.has(`${num}`)) throw new Error('Given Page number is invalid');
return this.fetched_tracks.get(`${num}`); return this.fetched_tracks.get(`${num}`);
} }
/**
* Spotify Album total no of pages in a album
*
* For getting all songs in a album,
*
* ```ts
* const album = await play.spotify('album url')
*
* await album.fetch()
*
* const result = []
*
* for (let i = 0; i <= album.tota_pages; i++) {
* result.push(album.page(i))
* }
* ```
*/
get total_pages() { get total_pages() {
return this.fetched_tracks.size; return this.fetched_tracks.size;
} }
/**
* Spotify Album total no of tracks that have been fetched so far.
*/
get total_tracks() { get total_tracks() {
const page_number: number = this.total_pages; const page_number: number = this.total_pages;
return (page_number - 1) * 100 + (this.fetched_tracks.get(`${page_number}`) as SpotifyTrack[]).length; return (page_number - 1) * 100 + (this.fetched_tracks.get(`${page_number}`) as SpotifyTrack[]).length;
} }
toJSON() { toJSON() : AlbumJSON {
return { return {
name: this.name, name: this.name,
id : this.id,
type: this.type, type: this.type,
url: this.url, url: this.url,
thumbnail: this.thumbnail, thumbnail: this.thumbnail,
@ -290,7 +531,7 @@ export class SpotifyAlbum {
copyrights: this.copyrights, copyrights: this.copyrights,
release_date: this.release_date, release_date: this.release_date,
release_date_precision: this.release_date_precision, release_date_precision: this.release_date_precision,
total_tracks: this.total_tracks tracksCount : this.tracksCount
}; };
} }
} }

View File

@ -0,0 +1,118 @@
import { SpotifyArtists, SpotifyCopyright, SpotifyThumbnail, SpotifyTrackAlbum } from './classes'
export interface TrackJSON{
/**
* Spotify Track Name
*/
name: string;
/**
* Spotify Track ID
*/
id: string;
/**
* Spotify Track url
*/
url: string;
/**
* Spotify Track explicit info.
*/
explicit: boolean;
/**
* Spotify Track Duration in seconds
*/
durationInSec: number;
/**
* Spotify Track Duration in milli seconds
*/
durationInMs: number;
/**
* Spotify Track Artists data [ array ]
*/
artists: SpotifyArtists[];
/**
* Spotify Track Album data
*/
album: SpotifyTrackAlbum | undefined;
/**
* Spotify Track Thumbnail Data
*/
thumbnail: SpotifyThumbnail | undefined;
}
export interface PlaylistJSON {
/**
* Spotify Playlist Name
*/
name: string;
/**
* Spotify Playlist collaborative boolean.
*/
collaborative: boolean;
/**
* Spotify Playlist Description
*/
description: string;
/**
* Spotify Playlist URL
*/
url: string;
/**
* Spotify Playlist ID
*/
id: string;
/**
* Spotify Playlist Thumbnail Data
*/
thumbnail: SpotifyThumbnail;
/**
* Spotify Playlist Owner Artist data
*/
owner: SpotifyArtists;
/**
* Spotify Playlist total tracks Count
*/
tracksCount: number;
}
export interface AlbumJSON{
/**
* Spotify Album Name
*/
name: string;
/**
* Spotify Class type. == "album"
*/
type: 'track' | 'playlist' | 'album';
/**
* Spotify Album url
*/
url: string;
/**
* Spotify Album id
*/
id: string;
/**
* Spotify Album Thumbnail data
*/
thumbnail: SpotifyThumbnail;
/**
* Spotify Album artists [ array ]
*/
artists: SpotifyArtists[];
/**
* Spotify Album copyright data [ array ]
*/
copyrights: SpotifyCopyright[];
/**
* Spotify Album Release date
*/
release_date: string;
/**
* Spotify Album Release Date **precise**
*/
release_date_precision: string;
/**
* Spotify Album total no of tracks
*/
tracksCount: number;
}

View File

@ -214,3 +214,5 @@ export function setSpotifyToken(options: SpotifyDataOptions) {
spotifyData.file = false; spotifyData.file = false;
refreshToken(); refreshToken();
} }
export { SpotifyTrack, SpotifyAlbum, SpotifyPlaylist }

View File

@ -1,30 +1,60 @@
export interface ChannelIconInterface { export interface ChannelIconInterface {
url?: string; /**
* YouTube Channel Icon URL
*/
url: string;
/**
* YouTube Channel Icon Width
*/
width: number; width: number;
/**
* YouTube Channel Icon Height
*/
height: number; height: number;
} }
/** /**
* Class for YouTube Channel url * YouTube Channel Class
*/ */
export class YouTubeChannel { export class YouTubeChannel {
/**
* YouTube Channel Title
*/
name?: string; name?: string;
/**
* YouTube Channel Verified status.
*/
verified?: boolean; verified?: boolean;
/**
* YouTube Channel artist if any.
*/
artist?: boolean; artist?: boolean;
/**
* YouTube Channel ID.
*/
id?: string; id?: string;
/**
* YouTube Class type. == "channel"
*/
type: 'video' | 'playlist' | 'channel'; type: 'video' | 'playlist' | 'channel';
/**
* YouTube Channel Url
*/
url?: string; url?: string;
/**
* YouTube Channel Icon data.
*/
icon?: ChannelIconInterface; icon?: ChannelIconInterface;
/**
* YouTube Channel subscribers count.
*/
subscribers?: string; subscribers?: string;
/**
constructor(data: any) { * YouTube Channel Constructor
* @param data YouTube Channel data that we recieve from basic info or from search
*/
constructor(data: any = {}) {
if (!data) throw new Error(`Cannot instantiate the ${this.constructor.name} class without data!`); if (!data) throw new Error(`Cannot instantiate the ${this.constructor.name} class without data!`);
this.type = 'channel'; this.type = 'channel';
this._patch(data);
}
private _patch(data: any): void {
if (!data) data = {};
this.name = data.name || null; this.name = data.name || null;
this.verified = !!data.verified || false; this.verified = !!data.verified || false;
this.artist = !!data.artist || false; this.artist = !!data.artist || false;
@ -45,21 +75,62 @@ export class YouTubeChannel {
const def = this.icon.url.split('=s')[1].split('-c')[0]; const def = this.icon.url.split('=s')[1].split('-c')[0];
return this.icon.url.replace(`=s${def}-c`, `=s${options.size}-c`); return this.icon.url.replace(`=s${def}-c`, `=s${options.size}-c`);
} }
/**
* Converts Channel Class to channel name.
* @returns name of channel
*/
toString(): string { toString(): string {
return this.name || ''; return this.name || '';
} }
/**
toJSON() { * Converts Channel Class to JSON format
* @returns json data of the channel
*/
toJSON() : ChannelJSON {
return { return {
name: this.name, name: this.name,
verified: this.verified, verified: this.verified,
artist: this.artist, artist: this.artist,
id: this.id, id: this.id,
url: this.url, url: this.url,
iconURL: this.iconURL(), icon: this.icon,
type: this.type, type: this.type,
subscribers: this.subscribers subscribers: this.subscribers
}; };
} }
} }
interface ChannelJSON{
/**
* YouTube Channel Title
*/
name?: string;
/**
* YouTube Channel Verified status.
*/
verified?: boolean;
/**
* YouTube Channel artist if any.
*/
artist?: boolean;
/**
* YouTube Channel ID.
*/
id?: string;
/**
* Type of Class [ Channel ]
*/
type: 'video' | 'playlist' | 'channel';
/**
* YouTube Channel Url
*/
url?: string;
/**
* YouTube Channel Icon data.
*/
icon?: ChannelIconInterface;
/**
* YouTube Channel subscribers count.
*/
subscribers?: string;
}

View File

@ -9,21 +9,69 @@ export interface FormatInterface {
targetDurationSec: number; targetDurationSec: number;
maxDvrDurationSec: number; maxDvrDurationSec: number;
} }
/**
export class LiveStreaming { * YouTube Live Stream class for playing audio from Live Stream videos.
*/
export class LiveStream {
/**
* Readable Stream through which data passes
*/
stream: Readable; stream: Readable;
/**
* Type of audio data that we recieved from live stream youtube url.
*/
type: StreamType; type: StreamType;
/**
* Base URL in dash manifest file.
*/
private base_url: string; private base_url: string;
/**
* Given Dash URL.
*/
private url: string; private url: string;
/**
* Interval to fetch data again to dash url.
*/
private interval: number; private interval: number;
/**
* Sequence count of urls in dash file.
*/
private packet_count: number; private packet_count: number;
/**
* Timer that creates loop from interval time provided.
*/
private timer: Timer; private timer: Timer;
/**
* Live Stream Video url.
*/
private video_url: string; private video_url: string;
/**
* Timer used to update dash url so as to avoid 404 errors after long hours of streaming.
*
* It updates dash_url every 30 minutes.
*/
private dash_timer: Timer; private dash_timer: Timer;
/**
* Segments of url that we recieve in dash file.
*
* base_url + segment_urls[0] = One complete url for one segment.
*/
private segments_urls: string[]; private segments_urls: string[];
/**
* Incoming message that we recieve.
*
* Storing this is essential.
* This helps to destroy the TCP connection completely if you stopped player in between the stream
*/
private request: IncomingMessage | null; private request: IncomingMessage | null;
/**
* Live Stream Class Constructor
* @param dash_url dash manifest URL
* @param target_interval interval time for fetching dash data again
* @param video_url Live Stream video url.
*/
constructor(dash_url: string, target_interval: number, video_url: string) { constructor(dash_url: string, target_interval: number, video_url: string) {
this.stream = new Readable({ highWaterMark: 10 * 1000 * 1000, read() {} }); this.stream = new Readable({ highWaterMark: 5 * 1000 * 1000, read() {} });
this.type = StreamType.Arbitrary; this.type = StreamType.Arbitrary;
this.url = dash_url; this.url = dash_url;
this.base_url = ''; this.base_url = '';
@ -44,7 +92,11 @@ export class LiveStreaming {
}); });
this.start(); this.start();
} }
/**
* Updates dash url.
*
* Used by dash_timer for updating dash_url every 30 minutes.
*/
private async dash_updater() { private async dash_updater() {
const info = await video_info(this.video_url); const info = await video_info(this.video_url);
if ( if (
@ -52,10 +104,14 @@ export class LiveStreaming {
info.LiveStreamData.hlsManifestUrl !== null && info.LiveStreamData.hlsManifestUrl !== null &&
info.video_details.durationInSec === 0 info.video_details.durationInSec === 0
) { ) {
this.url = info.LiveStreamData.dashManifestUrl; this.url = info.LiveStreamData.dashManifestUrl as string;
} }
} }
/**
* Parses data recieved from dash_url.
*
* Updates base_url , segments_urls array.
*/
private async dash_getter() { private async dash_getter() {
const response = await request(this.url); const response = await request(this.url);
const audioFormat = response const audioFormat = response
@ -68,7 +124,11 @@ export class LiveStreaming {
this.segments_urls = list.replace(new RegExp('<SegmentURL media="', 'g'), '').split('"/>'); this.segments_urls = list.replace(new RegExp('<SegmentURL media="', 'g'), '').split('"/>');
if (this.segments_urls[this.segments_urls.length - 1] === '') this.segments_urls.pop(); if (this.segments_urls[this.segments_urls.length - 1] === '') this.segments_urls.pop();
} }
/**
* This cleans every used variable in class.
*
* This is used to prevent re-use of this class and helping garbage collector to collect it.
*/
private cleanup() { private cleanup() {
this.timer.destroy(); this.timer.destroy();
this.dash_timer.destroy(); this.dash_timer.destroy();
@ -81,7 +141,12 @@ export class LiveStreaming {
this.packet_count = 0; this.packet_count = 0;
this.interval = 0; this.interval = 0;
} }
/**
* This starts function in Live Stream Class.
*
* Gets data from dash url and pass it to dash getter function.
* Get data from complete segment url and pass data to Stream.
*/
private async start() { private async start() {
if (this.stream.destroyed) { if (this.stream.destroyed) {
this.cleanup(); this.cleanup();
@ -116,25 +181,75 @@ export class LiveStreaming {
this.timer.reuse(); this.timer.reuse();
} }
/**
* Deprecated Functions
*/
pause() {} pause() {}
/**
* Deprecated Functions
*/
resume() {} resume() {}
} }
/** /**
* Class for YouTube Stream * YouTube Stream Class for playing audio from normal videos.
*/ */
export class Stream { export class Stream {
/**
* Readable Stream through which data passes
*/
stream: Readable; stream: Readable;
/**
* Type of audio data that we recieved from normal youtube url.
*/
type: StreamType; type: StreamType;
/**
* Audio Endpoint Format Url to get data from.
*/
private url: string; private url: string;
/**
* Used to calculate no of bytes data that we have recieved
*/
private bytes_count: number; private bytes_count: number;
/**
* Calculate per second bytes by using contentLength (Total bytes) / Duration (in seconds)
*/
private per_sec_bytes: number; private per_sec_bytes: number;
/**
* Total length of audio file in bytes
*/
private content_length: number; private content_length: number;
/**
* YouTube video url. [ Used only for retrying purposes only. ]
*/
private video_url: string; private video_url: string;
/**
* Timer for looping data every 265 seconds.
*/
private timer: Timer; private timer: Timer;
/**
* Quality given by user. [ Used only for retrying purposes only. ]
*/
private quality: number; private quality: number;
/**
* Proxy config given by user. [ Used only for retrying purposes only. ]
*/
private proxy: Proxy[] | undefined; private proxy: Proxy[] | undefined;
/**
* Incoming message that we recieve.
*
* Storing this is essential.
* This helps to destroy the TCP connection completely if you stopped player in between the stream
*/
private request: IncomingMessage | null; private request: IncomingMessage | null;
/**
* YouTube Stream Class constructor
* @param url Audio Endpoint url.
* @param type Type of Stream
* @param duration Duration of audio playback [ in seconds ]
* @param contentLength Total length of Audio file in bytes.
* @param video_url YouTube video url.
* @param options Options provided to stream function.
*/
constructor( constructor(
url: string, url: string,
type: StreamType, type: StreamType,
@ -143,7 +258,7 @@ export class Stream {
video_url: string, video_url: string,
options: StreamOptions options: StreamOptions
) { ) {
this.stream = new Readable({ highWaterMark: 10 * 1000 * 1000, read() {} }); this.stream = new Readable({ highWaterMark: 5 * 1000 * 1000, read() {} });
this.url = url; this.url = url;
this.quality = options.quality as number; this.quality = options.quality as number;
this.proxy = options.proxy || undefined; this.proxy = options.proxy || undefined;
@ -163,19 +278,29 @@ export class Stream {
}); });
this.loop(); this.loop();
} }
/**
* Retry if we get 404 or 403 Errors.
*/
private async retry() { private async retry() {
const info = await video_info(this.video_url, { proxy: this.proxy }); const info = await video_info(this.video_url, { proxy: this.proxy });
const audioFormat = parseAudioFormats(info.format); const audioFormat = parseAudioFormats(info.format);
this.url = audioFormat[this.quality].url; this.url = audioFormat[this.quality].url;
} }
/**
* This cleans every used variable in class.
*
* This is used to prevent re-use of this class and helping garbage collector to collect it.
*/
private cleanup() { private cleanup() {
this.request?.destroy(); this.request?.destroy();
this.request = null; this.request = null;
this.url = ''; this.url = '';
} }
/**
* Getting data from audio endpoint url and passing it to stream.
*
* If 404 or 403 occurs, it will retry again.
*/
private async loop() { private async loop() {
if (this.stream.destroyed) { if (this.stream.destroyed) {
this.timer.destroy(); this.timer.destroy();
@ -226,24 +351,62 @@ export class Stream {
} }
}); });
} }
/**
* Pauses timer.
* Stops running of loop.
*
* Useful if you don't want to get excess data to be stored in stream.
*/
pause() { pause() {
this.timer.pause(); this.timer.pause();
} }
/**
* Resumes timer.
* Starts running of loop.
*/
resume() { resume() {
this.timer.resume(); this.timer.resume();
} }
} }
/**
* Timer Class.
*
* setTimeout + extra features ( re-starting, pausing, resuming ).
*/
export class Timer { export class Timer {
/**
* Boolean for checking if Timer is destroyed or not.
*/
private destroyed: boolean; private destroyed: boolean;
/**
* Boolean for checking if Timer is paused or not.
*/
private paused: boolean; private paused: boolean;
/**
* setTimeout function
*/
private timer: NodeJS.Timer; private timer: NodeJS.Timer;
/**
* Callback to be executed once timer finishes.
*/
private callback: () => void; private callback: () => void;
/**
* Seconds time when it is started.
*/
private time_start: number; private time_start: number;
/**
* Total time left.
*/
private time_left: number; private time_left: number;
/**
* Total time given by user [ Used only for re-using timer. ]
*/
private time_total: number; private time_total: number;
/**
* Constructor for Timer Class
* @param callback Function to execute when timer is up.
* @param time Total time to wait before execution.
*/
constructor(callback: () => void, time: number) { constructor(callback: () => void, time: number) {
this.callback = callback; this.callback = callback;
this.time_total = time; this.time_total = time;
@ -253,7 +416,10 @@ export class Timer {
this.time_start = process.hrtime()[0]; this.time_start = process.hrtime()[0];
this.timer = setTimeout(this.callback, this.time_total * 1000); this.timer = setTimeout(this.callback, this.time_total * 1000);
} }
/**
* Pauses Timer
* @returns Boolean to tell that if it is paused or not.
*/
pause() { pause() {
if (!this.paused && !this.destroyed) { if (!this.paused && !this.destroyed) {
this.paused = true; this.paused = true;
@ -262,7 +428,10 @@ export class Timer {
return true; return true;
} else return false; } else return false;
} }
/**
* Resumes Timer
* @returns Boolean to tell that if it is resumed or not.
*/
resume() { resume() {
if (this.paused && !this.destroyed) { if (this.paused && !this.destroyed) {
this.paused = false; this.paused = false;
@ -271,7 +440,10 @@ export class Timer {
return true; return true;
} else return false; } else return false;
} }
/**
* Reusing of timer
* @returns Boolean to tell if it is re-used or not.
*/
reuse() { reuse() {
if (!this.destroyed) { if (!this.destroyed) {
clearTimeout(this.timer); clearTimeout(this.timer);
@ -282,7 +454,11 @@ export class Timer {
return true; return true;
} else return false; } else return false;
} }
/**
* Destroy timer.
*
* It can't be used again.
*/
destroy() { destroy() {
clearTimeout(this.timer); clearTimeout(this.timer);
this.destroyed = true; this.destroyed = true;

View File

@ -2,35 +2,77 @@ import { getPlaylistVideos, getContinuationToken } from '../utils/extractor';
import { request } from '../../Request'; import { request } from '../../Request';
import { YouTubeChannel } from './Channel'; import { YouTubeChannel } from './Channel';
import { YouTubeVideo } from './Video'; import { YouTubeVideo } from './Video';
import { YouTubeThumbnail } from './Thumbnail';
const BASE_API = 'https://www.youtube.com/youtubei/v1/browse?key='; const BASE_API = 'https://www.youtube.com/youtubei/v1/browse?key=';
/** /**
* Class for YouTube Playlist url * YouTube Playlist Class containing vital informations about playlist.
*/ */
export class YouTubePlayList { export class YouTubePlayList {
/**
* YouTube Playlist ID
*/
id?: string; id?: string;
/**
* YouTube Playlist Name
*/
title?: string; title?: string;
/**
* YouTube Class type. == "playlist"
*/
type: 'video' | 'playlist' | 'channel'; type: 'video' | 'playlist' | 'channel';
/**
* Total no of videos in that playlist
*/
videoCount?: number; videoCount?: number;
/**
* Time when playlist was last updated
*/
lastUpdate?: string; lastUpdate?: string;
/**
* Total views of that playlist
*/
views?: number; views?: number;
/**
* YouTube Playlist url
*/
url?: string; url?: string;
/**
* YouTube Playlist url with starting video url.
*/
link?: string; link?: string;
/**
* YouTube Playlist channel data
*/
channel?: YouTubeChannel; channel?: YouTubeChannel;
thumbnail?: { /**
id: string | undefined; * YouTube Playlist thumbnail Data
width: number | undefined; */
height: number | undefined; thumbnail?: YouTubeThumbnail
url: string | undefined; /**
}; * Videos array containing data of first 100 videos
private videos?: []; */
private videos?: YouTubeVideo[];
/**
* Map contaning data of all fetched videos
*/
private fetched_videos: Map<string, YouTubeVideo[]>; private fetched_videos: Map<string, YouTubeVideo[]>;
/**
* Token containing API key, Token, ClientVersion.
*/
private _continuation: { private _continuation: {
api?: string; api?: string;
token?: string; token?: string;
clientVersion?: string; clientVersion?: string;
} = {}; } = {};
/**
* Total no of pages count.
*/
private __count: number; private __count: number;
/**
* Constructor for YouTube Playlist Class
* @param data Json Parsed YouTube Playlist data
* @param searchResult If the data is from search or not
*/
constructor(data: any, searchResult = false) { constructor(data: any, searchResult = false) {
if (!data) throw new Error(`Cannot instantiate the ${this.constructor.name} class without data!`); if (!data) throw new Error(`Cannot instantiate the ${this.constructor.name} class without data!`);
this.__count = 0; this.__count = 0;
@ -39,7 +81,10 @@ export class YouTubePlayList {
if (searchResult) this.__patchSearch(data); if (searchResult) this.__patchSearch(data);
else this.__patch(data); else this.__patch(data);
} }
/**
* Updates variable according to a normal data.
* @param data Json Parsed YouTube Playlist data
*/
private __patch(data: any) { private __patch(data: any) {
this.id = data.id || undefined; this.id = data.id || undefined;
this.url = data.url || undefined; this.url = data.url || undefined;
@ -57,7 +102,10 @@ export class YouTubePlayList {
this._continuation.token = data.continuation?.token ?? undefined; this._continuation.token = data.continuation?.token ?? undefined;
this._continuation.clientVersion = data.continuation?.clientVersion ?? '<important data>'; this._continuation.clientVersion = data.continuation?.clientVersion ?? '<important data>';
} }
/**
* Updates variable according to a searched data.
* @param data Json Parsed YouTube Playlist data
*/
private __patchSearch(data: any) { private __patchSearch(data: any) {
this.id = data.id || undefined; this.id = data.id || undefined;
this.url = this.id ? `https://www.youtube.com/playlist?list=${this.id}` : undefined; this.url = this.id ? `https://www.youtube.com/playlist?list=${this.id}` : undefined;
@ -70,7 +118,13 @@ export class YouTubePlayList {
this.lastUpdate = undefined; this.lastUpdate = undefined;
this.views = 0; this.views = 0;
} }
/**
* Parses next segment of videos from playlist and returns parsed data.
* @param limit Total no of videos to parse.
*
* Default = Infinity
* @returns Array of YouTube Video Class
*/
async next(limit = Infinity): Promise<YouTubeVideo[]> { async next(limit = Infinity): Promise<YouTubeVideo[]> {
if (!this._continuation || !this._continuation.token) return []; if (!this._continuation || !this._continuation.token) return [];
@ -101,49 +155,139 @@ export class YouTubePlayList {
this._continuation.token = getContinuationToken(contents); this._continuation.token = getContinuationToken(contents);
return playlist_videos; return playlist_videos;
} }
/**
async fetch(max = Infinity) { * Fetches remaining data from playlist
*
* For fetching and getting all songs data, see `total_pages` property.
* @param max Max no of videos to fetch
*
* Default = Infinity
* @returns
*/
async fetch(max = Infinity): Promise<YouTubePlayList> {
const continuation = this._continuation.token; const continuation = this._continuation.token;
if (!continuation) return this; if (!continuation) return this;
if (max < 1) max = Infinity; if (max < 1) max = Infinity;
while (typeof this._continuation.token === 'string' && this._continuation.token.length) { while (typeof this._continuation.token === 'string' && this._continuation.token.length) {
if ((this.videos?.length as number) >= max) break;
this.__count++; this.__count++;
const res = await this.next(); const res = await this.next();
max -= res.length;
if(max <= 0) break;
if (!res.length) break; if (!res.length) break;
} }
return this; return this;
} }
/**
* YouTube Playlist is divided into pages.
*
* For example, if you want to get 101 - 200 songs
*
* ```ts
* const playlist = await play.playlist_info('playlist url')
*
* await playlist.fetch()
*
* const result = playlist.page(2)
* ```
* @param number Page number
* @returns Array of YouTube Video Class
*/
page(number: number): YouTubeVideo[] { page(number: number): YouTubeVideo[] {
if (!number) throw new Error('Page number is not provided'); if (!number) throw new Error('Page number is not provided');
if (!this.fetched_videos.has(`${number}`)) throw new Error('Given Page number is invalid'); if (!this.fetched_videos.has(`${number}`)) throw new Error('Given Page number is invalid');
return this.fetched_videos.get(`${number}`) as YouTubeVideo[]; return this.fetched_videos.get(`${number}`) as YouTubeVideo[];
} }
/**
* Gets total no of pages in that playlist class.
*
* For getting all songs in a playlist
*
* ```ts
* const playlist = await play.playlist_info('playlist url');
*
* await playlist.fetch();
*
* let result = [];
*
* for (let i = 0; i <= playlist.total_pages; i++) {
* result.push(playlist.page(i));
* }
* ```
*/
get total_pages() { get total_pages() {
return this.fetched_videos.size; return this.fetched_videos.size;
} }
/**
* This tells total no of videos that have been fetched so far.
*
* This can be equal to videosCount if all videos in playlist have been fetched and they are not hidden.
*/
get total_videos() { get total_videos() {
const page_number: number = this.total_pages; const page_number: number = this.total_pages;
return (page_number - 1) * 100 + (this.fetched_videos.get(`${page_number}`) as YouTubeVideo[]).length; return (page_number - 1) * 100 + (this.fetched_videos.get(`${page_number}`) as YouTubeVideo[]).length;
} }
/**
toJSON() { * Converts Playlist Class to a json parsed data.
* @returns
*/
toJSON() : PlaylistJSON {
return { return {
id: this.id, id: this.id,
title: this.title, title: this.title,
thumbnail: this.thumbnail, thumbnail: this.thumbnail?.toJSON() || this.thumbnail,
channel: { channel: this.channel,
name: this.channel?.name,
id: this.channel?.id,
icon: this.channel?.iconURL()
},
url: this.url, url: this.url,
videos: this.videos videos: this.videos
}; };
} }
} }
interface PlaylistJSON{
/**
* YouTube Playlist ID
*/
id?: string;
/**
* YouTube Playlist Name
*/
title?: string;
/**
* Total no of videos in that playlist
*/
videoCount?: number;
/**
* Time when playlist was last updated
*/
lastUpdate?: string;
/**
* Total views of that playlist
*/
views?: number;
/**
* YouTube Playlist url
*/
url?: string;
/**
* YouTube Playlist url with starting video url.
*/
link?: string;
/**
* YouTube Playlist channel data
*/
channel?: YouTubeChannel;
/**
* YouTube Playlist thumbnail Data
*/
thumbnail?: {
id: string | undefined;
width: number | undefined;
height: number | undefined;
url: string | undefined;
};
/**
* first 100 videos in that playlist
*/
videos? : YouTubeVideo[]
}

View File

@ -0,0 +1,22 @@
export class YouTubeThumbnail {
id : string;
url : string;
width : number;
height : number;
constructor(data : any){
this.id = data.id
this.url = data.url
this.width = data.width
this.height = data.height
}
toJSON(){
return {
id : this.id,
url : this.url,
width : this.width,
height : this.height
}
}
}

View File

@ -1,56 +1,145 @@
import { YouTubeChannel } from './Channel'; import { YouTubeChannel } from './Channel';
import { YouTubeThumbnail } from './Thumbnail';
interface VideoOptions { interface VideoOptions {
/**
* YouTube Video ID
*/
id?: string; id?: string;
url?: string; /**
* YouTube video url
*/
url: string;
/**
* YouTube Video title
*/
title?: string; title?: string;
/**
* YouTube Video description.
*/
description?: string; description?: string;
/**
* YouTube Video Duration Formatted
*/
durationRaw: string; durationRaw: string;
/**
* YouTube Video Duration in seconds
*/
durationInSec: number; durationInSec: number;
/**
* YouTube Video Uploaded Date
*/
uploadedAt?: string; uploadedAt?: string;
/**
* YouTube Views
*/
views: number; views: number;
/**
* YouTube Thumbnail Data
*/
thumbnail?: { thumbnail?: {
id: string | undefined; id: string | undefined;
width: number | undefined; width: number | undefined;
height: number | undefined; height: number | undefined;
url: string | undefined; url: string | undefined;
}; };
channel?: any; /**
type: string; * YouTube Video's uploader Channel Data
ratings: { */
channel?: YouTubeChannel;
/**
* YouTube Video's likes
*/
likes: number; likes: number;
/**
* YouTube Video's dislikes
*/
dislikes: number; dislikes: number;
}; /**
* YouTube Video live status
*/
live: boolean; live: boolean;
/**
* YouTube Video private status
*/
private: boolean; private: boolean;
/**
* YouTube Video tags
*/
tags: string[]; tags: string[];
} }
/** /**
* Class for YouTube Video url * Class for YouTube Video url
*/ */
export class YouTubeVideo { export class YouTubeVideo {
/**
* YouTube Video ID
*/
id?: string; id?: string;
/**
* YouTube video url
*/
url: string; url: string;
/**
* YouTube Class type. == "video"
*/
type: 'video' | 'playlist' | 'channel'; type: 'video' | 'playlist' | 'channel';
/**
* YouTube Video title
*/
title?: string; title?: string;
/**
* YouTube Video description.
*/
description?: string; description?: string;
/**
* YouTube Video Duration Formatted
*/
durationRaw: string; durationRaw: string;
/**
* YouTube Video Duration in seconds
*/
durationInSec: number; durationInSec: number;
/**
* YouTube Video Uploaded Date
*/
uploadedAt?: string; uploadedAt?: string;
/**
* YouTube Views
*/
views: number; views: number;
thumbnail?: { /**
id: string | undefined; * YouTube Thumbnail Data
width: number | undefined; */
height: number | undefined; thumbnail?: YouTubeThumbnail;
url: string | undefined; /**
}; * YouTube Video's uploader Channel Data
*/
channel?: YouTubeChannel; channel?: YouTubeChannel;
/**
* YouTube Video's likes
*/
likes: number; likes: number;
/**
* YouTube Video's dislikes
*/
dislikes: number; dislikes: number;
/**
* YouTube Video live status
*/
live: boolean; live: boolean;
/**
* YouTube Video private status
*/
private: boolean; private: boolean;
/**
* YouTube Video tags
*/
tags: string[]; tags: string[];
/**
* Constructor for YouTube Video Class
* @param data JSON parsed data.
*/
constructor(data: any) { constructor(data: any) {
if (!data) throw new Error(`Can not initiate ${this.constructor.name} without data`); if (!data) throw new Error(`Can not initiate ${this.constructor.name} without data`);
@ -71,12 +160,18 @@ export class YouTubeVideo {
this.private = !!data.private; this.private = !!data.private;
this.tags = data.tags || []; this.tags = data.tags || [];
} }
/**
get toString(): string { * Converts class to title name of video.
* @returns Title name
*/
toString(): string {
return this.url || ''; return this.url || '';
} }
/**
get toJSON(): VideoOptions { * Converts class to JSON data
* @returns JSON data.
*/
toJSON(): VideoOptions {
return { return {
id: this.id, id: this.id,
url: this.url, url: this.url,
@ -85,15 +180,12 @@ export class YouTubeVideo {
durationInSec: this.durationInSec, durationInSec: this.durationInSec,
durationRaw: this.durationRaw, durationRaw: this.durationRaw,
uploadedAt: this.uploadedAt, uploadedAt: this.uploadedAt,
thumbnail: this.thumbnail, thumbnail: this.thumbnail?.toJSON() || this.thumbnail,
channel: this.channel, channel: this.channel,
views: this.views, views: this.views,
type: this.type,
tags: this.tags, tags: this.tags,
ratings: {
likes: this.likes, likes: this.likes,
dislikes: this.dislikes dislikes: this.dislikes,
},
live: this.live, live: this.live,
private: this.private private: this.private
}; };

View File

@ -2,3 +2,6 @@ export { stream, stream_from_info, YouTubeStream } from './stream';
export * from './utils'; export * from './utils';
export { YouTube } from './search'; export { YouTube } from './search';
export { cookieHeaders } from './utils/cookie'; export { cookieHeaders } from './utils/cookie';
export { YouTubeVideo } from './classes/Video'
export { YouTubePlayList } from './classes/Playlist'
export { YouTubeChannel } from './classes/Channel'

View File

@ -1,6 +1,7 @@
import { video_info } from '.'; import { video_info } from '.';
import { LiveStreaming, Stream } from './classes/LiveStream'; import { LiveStream, Stream } from './classes/LiveStream';
import { ProxyOptions as Proxy } from './../Request'; import { ProxyOptions as Proxy } from './../Request';
import { InfoData } from './utils/constants';
export enum StreamType { export enum StreamType {
Arbitrary = 'arbitrary', Arbitrary = 'arbitrary',
@ -16,16 +17,6 @@ export interface StreamOptions {
htmldata?: boolean; htmldata?: boolean;
} }
export interface InfoData {
LiveStreamData: {
isLive: boolean;
dashManifestUrl: string;
hlsManifestUrl: string;
};
html5player: string;
format: any[];
video_details: any;
}
/** /**
* Command to find audio formats from given format array * Command to find audio formats from given format array
* @param formats Formats to search from * @param formats Formats to search from
@ -46,7 +37,7 @@ export function parseAudioFormats(formats: any[]) {
/** /**
* Type for YouTube Stream * Type for YouTube Stream
*/ */
export type YouTubeStream = Stream | LiveStreaming; export type YouTubeStream = Stream | LiveStream;
/** /**
* Stream command for YouTube * Stream command for YouTube
* @param url YouTube URL * @param url YouTube URL
@ -58,12 +49,12 @@ export async function stream(url: string, options: StreamOptions = {}): Promise<
const final: any[] = []; const final: any[] = [];
if ( if (
info.LiveStreamData.isLive === true && info.LiveStreamData.isLive === true &&
info.LiveStreamData.hlsManifestUrl !== null && info.LiveStreamData.dashManifestUrl !== null &&
info.video_details.durationInSec === 0 info.video_details.durationInSec === 0
) { ) {
return new LiveStreaming( return new LiveStream(
info.LiveStreamData.dashManifestUrl, info.LiveStreamData.dashManifestUrl,
info.format[info.format.length - 1].targetDurationSec, info.format[info.format.length - 1].targetDurationSec as number,
info.video_details.url info.video_details.url
); );
} }
@ -95,12 +86,12 @@ export async function stream_from_info(info: InfoData, options: StreamOptions =
const final: any[] = []; const final: any[] = [];
if ( if (
info.LiveStreamData.isLive === true && info.LiveStreamData.isLive === true &&
info.LiveStreamData.hlsManifestUrl !== null && info.LiveStreamData.dashManifestUrl !== null &&
info.video_details.durationInSec === 0 info.video_details.durationInSec === 0
) { ) {
return new LiveStreaming( return new LiveStream(
info.LiveStreamData.dashManifestUrl, info.LiveStreamData.dashManifestUrl,
info.format[info.format.length - 1].targetDurationSec, info.format[info.format.length - 1].targetDurationSec as number,
info.video_details.url info.video_details.url
); );
} }

View File

@ -0,0 +1,39 @@
import { YouTubeVideo } from "../classes/Video";
export interface LiveStreamData {
isLive: boolean;
dashManifestUrl: string | null
hlsManifestUrl: string | null
}
export interface formatData {
itag: number;
mimeType: string
bitrate: number
width: number
height: number
lastModified: string
contentLength: string
quality: string
fps: number
qualityLabel: string
projectionType: string
averageBitrate: number
audioQuality: string
approxDurationMs: string
audioSampleRate: string
audioChannels: number
url : string
signatureCipher : string;
cipher : string;
loudnessDb : number;
targetDurationSec : number;
}
export interface InfoData{
LiveStreamData : LiveStreamData
html5player : string
format : Partial<formatData>[]
video_details : YouTubeVideo
related_videos: string[]
}

View File

@ -2,7 +2,7 @@ import { ProxyOptions as Proxy, request } from './../../Request/index';
import { format_decipher } from './cipher'; import { format_decipher } from './cipher';
import { YouTubeVideo } from '../classes/Video'; import { YouTubeVideo } from '../classes/Video';
import { YouTubePlayList } from '../classes/Playlist'; import { YouTubePlayList } from '../classes/Playlist';
import { InfoData } from '../stream'; import { InfoData } from './constants';
interface InfoOptions { interface InfoOptions {
proxy?: Proxy[]; proxy?: Proxy[];
@ -79,7 +79,7 @@ export function extractID(url: string): string {
* @param options cookie and proxy parameters to add * @param options cookie and proxy parameters to add
* @returns Data containing video_details, LiveStreamData and formats of video url. * @returns Data containing video_details, LiveStreamData and formats of video url.
*/ */
export async function video_basic_info(url: string, options: InfoOptions = {}) { export async function video_basic_info(url: string, options: InfoOptions = {}) : Promise<InfoData> {
let body: string; let body: string;
if (options.htmldata) { if (options.htmldata) {
body = url; body = url;
@ -187,7 +187,7 @@ function parseSeconds(seconds: number): string {
* @param options cookie and proxy parameters to add * @param options cookie and proxy parameters to add
* @returns Data containing video_details, LiveStreamData and formats of video url. * @returns Data containing video_details, LiveStreamData and formats of video url.
*/ */
export async function video_info(url: string, options: InfoOptions = {}) { export async function video_info(url: string, options: InfoOptions = {}): Promise<InfoData> {
const data = await video_basic_info(url, options); const data = await video_basic_info(url, options);
if (data.LiveStreamData.isLive === true && data.LiveStreamData.hlsManifestUrl !== null) { if (data.LiveStreamData.isLive === true && data.LiveStreamData.hlsManifestUrl !== null) {
return data; return data;

View File

@ -7,11 +7,14 @@ export {
extractID, extractID,
YouTube, YouTube,
YouTubeStream, YouTubeStream,
cookieHeaders cookieHeaders,
YouTubeChannel,
YouTubePlayList,
YouTubeVideo
} from './YouTube'; } from './YouTube';
export { spotify, sp_validate, refreshToken, is_expired, Spotify } from './Spotify'; export { spotify, sp_validate, refreshToken, is_expired, SpotifyAlbum, SpotifyPlaylist, SpotifyTrack, Spotify } from './Spotify';
export { soundcloud, so_validate, SoundCloud, SoundCloudStream, getFreeClientID } from './SoundCloud'; export { soundcloud, so_validate, SoundCloud, SoundCloudStream, getFreeClientID, SoundCloudPlaylist, SoundCloudTrack } from './SoundCloud';
export { deezer, dz_validate, dz_search, dz_advanced_track_search, Deezer } from './Deezer'; export { deezer, dz_validate, dz_advanced_track_search, Deezer, DeezerTrack, DeezerPlaylist, DeezerAlbum } from './Deezer';
export { setToken } from './token'; export { setToken } from './token';
enum AudioPlayerStatus { enum AudioPlayerStatus {
@ -30,6 +33,7 @@ interface SearchOptions {
soundcloud?: 'tracks' | 'playlists' | 'albums'; soundcloud?: 'tracks' | 'playlists' | 'albums';
deezer?: 'track' | 'playlist' | 'album'; deezer?: 'track' | 'playlist' | 'album';
}; };
fuzzy?: boolean;
} }
import readline from 'node:readline'; import readline from 'node:readline';
@ -46,11 +50,17 @@ import {
} from '.'; } from '.';
import { SpotifyAuthorize, sp_search } from './Spotify'; import { SpotifyAuthorize, sp_search } from './Spotify';
import { check_id, so_search, stream as so_stream, stream_from_info as so_stream_info } from './SoundCloud'; import { check_id, so_search, stream as so_stream, stream_from_info as so_stream_info } from './SoundCloud';
import { InfoData, stream as yt_stream, StreamOptions, stream_from_info as yt_stream_info } from './YouTube/stream'; import { stream as yt_stream, StreamOptions, stream_from_info as yt_stream_info } from './YouTube/stream';
import { SoundCloudTrack } from './SoundCloud/classes'; import { SoundCloudPlaylist, SoundCloudTrack } from './SoundCloud/classes';
import { yt_search } from './YouTube/search'; import { yt_search } from './YouTube/search';
import { EventEmitter } from 'stream'; import { EventEmitter } from 'stream';
import { Deezer, dz_search, dz_validate } from './Deezer'; import { Deezer, dz_search, dz_validate } from './Deezer';
import { InfoData } from './YouTube/utils/constants';
import { YouTubeVideo } from './YouTube/classes/Video';
import { YouTubePlayList } from './YouTube/classes/Playlist';
import { YouTubeChannel } from './YouTube/classes/Channel';
import { SpotifyAlbum, SpotifyPlaylist, SpotifyTrack } from './Spotify/classes';
import { DeezerAlbum, DeezerPlaylist, DeezerTrack } from './Deezer/classes';
/** /**
* Main stream Command for streaming through various sources * Main stream Command for streaming through various sources
* @param url The video / track url to make stream of * @param url The video / track url to make stream of
@ -79,7 +89,21 @@ export async function stream(url: string, options: StreamOptions = {}): Promise<
* @param query string to search. * @param query string to search.
* @param options contains limit and source to choose. * @param options contains limit and source to choose.
* @returns Array of YouTube or Spotify or SoundCloud or Deezer * @returns Array of YouTube or Spotify or SoundCloud or Deezer
deezer?: 'track' | 'playlist' | 'album';
*/ */
export async function search( query: string, options: { source : { deezer : "album" } } & SearchOptions) : Promise<DeezerAlbum[]>;
export async function search( query: string, options: { source : { deezer : "playlist" } } & SearchOptions) : Promise<DeezerPlaylist[]>;
export async function search( query: string, options: { source : { deezer : "track" } } & SearchOptions) : Promise<DeezerTrack[]>;
export async function search( query: string, options: { source : { soundcloud : "albums" } } & SearchOptions) : Promise<SoundCloudPlaylist[]>;
export async function search( query: string, options: { source : { soundcloud : "playlists" } } & SearchOptions) : Promise<SoundCloudPlaylist[]>;
export async function search( query: string, options: { source : { soundcloud : "tracks" } } & SearchOptions) : Promise<SoundCloudTrack[]>;
export async function search( query: string, options: { source : { spotify : "album" } } & SearchOptions) : Promise<SpotifyAlbum[]>;
export async function search( query: string, options: { source : { spotify : "playlist" } } & SearchOptions) : Promise<SpotifyPlaylist[]>;
export async function search( query: string, options: { source : { spotify : "track" } } & SearchOptions) : Promise<SpotifyTrack[]>;
export async function search( query: string, options: { source : { youtube : "channel" } } & SearchOptions) : Promise<YouTubeChannel[]>;
export async function search( query: string, options: { source : { youtube : "playlist" } } & SearchOptions) : Promise<YouTubePlayList[]>;
export async function search( query: string, options: { source : { youtube : "video" } } & SearchOptions) : Promise<YouTubeVideo[]>;
export async function search( export async function search(
query: string, query: string,
options: SearchOptions = {} options: SearchOptions = {}
@ -90,7 +114,7 @@ export async function search(
else if (options.source.spotify) return await sp_search(query, options.source.spotify, options.limit); else if (options.source.spotify) return await sp_search(query, options.source.spotify, options.limit);
else if (options.source.soundcloud) return await so_search(query, options.source.soundcloud, options.limit); else if (options.source.soundcloud) return await so_search(query, options.source.soundcloud, options.limit);
else if (options.source.deezer) else if (options.source.deezer)
return await dz_search(query, { limit: options.limit, type: options.source.deezer }); return await dz_search(query, { limit: options.limit, type: options.source.deezer, fuzzy : options.fuzzy });
else throw new Error('Not possible to reach Here LOL. Easter Egg of play-dl if someone get this.'); else throw new Error('Not possible to reach Here LOL. Easter Egg of play-dl if someone get this.');
} }

5
typedoc.json Normal file
View File

@ -0,0 +1,5 @@
{
"entryPoints": ["./play-dl"],
"plugin" : "typedoc-plugin-missing-exports",
"out": "docs"
}