diff --git a/package-lock.json b/package-lock.json index c8359d58..dc731144 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,7 +13,6 @@ "license": "MIT", "dependencies": { "@protobuf-ts/runtime": "^2.7.0", - "flat": "^5.0.2", "linkedom": "^0.14.12", "undici": "^5.7.0" }, @@ -2914,14 +2913,6 @@ "node": ">=8" } }, - "node_modules/flat": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", - "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", - "bin": { - "flat": "cli.js" - } - }, "node_modules/flat-cache": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", @@ -7405,11 +7396,6 @@ "path-exists": "^4.0.0" } }, - "flat": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", - "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==" - }, "flat-cache": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", diff --git a/package.json b/package.json index 0838924f..7b60a4e6 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,6 @@ "license": "MIT", "dependencies": { "@protobuf-ts/runtime": "^2.7.0", - "flat": "^5.0.2", "linkedom": "^0.14.12", "undici": "^5.7.0" }, diff --git a/src/Innertube.ts b/src/Innertube.ts index 263db72a..273a837f 100644 --- a/src/Innertube.ts +++ b/src/Innertube.ts @@ -9,6 +9,10 @@ import History from './parser/youtube/History'; import Comments from './parser/youtube/Comments'; import NotificationsMenu from './parser/youtube/NotificationsMenu'; import VideoInfo, { DownloadOptions, FormatOptions } from './parser/youtube/VideoInfo'; +import NavigationEndpoint from './parser/classes/NavigationEndpoint'; + +import { ParsedResponse } from './parser'; +import { ActionsResponse } from './core/Actions'; import Feed from './core/Feed'; import YTMusic from './core/Music'; @@ -251,6 +255,12 @@ class Innertube { const info = await this.getBasicInfo(video_id); return info.download(options); } + + call(endpoint: NavigationEndpoint, args: { [ key: string ]: any; parse: true }): Promise; + call(endpoint: NavigationEndpoint, args?: { [ key: string ]: any; parse?: false }): Promise; + call(endpoint: NavigationEndpoint, args?: object): Promise { + return endpoint.callTest(this.actions, args); + } } export default Innertube; \ No newline at end of file diff --git a/src/core/AccountManager.ts b/src/core/AccountManager.ts index 0c9e81ba..59a2dd2e 100644 --- a/src/core/AccountManager.ts +++ b/src/core/AccountManager.ts @@ -1,16 +1,14 @@ import Proto from '../proto/index'; import Actions from './Actions'; -import Constants from '../utils/Constants'; -import { throwIfMissing, findNode } from '../utils/Utils'; import Analytics from '../parser/youtube/Analytics'; import TimeWatched from '../parser/youtube/TimeWatched'; import AccountInfo from '../parser/youtube/AccountInfo'; +import Settings from '../parser/youtube/Settings'; class AccountManager { #actions; channel; - settings; constructor(actions: Actions) { this.#actions = actions; @@ -30,84 +28,6 @@ class AccountManager { */ getBasicAnalytics: () => this.getAnalytics() }; - - this.settings = { - notifications: { - /** - * Notify about activity from the channels you're subscribed to. - * @param option - ON | OFF - */ - setSubscriptions: (option: boolean) => this.#setSetting(Constants.ACCOUNT_SETTINGS.SUBSCRIPTIONS, 'SPaccount_notifications', option), - /** - * Recommended content notifications. - * @param option - ON | OFF - */ - setRecommendedVideos: (option: boolean) => this.#setSetting(Constants.ACCOUNT_SETTINGS.RECOMMENDED_VIDEOS, 'SPaccount_notifications', option), - /** - * Notify about activity on your channel. - * @param option - ON | OFF - */ - setChannelActivity: (option: boolean) => this.#setSetting(Constants.ACCOUNT_SETTINGS.CHANNEL_ACTIVITY, 'SPaccount_notifications', option), - /** - * Notify about replies to your comments. - * @param option - ON | OFF - */ - setCommentReplies: (option: boolean) => this.#setSetting(Constants.ACCOUNT_SETTINGS.COMMENT_REPLIES, 'SPaccount_notifications', option), - /** - * Notify when others mention your channel. - * @param option - ON | OFF - */ - setMentions: (option: boolean) => this.#setSetting(Constants.ACCOUNT_SETTINGS.USER_MENTION, 'SPaccount_notifications', option), - /** - * Notify when others share your content on their channels. - * @param option - ON | OFF - */ - setSharedContent: (option: boolean) => this.#setSetting(Constants.ACCOUNT_SETTINGS.SHARED_CONTENT, 'SPaccount_notifications', option) - }, - privacy: { - /** - * If set to true, your subscriptions won't be visible to others. - * @param option - ON | OFF - */ - setSubscriptionsPrivate: (option: boolean) => this.#setSetting(Constants.ACCOUNT_SETTINGS.SUBSCRIPTIONS_PRIVACY, 'SPaccount_privacy', option), - /** - * If set to true, saved playlists won't appear on your channel. - * @param option - ON | OFF - */ - setSavedPlaylistsPrivate: (option: boolean) => this.#setSetting(Constants.ACCOUNT_SETTINGS.PLAYLISTS_PRIVACY, 'SPaccount_privacy', option) - } - }; - } - - /** - * Internal method to perform changes on an account's settings. - */ - async #setSetting(setting_id: string, type: string, new_value: boolean) { - throwIfMissing({ setting_id, type, new_value }); - - const response = await this.#actions.browse(type); - - const contents = (() => { - switch (type.trim()) { - case 'SPaccount_notifications': - return findNode(response.data, 'contents', 'Your preferences', 13, false).options; - case 'SPaccount_privacy': - return findNode(response.data, 'contents', 'settingsSwitchRenderer', 13, false).options; - default: - // This is just for maximum compatibility, this is most definitely a bad way to handle this - throw new TypeError('undefined is not a function'); - } - })(); - - const option = contents.find((option: any) => option.settingsSwitchRenderer.enableServiceEndpoint.setSettingEndpoint.settingItemIdForClient == setting_id); - const setting_item_id = option.settingsSwitchRenderer.enableServiceEndpoint.setSettingEndpoint.settingItemId; - - const set_setting = await this.#actions.account('account/set_setting', { - new_value: type == 'SPaccount_privacy' ? !new_value : new_value, - setting_item_id - }); - - return set_setting; } /** @@ -130,6 +50,17 @@ class AccountManager { return new TimeWatched(response); } + /** + * Opens YouTube settings. + */ + async getSettings() { + const response = await this.#actions.execute('/browse', { + browseId: 'SPaccount_overview' + }); + + return new Settings(this.#actions, response); + } + /** * Retrieves basic channel analytics. */ diff --git a/src/core/Actions.ts b/src/core/Actions.ts index d795496c..37ca29d3 100644 --- a/src/core/Actions.ts +++ b/src/core/Actions.ts @@ -708,11 +708,19 @@ class Actions { if (Reflect.has(data, 'clientActions')) delete data.clientActions; + if (Reflect.has(data, 'settingItemIdForClient')) + delete data.settingItemIdForClient; + if (Reflect.has(data, 'action')) { data.actions = [ data.action ]; delete data.action; } + if (Reflect.has(data, 'boolValue')) { + data.newValue = { boolValue: data.boolValue }; + delete data.boolValue; + } + if (Reflect.has(data, 'token')) { data.continuation = data.token; delete data.token; diff --git a/src/core/InteractionManager.ts b/src/core/InteractionManager.ts index 91bb9714..2dcf2dfa 100644 --- a/src/core/InteractionManager.ts +++ b/src/core/InteractionManager.ts @@ -1,4 +1,4 @@ -import { throwIfMissing, findNode } from '../utils/Utils'; +import { throwIfMissing } from '../utils/Utils'; import Actions from './Actions'; class InteractionManager { @@ -79,12 +79,12 @@ class InteractionManager { text }); - const translated_content = findNode(response.data, 'frameworkUpdates', 'content', 7, false); + const mutation = response.data.frameworkUpdates.entityBatchUpdate.mutations[0].payload.commentEntityPayload; return { success: response.success, status_code: response.status_code, - translated_content: translated_content.content, + translated_content: mutation.translatedContent.content, data: response.data }; } diff --git a/src/parser/classes/ChannelOptions.ts b/src/parser/classes/ChannelOptions.ts new file mode 100644 index 00000000..d9bed584 --- /dev/null +++ b/src/parser/classes/ChannelOptions.ts @@ -0,0 +1,24 @@ +import Text from './misc/Text'; +import Thumbnail from './misc/Thumbnail'; +import NavigationEndpoint from './NavigationEndpoint'; + +import { YTNode } from '../helpers'; + +class ChannelOptions extends YTNode { + static type = 'ChannelOptions'; + + avatar: Thumbnail[]; + endpoint: NavigationEndpoint; + name: string; + links: Text[]; + + constructor(data: any) { + super(); + this.avatar = Thumbnail.fromResponse(data.avatar); + this.endpoint = new NavigationEndpoint(data.avatarEndpoint); + this.name = data.name; + this.links = data.links.map((link: any) => new Text(link)); + } +} + +export default ChannelOptions; \ No newline at end of file diff --git a/src/parser/classes/CopyLink.ts b/src/parser/classes/CopyLink.ts new file mode 100644 index 00000000..3d2581d9 --- /dev/null +++ b/src/parser/classes/CopyLink.ts @@ -0,0 +1,20 @@ +import Parser from '../index'; +import Button from './Button'; +import { YTNode } from '../helpers'; + +class CopyLink extends YTNode { + static type = 'CopyLink'; + + copy_button: Button | null; + short_url: string; + style: string; + + constructor(data: any) { + super(); + this.copy_button = Parser.parseItem