Use native DomParser instead of opml-to-json dependency (#2769)

* Use native DomParser instead of opml-to-json dependency

* Add support for Invidious feed URLs

* Fallback to HTML parser, as HTML is much less strict than XML

* Log error before falling back to the HTML parser
This commit is contained in:
absidue 2022-11-15 09:10:51 +01:00 committed by GitHub
parent 41d42f1d96
commit 8a37692f77
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 77 additions and 96 deletions

View File

@ -61,7 +61,6 @@
"lodash.debounce": "^4.0.8",
"marked": "^4.1.1",
"nedb-promises": "^6.2.1",
"opml-to-json": "^1.0.1",
"process": "^0.11.10",
"socks-proxy-agent": "^7.0.0",
"video.js": "7.18.1",

View File

@ -6,7 +6,6 @@ import FtFlexBox from '../ft-flex-box/ft-flex-box.vue'
import FtPrompt from '../ft-prompt/ft-prompt.vue'
import { MAIN_PROFILE_ID } from '../../../constants'
import { opmlToJSON } from 'opml-to-json'
import ytch from 'yt-channel-info'
import { calculateColorLuminance, getRandomColor } from '../../helpers/colors'
import {
@ -330,79 +329,97 @@ export default Vue.extend({
},
importOpmlYouTubeSubscriptions: async function (data) {
let json
let xmlDom
const domParser = new DOMParser()
try {
json = await opmlToJSON(data)
xmlDom = domParser.parseFromString(data, 'application/xml')
// https://developer.mozilla.org/en-US/docs/Web/API/DOMParser/parseFromString#error_handling
const errorNode = xmlDom.querySelector('parsererror')
if (errorNode) {
throw errorNode.textContent
}
} catch (err) {
console.error('error reading OPML subscriptions file, falling back to HTML parser...')
console.error(err)
console.error('error reading')
const message = this.$t('Settings.Data Settings.Invalid subscriptions file')
showToast(`${message}: ${err}`)
// try parsing with the html parser instead which is more lenient
try {
const htmlDom = domParser.parseFromString(data, 'text/html')
xmlDom = htmlDom
} catch {
const message = this.$t('Settings.Data Settings.Invalid subscriptions file')
showToast(`${message}: ${err}`)
return
}
}
if (json !== undefined) {
let feedData = json.children[0].children
if (typeof feedData === 'undefined') {
if (json.title.includes('gPodder')) {
feedData = json.children
const feedData = xmlDom.querySelectorAll('body outline[xmlUrl]')
if (feedData.length === 0) {
const message = this.$t('Settings.Data Settings.Invalid subscriptions file')
showToast(message)
return
}
const subscriptions = []
showToast(this.$t('Settings.Data Settings.This might take a while, please wait'))
this.updateShowProgressBar(true)
this.setProgressBarPercentage(0)
let count = 0
feedData.forEach(async (channel) => {
const xmlUrl = channel.getAttribute('xmlUrl')
let channelId
if (xmlUrl.includes('https://www.youtube.com/feeds/videos.xml?channel_id=')) {
channelId = new URL(xmlUrl).searchParams.get('channel_id')
} else if (xmlUrl.includes('/feed/channel/')) {
// handle invidious exports https://yewtu.be/feed/channel/{CHANNELID}
channelId = new URL(xmlUrl).pathname.split('/').filter(part => part).at(-1)
} else {
console.error(`Unknown xmlUrl format: ${xmlUrl}`)
}
const subExists = this.primaryProfile.subscriptions.findIndex((sub) => {
return sub.id === channelId
})
if (subExists === -1) {
let channelInfo
if (this.backendPreference === 'invidious') {
channelInfo = await this.getChannelInfoInvidious(channelId)
} else {
const message = this.$t('Settings.Data Settings.Invalid subscriptions file')
showToast(message)
return
channelInfo = await this.getChannelInfoLocal(channelId)
}
if (typeof channelInfo.author !== 'undefined') {
const subscription = {
id: channelId,
name: channelInfo.author,
thumbnail: channelInfo.authorThumbnails[1].url
}
subscriptions.push(subscription)
}
}
const subscriptions = []
count++
showToast(this.$t('Settings.Data Settings.This might take a while, please wait'))
const progressPercentage = (count / feedData.length) * 100
this.setProgressBarPercentage(progressPercentage)
this.updateShowProgressBar(true)
this.setProgressBarPercentage(0)
if (count === feedData.length) {
this.primaryProfile.subscriptions = this.primaryProfile.subscriptions.concat(subscriptions)
this.updateProfile(this.primaryProfile)
let count = 0
feedData.forEach(async (channel, index) => {
const channelId = channel.xmlurl.replace('https://www.youtube.com/feeds/videos.xml?channel_id=', '')
const subExists = this.primaryProfile.subscriptions.findIndex((sub) => {
return sub.id === channelId
})
if (subExists === -1) {
let channelInfo
if (this.backendPreference === 'invidious') {
channelInfo = await this.getChannelInfoInvidious(channelId)
} else {
channelInfo = await this.getChannelInfoLocal(channelId)
}
if (typeof channelInfo.author !== 'undefined') {
const subscription = {
id: channelId,
name: channelInfo.author,
thumbnail: channelInfo.authorThumbnails[1].url
}
subscriptions.push(subscription)
}
if (subscriptions.length < count) {
showToast(this.$t('Settings.Data Settings.One or more subscriptions were unable to be imported'))
} else {
showToast(this.$t('Settings.Data Settings.All subscriptions have been successfully imported'))
}
count++
const progressPercentage = (count / feedData.length) * 100
this.setProgressBarPercentage(progressPercentage)
if (count === feedData.length) {
this.primaryProfile.subscriptions = this.primaryProfile.subscriptions.concat(subscriptions)
this.updateProfile(this.primaryProfile)
if (subscriptions.length < count) {
showToast(this.$t('Settings.Data Settings.One or more subscriptions were unable to be imported'))
} else {
showToast(this.$t('Settings.Data Settings.All subscriptions have been successfully imported'))
}
this.updateShowProgressBar(false)
}
})
}
this.updateShowProgressBar(false)
}
})
},
importNewPipeSubscriptions: async function (newPipeData) {

View File

@ -5164,11 +5164,6 @@ is-wsl@^2.2.0:
dependencies:
is-docker "^2.0.0"
isarray@0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf"
integrity sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=
isarray@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
@ -6112,21 +6107,6 @@ open@^8.0.9:
is-docker "^2.1.1"
is-wsl "^2.2.0"
opml-to-json@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/opml-to-json/-/opml-to-json-1.0.1.tgz#a2cd65f9cc533f7d0e31bb0b5fdce8f55f8f1333"
integrity sha512-o/czEw150VWKww6Xa9as9ozTqj6L6LnxsVTfmKGFQ3eZiolTQBcDVRA87hzTBtflVZyeP6x0QYTMBM86cTW/kQ==
dependencies:
opmlparser "^0.8.0"
opmlparser@^0.8.0:
version "0.8.0"
resolved "https://registry.yarnpkg.com/opmlparser/-/opmlparser-0.8.0.tgz#a5d74834b136af9a639013f5dc39746b7c27063f"
integrity sha1-pddINLE2r5pjkBP13Dl0a3wnBj8=
dependencies:
readable-stream "~1.1.10"
sax "~0.6.0"
optionator@^0.9.1:
version "0.9.1"
resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.1.tgz#4f236a6373dae0566a6d43e1326674f50c291499"
@ -6855,16 +6835,6 @@ readable-stream@^3.0.6, readable-stream@^3.5.0, readable-stream@^3.6.0:
string_decoder "^1.1.1"
util-deprecate "^1.0.1"
readable-stream@~1.1.10:
version "1.1.14"
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9"
integrity sha1-fPTFTvZI44EwhMY23SB54WbAgdk=
dependencies:
core-util-is "~1.0.0"
inherits "~2.0.1"
isarray "0.0.1"
string_decoder "~0.10.x"
readdirp@~3.6.0:
version "3.6.0"
resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7"
@ -7636,11 +7606,6 @@ string_decoder@^1.1.1:
dependencies:
safe-buffer "~5.2.0"
string_decoder@~0.10.x:
version "0.10.31"
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94"
integrity sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=
string_decoder@~1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8"