From 7a4c8241d9b0ed7779f42bf9c43455ea2a96aabe Mon Sep 17 00:00:00 2001 From: ChunkyProgrammer <78101139+ChunkyProgrammer@users.noreply.github.com> Date: Sat, 25 Jan 2025 14:33:47 -0500 Subject: [PATCH] Add support for channel's `Courses` tab --- .../ChannelDetails/ChannelDetails.vue | 16 ++ .../distraction-settings.js | 4 + .../distraction-settings.vue | 6 + src/renderer/helpers/api/invidious.js | 11 +- src/renderer/store/modules/settings.js | 1 + src/renderer/store/modules/utils.js | 5 +- src/renderer/views/Channel/Channel.js | 147 +++++++++++++++++- src/renderer/views/Channel/Channel.vue | 15 ++ static/locales/en-US.yaml | 4 + 9 files changed, 204 insertions(+), 5 deletions(-) diff --git a/src/renderer/components/ChannelDetails/ChannelDetails.vue b/src/renderer/components/ChannelDetails/ChannelDetails.vue index adb685333529a..0aba9517da9a4 100644 --- a/src/renderer/components/ChannelDetails/ChannelDetails.vue +++ b/src/renderer/components/ChannelDetails/ChannelDetails.vue @@ -171,6 +171,22 @@ {{ $t("Channel.Podcasts.Podcasts").toUpperCase() }} + +
+

} @@ -240,6 +240,15 @@ export async function getInvidiousChannelPodcasts(channelId, continuation) { return await getInvidiousChannelTab('podcasts', channelId, continuation) } +/** + * @param {string} channelId + * @param {string | undefined | null} continuation + */ +export async function getInvidiousChannelCourses(channelId, continuation) { + /** @type {{continuation: string?, playlists: InvidiousPlaylistObject[]}} */ + return await getInvidiousChannelTab('courses', channelId, continuation) +} + /** * @param {string} channelId * @param {string} query diff --git a/src/renderer/store/modules/settings.js b/src/renderer/store/modules/settings.js index 4ee32fd53bf73..2f1fc4bafb853 100644 --- a/src/renderer/store/modules/settings.js +++ b/src/renderer/store/modules/settings.js @@ -186,6 +186,7 @@ const state = { hideChannelPlaylists: false, hideChannelReleases: false, hideChannelPodcasts: false, + hideChannelCourses: false, hideChannelShorts: false, hideChannelSubscriptions: false, hideCommentLikes: false, diff --git a/src/renderer/store/modules/utils.js b/src/renderer/store/modules/utils.js index e5b42ecb6d5dd..64541069d7166 100644 --- a/src/renderer/store/modules/utils.js +++ b/src/renderer/store/modules/utils.js @@ -475,7 +475,7 @@ const actions = { let urlType = 'unknown' const channelPattern = - /^\/(?:(?:channel|user|c)\/)?(?[^/]+)(?:\/(?join|featured|videos|shorts|live|streams|podcasts|releases|playlists|about|community|channels))?\/?$/ + /^\/(?:(?:channel|user|c)\/)?(?[^/]+)(?:\/(?join|featured|videos|shorts|live|streams|podcasts|releases|courses|playlists|about|community|channels))?\/?$/ const hashtagPattern = /^\/hashtag\/(?[^#&/?]+)$/ @@ -622,6 +622,9 @@ const actions = { case 'podcasts': subPath = 'podcasts' break + case 'courses': + subPath = 'courses' + break case 'releases': subPath = 'releases' break diff --git a/src/renderer/views/Channel/Channel.js b/src/renderer/views/Channel/Channel.js index 4e7d83336ff15..bb340d6b61d8e 100644 --- a/src/renderer/views/Channel/Channel.js +++ b/src/renderer/views/Channel/Channel.js @@ -26,6 +26,7 @@ import { getInvidiousChannelPlaylists, getInvidiousChannelPodcasts, getInvidiousChannelReleases, + getInvidiousChannelCourses, getInvidiousChannelShorts, getInvidiousChannelVideos, invidiousGetChannelId, @@ -87,6 +88,7 @@ export default defineComponent({ liveContinuationData: null, releaseContinuationData: null, podcastContinuationData: null, + coursesContinuationData: null, playlistContinuationData: null, searchContinuationData: null, communityContinuationData: null, @@ -112,6 +114,7 @@ export default defineComponent({ latestLive: [], latestReleases: [], latestPodcasts: [], + latestCourses: [], latestPlaylists: [], latestCommunityPosts: [], searchResults: [], @@ -134,6 +137,7 @@ export default defineComponent({ 'live', 'releases', 'podcasts', + 'courses', 'playlists', 'community', 'about' @@ -144,6 +148,7 @@ export default defineComponent({ 'live', 'releases', 'podcasts', + 'courses', 'playlists', 'community', 'about' @@ -234,6 +239,8 @@ export default defineComponent({ return !isNullOrEmpty(this.releaseContinuationData) case 'podcasts': return !isNullOrEmpty(this.podcastContinuationData) + case 'courses': + return !isNullOrEmpty(this.coursesContinuationData) case 'playlists': return !isNullOrEmpty(this.playlistContinuationData) case 'community': @@ -261,6 +268,10 @@ export default defineComponent({ return this.$store.getters.getHideChannelReleases }, + hideChannelCourses: function() { + return this.$store.getters.getHideChannelCourses + }, + hideChannelPlaylists: function() { return this.$store.getters.getHideChannelPlaylists }, @@ -302,6 +313,10 @@ export default defineComponent({ indexToRemove.push(values.indexOf('releases')) } + if (this.hideChannelCourses) { + indexToRemove.push(values.indexOf('courses')) + } + if (this.hideChannelHome) { indexToRemove.push(values.indexOf('home')) } @@ -668,6 +683,11 @@ export default defineComponent({ this.getChannelReleasesLocal() } + if (!this.hideChannelCourses && channel.has_courses) { + tabs.push('courses') + this.getChannelCoursesLocal() + } + if (!this.hideChannelPlaylists) { if (channel.has_playlists) { tabs.push('playlists') @@ -1126,6 +1146,10 @@ export default defineComponent({ this.channelInvidiousReleases() } + if (!this.hideChannelCourses && response.tabs.includes('courses')) { + this.channelInvidiousCourses() + } + if (!this.hideChannelPlaylists && response.tabs.includes('playlists')) { this.getPlaylistsInvidious() } @@ -1560,7 +1584,7 @@ export default defineComponent({ const parsedPodcasts = continuation.playlists.map(playlist => parseLocalListPlaylist(playlist, this.id, this.channelName)) this.latestPodcasts = this.latestPodcasts.concat(parsedPodcasts) - this.releaseContinuationData = continuation.has_continuation ? continuation : null + this.podcastContinuationData = continuation.has_continuation ? continuation : null } catch (err) { console.error(err) const errorMessage = this.$t('Local API Error (Click to copy)') @@ -1620,6 +1644,108 @@ export default defineComponent({ }) }, + getChannelCoursesLocal: async function () { + this.isElementListLoading = true + const expectedId = this.id + + try { + /** + * @type {import('youtubei.js').YT.Channel} + */ + const channel = this.channelInstance + const coursesTab = await channel.getCourses() + + if (expectedId !== this.id) { + return + } + + this.latestCourses = coursesTab.playlists.map(playlist => parseLocalListPlaylist(playlist, this.id, this.channelName)) + this.coursesContinuationData = coursesTab.has_continuation ? coursesTab : null + this.isElementListLoading = false + } catch (err) { + console.error(err) + const errorMessage = this.$t('Local API Error (Click to copy)') + showToast(`${errorMessage}: ${err}`, 10000, () => { + copyToClipboard(err) + }) + if (this.backendPreference === 'local' && this.backendFallback) { + showToast(this.$t('Falling back to Invidious API')) + this.channelInvidiousCourses() + } else { + this.isLoading = false + } + } + }, + + getChannelCoursesLocalMore: async function () { + try { + /** + * @type {import('youtubei.js').YT.ChannelListContinuation} + */ + const continuation = await this.coursesContinuationData.getContinuation() + + const parsedCourses = continuation.playlists.map(playlist => parseLocalListPlaylist(playlist, this.id, this.channelName)) + this.latestCourses = this.latestCourses.concat(parsedCourses) + this.coursesContinuationData = continuation.has_continuation ? continuation : null + } catch (err) { + console.error(err) + const errorMessage = this.$t('Local API Error (Click to copy)') + showToast(`${errorMessage}: ${err}`, 10000, () => { + copyToClipboard(err) + }) + } + }, + + channelInvidiousCourses: function() { + this.isElementListLoading = true + + getInvidiousChannelCourses(this.id).then((response) => { + this.coursesContinuationData = response.continuation || null + this.latestCourses = response.playlists + this.isElementListLoading = false + }).catch(async (err) => { + console.error(err) + const errorMessage = this.$t('Invidious API Error (Click to copy)') + showToast(`${errorMessage}: ${err}`, 10000, () => { + copyToClipboard(err) + }) + if (process.env.SUPPORTS_LOCAL_API && this.backendPreference === 'invidious' && this.backendFallback) { + showToast(this.$t('Falling back to Local API')) + if (!this.channelInstance) { + this.channelInstance = await getLocalChannel(this.id) + } + this.getChannelCoursesLocal() + } else { + this.isLoading = false + } + }) + }, + + channelInvidiousCoursesMore: function () { + if (this.coursesContinuationData === null) { + console.warn('There are no more courses available for this channel') + return + } + + getInvidiousChannelCourses(this.id, this.coursesContinuationData).then((response) => { + this.coursesContinuationData = response.continuation || null + this.latestCourses = this.latestCourses.concat(response.playlists) + this.isElementListLoading = false + }).catch((err) => { + console.error(err) + const errorMessage = this.$t('Invidious API Error (Click to copy)') + showToast(`${errorMessage}: ${err}`, 10000, () => { + copyToClipboard(err) + }) + if (process.env.SUPPORTS_LOCAL_API && this.backendPreference === 'invidious' && this.backendFallback) { + showToast(this.$t('Falling back to Local API')) + this.getChannelLocal() + } else { + this.isLoading = false + } + }) + }, + getCommunityPostsLocal: async function () { const expectedId = this.id @@ -1780,10 +1906,25 @@ export default defineComponent({ } break case 'releases': - this.getChannelReleasesLocalMore() + if (this.apiUsed === 'local') { + this.getChannelReleasesLocalMore() + } else { + this.channelInvidiousReleasesMore() + } break case 'podcasts': - this.getChannelPodcastsLocalMore() + if (this.apiUsed === 'local') { + this.getChannelPodcastsLocalMore() + } else { + this.channelInvidiousPodcastsMore() + } + break + case 'courses': + if (this.apiUsed === 'local') { + this.getChannelCoursesLocalMore() + } else { + this.channelInvidiousCoursesMore() + } break case 'playlists': switch (this.apiUsed) { diff --git a/src/renderer/views/Channel/Channel.vue b/src/renderer/views/Channel/Channel.vue index 660138cb480b6..b04041f6a6729 100644 --- a/src/renderer/views/Channel/Channel.vue +++ b/src/renderer/views/Channel/Channel.vue @@ -170,6 +170,21 @@ {{ $t("Channel.Releases.This channel does not currently have any releases") }}

+ + +

+ {{ $t("Channel.Courses.This channel does not currently have any courses") }} +

+