From 091671fa2848e0b5d93b18335ed6185b6e30da1c Mon Sep 17 00:00:00 2001 From: patrickkfkan Date: Wed, 11 Dec 2024 22:48:20 +0800 Subject: [PATCH] Merge from YouTube.js v12.1.0 (5f233ae) --- CHANGELOG.md | 82 +++++++++++++++++++ examples/auth/custom-oauth2-creds/index.ts | 9 +- .../custom-oauth2-creds/package-lock.json | 20 +++-- .../auth/custom-oauth2-creds/package.json | 2 +- package-lock.json | 9 +- package.json | 2 +- src/core/Actions.ts | 70 ++++++++++++---- src/core/Player.ts | 31 +++++-- src/core/Session.ts | 1 + src/parser/classes/ActiveAccountHeader.ts | 24 ++++++ src/parser/classes/BackgroundPromo.ts | 25 ++++++ .../classes/EngagementPanelTitleHeader.ts | 8 +- .../classes/PlaylistThumbnailOverlay.ts | 17 ++++ src/parser/classes/Video.ts | 5 ++ src/parser/classes/VideoCard.ts | 12 ++- src/parser/classes/VideoViewCount.ts | 2 + .../endpoints/ShowEngagementPanelEndpoint.ts | 15 ++++ src/parser/classes/menus/Menu.ts | 5 +- src/parser/classes/mweb/MobileTopbar.ts | 20 +++++ .../classes/mweb/MultiPageMenuSection.ts | 14 ++++ src/parser/classes/mweb/PivotBar.ts | 13 +++ src/parser/classes/mweb/PivotBarItem.ts | 27 ++++++ src/parser/classes/mweb/TopbarMenuButton.ts | 18 ++++ src/parser/nodes.ts | 9 ++ src/parser/parser.ts | 1 - src/types/Misc.ts | 2 +- src/utils/Constants.ts | 8 +- src/utils/HTTPClient.ts | 6 ++ 28 files changed, 404 insertions(+), 53 deletions(-) create mode 100644 src/parser/classes/ActiveAccountHeader.ts create mode 100644 src/parser/classes/BackgroundPromo.ts create mode 100644 src/parser/classes/PlaylistThumbnailOverlay.ts create mode 100644 src/parser/classes/endpoints/ShowEngagementPanelEndpoint.ts create mode 100644 src/parser/classes/mweb/MobileTopbar.ts create mode 100644 src/parser/classes/mweb/MultiPageMenuSection.ts create mode 100644 src/parser/classes/mweb/PivotBar.ts create mode 100644 src/parser/classes/mweb/PivotBarItem.ts create mode 100644 src/parser/classes/mweb/TopbarMenuButton.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index fdbcce9..ca74eff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,87 @@ # Changelog +## [12.1.0](https://github.com/LuanRT/YouTube.js/compare/v12.0.0...v12.1.0) (2024-12-10) + + +### Features + +* Add `MWEB` client ([4bf125b](https://github.com/LuanRT/YouTube.js/commit/4bf125b6a53460f631410e1ab949a16cc0c7d095)) +* **parser:** Add mobile guide nodes ([ad2ae51](https://github.com/LuanRT/YouTube.js/commit/ad2ae51b97d84dcb9d2547b4cfef7402f2410404)) + + +### Bug Fixes + +* **Player:** Bump cache version ([283172f](https://github.com/LuanRT/YouTube.js/commit/283172f22032d60b407cb0159a391a147b569432)) +* **Player:** Fix signature algorithm extraction ([#832](https://github.com/LuanRT/YouTube.js/issues/832)) ([ce4996c](https://github.com/LuanRT/YouTube.js/commit/ce4996cea7b0607cb7f9aca7ae9c9e439d964a5a)) + +## [12.0.0](https://github.com/LuanRT/YouTube.js/compare/v11.0.1...v12.0.0) (2024-12-05) + + +### ⚠ BREAKING CHANGES + +* **parser:** Remove old comment node +* **Log:** Convert Log class to module ([#814](https://github.com/LuanRT/YouTube.js/issues/814)) +* **parser:** Remove getters that have been deprecated for a long time ([#815](https://github.com/LuanRT/YouTube.js/issues/815)) +* **parser:** Implement endpoint/command parsers ([#812](https://github.com/LuanRT/YouTube.js/issues/812)) + +### Features + +* **account:** Add missing property `channel_handle` ([#789](https://github.com/LuanRT/YouTube.js/issues/789)) ([677e1f0](https://github.com/LuanRT/YouTube.js/commit/677e1f08075a4a59274f89f3eb65967d7d0ab01b)) +* Add `getCourses` ([#798](https://github.com/LuanRT/YouTube.js/issues/798)) ([cfb48fa](https://github.com/LuanRT/YouTube.js/commit/cfb48fab89792d87a7377eaf15a56d289d26769b)) +* **EngagementPanelTitleHeader:** Add `contextual_info` and `menu` ([af3a916](https://github.com/LuanRT/YouTube.js/commit/af3a91645d84798e744519ec8f24e565cc1ecdb1)) +* **Log:** Convert Log class to module ([#814](https://github.com/LuanRT/YouTube.js/issues/814)) ([fc55716](https://github.com/LuanRT/YouTube.js/commit/fc5571629eca037af7de03f4b903da6add1f300b)) +* **NavigationEndpoint:** Add name property ([bdebb9f](https://github.com/LuanRT/YouTube.js/commit/bdebb9f741291d2f0640274454c90b5ccda8ea5d)) +* **parser:** Add `AddToPlaylist` node ([2940f7b](https://github.com/LuanRT/YouTube.js/commit/2940f7b908ee720492994a41efdabb9fae08708c)) +* **parser:** Add `animated_image` to `PageHeaderView` ([#819](https://github.com/LuanRT/YouTube.js/issues/819)) ([8e50ebd](https://github.com/LuanRT/YouTube.js/commit/8e50ebd92583ae76b080fed4c7599684370dc09d)) +* **parser:** Add `ChangeEngagementPanelVisibilityAction` ([c2b2d7a](https://github.com/LuanRT/YouTube.js/commit/c2b2d7ad52d2cdd1d721ae4569fb6f8cb0540476)) +* **parser:** Add `ChangeEngagementPanelVisibilityEndpoint` ([2824900](https://github.com/LuanRT/YouTube.js/commit/28249008521b4cb600756f8ff83e10ec3037ba69)) +* **parser:** Add `LiveChatBannerChatSummary` node, update `TextRun` node ([#809](https://github.com/LuanRT/YouTube.js/issues/809)) ([7fb00fa](https://github.com/LuanRT/YouTube.js/commit/7fb00fa378574d1567d436f8a824fbb618db2373)) +* **parser:** Add `LiveChatBannerRedirect` node ([#799](https://github.com/LuanRT/YouTube.js/issues/799)) ([ad302b8](https://github.com/LuanRT/YouTube.js/commit/ad302b8b17c0bfc1d81728130d4ba25a88ed241f)) +* **parser:** add `LiveChatModeChangeMessage` node ([#811](https://github.com/LuanRT/YouTube.js/issues/811)) ([7156a58](https://github.com/LuanRT/YouTube.js/commit/7156a585c036a5000d0a50f3f4860a462762fdfe)) +* **parser:** Add `LiveChatSponsorshipsGiftPurchaseAnnouncement` and `LiveChatSponsorshipsHeader` nodes ([#793](https://github.com/LuanRT/YouTube.js/issues/793)) ([4e9c2a5](https://github.com/LuanRT/YouTube.js/commit/4e9c2a585bf84751dd4e3964f70fba284c8b8e38)) +* **parser:** Add `LiveChatSponsorshipsGiftRedemptionAnnouncement` node ([#795](https://github.com/LuanRT/YouTube.js/issues/795)) ([20f7971](https://github.com/LuanRT/YouTube.js/commit/20f797129973c6b91fa228e50d375b0c9d0226d2)) +* **parser:** Add `MenuFlexibleItem` ([bc9a0ed](https://github.com/LuanRT/YouTube.js/commit/bc9a0ed6c1dd7aac280e0461823827d71ce0991f)) +* **parser:** Add `NotificationAction` node ([d36ddb8](https://github.com/LuanRT/YouTube.js/commit/d36ddb804a03b7d22cd20c2b846f86dd49689c0c)) +* **parser:** Add `PlayerOverlayVideoDetails` node ([dc2ed04](https://github.com/LuanRT/YouTube.js/commit/dc2ed046b8424134c675f30e7452fbd6bda0d228)) +* **parser:** Add `RunAttestationCommand` ([4729016](https://github.com/LuanRT/YouTube.js/commit/4729016fb98e7045ee4043857be7eef780c01e35)) +* **parser:** Add `ShowEngagementPanelEndpoint` ([ec85b0f](https://github.com/LuanRT/YouTube.js/commit/ec85b0f9421156c674c5c4d4a3a2e39eca7dbfbf)) +* **parser:** Add `SignalAction` node ([feeb21b](https://github.com/LuanRT/YouTube.js/commit/feeb21b3ebb83772fcceb1f6b0a90c17db613451)) +* **parser:** Add `UnifiedSharePanel` ([4a1397f](https://github.com/LuanRT/YouTube.js/commit/4a1397f1bcc2ad9964626b11c90831b90989b6af)) +* **parser:** Add `UpdateSubscribeButtonAction` ([fdb7540](https://github.com/LuanRT/YouTube.js/commit/fdb754043b809223ae8938fbbdd5780f585b697e)) +* **parser:** Add `VideoViewCount` node ([ad448f8](https://github.com/LuanRT/YouTube.js/commit/ad448f8106116e44e65eb5f5351c38fc4a31d809)) +* **parser:** Add optional image property to LockupMetadataView ([#806](https://github.com/LuanRT/YouTube.js/issues/806)) ([0914299](https://github.com/LuanRT/YouTube.js/commit/091429921530d65daf8f5b281c7c54117ee9a474)) +* **Parser:** add support for parsing subtitle for `RichShelf` ([#805](https://github.com/LuanRT/YouTube.js/issues/805)) ([038efff](https://github.com/LuanRT/YouTube.js/commit/038efff17f3b12d80619c8990ca880e919d2bfe5)) +* **Parser:** Add support for parsing subtitle for `Shelf` ([#792](https://github.com/LuanRT/YouTube.js/issues/792)) ([34ae38c](https://github.com/LuanRT/YouTube.js/commit/34ae38cbf4aa0a42a6024fa99eb0fe553639c8ce)) +* **SubscribeButton:** Parse more endpoints ([8bf9eb7](https://github.com/LuanRT/YouTube.js/commit/8bf9eb7044ad9a5de0892207690195f5646df288)) +* **VideoViewCount:** Add `extra_short_view_count` field ([d10fe68](https://github.com/LuanRT/YouTube.js/commit/d10fe6834a0d063d94b65289d54a52ed3398eff4)) + + +### Bug Fixes + +* **ExpandableVideoDescriptionBody:** Parse attributed description ([360580e](https://github.com/LuanRT/YouTube.js/commit/360580ea6ea6fbdd7fbc0aa038d96b17de17e4f4)) +* **parser:** The AvatarView.image_processor property is optional ([#807](https://github.com/LuanRT/YouTube.js/issues/807)) ([4b178e4](https://github.com/LuanRT/YouTube.js/commit/4b178e4bfbc4cb003ed098afcd0370f98dbf834b)) +* **parser:** Update list of possible content_type in LockupView ([#808](https://github.com/LuanRT/YouTube.js/issues/808)) ([680da9f](https://github.com/LuanRT/YouTube.js/commit/680da9f501db02a9bed2fa8357df021e63024e5f)) +* **Player:** Add more ways to find the nsig algo ([acfb0c5](https://github.com/LuanRT/YouTube.js/commit/acfb0c58bec25782aa92963cd590a56967229d62)) +* **PlaylistAddToOption:** Use correct type for `contains_selected_videos` ([53d1c75](https://github.com/LuanRT/YouTube.js/commit/53d1c759b65ce9b6cb9f236c02828077d4f506cc)) +* **ReelPlayerOverlay:** Update `subscribe_button_renderer` type to include SubscribeButton ([daa5a29](https://github.com/LuanRT/YouTube.js/commit/daa5a2982b24f107681050f2b534986b4d374c5d)) +* **SignalAction:** Rename `action` to `signal` ([8ab760e](https://github.com/LuanRT/YouTube.js/commit/8ab760ea2e268a4f108b2b4a8d46193f5450bf4c)) +* **SubscribeButton:** Parse endpoints using `NavigationEndpoint` ([126a66f](https://github.com/LuanRT/YouTube.js/commit/126a66f317da0c6b486202ad04483b9799bfaf4c)) +* **UnifiedSharePanel:** Check if `thirdPartyNetworkSection` exists ([d3f6af0](https://github.com/LuanRT/YouTube.js/commit/d3f6af07754f75c578dc11e8ea4815ad91f0cac4)) +* **VideoAttributeView:** Parse `secondarySubtitle` only if exists ([0a99342](https://github.com/LuanRT/YouTube.js/commit/0a99342ccbd6f8b1c611ef6b157a599ff5ae2247)) +* **VideoCard:** fix parsing author, view count and published date ([#791](https://github.com/LuanRT/YouTube.js/issues/791)) ([a4394db](https://github.com/LuanRT/YouTube.js/commit/a4394dbb82203eeabcb8684ca9105f83e3b0fb1b)) +* **VideoSecondaryInfo:** Parse `show_more_text` and `show_less_text` correctly ([790f817](https://github.com/LuanRT/YouTube.js/commit/790f8172fc2bbdbf17f16b04a2676fd9088d8878)) + + +### Miscellaneous Chores + +* **parser:** Remove getters that have been deprecated for a long time ([#815](https://github.com/LuanRT/YouTube.js/issues/815)) ([9cf0d3f](https://github.com/LuanRT/YouTube.js/commit/9cf0d3f3b3099af3dd59bc4ca99fefe217a91020)) + + +### Code Refactoring + +* **parser:** Implement endpoint/command parsers ([#812](https://github.com/LuanRT/YouTube.js/issues/812)) ([7397aa3](https://github.com/LuanRT/YouTube.js/commit/7397aa3f6425cb2f3dcc625502fd1ce5a5db6db3)) +* **parser:** Remove old comment node ([2f087d4](https://github.com/LuanRT/YouTube.js/commit/2f087d47a0199870b313717f3d01598f8168be4b)) + ## [11.0.1](https://github.com/LuanRT/YouTube.js/compare/v11.0.0...v11.0.1) (2024-10-28) diff --git a/examples/auth/custom-oauth2-creds/index.ts b/examples/auth/custom-oauth2-creds/index.ts index 824e072..e4b8449 100644 --- a/examples/auth/custom-oauth2-creds/index.ts +++ b/examples/auth/custom-oauth2-creds/index.ts @@ -1,5 +1,5 @@ import express from 'express'; -import { Innertube, UniversalCache, YTNodes } from 'youtubei.js'; +import { Innertube, UniversalCache } from 'youtubei.js'; import { OAuth2Client } from 'google-auth-library'; const app = express(); @@ -46,8 +46,10 @@ app.get('/', async (_req, res) => { console.info('Innertube instance is logged in.'); const userInfo = await innertube.account.getInfo(); + + console.log(await innertube.getBasicInfo('R8vgwMYSQi8', 'ANDROID')); - return res.send({ userInfo }); + return res.send({ userInfo }); } if (!oAuth2Client) { @@ -65,8 +67,7 @@ app.get('/', async (_req, res) => { "http://gdata.youtube.com", "https://www.googleapis.com/auth/youtube", "https://www.googleapis.com/auth/youtube.force-ssl", - "https://www.googleapis.com/auth/youtube-paid-content", - "https://www.googleapis.com/auth/accounts.reauth", + "https://www.googleapis.com/auth/youtube-paid-content" ], include_granted_scopes: true, prompt: 'consent', diff --git a/examples/auth/custom-oauth2-creds/package-lock.json b/examples/auth/custom-oauth2-creds/package-lock.json index 4ccd253..75145af 100644 --- a/examples/auth/custom-oauth2-creds/package-lock.json +++ b/examples/auth/custom-oauth2-creds/package-lock.json @@ -9,7 +9,7 @@ "version": "1.0.0", "license": "ISC", "dependencies": { - "express": "^4.18.2", + "express": "^4.21.2", "google-auth-library": "^9.4.1", "youtubei.js": "^11.0.1" }, @@ -435,9 +435,9 @@ } }, "node_modules/express": { - "version": "4.21.1", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz", - "integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==", + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", "license": "MIT", "dependencies": { "accepts": "~1.3.8", @@ -459,7 +459,7 @@ "methods": "~1.1.2", "on-finished": "2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.10", + "path-to-regexp": "0.1.12", "proxy-addr": "~2.0.7", "qs": "6.13.0", "range-parser": "~1.2.1", @@ -474,6 +474,10 @@ }, "engines": { "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/extend": { @@ -927,9 +931,9 @@ } }, "node_modules/path-to-regexp": { - "version": "0.1.10", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz", - "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==", + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", "license": "MIT" }, "node_modules/proxy-addr": { diff --git a/examples/auth/custom-oauth2-creds/package.json b/examples/auth/custom-oauth2-creds/package.json index 272c89c..f96568f 100644 --- a/examples/auth/custom-oauth2-creds/package.json +++ b/examples/auth/custom-oauth2-creds/package.json @@ -10,7 +10,7 @@ "author": "", "license": "ISC", "dependencies": { - "express": "^4.18.2", + "express": "^4.21.2", "google-auth-library": "^9.4.1", "youtubei.js": "^11.0.1" }, diff --git a/package-lock.json b/package-lock.json index b4ddf82..20ef5f7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,7 +13,7 @@ "license": "MIT", "dependencies": { "@bufbuild/protobuf": "^2.0.0", - "jintr": "^3.0.2", + "jintr": "^3.1.0", "tslib": "^2.5.0", "undici": "^5.19.1" }, @@ -6156,13 +6156,12 @@ } }, "node_modules/jintr": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/jintr/-/jintr-3.0.2.tgz", - "integrity": "sha512-5g2EBudeJFOopjAX4exAv5OCCW1DgUISfoioCsm1h9Q9HJ41LmnZ6J52PCsqBlQihsmp0VDuxreAVzM7yk5nFA==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jintr/-/jintr-3.1.0.tgz", + "integrity": "sha512-azhCHApkRfBH8INpiUCwKBYaNCdB5G+x3NApsI2MxQXSlgFAx7rap3YwE3JAkN08GO8f3ilZsGB0Yvc+412ntQ==", "funding": [ "https://github.com/sponsors/LuanRT" ], - "license": "MIT", "dependencies": { "acorn": "^8.8.0" } diff --git a/package.json b/package.json index 0945abb..931ae28 100644 --- a/package.json +++ b/package.json @@ -107,7 +107,7 @@ "license": "MIT", "dependencies": { "@bufbuild/protobuf": "^2.0.0", - "jintr": "^3.0.2", + "jintr": "^3.1.0", "tslib": "^2.5.0", "undici": "^5.19.1" }, diff --git a/src/core/Actions.ts b/src/core/Actions.ts index 6194c8f..3fd9032 100644 --- a/src/core/Actions.ts +++ b/src/core/Actions.ts @@ -1,9 +1,14 @@ import type { - IBrowseResponse, IGetNotificationsMenuResponse, INextResponse, - IParsedResponse, IPlayerResponse, IRawResponse, - IResolveURLResponse, ISearchResponse, IUpdatedMetadataResponse + IBrowseResponse, + IGetNotificationsMenuResponse, + INextResponse, + IParsedResponse, + IPlayerResponse, + IRawResponse, + IResolveURLResponse, + ISearchResponse, + IUpdatedMetadataResponse } from '../parser/index.js'; - import { NavigateAction, Parser } from '../parser/index.js'; import { InnertubeError } from '../utils/Utils.js'; @@ -15,17 +20,25 @@ export interface ApiResponse { data: IRawResponse; } -export type InnertubeEndpoint = '/player' | '/search' | '/browse' | '/next' | '/reel' | '/updated_metadata' | '/notification/get_notification_menu' | string; +export type InnertubeEndpoint = + '/player' + | '/search' + | '/browse' + | '/next' + | '/reel' + | '/updated_metadata' + | '/notification/get_notification_menu' + | string; export type ParsedResponse = T extends '/player' ? IPlayerResponse : - T extends '/search' ? ISearchResponse : - T extends '/browse' ? IBrowseResponse : - T extends '/next' ? INextResponse : - T extends '/updated_metadata' ? IUpdatedMetadataResponse : - T extends '/navigation/resolve_url' ? IResolveURLResponse : - T extends '/notification/get_notification_menu' ? IGetNotificationsMenuResponse : - IParsedResponse; + T extends '/search' ? ISearchResponse : + T extends '/browse' ? IBrowseResponse : + T extends '/next' ? INextResponse : + T extends '/updated_metadata' ? IUpdatedMetadataResponse : + T extends '/navigation/resolve_url' ? IResolveURLResponse : + T extends '/notification/get_notification_menu' ? IGetNotificationsMenuResponse : + IParsedResponse; export default class Actions { public session: Session; @@ -52,7 +65,9 @@ export default class Actions { * @param client - The client to use. * @param params - Call parameters. */ - async stats(url: string, client: { client_name: string; client_version: string }, params: { [key: string]: any }): Promise { + async stats(url: string, client: { client_name: string; client_version: string }, params: { + [key: string]: any + }): Promise { const s_url = new URL(url); s_url.searchParams.set('ver', '2'); @@ -72,18 +87,39 @@ export default class Actions { * @param endpoint - The endpoint to call. * @param args - Call arguments */ - async execute(endpoint: T, args: { [key: string]: any; parse: true; protobuf?: false; serialized_data?: any }): Promise>; - async execute(endpoint: T, args?: { [key: string]: any; parse?: false; protobuf?: true; serialized_data?: any }): Promise; - async execute(endpoint: T, args?: { [key: string]: any; parse?: boolean; protobuf?: boolean; serialized_data?: any }): Promise | ApiResponse> { + async execute(endpoint: T, args: { + [key: string]: any; + parse: true; + protobuf?: false; + serialized_data?: any; + skip_auth_check?: boolean + }): Promise>; + async execute(endpoint: T, args?: { + [key: string]: any; + parse?: false; + protobuf?: true; + serialized_data?: any; + skip_auth_check?: boolean + }): Promise; + async execute(endpoint: T, args?: { + [key: string]: any; + parse?: boolean; + protobuf?: boolean; + serialized_data?: any; + skip_auth_check?: boolean + }): Promise | ApiResponse> { let data; if (args && !args.protobuf) { data = { ...args }; - if (Reflect.has(data, 'browseId')) { + if (Reflect.has(data, 'browseId') && !args.skip_auth_check) { if (this.#needsLogin(data.browseId) && !this.session.logged_in) throw new InnertubeError('You must be signed in to perform this operation.'); } + + if (Reflect.has(data, 'skip_auth_check')) + delete data.skip_auth_check; if (Reflect.has(data, 'override_endpoint')) delete data.override_endpoint; diff --git a/src/core/Player.ts b/src/core/Player.ts index 4e98547..50eeee0 100644 --- a/src/core/Player.ts +++ b/src/core/Player.ts @@ -140,6 +140,9 @@ export default class Player { case 'WEB': url_components.searchParams.set('cver', Constants.CLIENTS.WEB.VERSION); break; + case 'MWEB': + url_components.searchParams.set('cver', Constants.CLIENTS.MWEB.VERSION); + break; case 'WEB_REMIX': url_components.searchParams.set('cver', Constants.CLIENTS.YTMUSIC.VERSION); break; @@ -221,21 +224,37 @@ export default class Player { } static extractSigSourceCode(data: string): string { - const calls = getStringBetweenStrings(data, 'function(a){a=a.split("")', 'return a.join("")}'); + let calls = getStringBetweenStrings(data, 'function(a){a=a.split("")', 'return a.join("")}'); + let var_name = 'a'; + + if (!calls) { + calls = getStringBetweenStrings(data, 'function(J){J=J.split("")', 'return J.join("")}'); + var_name = 'J'; + } + const obj_name = calls?.split(/\.|\[/)?.[0]?.replace(';', '')?.trim(); const functions = getStringBetweenStrings(data, `var ${obj_name}={`, '};'); if (!functions || !calls) Log.warn(TAG, 'Failed to extract signature decipher algorithm.'); - return `function descramble_sig(a) { a = a.split(""); let ${obj_name}={${functions}}${calls} return a.join("") } descramble_sig(sig);`; + return `function descramble_sig(${var_name}) { ${var_name} = ${var_name}.split(""); let ${obj_name}={${functions}}${calls} return ${var_name}.join("") } descramble_sig(sig);`; } static extractNSigSourceCode(data: string): string | undefined { - const nsig_function = findFunction(data, { includes: 'enhanced_except' }); - if (nsig_function) { + // This used to be the prefix of the error tag (leaving it here for reference). + let nsig_function = findFunction(data, { includes: 'enhanced_except' }); + + // This is the suffix of the error tag. + if (!nsig_function) + nsig_function = findFunction(data, { includes: '-_w8_' }); + + // Usually, only this function uses these dates in the entire script. + if (!nsig_function) + nsig_function = findFunction(data, { includes: '1969' }); + + if (nsig_function) return `${nsig_function.result} ${nsig_function.name}(nsig);`; - } } get url(): string { @@ -243,6 +262,6 @@ export default class Player { } static get LIBRARY_VERSION(): number { - return 11; + return 12; } } \ No newline at end of file diff --git a/src/core/Session.ts b/src/core/Session.ts index 69e1746..4d69a3e 100644 --- a/src/core/Session.ts +++ b/src/core/Session.ts @@ -15,6 +15,7 @@ import type { OAuth2Tokens, OAuth2AuthErrorEventHandler, OAuth2AuthPendingEventH export enum ClientType { WEB = 'WEB', + MWEB = 'MWEB', KIDS = 'WEB_KIDS', MUSIC = 'WEB_REMIX', IOS = 'iOS', diff --git a/src/parser/classes/ActiveAccountHeader.ts b/src/parser/classes/ActiveAccountHeader.ts new file mode 100644 index 0000000..ad6fd40 --- /dev/null +++ b/src/parser/classes/ActiveAccountHeader.ts @@ -0,0 +1,24 @@ +import { YTNode } from '../helpers.js'; +import { type RawNode } from '../index.js'; +import Text from './misc/Text.js'; +import Thumbnail from './misc/Thumbnail.js'; +import NavigationEndpoint from './NavigationEndpoint.js'; + +export default class ActiveAccountHeader extends YTNode { + static type = 'ActiveAccountHeader'; + + public account_name: Text; + public account_photo: Thumbnail[]; + public endpoint: NavigationEndpoint; + public manage_account_title: Text; + public 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.serviceEndpoint); + this.manage_account_title = new Text(data.manageAccountTitle); + this.channel_handle = new Text(data.channelHandle); + } +} \ No newline at end of file diff --git a/src/parser/classes/BackgroundPromo.ts b/src/parser/classes/BackgroundPromo.ts new file mode 100644 index 0000000..6e7e7f6 --- /dev/null +++ b/src/parser/classes/BackgroundPromo.ts @@ -0,0 +1,25 @@ +import { YTNode } from '../helpers.js'; +import { Parser, type RawNode } from '../index.js'; +import Text from './misc/Text.js'; +import Button from './Button.js'; +import ButtonView from './ButtonView.js'; + +export default class BackgroundPromo extends YTNode { + static type = 'BackgroundPromo'; + + public body_text?: Text; + public cta_button?: Button | ButtonView | null; + public icon_type?: string; + public title?: Text; + + constructor(data: RawNode) { + super(); + this.body_text = new Text(data.bodyText); + this.cta_button = Parser.parseItem(data.ctaButton, [ Button, ButtonView ]); + + if (Reflect.has(data, 'icon')) + this.icon_type = data.icon.iconType; + + this.title = new Text(data.title); + } +} \ No newline at end of file diff --git a/src/parser/classes/EngagementPanelTitleHeader.ts b/src/parser/classes/EngagementPanelTitleHeader.ts index 6c6b0d7..e8a0cf6 100644 --- a/src/parser/classes/EngagementPanelTitleHeader.ts +++ b/src/parser/classes/EngagementPanelTitleHeader.ts @@ -6,12 +6,16 @@ import Button from './Button.js'; export default class EngagementPanelTitleHeader extends YTNode { static type = 'EngagementPanelTitleHeader'; - title: Text; - visibility_button: Button | null; + public title: Text; + public visibility_button: Button | null; + public contextual_info?: Text; + public menu: YTNode | null; constructor(data: RawNode) { super(); this.title = new Text(data.title); + this.contextual_info = data.contextualInfo ? new Text(data.contextualInfo) : undefined; this.visibility_button = Parser.parseItem(data.visibilityButton, Button); + this.menu = Parser.parseItem(data.menu); } } \ No newline at end of file diff --git a/src/parser/classes/PlaylistThumbnailOverlay.ts b/src/parser/classes/PlaylistThumbnailOverlay.ts new file mode 100644 index 0000000..56a7521 --- /dev/null +++ b/src/parser/classes/PlaylistThumbnailOverlay.ts @@ -0,0 +1,17 @@ +import { YTNode } from '../helpers.js'; +import { type RawNode } from '../index.js'; +import Text from './misc/Text.js'; + +export default class PlaylistThumbnailOverlay extends YTNode { + static type = 'PlaylistThumbnailOverlay'; + + public icon_type?: string; + public text: Text; + + constructor(data: RawNode) { + super(); + if (Reflect.has(data, 'icon')) + this.icon_type = data.icon.iconType; + this.text = new Text(data.text); + } +} \ No newline at end of file diff --git a/src/parser/classes/Video.ts b/src/parser/classes/Video.ts index 70022ef..428c145 100644 --- a/src/parser/classes/Video.ts +++ b/src/parser/classes/Video.ts @@ -38,6 +38,7 @@ export default class Video extends YTNode { show_action_menu: boolean; is_watched: boolean; menu: Menu | null; + byline_text?: Text; search_video_result_entity_key?: string; constructor(data: RawNode) { @@ -91,6 +92,10 @@ export default class Video extends YTNode { if (Reflect.has(data, 'searchVideoResultEntityKey')) { this.search_video_result_entity_key = data.searchVideoResultEntityKey; } + + if (Reflect.has(data, 'bylineText')) { + this.byline_text = new Text(data.bylineText); + } } get description(): string { diff --git a/src/parser/classes/VideoCard.ts b/src/parser/classes/VideoCard.ts index 2950a96..40662c2 100644 --- a/src/parser/classes/VideoCard.ts +++ b/src/parser/classes/VideoCard.ts @@ -5,16 +5,20 @@ import Video from './Video.js'; export default class VideoCard extends Video { static type = 'VideoCard'; + + public metadata_text?: Text; + public byline_text?: Text; constructor(data: RawNode) { super(data); if (Reflect.has(data, 'metadataText')) { - const metadata = new Text(data.metadataText); - if (metadata.text) { - this.short_view_count = new Text({ simpleText: metadata.text.split('·')[0].trim() } as RawNode); - this.published = new Text({ simpleText: metadata.text.split('·')[1].trim() } as RawNode); + this.metadata_text = new Text(data.metadataText); + if (this.metadata_text.text) { + this.short_view_count = new Text({ simpleText: this.metadata_text.text.split('·')[0]?.trim() } as RawNode); + this.published = new Text({ simpleText: this.metadata_text.text.split('·')[1]?.trim() } as RawNode); } } + if (Reflect.has(data, 'bylineText')) { this.author = new Author(data.bylineText, data.ownerBadges, data.channelThumbnailSupportedRenderers?.channelThumbnailWithLinkRenderer?.thumbnail); } diff --git a/src/parser/classes/VideoViewCount.ts b/src/parser/classes/VideoViewCount.ts index 2daef97..0feda29 100644 --- a/src/parser/classes/VideoViewCount.ts +++ b/src/parser/classes/VideoViewCount.ts @@ -7,12 +7,14 @@ export default class VideoViewCount extends YTNode { public original_view_count: string; public short_view_count: Text; + public extra_short_view_count: Text; public view_count: Text; constructor(data: RawNode) { super(); this.original_view_count = data.originalViewCount; this.short_view_count = new Text(data.shortViewCount); + this.extra_short_view_count = new Text(data.extraShortViewCount); this.view_count = new Text(data.viewCount); } } \ No newline at end of file diff --git a/src/parser/classes/endpoints/ShowEngagementPanelEndpoint.ts b/src/parser/classes/endpoints/ShowEngagementPanelEndpoint.ts new file mode 100644 index 0000000..e9c9e2b --- /dev/null +++ b/src/parser/classes/endpoints/ShowEngagementPanelEndpoint.ts @@ -0,0 +1,15 @@ +import { YTNode } from '../../helpers.js'; +import type { RawNode } from '../../index.js'; + +export default class ShowEngagementPanelEndpoint extends YTNode { + static type = 'ShowEngagementPanelEndpoint'; + + public panel_identifier: string; + public source_panel_identifier?: string; + + constructor(data: RawNode) { + super(); + this.panel_identifier = data.panelIdentifier; + this.source_panel_identifier = data.sourcePanelIdentifier; + } +} \ No newline at end of file diff --git a/src/parser/classes/menus/Menu.ts b/src/parser/classes/menus/Menu.ts index 08dd7c2..f0c2e75 100644 --- a/src/parser/classes/menus/Menu.ts +++ b/src/parser/classes/menus/Menu.ts @@ -8,20 +8,21 @@ import SegmentedLikeDislikeButtonView from '../SegmentedLikeDislikeButtonView.js import MenuFlexibleItem from './MenuFlexibleItem.js'; import LikeButton from '../LikeButton.js'; import ToggleButton from '../ToggleButton.js'; +import FlexibleActionsView from '../FlexibleActionsView.js'; export default class Menu extends YTNode { static type = 'Menu'; public items: ObservedArray; public flexible_items: ObservedArray; - public top_level_buttons: ObservedArray; + public top_level_buttons: ObservedArray; public label?: string; constructor(data: RawNode) { super(); this.items = Parser.parseArray(data.items); this.flexible_items = Parser.parseArray(data.flexibleItems, MenuFlexibleItem); - this.top_level_buttons = Parser.parseArray(data.topLevelButtons, [ ToggleButton, LikeButton, Button, ButtonView, SegmentedLikeDislikeButtonView ]); + this.top_level_buttons = Parser.parseArray(data.topLevelButtons, [ ToggleButton, LikeButton, Button, ButtonView, SegmentedLikeDislikeButtonView, FlexibleActionsView ]); if (Reflect.has(data, 'accessibility') && Reflect.has(data.accessibility, 'accessibilityData')) { this.label = data.accessibility.accessibilityData.label; diff --git a/src/parser/classes/mweb/MobileTopbar.ts b/src/parser/classes/mweb/MobileTopbar.ts new file mode 100644 index 0000000..4930aa4 --- /dev/null +++ b/src/parser/classes/mweb/MobileTopbar.ts @@ -0,0 +1,20 @@ +import { YTNode } from '../../helpers.js'; +import Text from '../misc/Text.js'; +import { Parser, type RawNode } from '../../index.js'; + +export default class MobileTopbar extends YTNode { + static type = 'MobileTopbar'; + + public placeholder_text: Text; + public buttons; + public logo_type?: string; + + constructor(data: RawNode) { + super(); + this.placeholder_text = new Text(data.placeholderText); + this.buttons = Parser.parseArray(data.buttons); + + if (Reflect.has(data, 'logo') && Reflect.has(data.logo, 'iconType')) + this.logo_type = data.logo.iconType; + } +} \ No newline at end of file diff --git a/src/parser/classes/mweb/MultiPageMenuSection.ts b/src/parser/classes/mweb/MultiPageMenuSection.ts new file mode 100644 index 0000000..138feb5 --- /dev/null +++ b/src/parser/classes/mweb/MultiPageMenuSection.ts @@ -0,0 +1,14 @@ +import type { ObservedArray } from '../../helpers.js'; +import { YTNode } from '../../helpers.js'; +import { Parser, type RawNode } from '../../index.js'; + +export default class MultiPageMenuSection extends YTNode { + static type = 'MultiPageMenuSection'; + + public items: ObservedArray | null; + + constructor(data: RawNode) { + super(); + this.items = Parser.parseArray(data.items); + } +} \ No newline at end of file diff --git a/src/parser/classes/mweb/PivotBar.ts b/src/parser/classes/mweb/PivotBar.ts new file mode 100644 index 0000000..f4b63b6 --- /dev/null +++ b/src/parser/classes/mweb/PivotBar.ts @@ -0,0 +1,13 @@ +import { YTNode } from '../../helpers.js'; +import { Parser, type RawNode } from '../../index.js'; + +export default class PivotBar extends YTNode { + static type = 'PivotBar'; + + public items; + + constructor(data: RawNode) { + super(); + this.items = Parser.parseArray(data.items); + } +} \ No newline at end of file diff --git a/src/parser/classes/mweb/PivotBarItem.ts b/src/parser/classes/mweb/PivotBarItem.ts new file mode 100644 index 0000000..dda664d --- /dev/null +++ b/src/parser/classes/mweb/PivotBarItem.ts @@ -0,0 +1,27 @@ +import { YTNode } from '../../helpers.js'; +import { type RawNode } from '../../index.js'; +import Text from '../misc/Text.js'; +import NavigationEndpoint from '../NavigationEndpoint.js'; + +export default class PivotBarItem extends YTNode { + static type = 'PivotBarItem'; + + public pivot_identifier: string; + public endpoint: NavigationEndpoint; + public title: Text; + public accessibility_label?: string; + public icon_type?: string; + + constructor(data: RawNode) { + super(); + this.pivot_identifier = data.pivotIdentifier; + this.endpoint = new NavigationEndpoint(data.navigationEndpoint); + this.title = new Text(data.title); + + if (Reflect.has(data, 'accessibility') && Reflect.has(data.accessibility, 'accessibilityData')) + this.accessibility_label = data.accessibility.accessibilityData.label; + + if (Reflect.has(data, 'icon') && Reflect.has(data.icon, 'iconType')) + this.icon_type = data.icon.iconType; + } +} \ No newline at end of file diff --git a/src/parser/classes/mweb/TopbarMenuButton.ts b/src/parser/classes/mweb/TopbarMenuButton.ts new file mode 100644 index 0000000..e2e2ce2 --- /dev/null +++ b/src/parser/classes/mweb/TopbarMenuButton.ts @@ -0,0 +1,18 @@ +import { YTNode } from '../../helpers.js'; +import { Parser, type RawNode } from '../../index.js'; + +export default class TopbarMenuButton extends YTNode { + static type = 'TopbarMenuButton'; + + public icon_type?: string; + public menu_renderer: YTNode | null; + public target_id: string; + + constructor(data: RawNode) { + super(); + if (Reflect.has(data, 'icon') && Reflect.has(data.icon, 'iconType')) + this.icon_type = data.icon.iconType; + this.menu_renderer = Parser.parseItem(data.menuRenderer); + this.target_id = data.targetId; + } +} \ No newline at end of file diff --git a/src/parser/nodes.ts b/src/parser/nodes.ts index 91328f9..a23778d 100644 --- a/src/parser/nodes.ts +++ b/src/parser/nodes.ts @@ -17,6 +17,7 @@ export { default as SignalAction } from './classes/actions/SignalAction.js'; export { default as UpdateChannelSwitcherPageAction } from './classes/actions/UpdateChannelSwitcherPageAction.js'; export { default as UpdateEngagementPanelAction } from './classes/actions/UpdateEngagementPanelAction.js'; export { default as UpdateSubscribeButtonAction } from './classes/actions/UpdateSubscribeButtonAction.js'; +export { default as ActiveAccountHeader } from './classes/ActiveAccountHeader.js'; export { default as AddToPlaylist } from './classes/AddToPlaylist.js'; export { default as Alert } from './classes/Alert.js'; export { default as AlertWithButton } from './classes/AlertWithButton.js'; @@ -24,6 +25,7 @@ export { default as AttributionView } from './classes/AttributionView.js'; export { default as AudioOnlyPlayability } from './classes/AudioOnlyPlayability.js'; export { default as AutomixPreviewVideo } from './classes/AutomixPreviewVideo.js'; export { default as AvatarView } from './classes/AvatarView.js'; +export { default as BackgroundPromo } from './classes/BackgroundPromo.js'; export { default as BackstageImage } from './classes/BackstageImage.js'; export { default as BackstagePost } from './classes/BackstagePost.js'; export { default as BackstagePostThread } from './classes/BackstagePostThread.js'; @@ -145,6 +147,7 @@ export { default as SearchEndpoint } from './classes/endpoints/SearchEndpoint.js export { default as ShareEndpoint } from './classes/endpoints/ShareEndpoint.js'; export { default as ShareEntityEndpoint } from './classes/endpoints/ShareEntityEndpoint.js'; export { default as ShareEntityServiceEndpoint } from './classes/endpoints/ShareEntityServiceEndpoint.js'; +export { default as ShowEngagementPanelEndpoint } from './classes/endpoints/ShowEngagementPanelEndpoint.js'; export { default as SignalServiceEndpoint } from './classes/endpoints/SignalServiceEndpoint.js'; export { default as SubscribeEndpoint } from './classes/endpoints/SubscribeEndpoint.js'; export { default as UnsubscribeEndpoint } from './classes/endpoints/UnsubscribeEndpoint.js'; @@ -322,6 +325,11 @@ export { default as MusicTastebuilderShelfThumbnail } from './classes/MusicTaste export { default as MusicThumbnail } from './classes/MusicThumbnail.js'; export { default as MusicTwoRowItem } from './classes/MusicTwoRowItem.js'; export { default as MusicVisualHeader } from './classes/MusicVisualHeader.js'; +export { default as MobileTopbar } from './classes/mweb/MobileTopbar.js'; +export { default as MultiPageMenuSection } from './classes/mweb/MultiPageMenuSection.js'; +export { default as PivotBar } from './classes/mweb/PivotBar.js'; +export { default as PivotBarItem } from './classes/mweb/PivotBarItem.js'; +export { default as TopbarMenuButton } from './classes/mweb/TopbarMenuButton.js'; export { default as NavigationEndpoint } from './classes/NavigationEndpoint.js'; export { default as Notification } from './classes/Notification.js'; export { default as NotificationAction } from './classes/NotificationAction.js'; @@ -355,6 +363,7 @@ export { default as PlaylistPanelVideoWrapper } from './classes/PlaylistPanelVid export { default as PlaylistSidebar } from './classes/PlaylistSidebar.js'; export { default as PlaylistSidebarPrimaryInfo } from './classes/PlaylistSidebarPrimaryInfo.js'; export { default as PlaylistSidebarSecondaryInfo } from './classes/PlaylistSidebarSecondaryInfo.js'; +export { default as PlaylistThumbnailOverlay } from './classes/PlaylistThumbnailOverlay.js'; export { default as PlaylistVideo } from './classes/PlaylistVideo.js'; export { default as PlaylistVideoList } from './classes/PlaylistVideoList.js'; export { default as PlaylistVideoThumbnail } from './classes/PlaylistVideoThumbnail.js'; diff --git a/src/parser/parser.ts b/src/parser/parser.ts index 5bd6daa..609b395 100644 --- a/src/parser/parser.ts +++ b/src/parser/parser.ts @@ -76,7 +76,6 @@ const IGNORED_LIST = new Set([ 'SearchPyv', 'MealbarPromo', 'PrimetimePromo', - 'BackgroundPromo', 'PromotedSparklesWeb', 'CompactPromotedVideo', 'BrandVideoShelf', diff --git a/src/types/Misc.ts b/src/types/Misc.ts index ba6012c..f6943b3 100644 --- a/src/types/Misc.ts +++ b/src/types/Misc.ts @@ -1,7 +1,7 @@ import type { SessionOptions } from '../core/index.js'; export type InnerTubeConfig = SessionOptions; -export type InnerTubeClient = 'IOS' | 'WEB' | 'ANDROID' | 'YTMUSIC' | 'YTMUSIC_ANDROID' | 'YTSTUDIO_ANDROID' | 'TV' | 'TV_EMBEDDED' | 'YTKIDS' | 'WEB_EMBEDDED' | 'WEB_CREATOR'; +export type InnerTubeClient = 'IOS' | 'WEB' | 'MWEB' | 'ANDROID' | 'YTMUSIC' | 'YTMUSIC_ANDROID' | 'YTSTUDIO_ANDROID' | 'TV' | 'TV_EMBEDDED' | 'YTKIDS' | 'WEB_EMBEDDED' | 'WEB_CREATOR'; export type UploadDate = 'all' | 'hour' | 'today' | 'week' | 'month' | 'year'; export type SearchType = 'all' | 'video' | 'channel' | 'playlist' | 'movie'; diff --git a/src/utils/Constants.ts b/src/utils/Constants.ts index 07ffbf3..4b9d710 100644 --- a/src/utils/Constants.ts +++ b/src/utils/Constants.ts @@ -38,6 +38,12 @@ export const CLIENTS = Object.freeze({ API_VERSION: 'v1', STATIC_VISITOR_ID: '6zpwvWUNAco' }, + MWEB: { + NAME_ID: '2', + NAME: 'MWEB', + VERSION: '2.20241205.01.00', + API_VERSION: 'v1' + }, WEB_KIDS: { NAME_ID: '76', NAME: 'WEB_KIDS', @@ -105,4 +111,4 @@ export const INNERTUBE_HEADERS_BASE = Object.freeze({ 'content-type': 'application/json' }); -export const SUPPORTED_CLIENTS = [ 'IOS', 'WEB', 'YTKIDS', 'YTMUSIC', 'ANDROID', 'YTSTUDIO_ANDROID', 'YTMUSIC_ANDROID', 'TV', 'TV_EMBEDDED', 'WEB_EMBEDDED', 'WEB_CREATOR' ]; \ No newline at end of file +export const SUPPORTED_CLIENTS = [ 'IOS', 'WEB', 'MWEB', 'YTKIDS', 'YTMUSIC', 'ANDROID', 'YTSTUDIO_ANDROID', 'YTMUSIC_ANDROID', 'TV', 'TV_EMBEDDED', 'WEB_EMBEDDED', 'WEB_CREATOR' ]; \ No newline at end of file diff --git a/src/utils/HTTPClient.ts b/src/utils/HTTPClient.ts index 38db55e..18762f3 100644 --- a/src/utils/HTTPClient.ts +++ b/src/utils/HTTPClient.ts @@ -190,6 +190,12 @@ export default class HTTPClient { } switch (client.toUpperCase()) { + case 'MWEB': + ctx.client.clientVersion = Constants.CLIENTS.MWEB.VERSION; + ctx.client.clientName = Constants.CLIENTS.MWEB.NAME; + ctx.client.clientFormFactor = 'SMALL_FORM_FACTOR'; + ctx.client.platform = 'MOBILE'; + break; case 'IOS': ctx.client.deviceMake = 'Apple'; ctx.client.deviceModel = Constants.CLIENTS.IOS.DEVICE_MODEL;