2020-02-16 19:30:00 +01:00
|
|
|
import Vue from 'vue'
|
2020-08-31 23:35:22 +02:00
|
|
|
import { mapActions, mapMutations } from 'vuex'
|
|
|
|
import FtLoader from '../../components/ft-loader/ft-loader.vue'
|
2020-02-16 19:30:00 +01:00
|
|
|
import FtCard from '../../components/ft-card/ft-card.vue'
|
2020-09-02 05:20:21 +02:00
|
|
|
import FtButton from '../../components/ft-button/ft-button.vue'
|
|
|
|
import FtIconButton from '../../components/ft-icon-button/ft-icon-button.vue'
|
2020-05-28 04:48:41 +02:00
|
|
|
import FtFlexBox from '../../components/ft-flex-box/ft-flex-box.vue'
|
2020-02-16 19:30:00 +01:00
|
|
|
import FtElementList from '../../components/ft-element-list/ft-element-list.vue'
|
|
|
|
|
2020-08-31 23:35:22 +02:00
|
|
|
import ytch from 'yt-channel-info'
|
2020-09-02 05:20:21 +02:00
|
|
|
import Parser from 'rss-parser'
|
2020-08-31 23:35:22 +02:00
|
|
|
|
2020-02-16 19:30:00 +01:00
|
|
|
export default Vue.extend({
|
|
|
|
name: 'Subscriptions',
|
|
|
|
components: {
|
2020-08-31 23:35:22 +02:00
|
|
|
'ft-loader': FtLoader,
|
2020-02-16 19:30:00 +01:00
|
|
|
'ft-card': FtCard,
|
2020-09-02 05:20:21 +02:00
|
|
|
'ft-button': FtButton,
|
|
|
|
'ft-icon-button': FtIconButton,
|
2020-05-28 04:48:41 +02:00
|
|
|
'ft-flex-box': FtFlexBox,
|
2020-02-16 19:30:00 +01:00
|
|
|
'ft-element-list': FtElementList
|
|
|
|
},
|
2020-08-31 23:35:22 +02:00
|
|
|
data: function () {
|
|
|
|
return {
|
|
|
|
isLoading: false,
|
|
|
|
dataLimit: 100,
|
|
|
|
videoList: []
|
|
|
|
}
|
|
|
|
},
|
|
|
|
computed: {
|
2020-09-02 05:20:21 +02:00
|
|
|
usingElectron: function () {
|
|
|
|
return this.$store.getters.getUsingElectron
|
|
|
|
},
|
|
|
|
|
2020-08-31 23:35:22 +02:00
|
|
|
backendPreference: function () {
|
|
|
|
return this.$store.getters.getBackendPreference
|
|
|
|
},
|
|
|
|
|
|
|
|
backendFallback: function () {
|
|
|
|
return this.$store.getters.getBackendFallback
|
|
|
|
},
|
|
|
|
|
2020-09-02 05:20:21 +02:00
|
|
|
invidiousInstance: function () {
|
|
|
|
return this.$store.getters.getInvidiousInstance
|
|
|
|
},
|
|
|
|
|
|
|
|
hideWatchedSubs: function () {
|
|
|
|
return this.$store.getters.getHideWatchedSubs
|
|
|
|
},
|
|
|
|
|
|
|
|
useRssFeeds: function () {
|
|
|
|
return this.$store.getters.getUseRssFeeds
|
|
|
|
},
|
|
|
|
|
2020-08-31 23:35:22 +02:00
|
|
|
profileList: function () {
|
|
|
|
return this.$store.getters.getProfileList
|
|
|
|
},
|
|
|
|
|
2020-09-02 05:20:21 +02:00
|
|
|
activeVideoList: function () {
|
|
|
|
if (this.videoList.length < this.dataLimit) {
|
|
|
|
return this.videoList
|
|
|
|
} else {
|
|
|
|
return this.videoList.slice(0, this.dataLimit)
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2020-08-31 23:35:22 +02:00
|
|
|
activeProfile: function () {
|
|
|
|
return this.$store.getters.getActiveProfile
|
|
|
|
},
|
|
|
|
|
|
|
|
profileSubscriptions: function () {
|
|
|
|
return this.$store.getters.getProfileSubscriptions
|
|
|
|
},
|
|
|
|
|
|
|
|
allSubscriptionsList: function () {
|
2020-09-02 05:20:21 +02:00
|
|
|
return this.$store.getters.getAllSubscriptionsList
|
2020-08-31 23:35:22 +02:00
|
|
|
},
|
|
|
|
|
2020-09-02 05:20:21 +02:00
|
|
|
historyCache: function () {
|
|
|
|
return this.$store.getters.getHistoryCache
|
|
|
|
},
|
2020-08-31 23:35:22 +02:00
|
|
|
|
2020-09-02 05:20:21 +02:00
|
|
|
activeSubscriptionList: function () {
|
|
|
|
return this.profileList[this.activeProfile].subscriptions
|
|
|
|
}
|
|
|
|
},
|
|
|
|
watch: {
|
|
|
|
activeProfile: async function (val) {
|
|
|
|
if (this.allSubscriptionsList.length !== 0) {
|
|
|
|
this.isLoading = true
|
|
|
|
this.videoList = await Promise.all(this.allSubscriptionsList.filter((video) => {
|
|
|
|
const channelIndex = this.activeSubscriptionList.findIndex((x) => {
|
|
|
|
return x.id === video.authorId
|
|
|
|
})
|
2020-08-31 23:35:22 +02:00
|
|
|
|
2020-09-02 05:20:21 +02:00
|
|
|
const historyIndex = this.historyCache.findIndex((x) => {
|
|
|
|
return x.videoId === video.videoId
|
|
|
|
})
|
2020-08-31 23:35:22 +02:00
|
|
|
|
2020-09-02 05:20:21 +02:00
|
|
|
if (this.hideWatchedSubs) {
|
|
|
|
return channelIndex !== -1 && historyIndex === -1
|
|
|
|
} else {
|
|
|
|
return channelIndex !== -1
|
|
|
|
}
|
|
|
|
}))
|
|
|
|
this.isLoading = false
|
|
|
|
} else {
|
|
|
|
this.getSubscriptions()
|
|
|
|
}
|
2020-08-31 23:35:22 +02:00
|
|
|
}
|
|
|
|
},
|
2020-09-02 05:20:21 +02:00
|
|
|
mounted: async function () {
|
|
|
|
this.isLoading = true
|
|
|
|
const dataLimit = sessionStorage.getItem('subscriptionLimit')
|
|
|
|
if (dataLimit !== null) {
|
|
|
|
this.dataLimit = dataLimit
|
|
|
|
}
|
2020-09-03 03:04:08 +02:00
|
|
|
|
|
|
|
if (this.profileSubscriptions.videoList.length !== 0) {
|
|
|
|
const subscriptionList = JSON.parse(JSON.stringify(this.profileSubscriptions))
|
|
|
|
if (this.hideWatchedSubs) {
|
|
|
|
this.videoList = await Promise.all(subscriptionList.videoList.filter((video) => {
|
|
|
|
const historyIndex = this.historyCache.findIndex((x) => {
|
|
|
|
return x.videoId === video.videoId
|
|
|
|
})
|
|
|
|
|
|
|
|
return historyIndex === -1
|
|
|
|
}))
|
2020-09-02 05:20:21 +02:00
|
|
|
} else {
|
2020-09-03 03:04:08 +02:00
|
|
|
this.videoList = subscriptionList.videoList
|
2020-09-02 05:20:21 +02:00
|
|
|
}
|
2020-09-03 03:04:08 +02:00
|
|
|
|
|
|
|
this.isLoading = false
|
|
|
|
} else {
|
|
|
|
setTimeout(async () => {
|
|
|
|
this.getSubscriptions()
|
|
|
|
}, 200)
|
|
|
|
}
|
2020-08-31 23:35:22 +02:00
|
|
|
},
|
|
|
|
methods: {
|
2020-09-02 05:20:21 +02:00
|
|
|
getSubscriptions: function () {
|
2020-08-31 23:35:22 +02:00
|
|
|
if (this.activeSubscriptionList.length === 0) {
|
2020-09-02 05:20:21 +02:00
|
|
|
this.isLoading = false
|
|
|
|
this.videoList = []
|
2020-08-31 23:35:22 +02:00
|
|
|
return
|
|
|
|
}
|
|
|
|
this.isLoading = true
|
|
|
|
this.updateShowProgressBar(true)
|
2020-09-02 05:20:21 +02:00
|
|
|
this.setProgressBarPercentage(0)
|
2020-08-31 23:35:22 +02:00
|
|
|
|
|
|
|
let videoList = []
|
|
|
|
let channelCount = 0
|
|
|
|
|
|
|
|
this.activeSubscriptionList.forEach(async (channel) => {
|
2020-09-02 05:20:21 +02:00
|
|
|
let videos = []
|
|
|
|
|
|
|
|
if (!this.usingElectron || this.backendPreference === 'invidious') {
|
|
|
|
if (this.useRssFeeds) {
|
|
|
|
videos = await this.getChannelVideosInvidiousRSS(channel.id)
|
|
|
|
} else {
|
|
|
|
videos = await this.getChannelVideosInvidiousScraper(channel.id)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (this.useRssFeeds) {
|
|
|
|
videos = await this.getChannelVideosLocalRSS(channel.id)
|
|
|
|
} else {
|
|
|
|
videos = await this.getChannelVideosLocalScraper(channel.id)
|
|
|
|
}
|
|
|
|
}
|
2020-08-31 23:35:22 +02:00
|
|
|
|
|
|
|
videoList = videoList.concat(videos)
|
|
|
|
channelCount++
|
|
|
|
const percentageComplete = (channelCount / this.activeSubscriptionList.length) * 100
|
|
|
|
this.setProgressBarPercentage(percentageComplete)
|
|
|
|
|
|
|
|
if (channelCount === this.activeSubscriptionList.length) {
|
|
|
|
videoList = await Promise.all(videoList.sort((a, b) => {
|
|
|
|
return b.publishedDate - a.publishedDate
|
|
|
|
}))
|
|
|
|
|
|
|
|
const profileSubscriptions = {
|
|
|
|
activeProfile: this.activeProfile,
|
|
|
|
videoList: videoList
|
|
|
|
}
|
|
|
|
|
2020-09-02 05:20:21 +02:00
|
|
|
this.videoList = await Promise.all(videoList.filter((video) => {
|
|
|
|
if (this.hideWatchedSubs) {
|
|
|
|
const historyIndex = this.historyCache.findIndex((x) => {
|
|
|
|
return x.videoId === video.videoId
|
|
|
|
})
|
|
|
|
|
|
|
|
return historyIndex === -1
|
|
|
|
} else {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}))
|
2020-08-31 23:35:22 +02:00
|
|
|
this.updateProfileSubscriptions(profileSubscriptions)
|
|
|
|
this.isLoading = false
|
|
|
|
this.updateShowProgressBar(false)
|
2020-09-02 05:20:21 +02:00
|
|
|
|
|
|
|
if (this.activeProfile === 0) {
|
|
|
|
this.updateAllSubscriptionsList(profileSubscriptions.videoList)
|
|
|
|
}
|
2020-08-31 23:35:22 +02:00
|
|
|
}
|
|
|
|
})
|
|
|
|
},
|
|
|
|
|
2020-09-04 03:56:01 +02:00
|
|
|
getChannelVideosLocalScraper: function (channelId, failedAttempts = 0) {
|
2020-08-31 23:35:22 +02:00
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
ytch.getChannelVideos(channelId, 'latest').then(async (response) => {
|
|
|
|
const videos = await Promise.all(response.items.map(async (video) => {
|
2020-09-03 03:00:24 +02:00
|
|
|
if (video.liveNow) {
|
|
|
|
video.publishedDate = new Date().getTime()
|
|
|
|
} else {
|
|
|
|
video.publishedDate = await this.calculatePublishedDate(video.publishedText)
|
|
|
|
}
|
2020-08-31 23:35:22 +02:00
|
|
|
return video
|
|
|
|
}))
|
|
|
|
|
|
|
|
resolve(videos)
|
|
|
|
}).catch((err) => {
|
|
|
|
console.log(err)
|
|
|
|
const errorMessage = this.$t('Local API Error (Click to copy)')
|
|
|
|
this.showToast({
|
|
|
|
message: `${errorMessage}: ${err}`,
|
|
|
|
time: 10000,
|
|
|
|
action: () => {
|
|
|
|
navigator.clipboard.writeText(err)
|
|
|
|
}
|
|
|
|
})
|
2020-09-04 03:56:01 +02:00
|
|
|
switch (failedAttempts) {
|
|
|
|
case 0:
|
|
|
|
resolve(this.getChannelVideosLocalRSS(channelId, failedAttempts + 1))
|
|
|
|
break
|
|
|
|
case 1:
|
|
|
|
if (this.backendFallback) {
|
|
|
|
this.showToast({
|
|
|
|
message: this.$t('Falling back to the Invidious API')
|
|
|
|
})
|
|
|
|
resolve(this.getChannelVideosInvidiousScraper(channelId, failedAttempts + 1))
|
|
|
|
} else {
|
|
|
|
resolve([])
|
|
|
|
}
|
|
|
|
break
|
|
|
|
case 2:
|
|
|
|
resolve(this.getChannelVideosLocalRSS(channelId, failedAttempts + 1))
|
|
|
|
break
|
|
|
|
default:
|
|
|
|
resolve([])
|
|
|
|
}
|
2020-08-31 23:35:22 +02:00
|
|
|
})
|
|
|
|
})
|
|
|
|
},
|
|
|
|
|
2020-09-04 03:56:01 +02:00
|
|
|
getChannelVideosLocalRSS: function (channelId, failedAttempts = 0) {
|
2020-09-02 05:20:21 +02:00
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
const parser = new Parser()
|
|
|
|
const feedUrl = `https://www.youtube.com/feeds/videos.xml?channel_id=${channelId}`
|
|
|
|
|
|
|
|
parser.parseURL(feedUrl).then(async (feed) => {
|
2020-09-10 00:58:35 +02:00
|
|
|
const items = await Promise.all(feed.items.map((video) => {
|
2020-09-02 05:20:21 +02:00
|
|
|
video.authorId = channelId
|
|
|
|
video.videoId = video.id.replace('yt:video:', '')
|
|
|
|
video.type = 'video'
|
|
|
|
video.lengthSeconds = '0:00'
|
|
|
|
video.isRSS = true
|
2020-08-31 23:35:22 +02:00
|
|
|
|
2020-09-10 00:58:35 +02:00
|
|
|
video.publishedDate = new Date(video.pubDate)
|
|
|
|
|
|
|
|
if (video.publishedDate.toString() === 'Invalid Date') {
|
|
|
|
video.publishedDate = new Date(video.isoDate)
|
|
|
|
}
|
|
|
|
|
|
|
|
video.publishedText = video.publishedDate.toLocaleString()
|
|
|
|
|
2020-09-02 05:20:21 +02:00
|
|
|
return video
|
2020-09-10 00:58:35 +02:00
|
|
|
}))
|
|
|
|
|
|
|
|
resolve(items)
|
2020-09-02 05:20:21 +02:00
|
|
|
}).catch((err) => {
|
|
|
|
console.log(err)
|
2020-09-04 03:56:01 +02:00
|
|
|
const errorMessage = this.$t('Local API Error (Click to copy)')
|
|
|
|
this.showToast({
|
|
|
|
message: `${errorMessage}: ${err}`,
|
|
|
|
time: 10000,
|
|
|
|
action: () => {
|
|
|
|
navigator.clipboard.writeText(err)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
switch (failedAttempts) {
|
|
|
|
case 0:
|
|
|
|
resolve(this.getChannelVideosLocalScraper(channelId, failedAttempts + 1))
|
|
|
|
break
|
|
|
|
case 1:
|
|
|
|
if (this.backendFallback) {
|
|
|
|
this.showToast({
|
|
|
|
message: this.$t('Falling back to the Invidious API')
|
|
|
|
})
|
|
|
|
resolve(this.getChannelVideosInvidiousRSS(channelId, failedAttempts + 1))
|
|
|
|
} else {
|
|
|
|
resolve([])
|
|
|
|
}
|
|
|
|
break
|
|
|
|
case 2:
|
|
|
|
resolve(this.getChannelVideosLocalScraper(channelId, failedAttempts + 1))
|
|
|
|
break
|
|
|
|
default:
|
|
|
|
resolve([])
|
|
|
|
}
|
2020-09-02 05:20:21 +02:00
|
|
|
})
|
|
|
|
})
|
2020-08-31 23:35:22 +02:00
|
|
|
},
|
|
|
|
|
2020-09-04 03:56:01 +02:00
|
|
|
getChannelVideosInvidiousScraper: function (channelId, failedAttempts = 0) {
|
2020-09-02 05:20:21 +02:00
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
const subscriptionsPayload = {
|
|
|
|
resource: 'channels/latest',
|
|
|
|
id: channelId,
|
|
|
|
params: {}
|
|
|
|
}
|
|
|
|
|
2020-09-04 03:56:01 +02:00
|
|
|
this.invidiousAPICall(subscriptionsPayload).then(async (result) => {
|
|
|
|
resolve(await Promise.all(result.map((video) => {
|
|
|
|
video.publishedDate = new Date(video.published * 1000)
|
|
|
|
return video
|
|
|
|
})))
|
|
|
|
}).catch((err) => {
|
|
|
|
console.log(err)
|
|
|
|
const errorMessage = this.$t('Invidious API Error (Click to copy)')
|
|
|
|
this.showToast({
|
|
|
|
message: `${errorMessage}: ${err.responseText}`,
|
|
|
|
time: 10000,
|
|
|
|
action: () => {
|
|
|
|
navigator.clipboard.writeText(err)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
switch (failedAttempts) {
|
|
|
|
case 0:
|
|
|
|
resolve(this.getChannelVideosInvidiousRSS(channelId, failedAttempts + 1))
|
|
|
|
break
|
|
|
|
case 1:
|
|
|
|
if (this.backendFallback) {
|
|
|
|
this.showToast({
|
|
|
|
message: this.$t('Falling back to the local API')
|
|
|
|
})
|
|
|
|
resolve(this.getChannelVideosLocalScraper(channelId, failedAttempts + 1))
|
|
|
|
} else {
|
|
|
|
resolve([])
|
|
|
|
}
|
|
|
|
break
|
|
|
|
case 2:
|
|
|
|
resolve(this.getChannelVideosInvidiousRSS(channelId, failedAttempts + 1))
|
|
|
|
break
|
|
|
|
default:
|
|
|
|
resolve([])
|
|
|
|
}
|
2020-09-02 05:20:21 +02:00
|
|
|
})
|
|
|
|
})
|
2020-08-31 23:35:22 +02:00
|
|
|
},
|
|
|
|
|
2020-09-04 03:56:01 +02:00
|
|
|
getChannelVideosInvidiousRSS: function (channelId, failedAttempts = 0) {
|
2020-09-02 05:20:21 +02:00
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
const parser = new Parser()
|
|
|
|
const feedUrl = `${this.invidiousInstance}/feed/channel/${channelId}`
|
|
|
|
|
|
|
|
parser.parseURL(feedUrl).then(async (feed) => {
|
|
|
|
resolve(await Promise.all(feed.items.map((video) => {
|
|
|
|
video.authorId = channelId
|
|
|
|
video.videoId = video.id.replace('yt:video:', '')
|
|
|
|
video.type = 'video'
|
|
|
|
video.publishedDate = new Date(video.pubDate)
|
|
|
|
video.publishedText = video.publishedDate.toLocaleString()
|
|
|
|
video.lengthSeconds = '0:00'
|
|
|
|
video.isRSS = true
|
|
|
|
|
|
|
|
return video
|
|
|
|
})))
|
2020-09-04 03:56:01 +02:00
|
|
|
}).catch((err) => {
|
|
|
|
console.log(err)
|
|
|
|
const errorMessage = this.$t('Invidious API Error (Click to copy)')
|
|
|
|
this.showToast({
|
|
|
|
message: `${errorMessage}: ${err.responseText}`,
|
|
|
|
time: 10000,
|
|
|
|
action: () => {
|
|
|
|
navigator.clipboard.writeText(err)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
switch (failedAttempts) {
|
|
|
|
case 0:
|
|
|
|
resolve(this.getChannelVideosInvidiousScraper(channelId, failedAttempts + 1))
|
|
|
|
break
|
|
|
|
case 1:
|
|
|
|
if (this.backendFallback) {
|
|
|
|
this.showToast({
|
|
|
|
message: this.$t('Falling back to the local API')
|
|
|
|
})
|
|
|
|
resolve(this.getChannelVideosLocalRSS(channelId, failedAttempts + 1))
|
|
|
|
} else {
|
|
|
|
resolve([])
|
|
|
|
}
|
|
|
|
break
|
|
|
|
case 2:
|
|
|
|
resolve(this.getChannelVideosInvidiousScraper(channelId, failedAttempts + 1))
|
|
|
|
break
|
|
|
|
default:
|
|
|
|
resolve([])
|
|
|
|
}
|
2020-09-02 05:20:21 +02:00
|
|
|
})
|
|
|
|
})
|
|
|
|
},
|
|
|
|
|
|
|
|
increaseLimit: function () {
|
|
|
|
this.dataLimit += 100
|
|
|
|
sessionStorage.setItem('subscriptionLimit', this.dataLimit)
|
2020-08-31 23:35:22 +02:00
|
|
|
},
|
|
|
|
|
|
|
|
...mapActions([
|
|
|
|
'showToast',
|
2020-09-02 05:20:21 +02:00
|
|
|
'invidiousAPICall',
|
2020-08-31 23:35:22 +02:00
|
|
|
'updateShowProgressBar',
|
|
|
|
'updateProfileSubscriptions',
|
2020-09-02 05:20:21 +02:00
|
|
|
'updateAllSubscriptionsList',
|
2020-08-31 23:35:22 +02:00
|
|
|
'calculatePublishedDate'
|
|
|
|
]),
|
|
|
|
|
|
|
|
...mapMutations([
|
|
|
|
'setProgressBarPercentage'
|
|
|
|
])
|
2020-02-16 19:30:00 +01:00
|
|
|
}
|
|
|
|
})
|