mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2025-02-01 00:18:14 +01:00
Add:RSS feeds for audiobooks #606
This commit is contained in:
parent
06cc2a1b21
commit
2a235b8324
@ -122,7 +122,7 @@ export default {
|
|||||||
|
|
||||||
console.log('Payload', payload)
|
console.log('Payload', payload)
|
||||||
this.$axios
|
this.$axios
|
||||||
.$post(`/api/podcasts/${this.libraryItemId}/open-feed`, payload)
|
.$post(`/api/items/${this.libraryItemId}/open-feed`, payload)
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
if (data.success) {
|
if (data.success) {
|
||||||
console.log('Opened RSS Feed', data)
|
console.log('Opened RSS Feed', data)
|
||||||
@ -143,7 +143,7 @@ export default {
|
|||||||
closeFeed() {
|
closeFeed() {
|
||||||
this.processing = true
|
this.processing = true
|
||||||
this.$axios
|
this.$axios
|
||||||
.$post(`/api/podcasts/${this.libraryItem.id}/close-feed`)
|
.$post(`/api/items/${this.libraryItem.id}/close-feed`)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.$toast.success('RSS Feed Closed')
|
this.$toast.success('RSS Feed Closed')
|
||||||
this.show = false
|
this.show = false
|
||||||
|
@ -383,10 +383,10 @@ export default {
|
|||||||
return this.$store.getters['user/getUserCanDownload']
|
return this.$store.getters['user/getUserCanDownload']
|
||||||
},
|
},
|
||||||
showRssFeedBtn() {
|
showRssFeedBtn() {
|
||||||
if (!this.rssFeedUrl && !this.podcastEpisodes.length) return false // Cannot open RSS feed with no episodes
|
if (!this.rssFeedUrl && !this.podcastEpisodes.length && !this.tracks.length) return false // Cannot open RSS feed with no episodes/tracks
|
||||||
|
|
||||||
// If rss feed is open then show feed url to users otherwise just show to admins
|
// If rss feed is open then show feed url to users otherwise just show to admins
|
||||||
return this.isPodcast && (this.userIsAdminOrUp || this.rssFeedUrl)
|
return this.userIsAdminOrUp || this.rssFeedUrl
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
@ -405,6 +405,38 @@ class LibraryItemController {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// POST: api/items/:id/open-feed
|
||||||
|
async openRSSFeed(req, res) {
|
||||||
|
if (!req.user.isAdminOrUp) {
|
||||||
|
Logger.error(`[LibraryItemController] Non-admin user attempted to open RSS feed`, req.user.username)
|
||||||
|
return res.sendStatus(500)
|
||||||
|
}
|
||||||
|
|
||||||
|
const feedData = this.rssFeedManager.openFeedForItem(req.user, req.libraryItem, req.body)
|
||||||
|
if (feedData.error) {
|
||||||
|
return res.json({
|
||||||
|
success: false,
|
||||||
|
error: feedData.error
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
success: true,
|
||||||
|
feedUrl: feedData.feedUrl
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async closeRSSFeed(req, res) {
|
||||||
|
if (!req.user.isAdminOrUp) {
|
||||||
|
Logger.error(`[LibraryItemController] Non-admin user attempted to close RSS feed`, req.user.username)
|
||||||
|
return res.sendStatus(500)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.rssFeedManager.closeFeedForItem(req.params.id)
|
||||||
|
|
||||||
|
res.sendStatus(200)
|
||||||
|
}
|
||||||
|
|
||||||
middleware(req, res, next) {
|
middleware(req, res, next) {
|
||||||
var item = this.db.libraryItems.find(li => li.id === req.params.id)
|
var item = this.db.libraryItems.find(li => li.id === req.params.id)
|
||||||
if (!item || !item.media) return res.sendStatus(404)
|
if (!item || !item.media) return res.sendStatus(404)
|
||||||
|
@ -173,37 +173,6 @@ class PodcastController {
|
|||||||
res.sendStatus(200)
|
res.sendStatus(200)
|
||||||
}
|
}
|
||||||
|
|
||||||
async openPodcastFeed(req, res) {
|
|
||||||
if (!req.user.isAdminOrUp) {
|
|
||||||
Logger.error(`[PodcastController] Non-admin user attempted to open podcast feed`, req.user.username)
|
|
||||||
return res.sendStatus(500)
|
|
||||||
}
|
|
||||||
|
|
||||||
const feedData = this.rssFeedManager.openPodcastFeed(req.user, req.libraryItem, req.body)
|
|
||||||
if (feedData.error) {
|
|
||||||
return res.json({
|
|
||||||
success: false,
|
|
||||||
error: feedData.error
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
res.json({
|
|
||||||
success: true,
|
|
||||||
feedUrl: feedData.feedUrl
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
async closePodcastFeed(req, res) {
|
|
||||||
if (!req.user.isAdminOrUp) {
|
|
||||||
Logger.error(`[PodcastController] Non-admin user attempted to close podcast feed`, req.user.username)
|
|
||||||
return res.sendStatus(500)
|
|
||||||
}
|
|
||||||
|
|
||||||
this.rssFeedManager.closePodcastFeedForItem(req.params.id)
|
|
||||||
|
|
||||||
res.sendStatus(200)
|
|
||||||
}
|
|
||||||
|
|
||||||
async updateEpisode(req, res) {
|
async updateEpisode(req, res) {
|
||||||
var libraryItem = req.libraryItem
|
var libraryItem = req.libraryItem
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
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 { Podcast } = require('podcast')
|
const { Podcast } = require('podcast')
|
||||||
const { getId } = require('../utils/index')
|
|
||||||
const Logger = require('../Logger')
|
const Logger = require('../Logger')
|
||||||
|
|
||||||
// Not functional at the moment
|
// Not functional at the moment
|
||||||
@ -60,36 +60,72 @@ class RssFeedManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
openFeed(userId, slug, libraryItem, serverAddress) {
|
openFeed(userId, slug, libraryItem, serverAddress) {
|
||||||
const podcast = libraryItem.media
|
const media = libraryItem.media
|
||||||
|
const mediaMetadata = media.metadata
|
||||||
|
const isPodcast = libraryItem.mediaType === 'podcast'
|
||||||
|
|
||||||
const feedUrl = `${serverAddress}/feed/${slug}`
|
const feedUrl = `${serverAddress}/feed/${slug}`
|
||||||
// Removed Podcast npm package and ip package
|
const author = isPodcast ? mediaMetadata.author : mediaMetadata.authorName
|
||||||
|
|
||||||
const feed = new Podcast({
|
const feed = new Podcast({
|
||||||
title: podcast.metadata.title,
|
title: mediaMetadata.title,
|
||||||
description: podcast.metadata.description,
|
description: mediaMetadata.description,
|
||||||
feedUrl,
|
feedUrl,
|
||||||
siteUrl: serverAddress,
|
siteUrl: `${serverAddress}/items/${libraryItem.id}`,
|
||||||
imageUrl: podcast.coverPath ? `${serverAddress}/feed/${slug}/cover` : `${serverAddress}/Logo.png`,
|
imageUrl: media.coverPath ? `${serverAddress}/feed/${slug}/cover` : `${serverAddress}/Logo.png`,
|
||||||
author: podcast.metadata.author || 'advplyr',
|
author: author || 'advplyr',
|
||||||
language: 'en'
|
language: 'en'
|
||||||
})
|
})
|
||||||
podcast.episodes.forEach((episode) => {
|
|
||||||
var contentUrl = episode.audioTrack.contentUrl.replace(/\\/g, '/')
|
|
||||||
contentUrl = contentUrl.replace(`/s/item/${libraryItem.id}`, `/feed/${slug}/item`)
|
|
||||||
|
|
||||||
feed.addItem({
|
if (isPodcast) { // PODCAST EPISODES
|
||||||
title: episode.title,
|
media.episodes.forEach((episode) => {
|
||||||
description: episode.description || '',
|
var contentUrl = episode.audioTrack.contentUrl.replace(/\\/g, '/')
|
||||||
enclosure: {
|
contentUrl = contentUrl.replace(`/s/item/${libraryItem.id}`, `/feed/${slug}/item`)
|
||||||
|
|
||||||
|
feed.addItem({
|
||||||
|
title: episode.title,
|
||||||
|
description: episode.description || '',
|
||||||
|
enclosure: {
|
||||||
|
url: `${serverAddress}${contentUrl}`,
|
||||||
|
type: episode.audioTrack.mimeType,
|
||||||
|
size: episode.size
|
||||||
|
},
|
||||||
|
date: episode.pubDate || '',
|
||||||
url: `${serverAddress}${contentUrl}`,
|
url: `${serverAddress}${contentUrl}`,
|
||||||
type: episode.audioTrack.mimeType,
|
author: author || 'advplyr'
|
||||||
size: episode.size
|
})
|
||||||
},
|
|
||||||
date: episode.pubDate || '',
|
|
||||||
url: `${serverAddress}${contentUrl}`,
|
|
||||||
author: podcast.metadata.author || 'advplyr'
|
|
||||||
})
|
})
|
||||||
})
|
} else { // AUDIOBOOK EPISODES
|
||||||
|
|
||||||
|
// Example: <pubDate>Fri, 04 Feb 2015 00:00:00 GMT</pubDate>
|
||||||
|
const audiobookPubDate = date.format(new Date(libraryItem.addedAt), 'ddd, DD MMM YYYY HH:mm:ss [GMT]')
|
||||||
|
|
||||||
|
media.tracks.forEach((audioTrack) => {
|
||||||
|
var contentUrl = audioTrack.contentUrl.replace(/\\/g, '/')
|
||||||
|
contentUrl = contentUrl.replace(`/s/item/${libraryItem.id}`, `/feed/${slug}/item`)
|
||||||
|
|
||||||
|
var title = audioTrack.title
|
||||||
|
if (media.chapters.length) {
|
||||||
|
// If audio track start and chapter start are within 1 seconds of eachother then use the chapter title
|
||||||
|
var matchingChapter = media.chapters.find(ch => Math.abs(ch.start - audioTrack.startOffset) < 1)
|
||||||
|
if (matchingChapter && matchingChapter.title) title = matchingChapter.title
|
||||||
|
}
|
||||||
|
|
||||||
|
feed.addItem({
|
||||||
|
title,
|
||||||
|
description: '',
|
||||||
|
enclosure: {
|
||||||
|
url: `${serverAddress}${contentUrl}`,
|
||||||
|
type: audioTrack.mimeType,
|
||||||
|
size: audioTrack.metadata.size
|
||||||
|
},
|
||||||
|
date: audiobookPubDate,
|
||||||
|
url: `${serverAddress}${contentUrl}`,
|
||||||
|
author: author || 'advplyr'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
const feedData = {
|
const feedData = {
|
||||||
id: slug,
|
id: slug,
|
||||||
@ -97,7 +133,7 @@ class RssFeedManager {
|
|||||||
userId,
|
userId,
|
||||||
libraryItemId: libraryItem.id,
|
libraryItemId: libraryItem.id,
|
||||||
libraryItemPath: libraryItem.path,
|
libraryItemPath: libraryItem.path,
|
||||||
mediaCoverPath: podcast.coverPath,
|
mediaCoverPath: media.coverPath,
|
||||||
serverAddress: serverAddress,
|
serverAddress: serverAddress,
|
||||||
feedUrl,
|
feedUrl,
|
||||||
feed
|
feed
|
||||||
@ -106,7 +142,7 @@ class RssFeedManager {
|
|||||||
return feedData
|
return feedData
|
||||||
}
|
}
|
||||||
|
|
||||||
openPodcastFeed(user, libraryItem, options) {
|
openFeedForItem(user, libraryItem, options) {
|
||||||
const serverAddress = options.serverAddress
|
const serverAddress = options.serverAddress
|
||||||
const slug = options.slug
|
const slug = options.slug
|
||||||
|
|
||||||
@ -118,12 +154,12 @@ class RssFeedManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const feedData = this.openFeed(user.id, slug, libraryItem, serverAddress)
|
const feedData = this.openFeed(user.id, slug, libraryItem, serverAddress)
|
||||||
Logger.debug(`[RssFeedManager] Opened podcast feed ${feedData.feedUrl}`)
|
Logger.debug(`[RssFeedManager] Opened RSS feed ${feedData.feedUrl}`)
|
||||||
this.emitter('rss_feed_open', { libraryItemId: libraryItem.id, feedUrl: feedData.feedUrl })
|
this.emitter('rss_feed_open', { libraryItemId: libraryItem.id, feedUrl: feedData.feedUrl })
|
||||||
return feedData
|
return feedData
|
||||||
}
|
}
|
||||||
|
|
||||||
closePodcastFeedForItem(libraryItemId) {
|
closeFeedForItem(libraryItemId) {
|
||||||
var feed = this.findFeedForItem(libraryItemId)
|
var feed = this.findFeedForItem(libraryItemId)
|
||||||
if (!feed) return
|
if (!feed) return
|
||||||
this.closeRssFeed(feed.id)
|
this.closeRssFeed(feed.id)
|
||||||
|
@ -95,6 +95,8 @@ class ApiRouter {
|
|||||||
this.router.get('/items/:id/scan', LibraryItemController.middleware.bind(this), LibraryItemController.scan.bind(this))
|
this.router.get('/items/:id/scan', LibraryItemController.middleware.bind(this), LibraryItemController.scan.bind(this))
|
||||||
this.router.get('/items/:id/audio-metadata', LibraryItemController.middleware.bind(this), LibraryItemController.updateAudioFileMetadata.bind(this))
|
this.router.get('/items/:id/audio-metadata', LibraryItemController.middleware.bind(this), LibraryItemController.updateAudioFileMetadata.bind(this))
|
||||||
this.router.post('/items/:id/chapters', LibraryItemController.middleware.bind(this), LibraryItemController.updateMediaChapters.bind(this))
|
this.router.post('/items/:id/chapters', LibraryItemController.middleware.bind(this), LibraryItemController.updateMediaChapters.bind(this))
|
||||||
|
this.router.post('/items/:id/open-feed', LibraryItemController.middleware.bind(this), LibraryItemController.openRSSFeed.bind(this))
|
||||||
|
this.router.post('/items/:id/close-feed', LibraryItemController.middleware.bind(this), LibraryItemController.closeRSSFeed.bind(this))
|
||||||
|
|
||||||
this.router.post('/items/batch/delete', LibraryItemController.batchDelete.bind(this))
|
this.router.post('/items/batch/delete', LibraryItemController.batchDelete.bind(this))
|
||||||
this.router.post('/items/batch/update', LibraryItemController.batchUpdate.bind(this))
|
this.router.post('/items/batch/update', LibraryItemController.batchUpdate.bind(this))
|
||||||
@ -186,8 +188,6 @@ class ApiRouter {
|
|||||||
this.router.get('/podcasts/:id/downloads', PodcastController.middleware.bind(this), PodcastController.getEpisodeDownloads.bind(this))
|
this.router.get('/podcasts/:id/downloads', PodcastController.middleware.bind(this), PodcastController.getEpisodeDownloads.bind(this))
|
||||||
this.router.get('/podcasts/:id/clear-queue', PodcastController.middleware.bind(this), PodcastController.clearEpisodeDownloadQueue.bind(this))
|
this.router.get('/podcasts/:id/clear-queue', PodcastController.middleware.bind(this), PodcastController.clearEpisodeDownloadQueue.bind(this))
|
||||||
this.router.post('/podcasts/:id/download-episodes', PodcastController.middleware.bind(this), PodcastController.downloadEpisodes.bind(this))
|
this.router.post('/podcasts/:id/download-episodes', PodcastController.middleware.bind(this), PodcastController.downloadEpisodes.bind(this))
|
||||||
this.router.post('/podcasts/:id/open-feed', PodcastController.middleware.bind(this), PodcastController.openPodcastFeed.bind(this))
|
|
||||||
this.router.post('/podcasts/:id/close-feed', PodcastController.middleware.bind(this), PodcastController.closePodcastFeed.bind(this))
|
|
||||||
this.router.patch('/podcasts/:id/episode/:episodeId', PodcastController.middleware.bind(this), PodcastController.updateEpisode.bind(this))
|
this.router.patch('/podcasts/:id/episode/:episodeId', PodcastController.middleware.bind(this), PodcastController.updateEpisode.bind(this))
|
||||||
|
|
||||||
//
|
//
|
||||||
|
Loading…
Reference in New Issue
Block a user