Allow Unsubscribing from Deleted Channels (#2283)

* unsub from deleted

* reset error message on invidious channel load

* fix error channels not showing

* Use errorMessage instead of isErrorMessage

Co-authored-by: absidue <48293849+absidue@users.noreply.github.com>

* Change "Error Channels" to "Channels with Errors"

Co-authored-by: absidue <48293849+absidue@users.noreply.github.com>

* use find instead of find index

Co-Authored-By: absidue <48293849+absidue@users.noreply.github.com>
Co-Authored-By: PikachuEXE <pikachuexe@gmail.com>

Co-authored-by: absidue <48293849+absidue@users.noreply.github.com>
Co-authored-by: PikachuEXE <pikachuexe@gmail.com>
This commit is contained in:
ChunkyProgrammer 2022-06-03 10:04:50 -04:00 committed by GitHub
parent 74dc309803
commit da095adc8c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 131 additions and 60 deletions

View File

@ -4,7 +4,8 @@ const state = {
allSubscriptionsList: [], allSubscriptionsList: [],
profileSubscriptions: { profileSubscriptions: {
activeProfile: MAIN_PROFILE_ID, activeProfile: MAIN_PROFILE_ID,
videoList: [] videoList: [],
errorChannels: []
} }
} }

View File

@ -50,6 +50,7 @@ export default Vue.extend({
searchResults: [], searchResults: [],
shownElementList: [], shownElementList: [],
apiUsed: '', apiUsed: '',
errorMessage: '',
videoSelectValues: [ videoSelectValues: [
'newest', 'newest',
'oldest', 'oldest',
@ -90,16 +91,14 @@ export default Vue.extend({
return this.$store.getters.getActiveProfile return this.$store.getters.getActiveProfile
}, },
isSubscribed: function () { subscriptionInfo: function () {
const subIndex = this.activeProfile.subscriptions.findIndex((channel) => { return this.activeProfile.subscriptions.find((channel) => {
return channel.id === this.id return channel.id === this.id
}) }) ?? null
},
if (subIndex === -1) { isSubscribed: function () {
return false return this.subscriptionInfo !== null
} else {
return true
}
}, },
subscribedText: function () { subscribedText: function () {
@ -251,6 +250,11 @@ export default Vue.extend({
this.apiUsed = 'local' this.apiUsed = 'local'
const expectedId = this.id const expectedId = this.id
ytch.getChannelInfo({ channelId: expectedId }).then((response) => { ytch.getChannelInfo({ channelId: expectedId }).then((response) => {
if (response.alertMessage) {
this.setErrorMessage(response.alertMessage)
return
}
this.errorMessage = ''
if (expectedId !== this.id) { if (expectedId !== this.id) {
return return
} }
@ -401,8 +405,10 @@ export default Vue.extend({
this.bannerUrl = null this.bannerUrl = null
} }
this.errorMessage = ''
this.isLoading = false this.isLoading = false
}).catch((err) => { }).catch((err) => {
this.setErrorMessage(err.responseJSON.error)
console.log(err) console.log(err)
const errorMessage = this.$t('Invidious API Error (Click to copy)') const errorMessage = this.$t('Invidious API Error (Click to copy)')
this.showToast({ this.showToast({
@ -645,6 +651,16 @@ export default Vue.extend({
} }
}, },
setErrorMessage: function (errorMessage) {
this.isLoading = false
this.errorMessage = errorMessage
this.id = this.subscriptionInfo.id
this.channelName = this.subscriptionInfo.name
this.thumbnailUrl = this.subscriptionInfo.thumbnail
this.bannerUrl = null
this.subCount = null
},
handleFetchMore: function () { handleFetchMore: function () {
switch (this.currentTab) { switch (this.currentTab) {
case 'videos': case 'videos':

View File

@ -3,7 +3,7 @@
ref="search" ref="search"
> >
<ft-loader <ft-loader
v-if="isLoading" v-if="isLoading && !errorMessage"
:fullscreen="true" :fullscreen="true"
/> />
<ft-card <ft-card
@ -61,6 +61,7 @@
</div> </div>
<ft-flex-box <ft-flex-box
v-if="!errorMessage"
class="channelInfoTabs" class="channelInfoTabs"
> >
<div <div
@ -112,7 +113,7 @@
</div> </div>
</ft-card> </ft-card>
<ft-card <ft-card
v-if="!isLoading" v-if="!isLoading && !errorMessage"
class="card" class="card"
> >
<div <div
@ -194,6 +195,14 @@
</div> </div>
</div> </div>
</ft-card> </ft-card>
<ft-card
v-if="errorMessage"
class="card"
>
<p>
{{ errorMessage }}
</p>
</ft-card>
</div> </div>
</template> </template>

View File

@ -14,6 +14,10 @@
right: 10px; right: 10px;
} }
.channelBubble {
display: inline-block;
}
@media only screen and (max-width: 350px) { @media only screen and (max-width: 350px) {
.floatingTopButton { .floatingTopButton {
position: absolute position: absolute

View File

@ -6,6 +6,7 @@ import FtButton from '../../components/ft-button/ft-button.vue'
import FtIconButton from '../../components/ft-icon-button/ft-icon-button.vue' import FtIconButton from '../../components/ft-icon-button/ft-icon-button.vue'
import FtFlexBox from '../../components/ft-flex-box/ft-flex-box.vue' import FtFlexBox from '../../components/ft-flex-box/ft-flex-box.vue'
import FtElementList from '../../components/ft-element-list/ft-element-list.vue' import FtElementList from '../../components/ft-element-list/ft-element-list.vue'
import FtChannelBubble from '../../components/ft-channel-bubble/ft-channel-bubble.vue'
import ytch from 'yt-channel-info' import ytch from 'yt-channel-info'
import Parser from 'rss-parser' import Parser from 'rss-parser'
@ -19,13 +20,15 @@ export default Vue.extend({
'ft-button': FtButton, 'ft-button': FtButton,
'ft-icon-button': FtIconButton, 'ft-icon-button': FtIconButton,
'ft-flex-box': FtFlexBox, 'ft-flex-box': FtFlexBox,
'ft-element-list': FtElementList 'ft-element-list': FtElementList,
'ft-channel-bubble': FtChannelBubble
}, },
data: function () { data: function () {
return { return {
isLoading: false, isLoading: false,
dataLimit: 100, dataLimit: 100,
videoList: [] videoList: [],
errorChannels: []
} }
}, },
computed: { computed: {
@ -110,6 +113,7 @@ export default Vue.extend({
})) }))
} else { } else {
this.videoList = subscriptionList.videoList this.videoList = subscriptionList.videoList
this.errorChannels = subscriptionList.errorChannels
} }
} else { } else {
this.getProfileSubscriptions() this.getProfileSubscriptions()
@ -123,6 +127,10 @@ export default Vue.extend({
} }
}, },
methods: { methods: {
goToChannel: function (id) {
this.$router.push({ path: `/channel/${id}` })
},
getSubscriptions: function () { getSubscriptions: function () {
if (this.activeSubscriptionList.length === 0) { if (this.activeSubscriptionList.length === 0) {
this.isLoading = false this.isLoading = false
@ -144,10 +152,9 @@ export default Vue.extend({
let videoList = [] let videoList = []
let channelCount = 0 let channelCount = 0
this.errorChannels = []
this.activeSubscriptionList.forEach(async (channel) => { this.activeSubscriptionList.forEach(async (channel) => {
let videos = [] let videos = []
if (!this.usingElectron || this.backendPreference === 'invidious') { if (!this.usingElectron || this.backendPreference === 'invidious') {
if (useRss) { if (useRss) {
videos = await this.getChannelVideosInvidiousRSS(channel) videos = await this.getChannelVideosInvidiousRSS(channel)
@ -174,7 +181,8 @@ export default Vue.extend({
const profileSubscriptions = { const profileSubscriptions = {
activeProfile: this.activeProfile._id, activeProfile: this.activeProfile._id,
videoList: videoList videoList: videoList,
errorChannels: this.errorChannels
} }
this.videoList = await Promise.all(videoList.filter((video) => { this.videoList = await Promise.all(videoList.filter((video) => {
@ -226,6 +234,11 @@ export default Vue.extend({
getChannelVideosLocalScraper: function (channel, failedAttempts = 0) { getChannelVideosLocalScraper: function (channel, failedAttempts = 0) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
ytch.getChannelVideos({ channelId: channel.id, sortBy: 'latest' }).then(async (response) => { ytch.getChannelVideos({ channelId: channel.id, sortBy: 'latest' }).then(async (response) => {
if (response.alertMessage) {
this.errorChannels.push(channel)
resolve([])
return
}
const videos = await Promise.all(response.items.map(async (video) => { const videos = await Promise.all(response.items.map(async (video) => {
if (video.liveNow) { if (video.liveNow) {
video.publishedDate = new Date().getTime() video.publishedDate = new Date().getTime()
@ -297,33 +310,38 @@ export default Vue.extend({
resolve(items) resolve(items)
}).catch((err) => { }).catch((err) => {
console.log(err) console.log(err)
const errorMessage = this.$t('Local API Error (Click to copy)') if (err.toString().match(/404/)) {
this.showToast({ this.errorChannels.push(channel)
message: `${errorMessage}: ${err}`, resolve([])
time: 10000, } else {
action: () => { const errorMessage = this.$t('Local API Error (Click to copy)')
navigator.clipboard.writeText(err) this.showToast({
} message: `${errorMessage}: ${err}`,
}) time: 10000,
switch (failedAttempts) { action: () => {
case 0: navigator.clipboard.writeText(err)
resolve(this.getChannelVideosLocalScraper(channel, failedAttempts + 1))
break
case 1:
if (this.backendFallback) {
this.showToast({
message: this.$t('Falling back to Invidious API')
})
resolve(this.getChannelVideosInvidiousRSS(channel, failedAttempts + 1))
} else {
resolve([])
} }
break })
case 2: switch (failedAttempts) {
resolve(this.getChannelVideosLocalScraper(channel, failedAttempts + 1)) case 0:
break resolve(this.getChannelVideosLocalScraper(channel, failedAttempts + 1))
default: break
resolve([]) case 1:
if (this.backendFallback) {
this.showToast({
message: this.$t('Falling back to Invidious API')
})
resolve(this.getChannelVideosInvidiousRSS(channel, failedAttempts + 1))
} else {
resolve([])
}
break
case 2:
resolve(this.getChannelVideosLocalScraper(channel, failedAttempts + 1))
break
default:
resolve([])
}
} }
}) })
}) })
@ -403,25 +421,30 @@ export default Vue.extend({
navigator.clipboard.writeText(err) navigator.clipboard.writeText(err)
} }
}) })
switch (failedAttempts) { if (err.toString().match(/500/)) {
case 0: this.errorChannels.push(channel)
resolve(this.getChannelVideosInvidiousScraper(channel, failedAttempts + 1)) resolve([])
break } else {
case 1: switch (failedAttempts) {
if (this.backendFallback) { case 0:
this.showToast({ resolve(this.getChannelVideosInvidiousScraper(channel, failedAttempts + 1))
message: this.$t('Falling back to the local API') break
}) case 1:
resolve(this.getChannelVideosLocalRSS(channel, failedAttempts + 1)) if (this.backendFallback) {
} else { this.showToast({
message: this.$t('Falling back to the local API')
})
resolve(this.getChannelVideosLocalRSS(channel, failedAttempts + 1))
} else {
resolve([])
}
break
case 2:
resolve(this.getChannelVideosInvidiousScraper(channel, failedAttempts + 1))
break
default:
resolve([]) resolve([])
} }
break
case 2:
resolve(this.getChannelVideosInvidiousScraper(channel, failedAttempts + 1))
break
default:
resolve([])
} }
}) })
}) })

View File

@ -8,6 +8,22 @@
v-else v-else
class="card" class="card"
> >
<div
v-if="errorChannels.length !== 0"
>
<h3> {{ $t("Subscriptions.Error Channels") }}</h3>
<div>
<ft-channel-bubble
v-for="(channel, index) in errorChannels"
:key="index"
:channel-name="channel.name"
:channel-id="channel.id"
:channel-thumbnail="channel.thumbnail"
class="channelBubble"
@click="goToChannel(channel.id)"
/>
</div>
</div>
<h3>{{ $t("Subscriptions.Subscriptions") }}</h3> <h3>{{ $t("Subscriptions.Subscriptions") }}</h3>
<ft-flex-box <ft-flex-box
v-if="activeVideoList.length === 0" v-if="activeVideoList.length === 0"

View File

@ -78,6 +78,8 @@ Search Filters:
Subscriptions: Subscriptions:
# On Subscriptions Page # On Subscriptions Page
Subscriptions: Subscriptions Subscriptions: Subscriptions
# channels that were likely deleted
Error Channels: Channels with Errors
Latest Subscriptions: Latest Subscriptions Latest Subscriptions: Latest Subscriptions
This profile has a large number of subscriptions. Forcing RSS to avoid rate limiting: This This profile has a large number of subscriptions. Forcing RSS to avoid rate limiting: This
profile has a large number of subscriptions. Forcing RSS to avoid rate limiting profile has a large number of subscriptions. Forcing RSS to avoid rate limiting