Update channel names and thumbnails when refreshing subscriptions (#4688)

This commit is contained in:
absidue 2024-03-07 14:49:54 +01:00 committed by GitHub
parent fdd675c693
commit 393f889d9b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 272 additions and 58 deletions

View File

@ -121,6 +121,8 @@ export default defineComponent({
this.attemptedFetch = true
this.errorChannels = []
const subscriptionUpdates = []
const postListFromRemote = (await Promise.all(channelsToLoadFromRemote.map(async (channel) => {
let posts = []
if (!process.env.IS_ELECTRON || this.backendPreference === 'invidious') {
@ -137,6 +139,32 @@ export default defineComponent({
channelId: channel.id,
posts: posts,
})
if (posts.length > 0) {
const post = posts.find(post => post.authorId === channel.id)
if (post) {
const name = post.author
let thumbnailUrl = post.authorThumbnails?.[0]?.url
if (name || thumbnailUrl) {
if (thumbnailUrl) {
if (thumbnailUrl.startsWith('//')) {
thumbnailUrl = 'https:' + thumbnailUrl
} else if (thumbnailUrl.startsWith(`${this.currentInvidiousInstance}/ggpht`)) {
thumbnailUrl = thumbnailUrl.replace(`${this.currentInvidiousInstance}/ggpht`, 'https://yt3.googleusercontent.com')
}
}
subscriptionUpdates.push({
channelId: channel.id,
channelName: name,
channelThumbnailUrl: thumbnailUrl
})
}
}
}
return posts
}))).flatMap((o) => o)
postList.push(...postListFromRemote)
@ -147,6 +175,8 @@ export default defineComponent({
this.postList = postList
this.isLoading = false
this.updateShowProgressBar(false)
this.batchUpdateSubscriptionDetails(subscriptionUpdates)
},
maybeLoadPostsForSubscriptionsFromRemote: async function () {
@ -211,6 +241,7 @@ export default defineComponent({
...mapActions([
'updateShowProgressBar',
'batchUpdateSubscriptionDetails',
'updateSubscriptionPostsCacheByChannel',
]),

View File

@ -129,19 +129,23 @@ export default defineComponent({
this.attemptedFetch = true
this.errorChannels = []
const subscriptionUpdates = []
const videoListFromRemote = (await Promise.all(channelsToLoadFromRemote.map(async (channel) => {
let videos = []
let name, thumbnailUrl
if (!process.env.IS_ELECTRON || this.backendPreference === 'invidious') {
if (useRss) {
videos = await this.getChannelLiveInvidiousRSS(channel)
({ videos, name, thumbnailUrl } = await this.getChannelLiveInvidiousRSS(channel))
} else {
videos = await this.getChannelLiveInvidious(channel)
({ videos, name, thumbnailUrl } = await this.getChannelLiveInvidious(channel))
}
} else {
if (useRss) {
videos = await this.getChannelLiveLocalRSS(channel)
({ videos, name, thumbnailUrl } = await this.getChannelLiveLocalRSS(channel))
} else {
videos = await this.getChannelLiveLocal(channel)
({ videos, name, thumbnailUrl } = await this.getChannelLiveLocal(channel))
}
}
@ -152,6 +156,15 @@ export default defineComponent({
channelId: channel.id,
videos: videos,
})
if (name || thumbnailUrl) {
subscriptionUpdates.push({
channelId: channel.id,
channelName: name,
channelThumbnailUrl: thumbnailUrl
})
}
return videos
}))).flatMap((o) => o)
videoList.push(...videoListFromRemote)
@ -159,6 +172,8 @@ export default defineComponent({
this.videoList = updateVideoListAfterProcessing(videoList)
this.isLoading = false
this.updateShowProgressBar(false)
this.batchUpdateSubscriptionDetails(subscriptionUpdates)
},
maybeLoadVideosForSubscriptionsFromRemote: async function () {
@ -174,16 +189,18 @@ export default defineComponent({
getChannelLiveLocal: async function (channel, failedAttempts = 0) {
try {
const entries = await getLocalChannelLiveStreams(channel.id)
const result = await getLocalChannelLiveStreams(channel.id)
if (entries === null) {
if (result === null) {
this.errorChannels.push(channel)
return []
return {
videos: []
}
}
addPublishedDatesLocal(entries)
addPublishedDatesLocal(result.videos)
return entries
return result
} catch (err) {
console.error(err)
const errorMessage = this.$t('Local API Error (Click to copy)')
@ -198,12 +215,16 @@ export default defineComponent({
showToast(this.$t('Falling back to Invidious API'))
return await this.getChannelLiveInvidious(channel, failedAttempts + 1)
} else {
return []
return {
videos: []
}
}
case 2:
return await this.getChannelLiveLocalRSS(channel, failedAttempts + 1)
default:
return []
return {
videos: []
}
}
}
},
@ -227,7 +248,9 @@ export default defineComponent({
this.errorChannels.push(channel)
}
return []
return {
videos: []
}
}
return await parseYouTubeRSSFeed(await response.text(), channel.id)
@ -245,12 +268,16 @@ export default defineComponent({
showToast(this.$t('Falling back to Invidious API'))
return this.getChannelLiveInvidiousRSS(channel, failedAttempts + 1)
} else {
return []
return {
videos: []
}
}
case 2:
return this.getChannelLiveLocal(channel, failedAttempts + 1)
default:
return []
return {
videos: []
}
}
}
},
@ -269,7 +296,16 @@ export default defineComponent({
addPublishedDatesInvidious(videos)
resolve(videos)
let name
if (videos.length > 0) {
name = videos.find(video => video.author).author
}
resolve({
name,
videos
})
}).catch((err) => {
console.error(err)
const errorMessage = this.$t('Invidious API Error (Click to copy)')
@ -285,14 +321,18 @@ export default defineComponent({
showToast(this.$t('Falling back to Local API'))
resolve(this.getChannelLiveLocal(channel, failedAttempts + 1))
} else {
resolve([])
resolve({
videos: []
})
}
break
case 2:
resolve(this.getChannelLiveInvidiousRSS(channel, failedAttempts + 1))
break
default:
resolve([])
resolve({
videos: []
})
}
})
})
@ -306,7 +346,9 @@ export default defineComponent({
const response = await fetch(feedUrl)
if (response.status === 500 || response.status === 404) {
return []
return {
videos: []
}
}
return await parseYouTubeRSSFeed(await response.text(), channel.id)
@ -324,17 +366,22 @@ export default defineComponent({
showToast(this.$t('Falling back to Local API'))
return this.getChannelLiveLocalRSS(channel, failedAttempts + 1)
} else {
return []
return {
videos: []
}
}
case 2:
return this.getChannelLiveInvidious(channel, failedAttempts + 1)
default:
return []
return {
videos: []
}
}
}
},
...mapActions([
'batchUpdateSubscriptionDetails',
'updateShowProgressBar',
'updateSubscriptionLiveCacheByChannel',
]),

View File

@ -114,12 +114,16 @@ export default defineComponent({
this.attemptedFetch = true
this.errorChannels = []
const subscriptionUpdates = []
const videoListFromRemote = (await Promise.all(channelsToLoadFromRemote.map(async (channel) => {
let videos = []
let name
if (!process.env.IS_ELECTRON || this.backendPreference === 'invidious') {
videos = await this.getChannelShortsInvidious(channel)
({ videos, name } = await this.getChannelShortsInvidious(channel))
} else {
videos = await this.getChannelShortsLocal(channel)
({ videos, name } = await this.getChannelShortsLocal(channel))
}
channelCount++
@ -129,6 +133,14 @@ export default defineComponent({
channelId: channel.id,
videos: videos,
})
if (name) {
subscriptionUpdates.push({
channelId: channel.id,
channelName: name
})
}
return videos
}))).flatMap((o) => o)
videoList.push(...videoListFromRemote)
@ -136,6 +148,8 @@ export default defineComponent({
this.videoList = updateVideoListAfterProcessing(videoList)
this.isLoading = false
this.updateShowProgressBar(false)
this.batchUpdateSubscriptionDetails(subscriptionUpdates)
},
maybeLoadVideosForSubscriptionsFromRemote: async function () {
@ -168,7 +182,9 @@ export default defineComponent({
this.errorChannels.push(channel)
}
return []
return {
videos: []
}
}
return await parseYouTubeRSSFeed(await response.text(), channel.id)
@ -184,10 +200,14 @@ export default defineComponent({
showToast(this.$t('Falling back to Invidious API'))
return this.getChannelShortsInvidious(channel, failedAttempts + 1)
} else {
return []
return {
videos: []
}
}
default:
return []
return {
videos: []
}
}
}
},
@ -200,7 +220,9 @@ export default defineComponent({
const response = await fetch(feedUrl)
if (response.status === 500 || response.status === 404) {
return []
return {
videos: []
}
}
return await parseYouTubeRSSFeed(await response.text(), channel.id)
@ -216,15 +238,20 @@ export default defineComponent({
showToast(this.$t('Falling back to Local API'))
return this.getChannelShortsLocal(channel, failedAttempts + 1)
} else {
return []
return {
videos: []
}
}
default:
return []
return {
videos: []
}
}
}
},
...mapActions([
'batchUpdateSubscriptionDetails',
'updateShowProgressBar',
'updateSubscriptionShortsCacheByChannel',
]),

View File

@ -129,19 +129,23 @@ export default defineComponent({
this.attemptedFetch = true
this.errorChannels = []
const subscriptionUpdates = []
const videoListFromRemote = (await Promise.all(channelsToLoadFromRemote.map(async (channel) => {
let videos = []
let name, thumbnailUrl
if (!process.env.IS_ELECTRON || this.backendPreference === 'invidious') {
if (useRss) {
videos = await this.getChannelVideosInvidiousRSS(channel)
({ videos, name, thumbnailUrl } = await this.getChannelVideosInvidiousRSS(channel))
} else {
videos = await this.getChannelVideosInvidiousScraper(channel)
({ videos, name, thumbnailUrl } = await this.getChannelVideosInvidiousScraper(channel))
}
} else {
if (useRss) {
videos = await this.getChannelVideosLocalRSS(channel)
({ videos, name, thumbnailUrl } = await this.getChannelVideosLocalRSS(channel))
} else {
videos = await this.getChannelVideosLocalScraper(channel)
({ videos, name, thumbnailUrl } = await this.getChannelVideosLocalScraper(channel))
}
}
@ -152,6 +156,15 @@ export default defineComponent({
channelId: channel.id,
videos: videos,
})
if (name || thumbnailUrl) {
subscriptionUpdates.push({
channelId: channel.id,
channelName: name,
channelThumbnailUrl: thumbnailUrl
})
}
return videos
}))).flatMap((o) => o)
videoList.push(...videoListFromRemote)
@ -159,6 +172,8 @@ export default defineComponent({
this.videoList = updateVideoListAfterProcessing(videoList)
this.isLoading = false
this.updateShowProgressBar(false)
this.batchUpdateSubscriptionDetails(subscriptionUpdates)
},
maybeLoadVideosForSubscriptionsFromRemote: async function () {
@ -174,16 +189,18 @@ export default defineComponent({
getChannelVideosLocalScraper: async function (channel, failedAttempts = 0) {
try {
const videos = await getLocalChannelVideos(channel.id)
const result = await getLocalChannelVideos(channel.id)
if (videos === null) {
if (result === null) {
this.errorChannels.push(channel)
return []
return {
videos: []
}
}
addPublishedDatesLocal(videos)
addPublishedDatesLocal(result.videos)
return videos
return result
} catch (err) {
console.error(err)
const errorMessage = this.$t('Local API Error (Click to copy)')
@ -198,12 +215,16 @@ export default defineComponent({
showToast(this.$t('Falling back to Invidious API'))
return await this.getChannelVideosInvidiousScraper(channel, failedAttempts + 1)
} else {
return []
return {
videos: []
}
}
case 2:
return await this.getChannelVideosLocalRSS(channel, failedAttempts + 1)
default:
return []
return {
videos: []
}
}
}
},
@ -227,7 +248,9 @@ export default defineComponent({
this.errorChannels.push(channel)
}
return []
return {
videos: []
}
}
return await parseYouTubeRSSFeed(await response.text(), channel.id)
@ -245,12 +268,16 @@ export default defineComponent({
showToast(this.$t('Falling back to Invidious API'))
return this.getChannelVideosInvidiousRSS(channel, failedAttempts + 1)
} else {
return []
return {
videos: []
}
}
case 2:
return this.getChannelVideosLocalScraper(channel, failedAttempts + 1)
default:
return []
return {
videos: []
}
}
}
},
@ -266,7 +293,16 @@ export default defineComponent({
invidiousAPICall(subscriptionsPayload).then((result) => {
addPublishedDatesInvidious(result.videos)
resolve(result.videos)
let name
if (result.videos.length > 0) {
name = result.videos.find(video => video.type === 'video' && video.author).author
}
resolve({
name,
videos: result.videos
})
}).catch((err) => {
console.error(err)
const errorMessage = this.$t('Invidious API Error (Click to copy)')
@ -282,14 +318,18 @@ export default defineComponent({
showToast(this.$t('Falling back to Local API'))
resolve(this.getChannelVideosLocalScraper(channel, failedAttempts + 1))
} else {
resolve([])
resolve({
videos: []
})
}
break
case 2:
resolve(this.getChannelVideosInvidiousRSS(channel, failedAttempts + 1))
break
default:
resolve([])
resolve({
videos: []
})
}
})
})
@ -304,7 +344,9 @@ export default defineComponent({
if (response.status === 500 || response.status === 404) {
this.errorChannels.push(channel)
return []
return {
videos: []
}
}
return await parseYouTubeRSSFeed(await response.text(), channel.id)
@ -322,17 +364,22 @@ export default defineComponent({
showToast(this.$t('Falling back to Local API'))
return this.getChannelVideosLocalRSS(channel, failedAttempts + 1)
} else {
return []
return {
videos: []
}
}
case 2:
return this.getChannelVideosInvidiousScraper(channel, failedAttempts + 1)
default:
return []
return {
videos: []
}
}
}
},
...mapActions([
'batchUpdateSubscriptionDetails',
'updateShowProgressBar',
'updateSubscriptionVideosCacheByChannel',
]),

View File

@ -274,6 +274,9 @@ export async function getLocalChannel(id) {
return result
}
/**
* @param {string} id
*/
export async function getLocalChannelVideos(id) {
const innertube = await createInnertube()
@ -286,15 +289,22 @@ export async function getLocalChannelVideos(id) {
}))
const videosTab = new YT.Channel(null, response)
const { id: channelId = id, name, thumbnailUrl } = parseLocalChannelHeader(videosTab)
let videos
// if the channel doesn't have a videos tab, YouTube returns the home tab instead
// so we need to check that we got the right tab
if (videosTab.current_tab?.endpoint.metadata.url?.endsWith('/videos')) {
const { id: channelId = id, name } = parseLocalChannelHeader(videosTab)
return parseLocalChannelVideos(videosTab.videos, channelId, name)
videos = parseLocalChannelVideos(videosTab.videos, channelId, name)
} else {
return []
videos = []
}
return {
name,
thumbnailUrl,
videos
}
} catch (error) {
console.error(error)
@ -306,6 +316,9 @@ export async function getLocalChannelVideos(id) {
}
}
/**
* @param {string} id
*/
export async function getLocalChannelLiveStreams(id) {
const innertube = await createInnertube()
@ -318,15 +331,22 @@ export async function getLocalChannelLiveStreams(id) {
}))
const liveStreamsTab = new YT.Channel(null, response)
const { id: channelId = id, name, thumbnailUrl } = parseLocalChannelHeader(liveStreamsTab)
let videos
// if the channel doesn't have a live tab, YouTube returns the home tab instead
// so we need to check that we got the right tab
if (liveStreamsTab.current_tab?.endpoint.metadata.url?.endsWith('/streams')) {
const { id: channelId = id, name } = parseLocalChannelHeader(liveStreamsTab)
return parseLocalChannelVideos(liveStreamsTab.videos, channelId, name)
videos = parseLocalChannelVideos(liveStreamsTab.videos, channelId, name)
} else {
return []
videos = []
}
return {
name,
thumbnailUrl,
videos
}
} catch (error) {
console.error(error)

View File

@ -80,9 +80,14 @@ export async function parseYouTubeRSSFeed(rssString, channelId) {
promises.push(parseRSSEntry(entry, channelId, channelName))
}
return await Promise.all(promises)
return {
name: channelName,
videos: await Promise.all(promises)
}
} catch (e) {
return []
return {
videos: []
}
}
}

View File

@ -91,6 +91,43 @@ const actions = {
commit('setProfileList', profiles)
},
async batchUpdateSubscriptionDetails({ getters, dispatch }, channels) {
if (channels.length === 0) { return }
const profileList = getters.getProfileList
for (const profile of profileList) {
const currentProfileCopy = deepCopy(profile)
let profileUpdated = false
for (const { channelThumbnailUrl, channelName, channelId } of channels) {
const channel = currentProfileCopy.subscriptions.find((channel) => {
return channel.id === channelId
}) ?? null
if (channel === null) { continue }
if (channel.name !== channelName && channelName != null) {
channel.name = channelName
profileUpdated = true
}
if (channelThumbnailUrl) {
const thumbnail = channelThumbnailUrl.replace(/=s\d*/, '=s176') // change thumbnail size if different
if (channel.thumbnail !== thumbnail) {
channel.thumbnail = thumbnail
profileUpdated = true
}
}
}
if (profileUpdated) {
await dispatch('updateProfile', currentProfileCopy)
}
}
},
async updateSubscriptionDetails({ getters, dispatch }, { channelThumbnailUrl, channelName, channelId }) {
const thumbnail = channelThumbnailUrl?.replace(/=s\d*/, '=s176') ?? null // change thumbnail size if different
const profileList = getters.getProfileList