Skip to content

Commit

Permalink
dev: start parser refactor on the main codebase, see #65 and #44
Browse files Browse the repository at this point in the history
Things were getting a bit complicated and slow with the old parser so I decided to continue #44's work on the main codebase.
  • Loading branch information
LuanRT committed Jun 6, 2022
1 parent 0b4853c commit 3590201
Show file tree
Hide file tree
Showing 95 changed files with 2,259 additions and 79 deletions.
2 changes: 1 addition & 1 deletion .eslintrc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ rules:

no-template-curly-in-string: error
no-unreachable-loop: error
no-unused-private-class-members: error
no-unused-private-class-members: 'off'
no-prototype-builtins: 'off'
no-async-promise-executor: 'off'
no-case-declarations: 'off'
Expand Down
57 changes: 49 additions & 8 deletions lib/Innertube.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ const SessionBuilder = require('./core/SessionBuilder');
const AccountManager = require('./core/AccountManager');
const PlaylistManager = require('./core/PlaylistManager');
const InteractionManager = require('./core/InteractionManager');
const VideoInfo = require('./core/VideoInfo');
const Search = require('./core/Search');

const Utils = require('./utils/Utils');
const Request = require('./utils/Request');
Expand All @@ -27,9 +29,6 @@ const Signature = require('./deciphers/Signature');
* @namespace
*/
class Innertube {
/**
* @type {AxiosInstance}
*/
#axios;
#player;

Expand Down Expand Up @@ -59,7 +58,6 @@ class Innertube {
async #init() {
const session = await new SessionBuilder(this.config).build();

this.#axios = session.axios;
this.key = session.key;
this.version = session.api_version;
this.context = session.context;
Expand All @@ -68,6 +66,7 @@ class Innertube {
this.player_url = session.player.url;
this.sts = session.player.sts;

this.#axios = session.axios;
this.#player = session.player;

/**
Expand All @@ -76,7 +75,7 @@ class Innertube {
* @type {EventEmitter}
*/
this.ev = new EventEmitter();
this.oauth = new OAuth(this.ev, this.#axios);
this.oauth = new OAuth(this.ev, session.axios);

if (this.config.cookie) {
this.auth_apisid = Utils.getStringBetweenStrings(this.config.cookie, 'PAPISID=', ';');
Expand All @@ -89,7 +88,7 @@ class Innertube {
this.account = new AccountManager(this.actions);
this.playlist = new PlaylistManager(this.actions);
this.interact = new InteractionManager(this.actions);

return this;
}

Expand Down Expand Up @@ -190,9 +189,50 @@ class Innertube {

return suggestions;
}

/**
* Retrives video info.
* @returns {Promise.<VideoInfo>}
*/
async getInfo(video_id) {
Utils.throwIfMissing({ video_id });

const initial_info = this.actions.getVideoInfo(video_id);
const continuation = this.actions.next({ video_id });

const response = await Promise.all([ initial_info, continuation ]);
return new VideoInfo(response, this.actions, this.#player);
}

/**
* Searches a given query.
*
* WIP — this will replace {@link search} soon.
*
* @param {string} query - search query.
* @param {object} [options] - search options.
* @param {string} [options.client] - client used to perform the search, can be: `YTMUSIC` or `YOUTUBE`.
* @param {object} [options.filters] - search filters.
* @param {string} [options.filters.upload_date] - filter videos by upload date, can be: any | last_hour | today | this_week | this_month | this_year
* @param {string} [options.filters.type] - filter results by type, can be: any | video | channel | playlist | movie
* @param {string} [options.filters.duration] - filter videos by duration, can be: any | short | medium | long
* @param {string} [options.filters.sort_by] - filter video results by order, can be: relevance | rating | upload_date | view_count
*
* @returns {Promise.<Search>}
*/
async _search(query, options = { client: 'YOUTUBE' }) {
Utils.throwIfMissing({ query });

const response = await this.actions.search({ query, options, client: options.client });
return new Search(response, this.actions);
}

/**
* Retrieves video info.
*
* @deprecated do not use this, it is slow and inefficient.
* Use {@link getInfo} instead.
*
* @param {string} video_id - the video id.
* @return {Promise.<{ title: string; description: string; thumbnail: []; metadata: object }>}
*/
Expand Down Expand Up @@ -655,9 +695,10 @@ class Innertube {

return stream;
}


/** @readonly */
get axios() {
return this.#axios;
return this.#axios;
}
}

Expand Down
65 changes: 15 additions & 50 deletions lib/core/AccountManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

const Utils = require('../utils/Utils');
const Constants = require('../utils/Constants');
const Library = require('./Library');
const Analytics = require('./Analytics');
const Proto = require('../proto');

/** @namespace */
Expand Down Expand Up @@ -168,7 +170,7 @@ class AccountManager {

/**
* Retrieves time watched statistics.
* @returns {Promise.<[{ title: string; time: string; }]>}
* @returns {Promise.<{ title: string; time: string; }[]>}
*/
async getTimeWatched() {
const response = await this.#actions.browse('SPtime_watched', { client: 'ANDROID' });
Expand All @@ -190,61 +192,24 @@ class AccountManager {

/**
* Retrieves basic channel analytics.
*
* @returns {Promise.<{ metrics: { title: string; subtitle: string; metric_value: string;
* comparison_indicator: object; series_configuration: object; }[]; top_content: { views: string;
* published: string; thumbnails: object[]; duration: string; is_short: boolean }[]; }>}
* @returns {Promise.<Analytics>}
*/
async getAnalytics() {
const info = await this.getInfo();

const params = Proto.encodeChannelAnalyticsParams(info.channel_id);
const action = await this.#actions.browse('FEanalytics_screen', { params, client: 'ANDROID' });

const contents = Utils.findNode(action.data, 'contents', 'elementRenderer', 11, false);

const analytics = {
metrics: {},
top_content: {}
}

contents.forEach((el) => {
const element = el.elementRenderer.newElement;
const model = element.type.componentType.model;
const key = Object.keys(model)[0];
const response = await this.#actions.browse('FEanalytics_screen', { params, client: 'ANDROID' });

switch (key) {
case 'analyticsRootModel':
const sections = model.analyticsRootModel.analyticsKeyMetricsData.dataModel.sections;

analytics.metrics = sections.map((section) => ({
title: section.title,
subtitle: section.subtitle,
metric_value: section.metricValue,
comparison_indicator: section.comparisonIndicator,
series_configuration: section.seriesConfiguration
}));
break;
case 'analyticsVodCarouselCardModel':
const video_carousel = model.analyticsVodCarouselCardModel.videoCarouselData;

analytics.top_content = video_carousel?.videos.map((video) => ({
title: video.videoTitle,
metadata: {
views: video.videoDescription.split('·')[0].trim(),
published: video.videoDescription.split('·')[1].trim(),
thumbnails: video.thumbnailDetails.thumbnails,
duration: video.formattedLength,
is_short: video.isShort
}
})) || [];
break;
default:
break;
}
});

return analytics;
return new Analytics(response.data);
}

/**
* Returns the account's library.
* @returns {Promise.<Library>}
*/
async getLibrary() {
const response = await this.#actions.browse('FElibrary');
return new Library(response.data, this.#actions);
}
}

Expand Down
39 changes: 22 additions & 17 deletions lib/core/Actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@ const Proto = require('../proto');
const Utils = require('../utils/Utils');
const Constants = require('../utils/Constants');

/**
* API response.
* @typedef {Promise.<{ success: boolean; status_code: number; data: object; }>} Response
*/

/** namespace **/
class Actions {
#session;
Expand All @@ -31,7 +36,7 @@ class Actions {
* @param {boolean} [args.is_ctoken]
* @param {string} [args.client]
*
* @returns {Promise.<{ success: boolean; status_code: number; data: object }>}
* @returns {Response}
*/
async browse(id, args = {}) {
if (this.#needsLogin(id) && !this.#session.logged_in)
Expand Down Expand Up @@ -65,7 +70,7 @@ class Actions {
* @param {string} [args.comment_id]
* @param {string} [args.comment_action]
*
* @returns {Promise.<{ success: boolean; status_code: number; data: object; }>}
* @returns {Response}
*/
async engage(action, args = {}) {
if (!this.#session.logged_in && !args.hasOwnProperty('text'))
Expand Down Expand Up @@ -119,7 +124,7 @@ class Actions {
* @param {string} [args.new_value]
* @param {string} [args.setting_item_id]
*
* @returns {Promise.<{ success: boolean; status_code: number; data: object }>}
* @returns {Response}
*/
async account(action, args = {}) {
if (!this.#session.logged_in)
Expand Down Expand Up @@ -154,7 +159,7 @@ class Actions {
* @param {string} [args.client]
* @param {string} [args.ctoken]
*
* @returns {Promise.<{ success: boolean; status_code: number; data: object; }>}
* @returns {Response}
*/
async search(args = {}) {
const data = {};
Expand Down Expand Up @@ -183,7 +188,7 @@ class Actions {
* @param {object} args
* @param {string} args.query
*
* @returns {Promise.<{ success: boolean; status_code: number; data: object; }>}
* @returns {Response}
*/
async searchSound(args = {}) {
const data = {
Expand All @@ -203,7 +208,7 @@ class Actions {
* @param {string} [args.new_name]
* @param {string} [args.new_description]
*
* @returns {Promise.<{ success: boolean; status_code: number; data: object; }>}
* @returns {Response}
*/
async channel(action, args = {}) {
if (!this.#session.logged_in)
Expand Down Expand Up @@ -239,7 +244,7 @@ class Actions {
* @param {string} [args.playlist_id]
* @param {string} [args.action]
*
* @returns {Promise.<{ success: boolean; status_code: number; data: object; }>}
* @returns {Response}
*/
async playlist(action, args = {}) {
if (!this.#session.logged_in)
Expand Down Expand Up @@ -286,7 +291,7 @@ class Actions {
* @param {string} [args.channel_id]
* @param {string} [args.ctoken]
*
* @returns {Promise.<{ success: boolean; status_code: number; data: object; }>}
* @returns {Response}
*/
async notifications(action, args = {}) {
if (!this.#session.logged_in)
Expand Down Expand Up @@ -328,7 +333,7 @@ class Actions {
* @param {string} [args.ctoken]
* @param {string} [args.params]
*
* @returns {Promise.<{ success: boolean; status_code: number; data: object; }>}
* @returns {Response}
*/
async livechat(action, args = {}) {
const data = {};
Expand Down Expand Up @@ -369,7 +374,7 @@ class Actions {
* @param {object} args
* @param {string} args.video_id
*
* @returns {Promise.<{ success: boolean; status_code: number; data: object; }>}
* @returns {Response}
*/
async thumbnails(args = {}) {
const data = {
Expand All @@ -396,7 +401,7 @@ class Actions {
* @param {object} args
* @param {string} args.input
*
* @returns {Promise.<{ success: boolean; status_code: number; data: object; }>}
* @returns {Response}
*/
async geo(action, args = {}) {
if (!this.#session.logged_in)
Expand All @@ -420,7 +425,7 @@ class Actions {
* @param {object} [args.action]
* @param {string} [args.params]
*
* @returns {Promise.<{ success: boolean; status_code: number; data: object; }>}
* @returns {Response}
*/
async flag(action, args) {
if (!this.#session.logged_in)
Expand Down Expand Up @@ -450,7 +455,7 @@ class Actions {
* @param {string} action
* @param {string} args.input
*
* @returns {Promise.<{ success: boolean; status_code: number; data: object; }>}
* @returns {Response}
*/
async music(action, args) {
const data = {
Expand All @@ -471,7 +476,7 @@ class Actions {
* @param {string} [args.ctoken]
* @param {string} [args.client]
*
* @returns {Promise.<{ success: boolean; status_code: number; data: object; }>}
* @returns {Response}
*/
async next(args = {}) {
const data = {};
Expand All @@ -496,7 +501,7 @@ class Actions {
* @param {string} id
* @param {string} [cpn]
*
* @returns {Promise.<{ success: boolean; status_code: number; data: object; }>}
* @returns {Response}
*/
async getVideoInfo(id, cpn) {
const data = {
Expand Down Expand Up @@ -529,7 +534,7 @@ class Actions {
* @param {string} client
* @param {string} input
*
* @returns {Promise.<{ success: boolean; status_code: number; data: object; }>}
* @returns {Response}
*/
async getSearchSuggestions(client, query) {
if (!['YOUTUBE', 'YTMUSIC'].includes(client))
Expand Down Expand Up @@ -561,7 +566,7 @@ class Actions {
* @param {object} args
* @param {string} args.input
*
* @returns {Promise.<{ success: boolean; status_code: number; data: object; }>}
* @returns {Response}
*/
async getUserMentionSuggestions(args = {}) {
if (!this.#session.logged_in)
Expand Down
Loading

0 comments on commit 3590201

Please sign in to comment.