diff --git a/src/renderer/components/FtListLazyWrapper/FtListLazyWrapper.vue b/src/renderer/components/FtListLazyWrapper/FtListLazyWrapper.vue index 12e667db3105f..824f16151b524 100644 --- a/src/renderer/components/FtListLazyWrapper/FtListLazyWrapper.vue +++ b/src/renderer/components/FtListLazyWrapper/FtListLazyWrapper.vue @@ -258,8 +258,9 @@ const showResult = computed(() => { } const lowerCaseAuthor = props.data.author?.toLowerCase() - - if (channelsHidden.value.some(ch => ch.name === props.data.authorId) || channelsHidden.value.some(ch => ch.name === props.data.author) || (forbiddenTitles.value.some((text) => lowerCaseAuthor.includes(text)))) { + if (channelsHidden.value.some(ch => ch.name === props.data.authorId || (props.data.collaborators?.length > 0 && ch.name === props.data.collaborators[0].id)) || + channelsHidden.value.some(ch => ch.name === props.data.author) || + forbiddenTitles.value.some((text) => lowerCaseAuthor.includes(text))) { // hide videos by author return false } diff --git a/src/renderer/components/FtListVideo/FtListVideo.vue b/src/renderer/components/FtListVideo/FtListVideo.vue index ce92697b80848..f3328874199d1 100644 --- a/src/renderer/components/FtListVideo/FtListVideo.vue +++ b/src/renderer/components/FtListVideo/FtListVideo.vue @@ -411,6 +411,7 @@ const id = ref('') const title = ref('') const channelName = ref(null) const channelId = ref(null) +const collaborators = ref([]) const viewCount = ref(0) const parsedViewCount = ref('') const uploadedTime = ref('') @@ -715,12 +716,16 @@ function handleOptionsClick(option) { case 'openInvidiousChannel': openExternalLink(getInvidiousChannelUrl()) break - case 'hideChannel': - hideChannel(channelName.value, channelId.value) + case 'hideChannel': { + const name = collaborators.value.length > 0 ? collaborators.value[0].name : channelName.value + hideChannel(name, channelId.value) break - case 'unhideChannel': - unhideChannel(channelName.value, channelId.value) + } + case 'unhideChannel': { + const name = collaborators.value.length > 0 ? collaborators.value[0].name : channelName.value + unhideChannel(name, channelId.value) break + } } } @@ -1023,7 +1028,16 @@ function parseVideoData() { title.value = props.data.title channelName.value = props.data.author ?? null - channelId.value = props.data.authorId ?? null + collaborators.value = props.data.collaborators ?? [] + + if (props.data.authorId) { + channelId.value = props.data.authorId + } else if (collaborators.value.length > 0) { + // When video has collaborators, authorId is null and the first element in the collaborator array is the main author + channelId.value = collaborators.value[0].id + } else { + channelId.value = null + } if ((props.data.lengthSeconds === '' || props.data.lengthSeconds === '0:00') && historyEntryExists.value) { lengthSeconds.value = historyEntry.value.lengthSeconds diff --git a/src/renderer/components/FtListVideoLazy.vue b/src/renderer/components/FtListVideoLazy.vue index d757dbf6a5a8c..2824e8968d8a7 100644 --- a/src/renderer/components/FtListVideoLazy.vue +++ b/src/renderer/components/FtListVideoLazy.vue @@ -143,7 +143,7 @@ const shouldBeVisible = computed(() => { const lowerCaseTitle = props.data.title?.toLowerCase() const lowerCaseAuthor = props.data.author?.toLowerCase() - return !(channelsHidden.value.some(ch => ch.name === props.data.authorId) || + return !(channelsHidden.value.some(ch => ch.name === props.data.authorId || (props.data.collaborators?.length > 0 && ch.name === props.data.collaborators[0].id)) || channelsHidden.value.some(ch => ch.name === props.data.author) || (lowerCaseTitle && forbiddenTitles.value.some((text) => lowerCaseTitle.includes(text))) || (hideChannelsBasedOnText.value && lowerCaseAuthor && forbiddenTitles.value.some((text) => lowerCaseAuthor.includes(text)))) diff --git a/src/renderer/helpers/api/local.js b/src/renderer/helpers/api/local.js index c1651a600d39c..ab9ed614df770 100644 --- a/src/renderer/helpers/api/local.js +++ b/src/renderer/helpers/api/local.js @@ -1675,6 +1675,7 @@ export function parseLocalListVideo(item, channelId, channelName) { title: video.title.text?.trim(), author: video.author?.name ?? channelName, authorId: (video.author?.id != null && video.author.id !== 'N/A') ? video.author.id : channelId, + collaborators: video.author == null ? [] : parseLocalCollaborators(video.author.collaborators), viewCount: video.views.text == null ? null : extractNumberFromString(video.views.text), published, lengthSeconds: isLive ? '' : Utils.timeToSeconds(video.duration.text), @@ -1733,6 +1734,7 @@ export function parseLocalListVideo(item, channelId, channelName) { title: video.title.text?.trim(), author: video.author.name !== 'N/A' ? video.author.name : channelName, authorId: video.author.id !== 'N/A' ? video.author.id : channelId, + collaborators: parseLocalCollaborators(video.author.collaborators), description: video.description, viewCount, published, @@ -1909,12 +1911,20 @@ function parseLockupView(lockupView, channelId = undefined, channelName = undefi author = lockupView.metadata?.metadata?.metadata_rows[0].metadata_parts?.[0].avatar_stack.text?.text } + let collaborators = [] + const maybeCollaborators = lockupView.metadata.image?.renderer_context?.command_context?.on_tap?.command?.inline_content?.custom_content?.items + .filter(item => item.renderer_context?.command_context?.on_tap?.metadata?.page_type === 'WEB_PAGE_TYPE_CHANNEL') + if (maybeCollaborators) { + collaborators = parseLocalCollaborators(maybeCollaborators) + } + return { type: 'video', videoId: lockupView.content_id, title: lockupView.metadata.title.text?.trim(), author, authorId: lockupView.metadata.image?.renderer_context?.command_context?.on_tap?.payload.browseId ?? channelId, + collaborators, viewCount, published: calculatePublishedDate(publishedText, liveNow, isUpcoming, premiereDate), lengthSeconds, @@ -2471,6 +2481,16 @@ function parseLocalAttachment(attachment) { } } +/** + * @param {import('youtubei.js').YTNodes.ListItemView[]} collaborators + */ +function parseLocalCollaborators(collaborators) { + return collaborators.map(collaborator => ({ + id: collaborator.renderer_context?.command_context?.on_tap?.payload?.browseId, + name: collaborator.title?.text, + })) +} + export async function getHashtagLocal(hashtag) { const innertube = await createInnertube() return await innertube.getHashtag(hashtag) diff --git a/src/renderer/views/Watch/Watch.js b/src/renderer/views/Watch/Watch.js index 6222be1daf5f5..0a05fa4fd517b 100644 --- a/src/renderer/views/Watch/Watch.js +++ b/src/renderer/views/Watch/Watch.js @@ -1930,7 +1930,7 @@ export default defineComponent({ }, isHiddenVideo: function (forbiddenTitles, channelsHidden, video) { - return channelsHidden.some(ch => ch.name === video.authorId) || + return channelsHidden.some(ch => ch.name === video.authorId && (video.author.collaborators?.length > 0 && ch.name === video.author.collaborators[0].id)) || channelsHidden.some(ch => ch.name === video.author) || forbiddenTitles.some((text) => video.title?.toLowerCase().includes(text)) || forbiddenTitles.some((text) => video.author?.toLowerCase().includes(text))