From 170df2a0540a7dc45ee33815c766095b65a855ce Mon Sep 17 00:00:00 2001 From: absidue <48293849+absidue@users.noreply.github.com> Date: Mon, 1 May 2023 15:12:23 +0200 Subject: [PATCH] Upgrade YouTube.js from 4.3 to 5.0.2 (fixes throttling) (#3474) * Upgrade YouTube.js from 4.3 to 5.0.1 (fixes throttling) * Fix typo * Upgrade YouTube.js from 5.0.1 to 5.0.2 (fixes watch page views) --- package.json | 2 +- .../watch-video-comments.js | 4 +- .../watch-video-live-chat.js | 6 +- src/renderer/helpers/api/local.js | 95 +++++++------------ src/renderer/views/Channel/Channel.js | 40 ++++---- src/renderer/views/Watch/Watch.js | 8 +- yarn.lock | 8 +- 7 files changed, 67 insertions(+), 96 deletions(-) diff --git a/package.json b/package.json index 21172ffbe..f0224eab1 100644 --- a/package.json +++ b/package.json @@ -75,7 +75,7 @@ "vue-router": "^3.6.5", "vue-tiny-slider": "^0.1.39", "vuex": "^3.6.2", - "youtubei.js": "^4.3.0" + "youtubei.js": "^5.0.2" }, "devDependencies": { "@babel/core": "^7.21.5", diff --git a/src/renderer/components/watch-video-comments/watch-video-comments.js b/src/renderer/components/watch-video-comments/watch-video-comments.js index 42560289a..6f5ba5c80 100644 --- a/src/renderer/components/watch-video-comments/watch-video-comments.js +++ b/src/renderer/components/watch-video-comments/watch-video-comments.js @@ -170,7 +170,7 @@ export default defineComponent({ getCommentDataLocal: async function (more) { try { - /** @type {import('youtubei.js/dist/src/parser/youtube/Comments').default} */ + /** @type {import('youtubei.js').YT.Comments} */ let comments if (more) { comments = await this.nextPageToken.getContinuation() @@ -210,7 +210,7 @@ export default defineComponent({ try { const comment = this.commentData[index] - /** @type {import('youtubei.js/dist/src/parser/classes/comments/CommentThread').default} */ + /** @type {import('youtubei.js').YTNodes.CommentThread} */ const commentThread = comment.replyToken if (comment.replies.length > 0) { diff --git a/src/renderer/components/watch-video-live-chat/watch-video-live-chat.js b/src/renderer/components/watch-video-live-chat/watch-video-live-chat.js index e28bf0fc6..987920a3b 100644 --- a/src/renderer/components/watch-video-live-chat/watch-video-live-chat.js +++ b/src/renderer/components/watch-video-live-chat/watch-video-live-chat.js @@ -200,12 +200,12 @@ export default defineComponent({ }, /** - * @param {import('youtubei.js/dist/src/parser/classes/livechat/items/LiveChatTextMessage').default} comment + * @param {import('youtubei.js').YTNodes.LiveChatTextMessage} comment */ parseLiveChatComment: function (comment) { /** * can also be undefined if there is no badge - * @type {import('youtubei.js/dist/src/parser/classes/LiveChatAuthorBadge').default} + * @type {import('youtubei.js').YTNodes.LiveChatAuthorBadge} */ const badge = comment.author.badges.find(badge => badge.type === 'LiveChatAuthorBadge' && badge.custom_thumbnail) @@ -231,7 +231,7 @@ export default defineComponent({ }, /** - * @param {import('youtubei.js/dist/src/parser/classes/livechat/items/LiveChatPaidMessage').default} superChat + * @param {import('youtubei.js').YTNodes.LiveChatPaidMessage} superChat */ parseLiveChatSuperChat: function (superChat) { const parsedComment = { diff --git a/src/renderer/helpers/api/local.js b/src/renderer/helpers/api/local.js index 7068a8670..3766447d4 100644 --- a/src/renderer/helpers/api/local.js +++ b/src/renderer/helpers/api/local.js @@ -1,4 +1,4 @@ -import { Innertube, ClientType, Misc, Utils, YT } from 'youtubei.js' +import { ClientType, Endpoints, Innertube, Misc, Utils, YT } from 'youtubei.js' import Autolinker from 'autolinker' import { join } from 'path' @@ -65,14 +65,10 @@ export async function getLocalPlaylist(id) { return await innertube.getPlaylist(id) } -/** - * @typedef {import('youtubei.js/dist/src/core/TabbedFeed').default} TabbedFeed - */ - /** * @param {string} location * @param {string} tab - * @param {TabbedFeed|null} instance + * @param {import('youtubei.js').Mixins.TabbedFeed|null} instance */ export async function getLocalTrending(location, tab, instance) { if (instance === null) { @@ -107,11 +103,7 @@ export async function getLocalSearchResults(query, filters, safetyMode) { } /** - * @typedef {import('youtubei.js/dist/src/parser/youtube/Search').default} Search - */ - -/** - * @param {Search} continuationData + * @param {YT.Search} continuationData */ export async function getLocalSearchContinuation(continuationData) { const response = await continuationData.getContinuation() @@ -151,8 +143,8 @@ export async function getLocalComments(id, sortByNewest = false) { } /** - * @param {import('youtubei.js/dist/src/parser/classes/misc/Format').default[]} formats - * @param {import('youtubei.js/dist/index').Player} player + * @param {Misc.Format[]} formats + * @param {import('youtubei.js').Player} player */ function decipherFormats(formats, player) { for (const format of formats) { @@ -206,12 +198,12 @@ export async function getLocalChannelVideos(id) { const innertube = await createInnertube() try { - const response = await innertube.actions.execute('/browse', { - browseId: id, + const response = await innertube.actions.execute(Endpoints.BrowseEndpoint.PATH, Endpoints.BrowseEndpoint.build({ + browse_id: id, params: 'EgZ2aWRlb3PyBgQKAjoA' // protobuf for the videos tab (this is the one that YouTube uses, // it has some empty fields in the protobuf but it doesn't work if you remove them) - }) + })) const videosTab = new YT.Channel(null, response) @@ -233,8 +225,8 @@ export async function getLocalChannelVideos(id) { } /** - * @param {import('youtubei.js/dist/src/parser/classes/Video').default[]} videos - * @param {import('youtubei.js/dist/src/parser/classes/misc/Author').default} author + * @param {import('youtubei.js').YTNodes.Video[]} videos + * @param {Misc.Author} author */ export function parseLocalChannelVideos(videos, author) { const parsedVideos = videos.map(parseLocalListVideo) @@ -249,13 +241,13 @@ export function parseLocalChannelVideos(videos, author) { } /** - * @typedef {import('youtubei.js/dist/src/parser/classes/Playlist').default} Playlist - * @typedef {import('youtubei.js/dist/src/parser/classes/GridPlaylist').default} GridPlaylist + * @typedef {import('youtubei.js').YTNodes.Playlist} Playlist + * @typedef {import('youtubei.js').YTNodes.GridPlaylist} GridPlaylist */ /** * @param {Playlist|GridPlaylist} playlist - * @param {import('youtubei.js/dist/src/parser/classes/misc/Author').default} author + * @param {Misc.Author} author */ export function parseLocalListPlaylist(playlist, author = undefined) { let channelName @@ -290,7 +282,7 @@ export function parseLocalListPlaylist(playlist, author = undefined) { } /** - * @param {Search} response + * @param {YT.Search} response */ function handleSearchResponse(response) { if (!response.results) { @@ -313,11 +305,7 @@ function handleSearchResponse(response) { } /** - * @typedef {import('youtubei.js/dist/src/parser/classes/PlaylistVideo').default} PlaylistVideo - */ - -/** - * @param {PlaylistVideo} video + * @param {import('youtubei.js').YTNodes.PlaylistVideo} video */ export function parseLocalPlaylistVideo(video) { return { @@ -333,7 +321,7 @@ export function parseLocalPlaylistVideo(video) { } /** - * @param {import('youtubei.js/dist/src/parser/classes/Video').default} video + * @param {import('youtubei.js').YTNodes.Video} video */ export function parseLocalListVideo(video) { return { @@ -344,7 +332,7 @@ export function parseLocalListVideo(video) { authorId: video.author.id, description: video.description, viewCount: extractNumberFromString(video.view_count.text), - publishedText: video.published.text !== 'N/A' ? video.published.text : null, + publishedText: video.published.isEmpty() ? null : video.published.text, lengthSeconds: isNaN(video.duration.seconds) ? '' : video.duration.seconds, liveNow: video.is_live, isUpcoming: video.is_upcoming || video.is_premiere, @@ -353,14 +341,14 @@ export function parseLocalListVideo(video) { } /** - * @param {import('youtubei.js/dist/src/parser/helpers').YTNode} item + * @param {import('youtubei.js').Helpers.YTNode} item */ function parseListItem(item) { switch (item.type) { case 'Video': return parseLocalListVideo(item) case 'Channel': { - /** @type {import('youtubei.js/dist/src/parser/classes/Channel').default} */ + /** @type {import('youtubei.js').YTNodes.Channel} */ const channel = item // see upstream TODO: https://github.com/LuanRT/YouTube.js/blob/main/src/parser/classes/Channel.ts#L33 @@ -370,17 +358,17 @@ function parseListItem(item) { let subscribers = null let videos = null let handle = null - if (channel.subscribers.text?.startsWith('@')) { - handle = channel.subscribers.text + if (channel.subscriber_count.text?.startsWith('@')) { + handle = channel.subscriber_count.text - if (channel.videos.text !== 'N/A') { - subscribers = channel.videos.text + if (!channel.video_count.isEmpty()) { + subscribers = channel.video_count.text } } else { - videos = extractNumberFromString(channel.videos.text) + videos = extractNumberFromString(channel.video_count.text) - if (channel.subscribers.text !== 'N/A') { - subscribers = channel.subscribers.text + if (!channel.subscriber_count.isEmpty()) { + subscribers = channel.subscriber_count.text } } @@ -403,11 +391,7 @@ function parseListItem(item) { } /** - * @typedef {import('youtubei.js/dist/src/parser/classes/CompactVideo').default} CompactVideo - */ - -/** - * @param {CompactVideo} video + * @param {import('youtubei.js').YTNodes.CompactVideo} video */ export function parseLocalWatchNextVideo(video) { return { @@ -417,7 +401,7 @@ export function parseLocalWatchNextVideo(video) { author: video.author.name, authorId: video.author.id, viewCount: extractNumberFromString(video.view_count.text), - publishedText: video.published.text === 'N/A' ? null : video.published.text, + publishedText: video.published.isEmpty() ? null : video.published.text, lengthSeconds: isNaN(video.duration.seconds) ? '' : video.duration.seconds, liveNow: video.is_live, isUpcoming: video.is_premiere @@ -452,12 +436,7 @@ function convertSearchFilters(filters) { } /** - * @typedef {import('youtubei.js/dist/src/parser/classes/misc/TextRun').default} TextRun - * @typedef {import('youtubei.js/dist/src/parser/classes/misc/EmojiRun').default} EmojiRun - */ - -/** - * @param {(TextRun|EmojiRun)[]} runs + * @param {(Misc.TextRun|Misc.EmojiRun)[]} runs * @param {number} emojiSize * @param {{looseChannelNameDetection: boolean}} options */ @@ -527,7 +506,7 @@ export function parseLocalTextRuns(runs, emojiSize = 16, options = { looseChanne break case 'WEB_PAGE_TYPE_UNKNOWN': default: { - const url = new URL(endpoint.payload?.content?.confirmDialogRenderer?.confirmButton?.buttonRenderer?.command?.urlEndpoint?.url || endpoint.payload.url) + const url = new URL((endpoint.dialog?.type === 'ConfirmDialog' && endpoint.dialog.confirm_button.endpoint.payload.url) || endpoint.payload.url) if (url.hostname === 'www.youtube.com' && url.pathname === '/redirect' && url.searchParams.has('q')) { // remove utm tracking parameters const realURL = new URL(url.searchParams.get('q')) @@ -569,11 +548,7 @@ export function parseLocalTextRuns(runs, emojiSize = 16, options = { looseChanne } /** - * @typedef {import('youtubei.js/dist/src/parser/classes/misc/Format').default} Format - */ - -/** - * @param {Format} format + * @param {Misc.Format} format */ export function mapLocalFormat(format) { return { @@ -589,8 +564,8 @@ export function mapLocalFormat(format) { } /** - * @param {import('youtubei.js/dist/src/parser/classes/comments/Comment').default} comment - * @param {import('youtubei.js/dist/src/parser/classes/comments/CommentThread').default} commentThread + * @param {import('youtubei.js').YTNodes.Comment} comment + * @param {import('youtubei.js').YTNodes.CommentThread} commentThread */ export function parseLocalComment(comment, commentThread = undefined) { let hasOwnerReplied = false @@ -625,7 +600,7 @@ export function parseLocalComment(comment, commentThread = undefined) { /** * video.js only supports MP4 DASH not WebM DASH * so we filter out the WebM DASH formats - * @param {Format[]} formats + * @param {Misc.Format[]} formats * @param {boolean} allowAv1 Use the AV1 formats if they are available */ export function filterLocalFormats(formats, allowAv1 = false) { @@ -683,7 +658,7 @@ export function parseLocalSubscriberCount(text) { /** * Parse community posts - * @param {import('youtubei.js/dist/src/parser/classes/BackstagePost').default} post + * @param {import('youtubei.js').YTNodes.BackstagePost} post */ export function parseLocalCommunityPost(post) { let replyCount = post.action_buttons?.reply_button?.text ?? null diff --git a/src/renderer/views/Channel/Channel.js b/src/renderer/views/Channel/Channel.js index f799d4c65..40ab111ba 100644 --- a/src/renderer/views/Channel/Channel.js +++ b/src/renderer/views/Channel/Channel.js @@ -412,7 +412,7 @@ export default defineComponent({ this.setErrorMessage(channel.alert) return } else if (channel.memo.has('ChannelAgeGate')) { - /** @type {import('youtubei.js/dist/src/parser/classes/ChannelAgeGate').default} */ + /** @type {import('youtubei.js').YTNodes.ChannelAgeGate} */ const ageGate = channel.memo.get('ChannelAgeGate')[0] channelName = ageGate.channel_title @@ -444,7 +444,7 @@ export default defineComponent({ // https://www.youtube.com/channel/UCXuqSBlHAE6Xw-yeJA0Tunw /** - * @type {import('youtubei.js/dist/src/parser/classes/C4TabbedHeader').default} + * @type {import('youtubei.js').YTNodes.C4TabbedHeader} */ const header = channel.header @@ -460,12 +460,12 @@ export default defineComponent({ // https://www.youtube.com/channel/UCOpNcN46UbXVtpKMrmU4Abg /** - * @type {import('youtubei.js/dist/src/parser/classes/CarouselHeader').default} + * @type {import('youtubei.js').YTNodes.CarouselHeader} */ const header = channel.header /** - * @type {import('youtubei.js/dist/src/parser/classes/TopicChannelDetails').default} + * @type {import('youtubei.js').YTNodes.TopicChannelDetails} */ const topicChannelDetails = header.contents.find(node => node.type === 'TopicChannelDetails') channelName = topicChannelDetails.title.text @@ -484,7 +484,7 @@ export default defineComponent({ // https://www.youtube.com/channel/UCQvWX73GQygcwXOTSf_VDVg /** - * @type {import('youtubei.js/dist/src/parser/classes/InteractiveTabbedHeader').default} + * @type {import('youtubei.js').YTNodes.InteractiveTabbedHeader} */ const header = channel.header channelName = header.title.text @@ -597,19 +597,19 @@ export default defineComponent({ getChannelAboutLocal: async function () { try { /** - * @type {import('youtubei.js/dist/src/parser/youtube/Channel').default} + * @type {import('youtubei.js').YT.Channel} */ const channel = this.channelInstance const about = await channel.getAbout() - this.description = about.description.text !== 'N/A' ? autolinker.link(about.description.text) : '' + this.description = about.description.isEmpty() ? '' : autolinker.link(about.description.text) - const views = extractNumberFromString(about.views.text) + const views = extractNumberFromString(about.view_count.text) this.views = isNaN(views) ? null : views - this.joined = about.joined.text !== 'N/A' ? new Date(about.joined.text.replace('Joined').trim()) : 0 + this.joined = about.joined_date.isEmpty() ? 0 : new Date(about.joined_date.text.replace('Joined').trim()) - this.location = about.country.text !== 'N/A' ? about.country.text : null + this.location = about.country.isEmpty() ? null : about.country.text } catch (err) { console.error(err) const errorMessage = this.$t('Local API Error (Click to copy)') @@ -631,7 +631,7 @@ export default defineComponent({ try { /** - * @type {import('youtubei.js/dist/src/parser/youtube/Channel').default} + * @type {import('youtubei.js').YT.Channel} */ const channel = this.channelInstance let videosTab = await channel.getVideos() @@ -668,7 +668,7 @@ export default defineComponent({ channelLocalNextPage: async function () { try { /** - * @type {import('youtubei.js/dist/src/parser/youtube/Channel').ChannelListContinuation|import('youtubei.js/dist/src/parser/youtube/Channel').FilteredChannelList} + * @type {import('youtubei.js').YT.ChannelListContinuation|import('youtubei.js').YT.FilteredChannelList} */ const continuation = await this.videoContinuationData.getContinuation() @@ -689,7 +689,7 @@ export default defineComponent({ try { /** - * @type {import('youtubei.js/dist/src/parser/youtube/Channel').default} + * @type {import('youtubei.js').YT.Channel} */ const channel = this.channelInstance let liveTab = await channel.getLiveStreams() @@ -726,7 +726,7 @@ export default defineComponent({ getChannelLiveLocalMore: async function () { try { /** - * @type {import('youtubei.js/dist/src/parser/youtube/Channel').ChannelListContinuation|import('youtubei.js/dist/src/parser/youtube/Channel').FilteredChannelList} + * @type {import('youtubei.js').YT.ChannelListContinuation|import('youtubei.js').YT.FilteredChannelList} */ const continuation = await this.liveContinuationData.getContinuation() @@ -909,7 +909,7 @@ export default defineComponent({ try { /** - * @type {import('youtubei.js/dist/src/parser/youtube/Channel').default} + * @type {import('youtubei.js').YT.Channel} */ const channel = this.channelInstance let playlistsTab = await channel.getPlaylists() @@ -919,7 +919,7 @@ export default defineComponent({ if (playlistsTab.content_type_filters.length > 1) { /** - * @type {import('youtubei.js/dist/src/parser/classes/ChannelSubMenu').default} + * @type {import('youtubei.js').YTNodes.ChannelSubMenu} */ const menu = playlistsTab.current_tab.content.sub_menu const createdPlaylistsFilter = menu.content_type_sub_menu_items.find(contentType => { @@ -964,7 +964,7 @@ export default defineComponent({ getChannelPlaylistsLocalMore: async function () { try { /** - * @type {import('youtubei.js/dist/src/parser/youtube/Channel').ChannelListContinuation} + * @type {import('youtubei.js').YT.ChannelListContinuation} */ const continuation = await this.playlistContinuationData.getContinuation() @@ -1054,12 +1054,12 @@ export default defineComponent({ try { /** - * @type {import('youtubei.js/dist/src/parser/youtube/Channel').default} + * @type {import('youtubei.js').YT.Channel} */ const channel = this.channelInstance /** - * @type {import('youtubei.js/dist/src/parser/youtube/Channel').default|import('youtubei.js/dist/src/parser/youtube/Channel').ChannelListContinuation} + * @type {import('youtubei.js').YT.Channel|import('youtubei.js').YT.ChannelListContinuation} */ let communityTab = await channel.getCommunity() if (expectedId !== this.id) { @@ -1095,7 +1095,7 @@ export default defineComponent({ getCommunityPostsLocalMore: async function () { try { /** - * @type {import('youtubei.js/dist/src/parser/youtube/Channel').ChannelListContinuation} + * @type {import('youtubei.js').YT.ChannelListContinuation} */ let continuation = await this.communityContinuationData.getContinuation() diff --git a/src/renderer/views/Watch/Watch.js b/src/renderer/views/Watch/Watch.js index 02113132a..f816c4d9c 100644 --- a/src/renderer/views/Watch/Watch.js +++ b/src/renderer/views/Watch/Watch.js @@ -268,8 +268,7 @@ export default defineComponent({ if (playabilityStatus.status === 'UNPLAYABLE') { /** - * @typedef {import('youtubei.js/dist/src/parser/classes/PlayerErrorMessage').default} PlayerErrorMessage - * @type {PlayerErrorMessage} + * @type {import ('youtubei.js').YTNodes.PlayerErrorMessage} */ const errorScreen = playabilityStatus.error_screen throw new Error(`[${playabilityStatus.status}] ${errorScreen.reason.text}: ${errorScreen.subreason.text}`) @@ -1217,10 +1216,7 @@ export default defineComponent({ }, /** - * @typedef {import('youtubei.js/dist/src/parser/youtube/VideoInfo').default} VideoInfo - */ - /** - * @param {VideoInfo} videoInfo + * @param {import('youtubei.js').YT.VideoInfo} videoInfo */ createLocalDashManifest: async function (videoInfo) { const xmlData = await videoInfo.toDash() diff --git a/yarn.lock b/yarn.lock index 0ea6152ae..3cd16231b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8446,10 +8446,10 @@ yocto-queue@^0.1.0: resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== -youtubei.js@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/youtubei.js/-/youtubei.js-4.3.0.tgz#2872ba878c3ee32c77152015267a47275da49459" - integrity sha512-HdU6Awdr1nUWy0Ph7WdmoYPWL0ovx+S4w40eeTzAENr5xiUENsLuXcvULRc2fRCIxi+n7Q6142VVhmM4yK/g5g== +youtubei.js@^5.0.2: + version "5.0.2" + resolved "https://registry.yarnpkg.com/youtubei.js/-/youtubei.js-5.0.2.tgz#2ce14dcb041fc18ff3ceb86c53abd78ce763f4f1" + integrity sha512-Fw2W0TaK50Vqkln3kr7DQ/b5Ve/f9UPh7DizPa4AWtabEyVJ68LNRaEbJp+5oSshbutHs8Q86zMtz+hxy2oQdw== dependencies: jintr "^1.0.0" linkedom "^0.14.12"