youtubei.js
Advanced tools
Comparing version 1.3.6 to 1.3.7-md
@@ -11,6 +11,6 @@ 'use strict'; | ||
* | ||
* @param {Innertube} session A valid Innertube session. | ||
* @param {string} engagement_type Type of engagement. | ||
* @param {object} args Engagement arguments. | ||
* @returns {Promise.<object>} { success: boolean, status_code: number } | { success: boolean, status_code: number, message: string } | ||
* @param {Innertube} session - A valid Innertube session. | ||
* @param {string} engagement_type - Type of engagement. | ||
* @param {object} args - Engagement arguments. | ||
* @returns {Promise.<{ success: boolean; status_code: number; data: object; message?: string }>} | ||
*/ | ||
@@ -20,4 +20,3 @@ async function engage(session, engagement_type, args = {}) { | ||
let data; | ||
const data = { context: session.context }; | ||
switch (engagement_type) { | ||
@@ -27,30 +26,19 @@ case 'like/like': | ||
case 'like/removelike': | ||
data = { | ||
context: session.context, | ||
target: { | ||
videoId: args.video_id | ||
} | ||
}; | ||
data.target = { | ||
videoId: args.video_id | ||
} | ||
break; | ||
case 'subscription/subscribe': | ||
case 'subscription/unsubscribe': | ||
data = { | ||
context: session.context, | ||
channelIds: [args.channel_id], | ||
params: engagement_type == 'subscription/subscribe' ? 'EgIIAhgA' : 'CgIIAhgA' | ||
}; | ||
data.channelIds = [args.channel_id]; | ||
data.params = engagement_type == 'subscription/subscribe' ? 'EgIIAhgA' : 'CgIIAhgA'; | ||
break; | ||
case 'comment/create_comment': | ||
data = { | ||
context: session.context, | ||
commentText: args.text, | ||
createCommentParams: Utils.encodeCommentParams(args.video_id) | ||
}; | ||
data.commentText = args.text; | ||
data.createCommentParams = Utils.encodeCommentParams(args.video_id); | ||
break; | ||
default: | ||
} | ||
const response = await Axios.post(`${Constants.URLS.YT_BASE_URL}/youtubei/v1/${engagement_type}${session.logged_in && session.cookie.length < 1 ? '' : `?key=${session.key}`}`, | ||
JSON.stringify(data), Constants.INNERTUBE_REQOPTS({ session, id: args.video_id, data })).catch((error) => error); | ||
const response = await session.YTRequester.post(`/${engagement_type}`, JSON.stringify(data)); | ||
if (response instanceof Error) return { success: false, status_code: response.response.status, message: response.message }; | ||
@@ -70,40 +58,24 @@ | ||
* @param {object} args - Action argumenets. | ||
* @returns {Promise.<object>} { success: boolean, status_code: number, data: object } | { success: boolean, status_code: number, message: string } | ||
* @returns {Promise.<{ success: boolean; status_code: number; data: object; message?: string }>} | ||
*/ | ||
async function browse(session, action_type, args = {}) { | ||
if (!session.logged_in && (action_type != 'lyrics' || action_type != 'music_playlist')) | ||
if (!session.logged_in && action_type !== 'lyrics' && action_type !== 'music_playlist') | ||
throw new Error('You are not signed-in'); | ||
let data; | ||
const data = { context: session.context }; | ||
switch (action_type) { | ||
case 'account_notifications': | ||
data = { | ||
context: session.context, | ||
browseId: 'SPaccount_notifications' | ||
}; | ||
data.browseId = 'SPaccount_notifications'; | ||
break; | ||
case 'account_privacy': | ||
data = { | ||
context: session.context, | ||
browseId: 'SPaccount_privacy' | ||
}; | ||
data.browseId = 'SPaccount_privacy'; | ||
break; | ||
case 'history': | ||
data = { | ||
context: session.context, | ||
browseId: 'FEhistory' | ||
} | ||
data.browseId = 'FEhistory'; | ||
break; | ||
case 'home_feed': | ||
data = { | ||
context: session.context, | ||
browseId: 'FEwhat_to_watch' | ||
}; | ||
data.browseId = 'FEwhat_to_watch'; | ||
break; | ||
case 'subscriptions_feed': | ||
data = { | ||
context: session.context, | ||
browseId: 'FEsubscriptions' | ||
}; | ||
data.browseId = 'FEsubscriptions'; | ||
break; | ||
@@ -114,24 +86,17 @@ case 'lyrics': | ||
context.client.originalUrl = Constants.URLS.YT_MUSIC_URL; | ||
context.client.originalUrl = Constants.URLS.YT_MUSIC; | ||
context.client.clientVersion = Constants.YTMUSIC_VERSION; | ||
context.client.clientName = 'WEB_REMIX'; | ||
data = { | ||
context, | ||
browseId: args.browse_id | ||
} | ||
data.context = context; | ||
data.browseId = args.browse_id; | ||
break; | ||
case 'playlist': | ||
data = { | ||
context: session.context, | ||
browseId: args.browse_id | ||
} | ||
data.browseId = args.browse_id; | ||
break; | ||
default: | ||
} | ||
const client_domain = args.ytmusic && Constants.URLS.YT_MUSIC_URL || Constants.URLS.YT_BASE_URL; | ||
const response = await Axios.post(`${client_domain}/youtubei/v1/browse${session.logged_in && session.cookie.length < 1 ? '' : `?key=${session.key}`}`, | ||
JSON.stringify(data), Constants.INNERTUBE_REQOPTS({ session, ytmusic: args.ytmusic })).catch((error) => error); | ||
const requester = args.ytmusic && session.YTMRequester || session.YTRequester; | ||
const response = await requester.post('/browse', JSON.stringify(data)); | ||
if (response instanceof Error) return { success: false, status_code: response.response.status, message: response.message }; | ||
@@ -153,3 +118,3 @@ | ||
* @param {object} args - Action argumenets. | ||
* @returns {Promise.<object>} { success: boolean, status_code: number, data: object } | { success: boolean, status_code: number, message: string } | ||
* @returns {Promise.<{ success: boolean; status_code: number; data: object; message?: string }>} | ||
*/ | ||
@@ -159,16 +124,11 @@ async function account(session, action_type, args = {}) { | ||
let data; | ||
const data = {}; | ||
switch (action_type) { | ||
case 'account/account_menu': | ||
data = { context: session.context }; | ||
data.context = session.context; | ||
break; | ||
case 'account/set_setting': | ||
data = { | ||
context: session.context, | ||
newValue: { | ||
boolValue: args.new_value | ||
}, | ||
settingItemId: args.setting_item_id | ||
} | ||
data.context = session.context; | ||
data.newValue = { boolValue: args.new_value }; | ||
data.settingItemId = arts.setting_item_id; | ||
break; | ||
@@ -178,6 +138,4 @@ default: | ||
} | ||
const response = await Axios.post(`${Constants.URLS.YT_BASE_URL}/youtubei/v1/${action_type}${session.logged_in && session.cookie.length < 1 ? '' : `?key=${session.key}`}`, | ||
JSON.stringify(data), Constants.INNERTUBE_REQOPTS({ session })).catch((error) => error); | ||
const response = await session.YTRequester.post(`/${action_type}`, JSON.stringify(data)); | ||
if (response instanceof Error) return { success: false, status_code: response.response.status, message: response.message }; | ||
@@ -193,3 +151,3 @@ | ||
/** | ||
* Accesses YouTube Music endpoints under /youtubei/v1/music/. | ||
* Accesses YouTube Music endpoints (/youtubei/v1/music/). | ||
* | ||
@@ -200,3 +158,3 @@ * @param {Innertube} session - A valid Innertube session. | ||
* @todo Implement more actions. | ||
* @returns | ||
* @returns {Promise.<{ success: boolean; status_code: number; data: object; message?: string }>} | ||
*/ | ||
@@ -206,3 +164,3 @@ async function music(session, action_type, args) { | ||
context.client.originalUrl = Constants.URLS.YT_MUSIC_URL; | ||
context.client.originalUrl = Constants.URLS.YT_MUSIC; | ||
context.client.clientVersion = Constants.YTMUSIC_VERSION; | ||
@@ -215,6 +173,4 @@ context.client.clientName = 'WEB_REMIX'; | ||
case 'get_search_suggestions': | ||
data = { | ||
context, | ||
input: args.input || '' | ||
}; | ||
data.context = context; | ||
data.input = args.input || ''; | ||
break; | ||
@@ -224,6 +180,4 @@ default: | ||
} | ||
const response = await Axios.post(`${Constants.URLS.YT_MUSIC_URL}/youtubei/v1/music/${action_type}${session.logged_in && session.cookie.length < 1 ? '' : `?key=${session.key}`}`, | ||
JSON.stringify(data), Constants.INNERTUBE_REQOPTS({ session, ytmusic: true })).catch((error) => error); | ||
const response = await session.YTMRequester.post(`/music/${action_type}`, JSON.stringify(data)); | ||
if (response instanceof Error) return { success: false, status_code: response.response.status, message: response.message }; | ||
@@ -239,3 +193,3 @@ | ||
/** | ||
* Performs searches on YouTube. | ||
* Searches a given query on YouTube/YTMusic. | ||
* | ||
@@ -245,3 +199,3 @@ * @param {Innertube} session - A valid Innertube session. | ||
* @param {object} args - Search arguments. | ||
* @returns {Promise.<object>} - { success: boolean, status_code: number, data: object } | { success: boolean, status_code: number, message: string } | ||
* @returns {Promise.<{ success: boolean; status_code: number; data: object; message?: string }>} | ||
*/ | ||
@@ -251,11 +205,7 @@ async function search(session, client, args = {}) { | ||
let data; | ||
const data = { context: session.context }; | ||
switch (client) { | ||
case 'YOUTUBE': | ||
data = { | ||
context: session.context, | ||
params: Utils.encodeFilter(args.options.period, args.options.duration, args.options.order), | ||
query: args.query | ||
}; | ||
data.params = Utils.encodeFilter(args.options.period, args.options.duration, args.options.order); | ||
data.query = args.query; | ||
break; | ||
@@ -265,10 +215,8 @@ case 'YTMUSIC': | ||
context.client.originalUrl = Constants.URLS.YT_MUSIC_URL; | ||
context.client.originalUrl = Constants.URLS.YT_MUSIC; | ||
context.client.clientVersion = Constants.YTMUSIC_VERSION; | ||
context.client.clientName = 'WEB_REMIX'; | ||
data = { | ||
context: context, | ||
query: args.query | ||
}; | ||
data.context = context; | ||
data.query = args.query; | ||
break; | ||
@@ -278,6 +226,5 @@ default: | ||
} | ||
const response = await Axios.post(`${client === 'YOUTUBE' && Constants.URLS.YT_BASE_URL || Constants.URLS.YT_MUSIC_URL}/youtubei/v1/search${session.logged_in && session.cookie.length < 1 ? '' : `?key=${session.key}`}`, | ||
JSON.stringify(data), Constants.INNERTUBE_REQOPTS({ session, ytmusic: client === 'YTMUSIC' })).catch((error) => error); | ||
const requester = client == 'YOUTUBE' && session.YTRequester || session.YTMRequester; | ||
const response = await requester.post('/search', JSON.stringify(data)); | ||
if (response instanceof Error) return { success: false, status_code: response.response.status, message: response.message }; | ||
@@ -298,3 +245,3 @@ | ||
* @param {object} args - Action arguments. | ||
* @returns {Promise.<object>} - { success: boolean, status_code: number, data: object } | { success: boolean, status_code: number, message: string } | ||
* @returns {Promise.<{ success: boolean; status_code: number; data: object; message?: string }>} | ||
*/ | ||
@@ -304,29 +251,20 @@ async function notifications(session, action_type, args = {}) { | ||
let data; | ||
const data = {}; | ||
switch (action_type) { | ||
case 'modify_channel_preference': | ||
let pref_types = { PERSONALIZED: 1, ALL: 2, NONE: 3 }; | ||
data = { | ||
context: session.context, | ||
params: Utils.encodeNotificationPref(args.channel_id, pref_types[args.pref.toUpperCase()]) | ||
}; | ||
const pref_types = { PERSONALIZED: 1, ALL: 2, NONE: 3 }; | ||
data.context = session.context; | ||
data.params = Utils.encodeNotificationPref(args.channel_id, pref_types[args.pref.toUpperCase()]); | ||
break; | ||
case 'get_notification_menu': | ||
data = { | ||
context: session.context, | ||
notificationsMenuRequestType: 'NOTIFICATIONS_MENU_REQUEST_TYPE_INBOX' | ||
}; | ||
data.context = session.context; | ||
data.notificationsMenuRequestType = 'NOTIFICATIONS_MENU_REQUEST_TYPE_INBOX'; | ||
break; | ||
case 'get_unseen_count': | ||
data = { | ||
context: session.context | ||
}; | ||
data.context = session.context; | ||
break; | ||
default: | ||
} | ||
const response = await Axios.post(`${Constants.URLS.YT_BASE_URL}/youtubei/v1/notification/${action_type}${session.logged_in && session.cookie.length < 1 ? '' : `?key=${session.key}`}`, | ||
JSON.stringify(data), Constants.INNERTUBE_REQOPTS({ session })).catch((error) => error); | ||
const response = await session.YTRequester.post(`/notification/${action_type}`, JSON.stringify(data)).catch((err) => err); | ||
if (response instanceof Error) return { success: false, status_code: response.response.status, message: response.message }; | ||
@@ -348,40 +286,29 @@ if (action_type === 'modify_channel_preference') return { success: true, status_code: response.status }; | ||
* @param {object} args - Action arguments. | ||
* @returns {Promise.<object>} - { success: boolean, status_code: number, data: object } | { success: boolean, status_code: number, message: string } | ||
* @returns {Promise.<{ success: boolean; data: object; message?: string }>} | ||
*/ | ||
async function livechat(session, action_type, args = {}) { | ||
let data; | ||
const data = {}; | ||
switch (action_type) { | ||
case 'live_chat/get_live_chat': | ||
data = { | ||
context: session.context, | ||
continuation: args.ctoken | ||
}; | ||
data.context = session.context; | ||
data.continuation = args.ctoken; | ||
break; | ||
case 'live_chat/send_message': | ||
data = { | ||
context: session.context, | ||
params: Utils.encodeMessageParams(args.channel_id, args.video_id), | ||
clientMessageId: `ytjs-${Uuid.v4()}`, | ||
richMessage: { | ||
textSegments: [{ text: args.text }] | ||
} | ||
}; | ||
data.context = session.context; | ||
data.params = Utils.encodeMessageParams(args.channel_id, args.video_id); | ||
data.clientMessageId = `ytjs-${Uuid.v4()}`; | ||
data.richMessage = { | ||
textSegments: [{ text: args.text }] | ||
} | ||
break; | ||
case 'live_chat/get_item_context_menu': | ||
data = { | ||
context: session.context | ||
}; | ||
data.context = session.context; | ||
break; | ||
case 'live_chat/moderate': | ||
data = { | ||
context: session.context, | ||
params: args.cmd_params | ||
}; | ||
data.context = session.context; | ||
data.params = args.cmd_params; | ||
break; | ||
case 'updated_metadata': | ||
data = { | ||
context: session.context, | ||
videoId: args.video_id | ||
}; | ||
data.context = session.context; | ||
data.videoId = args.video_id; | ||
args.continuation && (data.continuation = args.continuation); | ||
@@ -391,8 +318,6 @@ break; | ||
} | ||
const response = await Axios.post(`${Constants.URLS.YT_BASE_URL}/youtubei/v1/${action_type}${session.logged_in && session.cookie.length < 1 ? '' : `?key=${session.key}`}`, | ||
JSON.stringify(data), Constants.INNERTUBE_REQOPTS({ session, params: args.params })).catch((error) => error); | ||
const response = await session.YTRequester.post(`/${action_type}`, JSON.stringify(data)).catch((err) => err); | ||
if (response instanceof Error) return { success: false, message: response.message }; | ||
return { success: true, data: response.data }; | ||
@@ -402,3 +327,3 @@ } | ||
/** | ||
* Gets detailed data for a video. | ||
* Retrieves video data. | ||
* | ||
@@ -410,11 +335,4 @@ * @param {Innertube} session - A valid Innertube session. | ||
async function getVideoInfo(session, args = {}) { | ||
let response; | ||
!args.desktop && | ||
(response = await Axios.get(`${Constants.URLS.YT_WATCH_PAGE}?v=${args.id}&t=8s&pbj=1`, Constants.INNERTUBE_REQOPTS({ session, id: args.id, desktop: false })).catch((error) => error)) || | ||
(response = await Axios.post(`${Constants.URLS.YT_BASE_URL}/youtubei/v1/player${session.logged_in && session.cookie.length < 1 ? '' : `?key=${session.key}`}`, | ||
JSON.stringify(Constants.VIDEO_INFO_REQBODY(args.id, session.sts, session.context)), Constants.INNERTUBE_REQOPTS({ session, id: args.id, desktop: true })).catch((error) => error)); | ||
const response = await session.YTRequester.post(`/player`, JSON.stringify(Constants.VIDEO_INFO_REQBODY(args.id, session.sts, session.context))).catch((err) => err); | ||
if (response instanceof Error) throw new Error(`Could not get video info: ${response.message}`); | ||
return response.data; | ||
@@ -428,3 +346,3 @@ } | ||
* @param {object} args - Continuation arguments. | ||
* @returns {Promise.<object>} - { success: boolean, status_code: number, data: object } | { success: boolean, status_code: number, message: string } | ||
* @returns {Promise.<{ success: boolean; status_code: number; data: object; message?: string }>} | ||
*/ | ||
@@ -440,3 +358,3 @@ async function getContinuation(session, args = {}) { | ||
context.client.originalUrl = Constants.URLS.YT_MUSIC_URL; | ||
context.client.originalUrl = Constants.URLS.YT_MUSIC; | ||
context.client.clientVersion = Constants.YTMUSIC_VERSION; | ||
@@ -459,9 +377,12 @@ context.client.clientName = 'WEB_REMIX'; | ||
} | ||
const requester = args.ytmusic && session.YTMRequester || session.YTRequester; | ||
const response = await requester.post('/next', JSON.stringify(data)); | ||
if (response instanceof Error) return { | ||
success: false, | ||
status_code: response.response.status, | ||
message: response.message | ||
}; | ||
const client_domain = args.ytmusic && Constants.URLS.YT_MUSIC_URL || Constants.URLS.YT_BASE_URL; | ||
const response = await Axios.post(`${client_domain}/youtubei/v1/next${session.logged_in && session.cookie.length < 1 ? '' : `?key=${session.key}`}`, | ||
JSON.stringify(data), Constants.INNERTUBE_REQOPTS({ session, ytmusic: args.ytmusic })).catch((error) => error); | ||
if (response instanceof Error) return { success: false, status_code: response.response.status, message: response.message }; | ||
return { | ||
@@ -477,5 +398,5 @@ success: true, | ||
* | ||
* @param {Innertube} session | ||
* @param {string} query | ||
* @returns | ||
* @param {Innertube} session - A valid innertube session | ||
* @param {string} query - Search query | ||
* @returns {Promise.<{ success: boolean; status_code: number; data: object; message?: string }>} | ||
*/ | ||
@@ -486,3 +407,7 @@ async function getYTSearchSuggestions(session, query) { | ||
if (response instanceof Error) return { success: false, status_code: response.response.status, message: response.message }; | ||
if (response instanceof Error) return { | ||
success: false, | ||
status_code: response.response.status, | ||
message: response.message | ||
}; | ||
@@ -496,2 +421,2 @@ return { | ||
module.exports = { engage, browse, account, music, search, notifications, livechat, getVideoInfo, getContinuation, getYTSearchSuggestions }; | ||
module.exports = { engage, browse, account, music, search, notifications, livechat, getVideoInfo, getContinuation, getYTSearchSuggestions }; |
@@ -7,7 +7,7 @@ 'use strict'; | ||
URLS: { | ||
YT_BASE_URL: 'https://www.youtube.com', | ||
YT_MUSIC_URL: 'https://music.youtube.com', | ||
YT_MOBILE_URL: 'https://m.youtube.com', | ||
YT_WATCH_PAGE: 'https://m.youtube.com/watch', | ||
YT_SUGGESTIONS: 'https://suggestqueries.google.com/complete/' | ||
YT_BASE: 'https://www.youtube.com', | ||
YT_BASE_API: 'https://www.youtube.com/youtubei/', | ||
YT_SUGGESTIONS: 'https://suggestqueries.google.com/complete/', | ||
YT_MUSIC: 'https://music.youtube.com', | ||
YT_MUSIC_BASE_API: 'https://music.youtube.com/youtubei/' | ||
}, | ||
@@ -37,5 +37,5 @@ OAUTH: { | ||
'Accept-Language': 'en-US,en', | ||
'Accept-Encoding': 'gzip', | ||
'Upgrade-Insecure-Requests': 1 | ||
} | ||
'Accept-Encoding': 'gzip' | ||
}, | ||
}; | ||
@@ -51,32 +51,26 @@ }, | ||
}, | ||
INNERTUBE_REQOPTS: (info) => { | ||
info.desktop === undefined && (info.desktop = true); | ||
const origin = info.ytmusic && 'https://music.youtube.com' || | ||
info.desktop && 'https://www.youtube.com' || 'https://m.youtube.com'; | ||
INNERTUBE_HEADERS: (info) => { | ||
const origin = info.ytmusic && 'https://music.youtube.com' || 'https://www.youtube.com'; | ||
let req_opts = { | ||
params: info.params || {}, | ||
headers: { | ||
'accept': '*/*', | ||
'user-agent': Utils.getRandomUserAgent(info.desktop ? 'desktop' : 'mobile').userAgent, | ||
'content-type': 'application/json', | ||
'accept-language': 'en-US,en;q=0.9', | ||
'x-goog-authuser': 0, | ||
'x-goog-visitor-id': info.session.context.client.visitorData || '', | ||
'x-youtube-client-name': info.desktop ? 1 : 2, | ||
'x-youtube-client-version': info.session.context.client.clientVersion, | ||
'x-youtube-chrome-connected': 'source=Chrome,mode=0,enable_account_consistency=true,supervised=false,consistency_enabled_by_default=false', | ||
'x-origin': origin, | ||
'origin': origin, | ||
} | ||
const headers = { | ||
'accept': '*/*', | ||
'user-agent': Utils.getRandomUserAgent('desktop').userAgent, | ||
'content-type': 'application/json', | ||
'accept-language': 'en-US,en;q=0.9', | ||
'x-goog-authuser': 0, | ||
'x-goog-visitor-id': info.session.context.client.visitorData || '', | ||
'x-youtube-client-name': 1, | ||
'x-youtube-client-version': info.session.context.client.clientVersion, | ||
'x-youtube-chrome-connected': 'source=Chrome,mode=0,enable_account_consistency=true,supervised=false,consistency_enabled_by_default=false', | ||
'x-origin': origin, | ||
'origin': origin | ||
}; | ||
info.id && (req_opts.headers.referer = (info.desktop ? 'https://www.youtube.com' : 'https://m.youtube.com') + '/watch?v=' + info.id); | ||
if (info.session.logged_in && info.desktop) { | ||
req_opts.headers.Cookie = info.session.cookie; | ||
req_opts.headers.authorization = info.session.cookie.length < 1 ? `Bearer ${info.session.access_token}` : info.session.auth_apisid; | ||
if (info.session.logged_in) { | ||
headers.Cookie = info.session.cookie; | ||
headers.authorization = info.session.cookie.length && info.session.auth_apisid || `Bearer ${info.session.access_token}`; | ||
} | ||
return req_opts; | ||
return headers | ||
}, | ||
@@ -104,3 +98,3 @@ VIDEO_INFO_REQBODY: (id, sts, context) => { | ||
METADATA_KEYS: [ | ||
'embed', 'view_count', 'average_rating', | ||
'embed', 'view_count', 'average_rating', 'allow_ratings', | ||
'length_seconds', 'channel_id', 'channel_url', | ||
@@ -115,3 +109,3 @@ 'external_channel_id', 'is_live_content', 'is_family_safe', | ||
'is_owner_viewing', 'is_unplugged_corpus', | ||
'is_crawlable', 'allow_ratings', 'author' | ||
'is_crawlable', 'author' | ||
], | ||
@@ -118,0 +112,0 @@ ACCOUNT_SETTINGS: { |
@@ -37,3 +37,3 @@ 'use strict'; | ||
async #init() { | ||
const response = await Axios.get(Constants.URLS.YT_BASE_URL, Constants.DEFAULT_HEADERS(this)).catch((error) => error); | ||
const response = await Axios.get(Constants.URLS.YT_BASE, Constants.DEFAULT_HEADERS(this)).catch((error) => error); | ||
if (response instanceof Error) throw new Error(`Could not retrieve Innertube session: ${response.message}`); | ||
@@ -44,3 +44,4 @@ | ||
if (data.INNERTUBE_CONTEXT) { | ||
this.key = data.INNERTUBE_API_KEY; | ||
this.key = data.INNERTUBE_API_KEY; | ||
this.version = data.INNERTUBE_API_VERSION; | ||
this.context = data.INNERTUBE_CONTEXT; | ||
@@ -56,4 +57,4 @@ | ||
/** | ||
* @event auth - Fired when signing in to an account. | ||
* @event update-credentials - Fired when the access token is no longer valid. | ||
* @event Innertube#auth - Fired when signing in to an account. | ||
* @event Innertube#update-credentials - Fired when the access token is no longer valid. | ||
* @type {EventEmitter} | ||
@@ -65,8 +66,23 @@ */ | ||
await this.#player.init(); | ||
if (this.logged_in && this.cookie.length > 1) { | ||
if (this.logged_in && this.cookie.length) { | ||
this.auth_apisid = Utils.getStringBetweenStrings(this.cookie, 'PAPISID=', ';'); | ||
this.auth_apisid = Utils.generateSidAuth(this.auth_apisid); | ||
} | ||
// Axios instances | ||
this.YTRequester = Axios.create({ | ||
baseURL: Constants.URLS.YT_BASE_API + this.version, | ||
timeout: 15000, | ||
headers: Constants.INNERTUBE_HEADERS({ session: this, ytmusic: false }), | ||
params: { key: this.key } | ||
}); | ||
this.YTMRequester = Axios.create({ | ||
baseURL: Constants.URLS.YT_MUSIC_BASE_API + this.version, | ||
timeout: 15000, | ||
headers: Constants.INNERTUBE_HEADERS({ session: this, ytmusic: true }), | ||
params: { key: this.key } | ||
}); | ||
this.#initMethods(); | ||
@@ -269,4 +285,12 @@ } else { | ||
this.logged_in = true; | ||
// API key is not needed if logged in via OAuth | ||
delete this.YTRequester.defaults.params.key; | ||
delete this.YTMRequester.defaults.params.key; | ||
// Update default headers | ||
this.YTRequester.defaults.headers = Constants.INNERTUBE_HEADERS({ session: this, ytmusic: false }); | ||
this.YTMRequester.defaults.headers = Constants.INNERTUBE_HEADERS({ session: this, ytmusic: true }); | ||
resolve(); | ||
resolve(); | ||
} else { | ||
@@ -279,2 +303,9 @@ oauth.on('auth', (data) => { | ||
this.logged_in = true; | ||
delete this.YTRequester.defaults.params.key; | ||
delete this.YTMRequester.defaults.params.key; | ||
this.YTRequester.defaults.headers = Constants.INNERTUBE_HEADERS({ session: this, ytmusic: false }); | ||
this.YTMRequester.defaults.headers = Constants.INNERTUBE_HEADERS({ session: this, ytmusic: true }); | ||
resolve(); | ||
@@ -382,26 +413,30 @@ } else { | ||
const data = await Actions.getVideoInfo(this, { id: video_id, is_desktop: false }); | ||
const refined_data = new Parser(this, data, { client: 'YOUTUBE', data_type: 'VIDEO_INFO', desktop_v: false }).parse(); | ||
const data = await Actions.getVideoInfo(this, { id: video_id }); | ||
const continuation = await Actions.getContinuation(this, { video_id }); | ||
data.continuation = continuation.data; | ||
const details = new Parser(this, data, { client: 'YOUTUBE', data_type: 'VIDEO_INFO' }).parse(); | ||
if (refined_data.metadata.is_live_content) { | ||
if (details.metadata.is_live_content) { | ||
const data_continuation = await Actions.getContinuation(this, { video_id }); | ||
if (data_continuation.data.contents.twoColumnWatchNextResults.conversationBar) { | ||
refined_data.getLivechat = () => new Livechat(this, data_continuation.data.contents.twoColumnWatchNextResults.conversationBar.liveChatRenderer.continuations[0].reloadContinuationData.continuation, refined_data.metadata.channel_id, video_id); | ||
details.getLivechat = () => new Livechat(this, data_continuation.data.contents.twoColumnWatchNextResults.conversationBar.liveChatRenderer.continuations[0].reloadContinuationData.continuation, details.metadata.channel_id, video_id); | ||
} else { | ||
refined_data.getLivechat = () => { }; | ||
details.getLivechat = () => { }; | ||
} | ||
} else { | ||
refined_data.getLivechat = () => { }; | ||
details.getLivechat = () => { }; | ||
} | ||
// Functions | ||
details.like = () => Actions.engage(this, 'like/like', { video_id }); | ||
details.dislike = () => Actions.engage(this, 'like/dislike', { video_id }); | ||
details.removeLike = () => Actions.engage(this, 'like/removelike', { video_id }); | ||
details.subscribe = () => Actions.engage(this, 'subscription/subscribe', { channel_id: details.metadata.channel_id }); | ||
details.unsubscribe = () => Actions.engage(this, 'subscription/unsubscribe', { channel_id: details.metadata.channel_id }); | ||
details.comment = (text) => Actions.engage(this, 'comment/create_comment', { video_id, text }); | ||
details.getComments = () => this.getComments(video_id); | ||
details.changeNotificationPreferences = (type) => Actions.notifications(this, 'modify_channel_preference', { channel_id: details.metadata.channel_id, pref: type || 'NONE' }); | ||
refined_data.like = () => Actions.engage(this, 'like/like', { video_id }); | ||
refined_data.dislike = () => Actions.engage(this, 'like/dislike', { video_id }); | ||
refined_data.removeLike = () => Actions.engage(this, 'like/removelike', { video_id }); | ||
refined_data.subscribe = () => Actions.engage(this, 'subscription/subscribe', { channel_id: refined_data.metadata.channel_id }); | ||
refined_data.unsubscribe = () => Actions.engage(this, 'subscription/unsubscribe', { channel_id: refined_data.metadata.channel_id }); | ||
refined_data.comment = (text) => Actions.engage(this, 'comment/create_comment', { video_id, text }); | ||
refined_data.getComments = () => this.getComments(video_id); | ||
refined_data.changeNotificationPreferences = (type) => Actions.notifications(this, 'modify_channel_preference', { channel_id: refined_data.metadata.channel_id, pref: type || 'NONE' }); | ||
return refined_data; | ||
return details; | ||
} | ||
@@ -518,11 +553,23 @@ | ||
title: item.videoRenderer.title.runs.map((run) => run.text).join(' '), | ||
channel: item.videoRenderer.shortBylineText && item.videoRenderer.shortBylineText.runs[0].text || 'N/A', | ||
description: item.videoRenderer.descriptionSnippet && item.videoRenderer.descriptionSnippet.runs[0].text || 'N/A', | ||
channel: { | ||
name: item.videoRenderer.shortBylineText && item.videoRenderer.shortBylineText.runs[0].text || 'N/A', | ||
url: item.videoRenderer.shortBylineText && `${Constants.URLS.YT_BASE}${item.videoRenderer.shortBylineText.runs[0].navigationEndpoint.commandMetadata.webCommandMetadata.url}` || 'N/A', | ||
}, | ||
metadata: { | ||
view_count: item.videoRenderer.viewCountText && item.videoRenderer.viewCountText.simpleText || 'N/A', | ||
short_view_count_text: { | ||
simple_text: item.videoRenderer.shortViewCountText && item.videoRenderer.shortViewCountText.simpleText || 'N/A', | ||
accessibility_label: item.videoRenderer.shortViewCountText && (item.videoRenderer.shortViewCountText.accessibility && item.videoRenderer.shortViewCountText.accessibility.accessibilityData.label || 'N/A') || 'N/A', | ||
}, | ||
thumbnail: item.videoRenderer.thumbnail && item.videoRenderer.thumbnail.thumbnails.slice(-1)[0] || [], | ||
moving_thumbnail: item.videoRenderer.richThumbnail && item.videoRenderer.richThumbnail.movingThumbnailRenderer.movingThumbnailDetails.thumbnails[0] || [], | ||
published: item.videoRenderer.publishedTimeText && item.videoRenderer.publishedTimeText.simpleText || 'N/A', | ||
duration: item.videoRenderer.lengthText && item.videoRenderer.lengthText.simpleText || 'N/A', | ||
badges: item.videoRenderer.badges && item.videoRenderer.badges.map((badge) => badge.metadataBadgeRenderer.label) || 'N/A', | ||
owner_badges: item.videoRenderer.ownerBadges && item.videoRenderer.ownerBadges.map((badge) => badge.metadataBadgeRenderer.tooltip) || 'N/A' | ||
duration: { | ||
seconds: Utils.timeToSeconds(item.videoRenderer.lengthText && item.videoRenderer.lengthText.simpleText || '0'), | ||
simple_text: item.videoRenderer.lengthText && item.videoRenderer.lengthText.simpleText || 'N/A', | ||
accessibility_label: item.videoRenderer.lengthText && item.videoRenderer.lengthText.accessibility.accessibilityData.label || 'N/A' | ||
}, | ||
badges: item.videoRenderer.badges && item.videoRenderer.badges.map((badge) => badge.metadataBadgeRenderer.label) || [], | ||
owner_badges: item.videoRenderer.ownerBadges && item.videoRenderer.ownerBadges.map((badge) => badge.metadataBadgeRenderer.tooltip) || [] | ||
} | ||
@@ -538,3 +585,3 @@ }; | ||
/** | ||
* Returns YouTube's home feed. | ||
* Returns YouTube's home feed (aka recommendations). | ||
* @returns {Promise.<[{ id: string; title: string; channel: string; metadata: {} }]>} | ||
@@ -547,3 +594,3 @@ */ | ||
const contents = response.data.contents.twoColumnBrowseResultsRenderer.tabs[0].tabRenderer.content.richGridRenderer.contents; | ||
return contents.map((item) => { | ||
@@ -556,8 +603,21 @@ const content = item.richItemRenderer && item.richItemRenderer.content.videoRenderer && | ||
title: content.videoRenderer.title.runs.map((run) => run.text).join(' '), | ||
channel: content.videoRenderer.shortBylineText && content.videoRenderer.shortBylineText.runs[0].text || 'N/A', | ||
description: content.videoRenderer.descriptionSnippet && content.videoRenderer.descriptionSnippet.runs[0].text || 'N/A', | ||
channel: { | ||
name: content.videoRenderer.shortBylineText && content.videoRenderer.shortBylineText.runs[0].text || 'N/A', | ||
url: content.videoRenderer.shortBylineText && `${Constants.URLS.YT_BASE}${content.videoRenderer.shortBylineText.runs[0].navigationEndpoint.commandMetadata.webCommandMetadata.url}` || 'N/A', | ||
}, | ||
metadata: { | ||
view_count: content.videoRenderer.viewCountText && content.videoRenderer.viewCountText.simpleText || 'N/A', | ||
thumbnail: content.videoRenderer.thumbnail && content.videoRenderer.thumbnail.thumbnails.slice(-1)[0] || [], | ||
moving_thumbnail: content.videoRenderer.richThumbnail && content.videoRenderer.richThumbnail.movingThumbnailRenderer.movingThumbnailDetails.thumbnails[0] || [], | ||
short_view_count_text: { | ||
simple_text: content.videoRenderer.shortViewCountText && content.videoRenderer.shortViewCountText.simpleText || 'N/A', | ||
accessibility_label: content.videoRenderer.shortViewCountText && (content.videoRenderer.shortViewCountText.accessibility && content.videoRenderer.shortViewCountText.accessibility.accessibilityData.label || 'N/A') || 'N/A', | ||
}, | ||
thumbnail: content.videoRenderer.thumbnail && content.videoRenderer.thumbnail.thumbnails.slice(-1)[0] || {}, | ||
moving_thumbnail: content.videoRenderer.richThumbnail && content.videoRenderer.richThumbnail.movingThumbnailRenderer.movingThumbnailDetails.thumbnails[0] || {}, | ||
published: content.videoRenderer.publishedTimeText && content.videoRenderer.publishedTimeText.simpleText || 'N/A', | ||
duration: { | ||
seconds: Utils.timeToSeconds(content.videoRenderer.lengthText && content.videoRenderer.lengthText.simpleText || '0'), | ||
simple_text: content.videoRenderer.lengthText && content.videoRenderer.lengthText.simpleText || 'N/A', | ||
accessibility_label: content.videoRenderer.lengthText && content.videoRenderer.lengthText.accessibility.accessibilityData.label || 'N/A' | ||
}, | ||
badges: content.videoRenderer.badges && content.videoRenderer.badges.map((badge) => badge.metadataBadgeRenderer.label) || [], | ||
@@ -594,9 +654,17 @@ owner_badges: content.videoRenderer.ownerBadges && content.videoRenderer.ownerBadges.map((badge) => badge.metadataBadgeRenderer.tooltip) || [] | ||
title: item.gridVideoRenderer.title.runs.map((run) => run.text).join(' '), | ||
channel: item.gridVideoRenderer.shortBylineText && item.gridVideoRenderer.shortBylineText.runs[0].text || 'N/A', | ||
channel: { | ||
name: item.gridVideoRenderer.shortBylineText && item.gridVideoRenderer.shortBylineText.runs[0].text || 'N/A', | ||
url: item.gridVideoRenderer.shortBylineText && `${Constants.URLS.YT_BASE}${item.gridVideoRenderer.shortBylineText.runs[0].navigationEndpoint.commandMetadata.webCommandMetadata.url}` || 'N/A', | ||
}, | ||
metadata: { | ||
view_count: item.gridVideoRenderer.viewCountText && item.gridVideoRenderer.viewCountText.simpleText || 'N/A', | ||
short_view_count_text: { | ||
simple_text: item.gridVideoRenderer.shortViewCountText && item.gridVideoRenderer.shortViewCountText.simpleText || 'N/A', | ||
accessibility_label: item.gridVideoRenderer.shortViewCountText && (item.gridVideoRenderer.shortViewCountText.accessibility && item.gridVideoRenderer.shortViewCountText.accessibility.accessibilityData.label || 'N/A') || 'N/A', | ||
}, | ||
thumbnail: item.gridVideoRenderer.thumbnail && item.gridVideoRenderer.thumbnail.thumbnails.slice(-1)[0] || [], | ||
moving_thumbnail: item.gridVideoRenderer.richThumbnail && item.gridVideoRenderer.richThumbnail.movingThumbnailRenderer.movingThumbnailDetails.thumbnails[0] || {}, | ||
published: item.gridVideoRenderer.publishedTimeText && item.gridVideoRenderer.publishedTimeText.simpleText || 'N/A', | ||
badges: item.gridVideoRenderer.badges && item.gridVideoRenderer.badges.map((badge) => badge.metadataBadgeRenderer.label) || 'N/A', | ||
owner_badges: item.gridVideoRenderer.ownerBadges && item.gridVideoRenderer.ownerBadges.map((badge) => badge.metadataBadgeRenderer.tooltip) || 'N/A' | ||
badges: item.gridVideoRenderer.badges && item.gridVideoRenderer.badges.map((badge) => badge.metadataBadgeRenderer.label) || [], | ||
owner_badges: item.gridVideoRenderer.ownerBadges && item.gridVideoRenderer.ownerBadges.map((badge) => badge.metadataBadgeRenderer.tooltip) || [] | ||
} | ||
@@ -733,3 +801,3 @@ }; | ||
const data = await Actions.getVideoInfo(this, { id, desktop: true }); | ||
const data = await Actions.getVideoInfo(this, { id }); | ||
const streaming_data = this.#chooseFormat(options, data); | ||
@@ -762,3 +830,3 @@ if (!streaming_data.selected_format) throw new Error('Could not find any suitable format.'); | ||
const stream = new Stream.PassThrough(); | ||
Actions.getVideoInfo(this, { id, desktop: true }).then(async (video_data) => { | ||
Actions.getVideoInfo(this, { id }).then(async (video_data) => { | ||
if (video_data.playabilityStatus.status === 'LOGIN_REQUIRED') | ||
@@ -892,2 +960,2 @@ return stream.emit('error', { message: 'You must login to download age-restricted videos.', error_type: 'LOGIN_REQUIRED', playability_status: video_data.playabilityStatus.status }); | ||
module.exports = Innertube; | ||
module.exports = Innertube; |
@@ -14,6 +14,5 @@ 'use strict'; | ||
this.oauth_code_url = `${Constants.URLS.YT_BASE_URL}/o/oauth2/device/code`; | ||
this.oauth_token_url = `${Constants.URLS.YT_BASE_URL}/o/oauth2/token`; | ||
this.guide_url = `${Constants.URLS.YT_BASE_URL}/youtubei/v1/guide`; | ||
this.oauth_code_url = `${Constants.URLS.YT_BASE}/o/oauth2/device/code`; | ||
this.oauth_token_url = `${Constants.URLS.YT_BASE}/o/oauth2/token`; | ||
this.model_name = Constants.OAUTH.MODEL_NAME; | ||
@@ -128,3 +127,3 @@ this.grant_type = Constants.OAUTH.GRANT_TYPE; | ||
* Gets a new access token using a refresh token. | ||
* @returns {object.<{ credentials: { access_token: string; refresh_token: string; expires: Date }; status: string }>} | ||
* @returns {Promise.<{ credentials: { access_token: string; refresh_token: string; expires: Date }; status: string }>} | ||
*/ | ||
@@ -177,3 +176,3 @@ async refreshAccessToken() { | ||
// This request is made to get the auth script url, hard-coding it isn't viable as it changes overtime. | ||
const yttv_response = await Axios.get(`${Constants.URLS.YT_BASE_URL}/tv`, Constants.OAUTH.HEADERS).catch((error) => error); | ||
const yttv_response = await Axios.get(`${Constants.URLS.YT_BASE}/tv`, Constants.OAUTH.HEADERS).catch((error) => error); | ||
if (yttv_response instanceof Error) throw new Error(`Could not extract client identity: ${yttv_response.message}`); | ||
@@ -183,4 +182,4 @@ | ||
const url_body = this.auth_script_regex.exec(yttv_response.data)[1]; | ||
const script_url = `${Constants.URLS.YT_BASE_URL}/${url_body}`; | ||
const script_url = `${Constants.URLS.YT_BASE}/${url_body}`; | ||
const response = await Axios.get(script_url, Constants.DEFAULT_HEADERS).catch((error) => error); | ||
@@ -204,2 +203,2 @@ if (response instanceof Error) throw new Error(`Could not extract client identity: ${response.message}`); | ||
module.exports = OAuth; | ||
module.exports = OAuth; |
@@ -48,8 +48,8 @@ 'use strict'; | ||
return { | ||
id: video.videoId, | ||
title: video.title.runs[0].text, | ||
description: video.detailedMetadataSnippets && video.detailedMetadataSnippets[0].snippetText.runs.map((item) => item.text).join('') || 'N/A', | ||
channel_url: `${Constants.URLS.YT_BASE}${video.ownerText.runs[0].navigationEndpoint.commandMetadata.webCommandMetadata.url}`, | ||
author: video.ownerText.runs[0].text, | ||
id: video.videoId, | ||
url: `https://youtu.be/${video.videoId}`, | ||
channel_url: `${Constants.URLS.YT_BASE_URL}${video.ownerText.runs[0].navigationEndpoint.commandMetadata.webCommandMetadata.url}`, | ||
metadata: { | ||
@@ -211,21 +211,16 @@ view_count: video.viewCountText && video.viewCountText.simpleText || 'N/A', | ||
} | ||
/** | ||
* Video data is parsed dynamically, so if youtube decides to add something new we won't have to change anything here. | ||
*/ | ||
#parseVideoInfo() { | ||
const desktop_v = this.args.desktop_v; | ||
const playability_status = this.data.playabilityStatus; | ||
const playability_status = desktop_v && this.data.playabilityStatus || | ||
this.data[2].playerResponse.playabilityStatus; | ||
if (playability_status.status == 'ERROR') | ||
throw new Error(`Could not retrieve details for this video: ${playability_status.status} - ${playability_status.reason}`); | ||
throw new Error(`Could not retrieve video details: ${playability_status.status} - ${playability_status.reason}`); | ||
const details = desktop_v && this.data.videoDetails || | ||
this.data[2].playerResponse.videoDetails; | ||
const microformat = desktop_v && this.data.microformat.playerMicroformatRenderer || | ||
this.data[2].playerResponse.microformat.playerMicroformatRenderer; | ||
const streaming_data = desktop_v && this.data.streamingData || | ||
this.data[2].playerResponse.streamingData; | ||
const details = this.data.videoDetails; | ||
const microformat = this.data.microformat.playerMicroformatRenderer; | ||
const streaming_data = this.data.streamingData; | ||
const response = { | ||
@@ -241,3 +236,4 @@ id: '', | ||
const dt_raw_data = Object.entries(details); | ||
// Extracts most of the metadata | ||
mf_raw_data.forEach((entry) => { | ||
@@ -254,3 +250,4 @@ const key = Utils.camelToSnake(entry[0]); | ||
}); | ||
// Extracts extra details | ||
dt_raw_data.forEach((entry) => { | ||
@@ -269,20 +266,48 @@ const key = Utils.camelToSnake(entry[0]); | ||
}); | ||
if (!desktop_v) { | ||
const dislike_available = this.data[3].response.contents.singleColumnWatchNextResults.results.results.contents[1] | ||
.slimVideoMetadataSectionRenderer.contents[1].slimVideoActionBarRenderer.buttons[1].slimMetadataToggleButtonRenderer | ||
.button.toggleButtonRenderer.defaultText.accessibility && true || false; | ||
response.metadata.likes = parseInt(this.data[3].response.contents.singleColumnWatchNextResults.results.results.contents[1] | ||
.slimVideoMetadataSectionRenderer.contents[1].slimVideoActionBarRenderer.buttons[0].slimMetadataToggleButtonRenderer | ||
.button.toggleButtonRenderer.defaultText.accessibility.accessibilityData.label.replace(/\D/g, '')); | ||
response.metadata.dislikes = dislike_available && parseInt(this.data[3].response.contents.singleColumnWatchNextResults.results.results.contents[1] | ||
.slimVideoMetadataSectionRenderer.contents[1].slimVideoActionBarRenderer.buttons[1].slimMetadataToggleButtonRenderer | ||
.button.toggleButtonRenderer.defaultText.accessibility.accessibilityData.label.replace(/\D/g, '')) || 0; | ||
// Data continuation is only required in getDetails() | ||
if (this.data.continuation) { | ||
const primary_info_renderer = this.data.continuation.contents.twoColumnWatchNextResults | ||
.results.results.contents.find((item) => item.videoPrimaryInfoRenderer).videoPrimaryInfoRenderer; | ||
const secondary_info_renderer = this.data.continuation.contents.twoColumnWatchNextResults | ||
.results.results.contents.find((item) => item.videoSecondaryInfoRenderer).videoSecondaryInfoRenderer; | ||
const like_btn = primary_info_renderer.videoActions.menuRenderer | ||
.topLevelButtons.find((item) => item.toggleButtonRenderer.defaultIcon.iconType == 'LIKE'); | ||
const dislike_btn = primary_info_renderer.videoActions.menuRenderer | ||
.topLevelButtons.find((item) => item.toggleButtonRenderer.defaultIcon.iconType == 'DISLIKE'); | ||
const notification_toggle_btn = secondary_info_renderer.subscribeButton.subscribeButtonRenderer | ||
?.notificationPreferenceButton?.subscriptionNotificationToggleButtonRenderer; | ||
// These will always be false if logged out. | ||
response.metadata.is_liked = like_btn.toggleButtonRenderer.isToggled; | ||
response.metadata.is_disliked = dislike_btn.toggleButtonRenderer.isToggled; | ||
response.metadata.is_subscribed = secondary_info_renderer.subscribeButton.subscribeButtonRenderer?.subscribed || false; | ||
response.metadata.subscriber_count = secondary_info_renderer.owner.videoOwnerRenderer?.subscriberCountText?.simpleText || 'N/A'; | ||
response.metadata.current_notification_preference = notification_toggle_btn?.states.find((state) => state.stateId == notification_toggle_btn.currentStateId) | ||
.state.buttonRenderer.icon.iconType || 'N/A'; | ||
// Simpler version of publish_date | ||
response.metadata.publish_date_text = primary_info_renderer.dateText.simpleText; | ||
// Only parse like count if it's enabled | ||
if (response.metadata.allow_ratings) { | ||
response.metadata.likes = { | ||
count: parseInt(like_btn.toggleButtonRenderer.defaultText.accessibility.accessibilityData.label.replace(/\D/g, '')), | ||
short_count_text: like_btn.toggleButtonRenderer.defaultText.simpleText | ||
}; | ||
} | ||
response.metadata.owner_badges = secondary_info_renderer.owner.videoOwnerRenderer?.badges?.map((badge) => badge.metadataBadgeRenderer.tooltip) || []; | ||
} | ||
streaming_data && streaming_data.adaptiveFormats && | ||
(response.metadata.available_qualities = [...new Set(streaming_data.adaptiveFormats.filter(v => v.qualityLabel) | ||
.map(v => v.qualityLabel).sort((a, b) => +a.replace(/\D/gi, '') - +b.replace(/\D/gi, '')))]) || | ||
(response.metadata.available_qualities = []); | ||
response.metadata.available_qualities = [...new Set(streaming_data.adaptiveFormats.filter(v => v.qualityLabel) | ||
.map(v => v.qualityLabel).sort((a, b) => + a.replace(/\D/gi, '') - + b.replace(/\D/gi, '')))]; | ||
return response; | ||
@@ -292,2 +317,2 @@ } | ||
module.exports = Parser; | ||
module.exports = Parser; |
@@ -21,3 +21,3 @@ 'use strict'; | ||
} else { | ||
const response = await Axios.get(`${Constants.URLS.YT_BASE_URL}${this.session.player_url}`, { path: this.session.playerUrl, headers: { 'content-type': 'text/javascript', 'user-agent': Utils.getRandomUserAgent('desktop').userAgent } }).catch((error) => error); | ||
const response = await Axios.get(`${Constants.URLS.YT_BASE}${this.session.player_url}`, { path: this.session.playerUrl, headers: { 'content-type': 'text/javascript', 'user-agent': Utils.getRandomUserAgent('desktop').userAgent } }).catch((error) => error); | ||
if (response instanceof Error) throw new Error('Could not download player script: ' + response.message); | ||
@@ -24,0 +24,0 @@ |
{ | ||
"name": "youtubei.js", | ||
"version": "1.3.6", | ||
"version": "1.3.7-md", | ||
"description": "An object-oriented library that allows you to search, get detailed info about videos, subscribe, unsubscribe, like, dislike, comment, download videos and much more!", | ||
@@ -5,0 +5,0 @@ "main": "index.js", |
@@ -93,5 +93,5 @@ <h1 align=center>YouTube.js</h1> | ||
{ | ||
id: string, | ||
title: string, | ||
description: string, | ||
id: string, | ||
url: string, | ||
@@ -215,3 +215,3 @@ metadata:{ | ||
Get details about a specific video: | ||
Get details about a given video: | ||
@@ -251,2 +251,3 @@ ```js | ||
external_channel_id: string, | ||
allow_ratings: boolean, | ||
is_live_content: boolean, | ||
@@ -256,2 +257,9 @@ is_family_safe: boolean, | ||
is_private: boolean, | ||
is_liked: boolean, | ||
is_disliked: boolean, | ||
is_subscribed: boolean, | ||
subscriber_count: string, | ||
current_notification_preference: string, | ||
likes: { count: number, short_count_text: string }, | ||
publish_date_text: string, | ||
has_ypc_metadata: boolean, | ||
@@ -334,5 +342,7 @@ category: string, | ||
title: string, | ||
description: string, | ||
channel: string, | ||
metadata: { | ||
view_count: string, | ||
short_view_count_text: { simple_text: string, accessibility_label: string }, | ||
thumbnail: { | ||
@@ -342,11 +352,16 @@ url: string, | ||
height: number | ||
}, | ||
moving_thumbnail: { | ||
}, | ||
moving_thumbnail: { | ||
url: string, | ||
widt: number, | ||
width: number, | ||
height: number | ||
}, | ||
published: string, | ||
badges: [Array], | ||
owner_badges: [Array] | ||
}, | ||
published: string, | ||
duration: { | ||
seconds: number, | ||
simple_text: string, | ||
accessibility_label: string | ||
}, | ||
badges: string, | ||
owner_badges: [Array] | ||
} | ||
@@ -379,15 +394,17 @@ } | ||
view_count: string, | ||
short_view_count_text: { simple_text: string, accessibility_label: string }, | ||
thumbnail: { | ||
url: string, | ||
width: number, | ||
height: number | ||
url: string, | ||
width: number, | ||
height: number | ||
}, | ||
moving_thumbnail: { | ||
url: string, | ||
width: number, | ||
height: number | ||
url: string, | ||
width: number, | ||
height: number | ||
}, | ||
published: string, | ||
badges: [Array], | ||
badges: string, | ||
owner_badges: [Array] | ||
} | ||
} | ||
@@ -403,15 +420,17 @@ //... | ||
view_count: string, | ||
short_view_count_text: { simple_text: string, accessibility_label: string }, | ||
thumbnail: { | ||
url: string, | ||
width: number, | ||
height: number | ||
url: string, | ||
width: number, | ||
height: number | ||
}, | ||
moving_thumbnail: { | ||
url: string, | ||
width: number, | ||
height: number | ||
url: string, | ||
width: number, | ||
height: number | ||
}, | ||
published: string, | ||
badges: [Array], | ||
badges: string, | ||
owner_badges: [Array] | ||
} | ||
} | ||
@@ -421,3 +440,2 @@ //... | ||
this_week: [ | ||
{ | ||
id: string, | ||
@@ -429,14 +447,15 @@ title: string, | ||
thumbnail: { | ||
url: string, | ||
width: number, | ||
height: number | ||
url: string, | ||
width: number, | ||
height: number | ||
}, | ||
moving_thumbnail: { | ||
url: string, | ||
width: number, | ||
height: number | ||
url: string, | ||
width: number, | ||
height: number | ||
}, | ||
published: string, | ||
badges: [Array], | ||
badges: string, | ||
owner_badges: [Array] | ||
} | ||
} | ||
@@ -466,5 +485,10 @@ // ... | ||
title: string, | ||
channel: string, | ||
description: string, | ||
channel: { | ||
name: string, | ||
url: string | ||
}, | ||
metadata: { | ||
view_count: string, | ||
short_view_count_text: { simple_text: string, accessibility_label: string }, | ||
thumbnail: { | ||
@@ -481,3 +505,7 @@ url: string, | ||
published: string, | ||
duration: string, | ||
duration: { | ||
seconds: number, | ||
simple_text: string, | ||
accessibility_label: string | ||
}, | ||
badges: string, | ||
@@ -618,3 +646,3 @@ owner_badges: [Array] | ||
The library makes it easy to interact with YouTube programatically. However, don't forget that you must be signed in to use the following features! | ||
The library makes it easy to interact with YouTube programmatically. However, don't forget that you must be signed in to use the following features! | ||
@@ -643,3 +671,3 @@ * Subscribe/Unsubscribe: | ||
// Options: ALL | NONE | PERSONALIZED | ||
await youtube.interact.changeNotificationPreferences('VIDEO_ID', 'ALL'); | ||
await youtube.interact.changeNotificationPreferences('CHANNEL_ID', 'ALL'); | ||
``` | ||
@@ -646,0 +674,0 @@ |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
2362
973
135992
18
1