Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add music profile (first pass at #207) #510

Closed
wants to merge 8 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions examples/music/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { Innertube, UniversalCache } from 'youtubei.js';

(async () => {
const yt = await Innertube.create({ cache: new UniversalCache(true, '~/ytjs-credcache') });

yt.session.on('auth-pending', (data) => {
console.log(`Go to ${data.verification_url} in your browser and enter code ${data.user_code} to authenticate.`);
});
yt.session.on('auth', async () => {
console.log('Sign in successful');
await yt.session.oauth.cacheCredentials();
});
yt.session.on('update-credentials', async () => {
await yt.session.oauth.cacheCredentials();
});

// Attempt to sign in
await yt.session.signIn();
const account = await yt.music.getAccount();

console.log(account.account_name, account.channel_handle, account.channel_id);

const profile = await account.getProfile();
console.debug(profile);
})();
14 changes: 14 additions & 0 deletions src/core/clients/Music.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import Account from '../../parser/ytmusic/Account.js';
import Album from '../../parser/ytmusic/Album.js';
import Artist from '../../parser/ytmusic/Artist.js';
import Explore from '../../parser/ytmusic/Explore.js';
Expand Down Expand Up @@ -368,4 +369,17 @@ export default class Music {

return search_suggestions_sections;
}

/**
* Retrieves artist's profile.
* @param artist_id - The artist id.
*/
async getAccount(): Promise<Account> {
if (!this.#session.logged_in)
throw new InnertubeError('You must be signed in to perform this operation.');

const response = await this.#actions.execute('account/account_menu', { client: 'YTMUSIC' });

return new Account(response, this.#actions);
}
}
16 changes: 16 additions & 0 deletions src/parser/classes/menus/MultiPageMenuSection.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { Parser } from '../../index.js';
import { YTNode } from '../../helpers.js';
import type { RawNode } from '../../index.js';
import CompactLink from '../CompactLink.js';
import type { ObservedArray } from '../../helpers.js';

export default class MultiPageMenuSection extends YTNode {
static type = 'MultiPageMenuSection';

items: ObservedArray<CompactLink> | null;

constructor(data: RawNode) {
super();
this.items = Parser.parse(data.items, true, [ CompactLink ]);
}
}
22 changes: 22 additions & 0 deletions src/parser/classes/ytmusic/ActiveAccountHeader.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import type { RawNode } from '../../index.js';
import { YTNode } from '../../helpers.js';
import Thumbnail from '../misc/Thumbnail.js';
import NavigationEndpoint from '../NavigationEndpoint.js';
import Text from '../misc/Text.js';

export default class ActiveAccountHeader extends YTNode {
static type = 'ActiveAccountHeader';

account_name: Text;
account_photo: Thumbnail[];
endpoint: NavigationEndpoint;
channel_handle: Text;

constructor(data: RawNode) {
super();
this.account_name = new Text(data.accountName);
this.account_photo = Thumbnail.fromResponse(data.accountPhoto);
this.endpoint = new NavigationEndpoint(data.settingsEndpoint);
this.channel_handle = new Text(data.channelHandle);
}
}
2 changes: 2 additions & 0 deletions src/parser/nodes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,7 @@ export { default as MenuServiceItem } from './classes/menus/MenuServiceItem.js';
export { default as MenuServiceItemDownload } from './classes/menus/MenuServiceItemDownload.js';
export { default as MultiPageMenu } from './classes/menus/MultiPageMenu.js';
export { default as MultiPageMenuNotificationSection } from './classes/menus/MultiPageMenuNotificationSection.js';
export { default as MultiPageMenuSection } from './classes/menus/MultiPageMenuSection.js';
export { default as MusicMenuItemDivider } from './classes/menus/MusicMenuItemDivider.js';
export { default as MusicMultiSelectMenu } from './classes/menus/MusicMultiSelectMenu.js';
export { default as MusicMultiSelectMenuItem } from './classes/menus/MusicMultiSelectMenuItem.js';
Expand Down Expand Up @@ -399,3 +400,4 @@ export { default as KidsBlocklistPickerItem } from './classes/ytkids/KidsBlockli
export { default as KidsCategoriesHeader } from './classes/ytkids/KidsCategoriesHeader.js';
export { default as KidsCategoryTab } from './classes/ytkids/KidsCategoryTab.js';
export { default as KidsHomeScreen } from './classes/ytkids/KidsHomeScreen.js';
export { default as ActiveAccountHeader } from './classes/ytmusic/ActiveAccountHeader.js';
61 changes: 61 additions & 0 deletions src/parser/ytmusic/Account.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { BrowseEndpoint } from '../../core/endpoints/index.js';
import type Actions from '../../core/Actions.js';
import type { ApiResponse } from '../../core/Actions.js';
import type { IParsedResponse } from '../types/ParsedResponse.js';
import { Parser } from '../../platform/lib.js';
import type { Memo, ObservedArray } from '../helpers.js';
import type Thumbnail from '../classes/misc/Thumbnail.js';
import ActiveAccountHeader from '../classes/ytmusic/ActiveAccountHeader.js';
import CompactLink from '../classes/CompactLink.js';
import Artist from './Artist.js';

export default class Account {
#page: IParsedResponse;
#actions: Actions;
#memos: Memo | undefined;
#artist: Artist | undefined;

account_name: string;
account_photo: Thumbnail[];
channel_handle: string;
channel_id: string;
items: ObservedArray<CompactLink>;

constructor(response: ApiResponse, actions: Actions) {
this.#page = Parser.parseResponse<IParsedResponse>(response.data);
this.#actions = actions;
this.#memos = this.#page.actions_memo;

if (this.#memos === undefined)
throw new Error('Could not find actions');

const profile = this.#memos.getType(ActiveAccountHeader).first();

if (profile === undefined)
throw new Error('Could not find ActiveAccountHeader');

this.account_name = profile.account_name.toString();
this.account_photo = profile.account_photo;
this.channel_handle = profile.channel_handle.toString();
this.items = this.#memos.getType(CompactLink);
const your_channel = this.items.find((lnk) => lnk.title.toString() === 'Your channel') as CompactLink;
this.channel_id = your_channel.endpoint.payload.browseId;
}

get page(): IParsedResponse {
return this.#page;
}

async getProfile(): Promise<Artist> {
if (this.#artist !== undefined) return this.#artist;

const response = await this.#actions.execute(
BrowseEndpoint.PATH, BrowseEndpoint.build({
client: 'YTMUSIC',
browse_id: this.channel_id
})
);

return new Artist(response, this.#actions);
}
}
Loading