mirror of https://github.com/FreeTubeApp/FreeTube
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:
parent
41d42f1d96
commit
8a37692f77
|
@ -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",
|
||||
|
|
|
@ -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) {
|
||||
|
|
35
yarn.lock
35
yarn.lock
|
@ -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"
|
||||
|
|
Loading…
Reference in New Issue