Skip to content

Commit

Permalink
refactor!: Deprecate account#getAnalytics, account#getTimeWatched
Browse files Browse the repository at this point in the history
… and `account#getAnalytics`

Due to recent changes by YouTube, these actions can no longer be executed using web based OAuth tokens nor cookies.
  • Loading branch information
LuanRT committed Oct 26, 2024
1 parent d9ac99d commit 0081e11
Show file tree
Hide file tree
Showing 10 changed files with 54 additions and 30 deletions.
3 changes: 2 additions & 1 deletion src/core/endpoints/account/AccountListEndpoint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export const PATH = '/account/accounts_list';
*/
export function build(): IAccountListRequest {
return {
client: 'ANDROID'
client: 'TV',
callCircumstance: 2
};
}
24 changes: 17 additions & 7 deletions src/core/managers/AccountManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import AccountInfo from '../../parser/youtube/AccountInfo.js';
import Analytics from '../../parser/youtube/Analytics.js';
import Settings from '../../parser/youtube/Settings.js';
import TimeWatched from '../../parser/youtube/TimeWatched.js';
import CopyLink from '../../parser/classes/CopyLink.js';

import { InnertubeError, u8ToBase64 } from '../../utils/Utils.js';
import { Account, BrowseEndpoint, Channel } from '../endpoints/index.js';
Expand All @@ -26,6 +27,7 @@ export default class AccountManager {
/**
* Edits channel name.
* @param new_name - The new channel name.
* @deprecated This method is deprecated and will be removed in a future release.
*/
editName: (new_name: string) => {
if (!this.#actions.session.logged_in)
Expand All @@ -41,6 +43,7 @@ export default class AccountManager {
/**
* Edits channel description.
* @param new_description - The new description.
* @deprecated This method is deprecated and will be removed in a future release.
*/
editDescription: (new_description: string) => {
if (!this.#actions.session.logged_in)
Expand All @@ -55,6 +58,7 @@ export default class AccountManager {
},
/**
* Retrieves basic channel analytics.
* @deprecated This method is deprecated and will be removed in a future release.
*/
getBasicAnalytics: () => this.getAnalytics()
};
Expand All @@ -77,6 +81,7 @@ export default class AccountManager {

/**
* Retrieves time watched statistics.
* @deprecated This method is deprecated and will be removed in a future release.
*/
async getTimeWatched(): Promise<TimeWatched> {
const response = await this.#actions.execute(
Expand All @@ -103,22 +108,27 @@ export default class AccountManager {

/**
* Retrieves basic channel analytics.
* @deprecated This method is deprecated and will be removed in a future release.
*/
async getAnalytics(): Promise<Analytics> {
const info = await this.getInfo();
const advanced_settings = await this.#actions.execute(
BrowseEndpoint.PATH, { browseId: 'SPaccount_advanced', parse: true }
);

const copy_link_button = advanced_settings.contents_memo?.getType(CopyLink).find((node) => node.short_url.startsWith('UC'));

const writer = ChannelAnalytics.encode({
if (!copy_link_button || !copy_link_button.short_url)
throw new InnertubeError('Channel ID not found');

const params = encodeURIComponent(u8ToBase64(ChannelAnalytics.encode({
params: {
channelId: info.footers?.endpoint.payload.browseId
channelId: copy_link_button.short_url
}
});

const params = encodeURIComponent(u8ToBase64(writer.finish()));
}).finish()));

const response = await this.#actions.execute(
BrowseEndpoint.PATH, BrowseEndpoint.build({
browse_id: 'FEanalytics_screen',
client: 'ANDROID',
params
})
);
Expand Down
9 changes: 5 additions & 4 deletions src/parser/classes/AccountItemSection.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
import { Parser } from '../index.js';
import AccountItem from './AccountItem.js';
import AccountItemSectionHeader from './AccountItemSectionHeader.js';
import { YTNode, observe, type ObservedArray } from '../helpers.js';
import { YTNode, type ObservedArray } from '../helpers.js';
import type { RawNode } from '../index.js';
import CompactLink from './CompactLink.js';

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

contents: ObservedArray<AccountItem>;
header: AccountItemSectionHeader | null;
public contents: ObservedArray<AccountItem | CompactLink>;
public header: AccountItemSectionHeader | null;

constructor(data: RawNode) {
super();
this.contents = observe<AccountItem>(data.contents.map((ac: RawNode) => new AccountItem(ac.accountItem)));
this.contents = Parser.parseArray(data.contents, [ AccountItem, CompactLink ]);
this.header = Parser.parseItem(data.header, AccountItemSectionHeader);
}
}
11 changes: 6 additions & 5 deletions src/parser/classes/AccountSectionList.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,19 @@ import { Parser } from '../index.js';
import AccountChannel from './AccountChannel.js';
import AccountItemSection from './AccountItemSection.js';

import { YTNode } from '../helpers.js';
import type { RawNode } from '../index.js';
import type { ObservedArray } from '../helpers.js';
import { YTNode } from '../helpers.js';

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

contents;
footers;
public contents: ObservedArray<AccountItemSection>;
public footers: ObservedArray<AccountChannel>;

constructor(data: RawNode) {
super();
this.contents = Parser.parseItem(data.contents[0], AccountItemSection);
this.footers = Parser.parseItem(data.footers[0], AccountChannel);
this.contents = Parser.parseArray(data.contents, AccountItemSection);
this.footers = Parser.parseArray(data.footers, AccountChannel);
}
}
6 changes: 3 additions & 3 deletions src/parser/classes/CopyLink.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ import Button from './Button.js';
export default class CopyLink extends YTNode {
static type = 'CopyLink';

copy_button: Button | null;
short_url: string;
style: string;
public copy_button: Button | null;
public short_url: string;
public style: string;

constructor(data: RawNode) {
super();
Expand Down
15 changes: 15 additions & 0 deletions src/parser/classes/actions/GetMultiPageMenuAction.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { Parser } from '../../index.js';
import { YTNode } from '../../helpers.js';
import type { RawNode } from '../../index.js';
import MultiPageMenu from '../menus/MultiPageMenu.js';

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

public menu: MultiPageMenu | null;

constructor(data: RawNode) {
super();
this.menu = Parser.parseItem(data.menu, MultiPageMenu);
}
}
7 changes: 2 additions & 5 deletions src/parser/classes/comments/Comment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,11 +148,8 @@ export default class Comment extends YTNode {
* Translates the comment to a given language.
* @param target_language - Ex; en, ja
*/
async translate(target_language: string): Promise<{
async translate(target_language: string): Promise<ApiResponse & {
content: any;
success: boolean;
status_code: number;
data: any;
}> {
if (!this.#actions)
throw new InnertubeError('An active caller must be provide to perform this operation.');
Expand All @@ -167,7 +164,7 @@ export default class Comment extends YTNode {
};

const action = ProtoUtils.encodeCommentActionParams(22, payload);
const response = await this.#actions.execute('comment/perform_comment_action', { action, client: 'ANDROID' });
const response = await this.#actions.execute('comment/perform_comment_action', { action });

// XXX: Should move this to Parser#parseResponse
const mutations = response.data.frameworkUpdates?.entityBatchUpdate?.mutations;
Expand Down
1 change: 1 addition & 0 deletions src/parser/nodes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export { default as AccountItemSection } from './classes/AccountItemSection.js';
export { default as AccountItemSectionHeader } from './classes/AccountItemSectionHeader.js';
export { default as AccountSectionList } from './classes/AccountSectionList.js';
export { default as AppendContinuationItemsAction } from './classes/actions/AppendContinuationItemsAction.js';
export { default as GetMultiPageMenuAction } from './classes/actions/GetMultiPageMenuAction.js';
export { default as OpenPopupAction } from './classes/actions/OpenPopupAction.js';
export { default as UpdateEngagementPanelAction } from './classes/actions/UpdateEngagementPanelAction.js';
export { default as Alert } from './classes/Alert.js';
Expand Down
5 changes: 1 addition & 4 deletions src/parser/youtube/AccountInfo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,11 @@ import AccountSectionList from '../classes/AccountSectionList.js';
import type { ApiResponse } from '../../core/index.js';
import type { IParsedResponse } from '../types/index.js';
import type AccountItemSection from '../classes/AccountItemSection.js';
import type AccountChannel from '../classes/AccountChannel.js';

export default class AccountInfo {
#page: IParsedResponse;

contents: AccountItemSection | null;
footers: AccountChannel | null;

constructor(response: ApiResponse) {
this.#page = Parser.parseResponse(response.data);
Expand All @@ -24,8 +22,7 @@ export default class AccountInfo {
if (!account_section_list)
throw new InnertubeError('Account section list not found');

this.contents = account_section_list.contents;
this.footers = account_section_list.footers;
this.contents = account_section_list.contents.first();
}

get page(): IParsedResponse {
Expand Down
3 changes: 2 additions & 1 deletion src/types/Endpoints.ts
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,8 @@ export interface IChannelEditDescriptionRequest extends ObjectSnakeToCamel<Chann
}

export interface IAccountListRequest {
client: 'ANDROID';
client: 'TV';
callCircumstance: number;
}

export type LikeEndpointOptions = {
Expand Down

0 comments on commit 0081e11

Please sign in to comment.