mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2025-02-06 00:16:02 +01:00
Add:Podcast RSS feed parser
This commit is contained in:
parent
83976b5549
commit
b6e4f3a8c5
@ -2,9 +2,11 @@ const express = require('express')
|
|||||||
const Path = require('path')
|
const Path = require('path')
|
||||||
const fs = require('fs-extra')
|
const fs = require('fs-extra')
|
||||||
const date = require('date-and-time')
|
const date = require('date-and-time')
|
||||||
|
const axios = require('axios')
|
||||||
|
|
||||||
const Logger = require('./Logger')
|
const Logger = require('./Logger')
|
||||||
const { isObject } = require('./utils/index')
|
const { isObject } = require('./utils/index')
|
||||||
|
const { parsePodcastRssFeedXml } = require('./utils/podcastUtils')
|
||||||
|
|
||||||
const BookController = require('./controllers/BookController')
|
const BookController = require('./controllers/BookController')
|
||||||
const LibraryController = require('./controllers/LibraryController')
|
const LibraryController = require('./controllers/LibraryController')
|
||||||
@ -179,6 +181,8 @@ class ApiController {
|
|||||||
this.router.post('/syncLocal', this.syncLocal.bind(this))
|
this.router.post('/syncLocal', this.syncLocal.bind(this))
|
||||||
|
|
||||||
this.router.post('/streams/:id/close', this.closeStream.bind(this))
|
this.router.post('/streams/:id/close', this.closeStream.bind(this))
|
||||||
|
|
||||||
|
this.router.post('/getPodcastFeed', this.getPodcastFeed.bind(this))
|
||||||
}
|
}
|
||||||
|
|
||||||
async findBooks(req, res) {
|
async findBooks(req, res) {
|
||||||
@ -527,5 +531,27 @@ class ApiController {
|
|||||||
this.streamManager.closeStreamApiRequest(userId, streamId)
|
this.streamManager.closeStreamApiRequest(userId, streamId)
|
||||||
res.sendStatus(200)
|
res.sendStatus(200)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getPodcastFeed(req, res) {
|
||||||
|
var url = req.body.rssFeed
|
||||||
|
if (!url) {
|
||||||
|
return res.status(400).send('Bad request')
|
||||||
|
}
|
||||||
|
|
||||||
|
axios.get(url).then(async (data) => {
|
||||||
|
if (!data || !data.data) {
|
||||||
|
Logger.error('Invalid podcast feed request response')
|
||||||
|
return res.status(500).send('Bad response from feed request')
|
||||||
|
}
|
||||||
|
var podcast = await parsePodcastRssFeedXml(data.data)
|
||||||
|
if (!podcast) {
|
||||||
|
return res.status(500).send('Invalid podcast RSS feed')
|
||||||
|
}
|
||||||
|
res.json(podcast)
|
||||||
|
}).catch((error) => {
|
||||||
|
console.error('Failed', error)
|
||||||
|
res.status(500).send(error)
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
module.exports = ApiController
|
module.exports = ApiController
|
110
server/utils/podcastUtils.js
Normal file
110
server/utils/podcastUtils.js
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
const Logger = require('../Logger')
|
||||||
|
const { xmlToJSON } = require('./index')
|
||||||
|
|
||||||
|
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 arrayFields = ['title', 'language', 'description', 'itunes:explicit', 'itunes:author']
|
||||||
|
var metadata = {
|
||||||
|
image: extractImage(channel),
|
||||||
|
categories: extractCategories(channel)
|
||||||
|
}
|
||||||
|
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
|
||||||
|
}
|
||||||
|
var arrayFields = ['title', 'pubDate', 'description', 'itunes:episodeType', 'itunes:episode', 'itunes:author', 'itunes:duration', 'itunes:explicit']
|
||||||
|
var episode = {
|
||||||
|
enclosure: {
|
||||||
|
...item.enclosure[0]['$']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
arrayFields.forEach((key) => {
|
||||||
|
var cleanKey = key.split(':').pop()
|
||||||
|
episode[cleanKey] = extractFirstArrayItem(item, key)
|
||||||
|
})
|
||||||
|
return episode
|
||||||
|
}
|
||||||
|
|
||||||
|
function extractPodcastEpisodes(items) {
|
||||||
|
var episodes = []
|
||||||
|
items.forEach((item) => {
|
||||||
|
var cleaned = extractEpisodeData(item)
|
||||||
|
if (cleaned) {
|
||||||
|
episodes.push(cleaned)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports.parsePodcastRssFeedXml = async (xml) => {
|
||||||
|
if (!xml) return null
|
||||||
|
var json = await xmlToJSON(xml)
|
||||||
|
if (!json || !json.rss) {
|
||||||
|
Logger.error('[podcastUtils] Invalid XML or RSS feed')
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return cleanPodcastJson(json.rss)
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user