Replace rss-parser with fetch and the native DomParser (#2726)
This commit is contained in:
parent
b5c486bf1f
commit
294df19f1b
|
@ -64,7 +64,6 @@
|
|||
"nedb-promises": "^6.2.1",
|
||||
"opml-to-json": "^1.0.1",
|
||||
"process": "^0.11.10",
|
||||
"rss-parser": "^3.12.0",
|
||||
"socks-proxy-agent": "^6.0.0",
|
||||
"video.js": "7.18.1",
|
||||
"videojs-contrib-quality-levels": "^2.1.0",
|
||||
|
|
|
@ -10,7 +10,6 @@ import FtButton from './components/ft-button/ft-button.vue'
|
|||
import FtToast from './components/ft-toast/ft-toast.vue'
|
||||
import FtProgressBar from './components/ft-progress-bar/ft-progress-bar.vue'
|
||||
import { marked } from 'marked'
|
||||
import Parser from 'rss-parser'
|
||||
import { IpcChannels } from '../constants'
|
||||
import packageDetails from '../../package.json'
|
||||
import { showToast } from './helpers/utils'
|
||||
|
@ -229,26 +228,30 @@ export default Vue.extend({
|
|||
|
||||
checkForNewBlogPosts: function () {
|
||||
if (this.checkForBlogPosts) {
|
||||
const parser = new Parser()
|
||||
const feedUrl = 'https://write.as/freetube/feed/'
|
||||
let lastAppWasRunning = localStorage.getItem('lastAppWasRunning')
|
||||
|
||||
if (lastAppWasRunning !== null) {
|
||||
lastAppWasRunning = new Date(lastAppWasRunning)
|
||||
}
|
||||
|
||||
parser.parseURL(feedUrl).then((response) => {
|
||||
const latestBlog = response.items[0]
|
||||
const latestPubDate = new Date(latestBlog.pubDate)
|
||||
fetch('https://write.as/freetube/feed/')
|
||||
.then(response => response.text())
|
||||
.then(response => {
|
||||
const xmlDom = new DOMParser().parseFromString(response, 'application/xml')
|
||||
|
||||
if (lastAppWasRunning === null || latestPubDate > lastAppWasRunning) {
|
||||
this.blogBannerMessage = this.$t('A new blog is now available, {blogTitle}. Click to view more', { blogTitle: latestBlog.title })
|
||||
this.latestBlogUrl = latestBlog.link
|
||||
this.showBlogBanner = true
|
||||
}
|
||||
const latestBlog = xmlDom.querySelector('item')
|
||||
const latestPubDate = new Date(latestBlog.querySelector('pubDate').textContent)
|
||||
|
||||
localStorage.setItem('lastAppWasRunning', new Date())
|
||||
})
|
||||
if (lastAppWasRunning === null || latestPubDate > lastAppWasRunning) {
|
||||
const title = latestBlog.querySelector('title').textContent
|
||||
|
||||
this.blogBannerMessage = this.$t('A new blog is now available, {blogTitle}. Click to view more', { blogTitle: title })
|
||||
this.latestBlogUrl = latestBlog.querySelector('link').textContent
|
||||
this.showBlogBanner = true
|
||||
}
|
||||
|
||||
localStorage.setItem('lastAppWasRunning', new Date())
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
@ -9,7 +9,6 @@ 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 Parser from 'rss-parser'
|
||||
import { MAIN_PROFILE_ID } from '../../../constants'
|
||||
import { calculatePublishedDate, showToast } from '../../helpers/utils'
|
||||
|
||||
|
@ -299,62 +298,40 @@ export default Vue.extend({
|
|||
})
|
||||
},
|
||||
|
||||
getChannelVideosLocalRSS: function (channel, failedAttempts = 0) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const parser = new Parser()
|
||||
const feedUrl = `https://www.youtube.com/feeds/videos.xml?channel_id=${channel.id}`
|
||||
getChannelVideosLocalRSS: async function (channel, failedAttempts = 0) {
|
||||
const feedUrl = `https://www.youtube.com/feeds/videos.xml?channel_id=${channel.id}`
|
||||
|
||||
parser.parseURL(feedUrl).then(async (feed) => {
|
||||
const items = await Promise.all(feed.items.map((video) => {
|
||||
video.authorId = channel.id
|
||||
video.videoId = video.id.replace('yt:video:', '')
|
||||
video.type = 'video'
|
||||
video.lengthSeconds = '0:00'
|
||||
video.isRSS = true
|
||||
try {
|
||||
const response = await fetch(feedUrl)
|
||||
|
||||
video.publishedDate = new Date(video.pubDate)
|
||||
if (response.status === 404) {
|
||||
this.errorChannels.push(channel)
|
||||
return []
|
||||
}
|
||||
|
||||
if (video.publishedDate.toString() === 'Invalid Date') {
|
||||
video.publishedDate = new Date(video.isoDate)
|
||||
}
|
||||
|
||||
video.publishedText = video.publishedDate.toLocaleString()
|
||||
|
||||
return video
|
||||
}))
|
||||
|
||||
resolve(items)
|
||||
}).catch((err) => {
|
||||
console.error(err)
|
||||
if (err.toString().match(/404/)) {
|
||||
this.errorChannels.push(channel)
|
||||
resolve([])
|
||||
} else {
|
||||
const errorMessage = this.$t('Local API Error (Click to copy)')
|
||||
showToast(`${errorMessage}: ${err}`, 10000, () => {
|
||||
this.copyToClipboard({ content: err })
|
||||
})
|
||||
switch (failedAttempts) {
|
||||
case 0:
|
||||
resolve(this.getChannelVideosLocalScraper(channel, failedAttempts + 1))
|
||||
break
|
||||
case 1:
|
||||
if (this.backendFallback) {
|
||||
showToast(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([])
|
||||
}
|
||||
}
|
||||
return await this.parseYouTubeRSSFeed(await response.text(), channel.id)
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
const errorMessage = this.$t('Local API Error (Click to copy)')
|
||||
showToast(`${errorMessage}: ${error}`, 10000, () => {
|
||||
this.copyToClipboard({ content: error })
|
||||
})
|
||||
})
|
||||
switch (failedAttempts) {
|
||||
case 0:
|
||||
return this.getChannelVideosLocalScraper(channel, failedAttempts + 1)
|
||||
case 1:
|
||||
if (this.backendFallback) {
|
||||
this.showToast(this.$t('Falling back to Invidious API'))
|
||||
return this.getChannelVideosInvidiousRSS(channel, failedAttempts + 1)
|
||||
} else {
|
||||
return []
|
||||
}
|
||||
case 2:
|
||||
return this.getChannelVideosLocalScraper(channel, failedAttempts + 1)
|
||||
default:
|
||||
return []
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
getChannelVideosInvidiousScraper: function (channel, failedAttempts = 0) {
|
||||
|
@ -398,54 +375,72 @@ export default Vue.extend({
|
|||
})
|
||||
},
|
||||
|
||||
getChannelVideosInvidiousRSS: function (channel, failedAttempts = 0) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const parser = new Parser()
|
||||
const feedUrl = `${this.currentInvidiousInstance}/feed/channel/${channel.id}`
|
||||
getChannelVideosInvidiousRSS: async function (channel, failedAttempts = 0) {
|
||||
const feedUrl = `${this.currentInvidiousInstance}/feed/channel/${channel.id}`
|
||||
|
||||
parser.parseURL(feedUrl).then(async (feed) => {
|
||||
resolve(await Promise.all(feed.items.map((video) => {
|
||||
video.authorId = channel.id
|
||||
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
|
||||
try {
|
||||
const response = await fetch(feedUrl)
|
||||
|
||||
return video
|
||||
})))
|
||||
}).catch((err) => {
|
||||
console.error(err)
|
||||
const errorMessage = this.$t('Invidious API Error (Click to copy)')
|
||||
showToast(`${errorMessage}: ${err}`, 10000, () => {
|
||||
this.copyToClipboard({ content: err })
|
||||
})
|
||||
if (err.toString().match(/500/)) {
|
||||
this.errorChannels.push(channel)
|
||||
resolve([])
|
||||
} else {
|
||||
switch (failedAttempts) {
|
||||
case 0:
|
||||
resolve(this.getChannelVideosInvidiousScraper(channel, failedAttempts + 1))
|
||||
break
|
||||
case 1:
|
||||
if (this.backendFallback) {
|
||||
showToast(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([])
|
||||
}
|
||||
}
|
||||
if (response.status === 500) {
|
||||
this.errorChannels.push(channel)
|
||||
return []
|
||||
}
|
||||
|
||||
return await this.parseYouTubeRSSFeed(await response.text(), channel.id)
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
const errorMessage = this.$t('Invidious API Error (Click to copy)')
|
||||
showToast(`${errorMessage}: ${error}`, 10000, () => {
|
||||
this.copyToClipboard({ content: error })
|
||||
})
|
||||
})
|
||||
switch (failedAttempts) {
|
||||
case 0:
|
||||
return this.getChannelVideosInvidiousScraper(channel, failedAttempts + 1)
|
||||
case 1:
|
||||
if (this.backendFallback) {
|
||||
this.showToast(this.$t('Falling back to the local API'))
|
||||
return this.getChannelVideosLocalRSS(channel, failedAttempts + 1)
|
||||
} else {
|
||||
return []
|
||||
}
|
||||
case 2:
|
||||
return this.getChannelVideosInvidiousScraper(channel, failedAttempts + 1)
|
||||
default:
|
||||
return []
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
async parseYouTubeRSSFeed(rssString, channelId) {
|
||||
const xmlDom = new DOMParser().parseFromString(rssString, 'application/xml')
|
||||
|
||||
const channelName = xmlDom.querySelector('author > name').textContent
|
||||
const entries = xmlDom.querySelectorAll('entry')
|
||||
|
||||
const promises = []
|
||||
|
||||
for (const entry of entries) {
|
||||
promises.push(this.parseRSSEntry(entry, channelId, channelName))
|
||||
}
|
||||
|
||||
return await Promise.all(promises)
|
||||
},
|
||||
|
||||
async parseRSSEntry(entry, channelId, channelName) {
|
||||
const published = new Date(entry.querySelector('published').textContent)
|
||||
return {
|
||||
authorId: channelId,
|
||||
author: channelName,
|
||||
// querySelector doesn't support xml namespaces so we have to use getElementsByTagName here
|
||||
videoId: entry.getElementsByTagName('yt:videoId')[0].textContent,
|
||||
title: entry.querySelector('title').textContent,
|
||||
publishedDate: published,
|
||||
publishedText: published.toLocaleString(),
|
||||
viewCount: entry.getElementsByTagName('media:statistics')[0]?.getAttribute('views') || null,
|
||||
type: 'video',
|
||||
lengthSeconds: '0:00',
|
||||
isRSS: true
|
||||
}
|
||||
},
|
||||
|
||||
increaseLimit: function () {
|
||||
|
|
25
yarn.lock
25
yarn.lock
|
@ -3553,7 +3553,7 @@ enquirer@^2.3.5:
|
|||
dependencies:
|
||||
ansi-colors "^4.1.1"
|
||||
|
||||
entities@^2.0.0, entities@^2.0.3:
|
||||
entities@^2.0.0:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/entities/-/entities-2.2.0.tgz#098dc90ebb83d8dffa089d55256b351d34c4da55"
|
||||
integrity sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==
|
||||
|
@ -7027,14 +7027,6 @@ roarr@^2.15.3:
|
|||
semver-compare "^1.0.0"
|
||||
sprintf-js "^1.1.2"
|
||||
|
||||
rss-parser@^3.12.0:
|
||||
version "3.12.0"
|
||||
resolved "https://registry.yarnpkg.com/rss-parser/-/rss-parser-3.12.0.tgz#b8888699ea46304a74363fbd8144671b2997984c"
|
||||
integrity sha512-aqD3E8iavcCdkhVxNDIdg1nkBI17jgqF+9OqPS1orwNaOgySdpvq6B+DoONLhzjzwV8mWg37sb60e4bmLK117A==
|
||||
dependencies:
|
||||
entities "^2.0.3"
|
||||
xml2js "^0.4.19"
|
||||
|
||||
run-parallel@^1.1.9:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee"
|
||||
|
@ -7104,7 +7096,7 @@ sass@^1.54.9:
|
|||
immutable "^4.0.0"
|
||||
source-map-js ">=0.6.2 <2.0.0"
|
||||
|
||||
sax@>=0.6.0, sax@^1.1.3, sax@^1.2.4:
|
||||
sax@^1.1.3, sax@^1.2.4:
|
||||
version "1.2.4"
|
||||
resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9"
|
||||
integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==
|
||||
|
@ -8482,14 +8474,6 @@ xml-name-validator@^4.0.0:
|
|||
resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-4.0.0.tgz#79a006e2e63149a8600f15430f0a4725d1524835"
|
||||
integrity sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==
|
||||
|
||||
xml2js@^0.4.19:
|
||||
version "0.4.23"
|
||||
resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.23.tgz#a0c69516752421eb2ac758ee4d4ccf58843eac66"
|
||||
integrity sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==
|
||||
dependencies:
|
||||
sax ">=0.6.0"
|
||||
xmlbuilder "~11.0.0"
|
||||
|
||||
xmlbuilder@>=11.0.1:
|
||||
version "15.1.1"
|
||||
resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-15.1.1.tgz#9dcdce49eea66d8d10b42cae94a79c3c8d0c2ec5"
|
||||
|
@ -8500,11 +8484,6 @@ xmlbuilder@^9.0.7:
|
|||
resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-9.0.7.tgz#132ee63d2ec5565c557e20f4c22df9aca686b10d"
|
||||
integrity sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0=
|
||||
|
||||
xmlbuilder@~11.0.0:
|
||||
version "11.0.1"
|
||||
resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-11.0.1.tgz#be9bae1c8a046e76b31127726347d0ad7002beb3"
|
||||
integrity sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==
|
||||
|
||||
xtend@^4.0.0, xtend@^4.0.1, xtend@^4.0.2, xtend@~4.0.1:
|
||||
version "4.0.2"
|
||||
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"
|
||||
|
|
Loading…
Reference in New Issue