2022-03-06 01:54:24 +01:00
|
|
|
const Logger = require('../Logger')
|
|
|
|
const { xmlToJSON } = require('./index')
|
2022-04-13 23:55:48 +02:00
|
|
|
const { stripHtml } = require('string-strip-html')
|
2022-03-06 01:54:24 +01:00
|
|
|
|
|
|
|
function extractFirstArrayItem(json, key) {
|
|
|
|
if (!json[key] || !json[key].length) return null
|
|
|
|
return json[key][0]
|
|
|
|
}
|
|
|
|
|
|
|
|
function extractImage(channel) {
|
|
|
|
if (!channel.image || !channel.image.url || !channel.image.url.length) {
|
|
|
|
if (!channel['itunes:image'] || !channel['itunes:image'].length || !channel['itunes:image'][0]['$']) {
|
|
|
|
return null
|
|
|
|
}
|
|
|
|
var itunesImage = channel['itunes:image'][0]['$']
|
|
|
|
return itunesImage.href || null
|
|
|
|
}
|
|
|
|
return channel.image.url[0] || null
|
|
|
|
}
|
|
|
|
|
|
|
|
function extractCategories(channel) {
|
|
|
|
if (!channel['itunes:category'] || !channel['itunes:category'].length) return []
|
|
|
|
var categories = channel['itunes:category']
|
|
|
|
var cleanedCats = []
|
|
|
|
categories.forEach((cat) => {
|
|
|
|
if (!cat['$'] || !cat['$'].text) return
|
|
|
|
var cattext = cat['$'].text
|
|
|
|
if (cat['itunes:category']) {
|
|
|
|
var subcats = extractCategories(cat)
|
|
|
|
if (subcats.length) {
|
|
|
|
cleanedCats = cleanedCats.concat(subcats.map((subcat) => `${cattext}:${subcat}`))
|
|
|
|
} else {
|
|
|
|
cleanedCats.push(cattext)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
cleanedCats.push(cattext)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
return cleanedCats
|
|
|
|
}
|
|
|
|
|
|
|
|
function extractPodcastMetadata(channel) {
|
|
|
|
var metadata = {
|
|
|
|
image: extractImage(channel),
|
2022-04-13 23:55:48 +02:00
|
|
|
categories: extractCategories(channel),
|
|
|
|
feedUrl: null,
|
|
|
|
description: null,
|
|
|
|
descriptionPlain: null
|
2022-03-06 01:54:24 +01:00
|
|
|
}
|
2022-04-13 23:55:48 +02:00
|
|
|
|
|
|
|
if (channel['itunes:new-feed-url']) {
|
|
|
|
metadata.feedUrl = extractFirstArrayItem(channel, 'itunes:new-feed-url')
|
|
|
|
} else if (channel['atom:link'] && channel['atom:link'].length && channel['atom:link'][0]['$']) {
|
|
|
|
metadata.feedUrl = channel['atom:link'][0]['$'].href || null
|
|
|
|
}
|
|
|
|
|
|
|
|
if (channel['description']) {
|
|
|
|
metadata.description = extractFirstArrayItem(channel, 'description')
|
|
|
|
metadata.descriptionPlain = stripHtml(metadata.description || '').result
|
|
|
|
}
|
|
|
|
|
|
|
|
var arrayFields = ['title', 'language', 'itunes:explicit', 'itunes:author', 'pubDate', 'link']
|
2022-03-06 01:54:24 +01:00
|
|
|
arrayFields.forEach((key) => {
|
|
|
|
var cleanKey = key.split(':').pop()
|
|
|
|
metadata[cleanKey] = extractFirstArrayItem(channel, key)
|
|
|
|
})
|
|
|
|
return metadata
|
|
|
|
}
|
|
|
|
|
|
|
|
function extractEpisodeData(item) {
|
|
|
|
// Episode must have url
|
|
|
|
if (!item.enclosure || !item.enclosure.length || !item.enclosure[0]['$'] || !item.enclosure[0]['$'].url) {
|
|
|
|
Logger.error(`[podcastUtils] Invalid podcast episode data`)
|
|
|
|
return null
|
|
|
|
}
|
2022-03-19 12:41:54 +01:00
|
|
|
var arrayFields = ['title', 'pubDate', 'description', 'itunes:episodeType', 'itunes:episode', 'itunes:author', 'itunes:duration', 'itunes:explicit', 'itunes:subtitle']
|
2022-03-06 01:54:24 +01:00
|
|
|
var episode = {
|
|
|
|
enclosure: {
|
|
|
|
...item.enclosure[0]['$']
|
|
|
|
}
|
|
|
|
}
|
|
|
|
arrayFields.forEach((key) => {
|
|
|
|
var cleanKey = key.split(':').pop()
|
|
|
|
episode[cleanKey] = extractFirstArrayItem(item, key)
|
|
|
|
})
|
|
|
|
return episode
|
|
|
|
}
|
|
|
|
|
2022-03-19 12:41:54 +01:00
|
|
|
function cleanEpisodeData(data) {
|
|
|
|
return {
|
|
|
|
title: data.title,
|
|
|
|
subtitle: data.subtitle || '',
|
|
|
|
description: data.description || '',
|
|
|
|
pubDate: data.pubDate || '',
|
|
|
|
episodeType: data.episodeType || '',
|
|
|
|
episode: data.episode || '',
|
|
|
|
author: data.author || '',
|
|
|
|
duration: data.duration || '',
|
|
|
|
explicit: data.explicit || '',
|
2022-03-22 01:24:38 +01:00
|
|
|
publishedAt: (new Date(data.pubDate)).valueOf(),
|
|
|
|
enclosure: data.enclosure
|
2022-03-19 12:41:54 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-06 01:54:24 +01:00
|
|
|
function extractPodcastEpisodes(items) {
|
|
|
|
var episodes = []
|
|
|
|
items.forEach((item) => {
|
2022-03-19 12:41:54 +01:00
|
|
|
var extracted = extractEpisodeData(item)
|
|
|
|
if (extracted) {
|
|
|
|
episodes.push(cleanEpisodeData(extracted))
|
2022-03-06 01:54:24 +01:00
|
|
|
}
|
|
|
|
})
|
|
|
|
return episodes
|
|
|
|
}
|
|
|
|
|
|
|
|
function cleanPodcastJson(rssJson) {
|
|
|
|
if (!rssJson.channel || !rssJson.channel.length) {
|
|
|
|
Logger.error(`[podcastUtil] Invalid podcast no channel object`)
|
|
|
|
return null
|
|
|
|
}
|
|
|
|
var channel = rssJson.channel[0]
|
|
|
|
if (!channel.item || !channel.item.length) {
|
|
|
|
Logger.error(`[podcastUtil] Invalid podcast no episodes`)
|
|
|
|
return null
|
|
|
|
}
|
|
|
|
var podcast = {
|
|
|
|
metadata: extractPodcastMetadata(channel),
|
|
|
|
episodes: extractPodcastEpisodes(channel.item)
|
|
|
|
}
|
|
|
|
return podcast
|
|
|
|
}
|
|
|
|
|
2022-04-13 23:55:48 +02:00
|
|
|
module.exports.parsePodcastRssFeedXml = async (xml, includeRaw = false) => {
|
2022-03-06 01:54:24 +01:00
|
|
|
if (!xml) return null
|
|
|
|
var json = await xmlToJSON(xml)
|
|
|
|
if (!json || !json.rss) {
|
|
|
|
Logger.error('[podcastUtils] Invalid XML or RSS feed')
|
|
|
|
return null
|
|
|
|
}
|
2022-04-13 23:55:48 +02:00
|
|
|
|
|
|
|
const podcast = cleanPodcastJson(json.rss)
|
|
|
|
if (!podcast) return null
|
|
|
|
|
|
|
|
if (includeRaw) {
|
|
|
|
return {
|
|
|
|
podcast,
|
|
|
|
rawJson: json
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
return {
|
|
|
|
podcast
|
|
|
|
}
|
|
|
|
}
|
2022-03-06 01:54:24 +01:00
|
|
|
}
|